176 Commits
1.0.1 ... 1.0.3

Author SHA1 Message Date
aac4ce8098 update 2020-01-20 23:34:53 +08:00
52b9ed47b0 update 2020-01-20 23:04:42 +08:00
6d76e30299 update 2020-01-20 20:12:13 +08:00
0675268a16 update 2020-01-20 17:13:14 +08:00
aaf94e61a2 update 2020-01-20 17:02:19 +08:00
47d5031fe6 update 2020-01-20 17:01:02 +08:00
8d4575ede9 update 2020-01-20 16:47:53 +08:00
e9a69cd0f6 update 2020-01-20 16:43:34 +08:00
625bcc59e8 update 2020-01-20 16:38:34 +08:00
34533ae10c update 2020-01-20 16:28:28 +08:00
b4fc90c133 update 2020-01-20 16:18:12 +08:00
12278e143e update 2020-01-20 16:16:59 +08:00
10510164e4 update 2020-01-20 15:24:48 +08:00
75e91b281c update 2020-01-20 14:47:36 +08:00
c027c1127b update 2020-01-20 14:04:03 +08:00
0dbdb55ed2 update 2020-01-20 14:01:53 +08:00
66042904e8 update 2020-01-20 13:58:50 +08:00
508961c4fb update 2020-01-20 13:57:30 +08:00
f03dbd1dcb update 2020-01-18 18:41:02 +08:00
e576d82955 update 2020-01-17 00:20:54 +08:00
88d7e2d35e update 2020-01-14 19:48:18 +08:00
9b6919e9c9 update 2020-01-14 19:45:19 +08:00
3e86cdcbbf update 2020-01-14 01:16:16 +08:00
9fc4a6129c update 2020-01-14 00:29:59 +08:00
db9743a67f update 2020-01-12 22:38:29 +08:00
0f5942bc03 update 2020-01-12 19:48:13 +08:00
28cc21e09b update 2020-01-12 02:35:40 +08:00
fc7f73b558 update 2020-01-12 02:32:15 +08:00
fb0ebea836 update 2020-01-12 02:16:05 +08:00
7d7cef5f37 update 2020-01-12 02:15:48 +08:00
abd488d247 update 2020-01-12 02:14:43 +08:00
e3916aaa06 update 2020-01-12 02:13:27 +08:00
32795cf541 update 2020-01-12 02:08:06 +08:00
415850184e update 2020-01-12 02:02:45 +08:00
afa3c0aa52 update 2020-01-12 01:53:05 +08:00
82875ae6f0 update 2020-01-12 01:48:01 +08:00
bcd85ef71a update 2020-01-12 01:47:54 +08:00
8f35924dee update 2020-01-12 01:41:49 +08:00
4a01b8f11e update 2020-01-12 01:27:36 +08:00
fc18168aa2 update 2020-01-12 00:49:13 +08:00
abdb6f80af update 2020-01-11 19:18:45 +08:00
fbf3a83104 update 2020-01-11 17:17:25 +08:00
515e23762f update 2020-01-11 15:39:07 +08:00
f7fdfadfb0 format 2020-01-11 13:36:52 +08:00
35f954cd84 update 2020-01-09 12:58:01 +08:00
ed0fe84687 update 2020-01-09 12:57:39 +08:00
a96ca5a363 update 2020-01-08 22:28:16 +08:00
6a051f469e update 2020-01-08 17:40:06 +08:00
5e9bc09396 Merge pull request #12 from ColetteContreras/refactor-cache
Use Cache for varieties of cache server
2020-01-08 17:38:44 +08:00
1ef8eab552 Use Cache for varieties of cache server 2020-01-08 01:34:48 +08:00
4d66941ef6 update 2020-01-06 13:48:58 +08:00
2e505a9759 update 2020-01-06 00:06:34 +08:00
85e0eb2760 update 2020-01-05 23:47:23 +08:00
c68440ce32 update 2020-01-05 23:30:07 +08:00
1cd0dbdd31 update 2020-01-05 23:14:42 +08:00
d268f6ddec Merge pull request #8 from chen-ran/master
Support mailgun
2020-01-05 22:07:02 +08:00
b75c33b1d9 support mailgun 2020-01-05 21:21:54 +08:00
0ee67ef270 update 2020-01-05 16:48:18 +08:00
44e57b7073 update 2020-01-05 15:48:51 +08:00
0de4a137bf update 2020-01-05 15:39:28 +08:00
392c849241 update 2020-01-05 15:35:12 +08:00
2f3f457ad9 update 2020-01-05 02:27:29 +08:00
776e866b3c update 2020-01-05 02:23:26 +08:00
9541bc8cd0 update 2020-01-05 02:16:41 +08:00
dc52c3191c update 2020-01-05 02:07:41 +08:00
85a29d3a8b update 2020-01-05 00:22:41 +08:00
7a0c9ce4c4 update 2020-01-04 18:11:13 +08:00
b4bb93e23e update 2020-01-04 18:08:05 +08:00
11d9654010 update 2020-01-04 17:46:53 +08:00
52163329da update 2020-01-04 17:36:31 +08:00
45b03b4fba update 2020-01-04 17:27:21 +08:00
0749372f34 update 2020-01-04 17:25:44 +08:00
588577d513 update 2020-01-04 17:21:46 +08:00
e3431c6ae7 update 2020-01-03 23:05:14 +08:00
2346b1a2dc update 2020-01-03 23:00:31 +08:00
c61f64d623 update 2020-01-03 22:42:27 +08:00
25fbdf0013 update 2020-01-03 21:29:25 +08:00
3a9b3ab32d update 2020-01-03 21:06:04 +08:00
a524fb6f76 update 2020-01-03 21:04:44 +08:00
2172f088a2 update 2020-01-03 20:55:54 +08:00
93fa074358 update 2020-01-03 20:09:21 +08:00
27e417a5f2 update 2020-01-03 20:04:20 +08:00
97e1d9b3bd update 2020-01-03 20:01:46 +08:00
1a0d8e9c55 update 2020-01-03 19:39:32 +08:00
b49b941e50 update 2020-01-03 19:13:04 +08:00
b9cee36641 update 2020-01-03 19:12:05 +08:00
2ecdc27921 update 2020-01-03 19:09:24 +08:00
2d58744de4 update 2020-01-03 12:38:25 +08:00
869a6a920b update 2020-01-03 12:36:44 +08:00
c82a189d76 update 2020-01-03 12:17:57 +08:00
7543819ef2 update 2020-01-03 01:47:15 +08:00
4ceca1957f update 2020-01-03 01:45:58 +08:00
44bd189259 update 2020-01-03 01:12:23 +08:00
b6752e3952 update 2020-01-03 00:45:33 +08:00
45ba9b0c15 update 2020-01-03 00:29:24 +08:00
52e7925cac update 2020-01-03 00:29:15 +08:00
54273a8f16 update 2020-01-03 00:24:45 +08:00
c6461f0bdf update 2020-01-02 23:32:31 +08:00
145f55ae29 update 2020-01-02 23:22:49 +08:00
a201b19940 update 2020-01-02 22:05:27 +08:00
f4166bed45 update 2020-01-02 22:03:24 +08:00
8f8be2ea33 update 2020-01-02 21:55:50 +08:00
df02973756 update 2020-01-02 21:53:02 +08:00
9e55cb2f5d update 2020-01-02 21:52:20 +08:00
5699fe09e9 update 2020-01-02 21:49:25 +08:00
34fa75b4cc update 2020-01-02 21:49:04 +08:00
96d3a27a5b update 2020-01-02 21:39:52 +08:00
b8c8335542 update 2020-01-02 21:38:32 +08:00
aceff450ec update 2020-01-02 13:09:59 +08:00
5d7b5eb8f6 update 2020-01-02 00:43:25 +08:00
afc9d64aab update 2020-01-02 00:21:39 +08:00
09e31dc70b update 2020-01-02 00:20:31 +08:00
d653541ef3 update 2020-01-02 00:19:04 +08:00
dc37499df9 update 2020-01-02 00:11:42 +08:00
2911680eaf update 2020-01-02 00:07:30 +08:00
6ff24f77b8 update 2020-01-02 00:06:23 +08:00
9f4c19bcab update 2020-01-02 00:05:54 +08:00
453a078cd5 update 2020-01-01 23:54:20 +08:00
20be5c3182 update 2020-01-01 23:50:50 +08:00
7639f07b83 update 2020-01-01 23:48:23 +08:00
2a693e4911 update 2020-01-01 23:42:25 +08:00
89d67279c6 update 2020-01-01 23:40:14 +08:00
8bc5004654 update 2020-01-01 18:05:44 +08:00
ec4c0ba339 update 2020-01-01 18:00:45 +08:00
b4e5eb26e3 update 2020-01-01 17:57:06 +08:00
1bf03b28c7 update 2020-01-01 17:55:16 +08:00
aa8f5bfa79 update 2020-01-01 16:35:24 +08:00
495aa04273 update 2020-01-01 16:35:14 +08:00
202a21c17a update 2020-01-01 16:20:44 +08:00
9eefb32a4c update 2020-01-01 15:59:53 +08:00
2eb594a4fa update 2020-01-01 02:25:49 +08:00
34e71ff049 update 2020-01-01 01:50:05 +08:00
63c1faba5e update 2020-01-01 01:36:22 +08:00
5b2e18f702 update 2019-12-31 18:16:43 +08:00
784b53c9f3 update 2019-12-31 18:13:42 +08:00
01d0f6b29d update 2019-12-31 17:49:52 +08:00
2d7d5a564e update 2019-12-31 17:49:24 +08:00
70c1d5c874 update 2019-12-31 15:58:53 +08:00
10029c8362 update 2019-12-31 15:57:12 +08:00
9cd377f8ed update 2019-12-31 15:54:24 +08:00
f5db443668 update 2019-12-31 15:53:27 +08:00
e1fce3ae37 update 2019-12-31 13:45:50 +08:00
fae5ef21e6 update 2019-12-31 01:06:48 +08:00
4c976b1cbe update 2019-12-31 01:05:41 +08:00
6de46f5c36 update 2019-12-31 01:04:55 +08:00
4992ea635c update 2019-12-30 22:52:02 +08:00
928e59a977 update 2019-12-30 22:45:42 +08:00
2be5b8e357 update 2019-12-30 22:45:10 +08:00
76ce89c17f update 2019-12-30 22:40:39 +08:00
8c5b32de90 update 2019-12-30 21:05:48 +08:00
0b89446b63 update 2019-12-30 19:03:15 +08:00
0d4a86c9e9 update 2019-12-30 17:54:50 +08:00
aa1d54137c update 2019-12-30 17:51:18 +08:00
eee6351e35 update 2019-12-30 17:48:26 +08:00
4b43bd2b9e update 2019-12-30 17:43:56 +08:00
ff6aeb92ec update 2019-12-30 16:57:21 +08:00
a9abdcd9d3 update 2019-12-30 16:48:59 +08:00
7965a020b6 update 2019-12-30 16:37:47 +08:00
17e626493a update 2019-12-30 16:26:25 +08:00
0de055a36b update 2019-12-30 16:25:32 +08:00
5070c851ed update 2019-12-30 16:21:50 +08:00
157a97b35d update 2019-12-30 15:13:11 +08:00
8e6ee35efb update 2019-12-29 13:33:42 +08:00
ec284241ee update 2019-12-29 13:29:52 +08:00
a3f59aac0a update 2019-12-29 13:21:36 +08:00
35c71be4a9 update 2019-12-29 12:12:33 +08:00
abf6dbf73c update 2019-12-29 01:07:38 +08:00
037ecc0a2a update 2019-12-28 17:46:47 +08:00
5a87d59d30 update 2019-12-28 17:34:08 +08:00
1351ec583e update 2019-12-28 17:31:56 +08:00
5b29257227 update 2019-12-28 12:31:47 +08:00
551073cb83 update 2019-12-28 11:41:47 +08:00
a1b466e2c2 update 2019-12-28 11:40:00 +08:00
0b3cf4a2a4 update 2019-12-27 18:01:19 +08:00
0dccfd9f09 update 2019-12-27 17:57:07 +08:00
988f088a58 update 2019-12-27 17:36:02 +08:00
145 changed files with 7372 additions and 5838 deletions

View File

@ -14,9 +14,9 @@ DB_USERNAME=root
DB_PASSWORD=123456
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
@ -31,6 +31,8 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=null
MAILGUN_DOMAIN=
MAILGUN_SECRET=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2019 Bruskyii Panda
Copyright (c) 2019 Tokumeikoi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -54,5 +54,5 @@ class CheckCommission extends Command
}
}
}
}

View File

@ -39,15 +39,15 @@ class CheckExpire extends Command
*/
public function handle()
{
$user = User::all();
foreach ($user as $item) {
if ($item->expired_at < time() || $item->u + $item->d >= $item->transfer_enable) {
$item->enable = 0;
$users = User::all();
foreach ($users as $user) {
if ($user->expired_at < time() || $user->u + $user->d >= $user->transfer_enable) {
$user->enable = 0;
} else {
$item->enable = 1;
$user->enable = 1;
}
$item->save();
$user->save();
}
}
}

View File

@ -7,6 +7,7 @@ use App\Models\Order;
use App\Models\User;
use App\Models\Plan;
use App\Utils\Helper;
use App\Models\Coupon;
class CheckOrder extends Command
{
@ -55,17 +56,24 @@ class CheckOrder extends Command
$this->orderHandle($item);
break;
}
}
}
private function orderHandle ($order) {
private function orderHandle($order)
{
$user = User::find($order->user_id);
return $this->buy($order, $user);
}
private function buy ($order, $user) {
private function buy($order, $user)
{
$plan = Plan::find($order->plan_id);
// change plan process, try out is enable and plan
if ((int)$order->type === 3 && (int)config('v2board.try_out_plan_id') !== (int)$user->plan_id) {
$transferEnableDifference = $plan->transfer_enable - ($user->transfer_enable / 1073741824);
$user->expired_at = $user->expired_at - ($transferEnableDifference * config('v2board.plan_transfer_hour', 12) * 3600);
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->enable = 1;
$user->plan_id = $plan->id;
@ -76,16 +84,21 @@ class CheckOrder extends Command
$order->save();
}
}
private function getTime ($str, $timestamp) {
private function getTime($str, $timestamp)
{
if ($timestamp < time()) {
$timestamp = time();
}
switch ($str) {
case 'month_price': return strtotime('+1 month', $timestamp);
case 'quarter_price': return strtotime('+3 month', $timestamp);
case 'half_year_price': return strtotime('+6 month', $timestamp);
case 'year_price': return strtotime('+12 month', $timestamp);
case 'month_price':
return strtotime('+1 month', $timestamp);
case 'quarter_price':
return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
}
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\MailLog;
use App\Jobs\SendEmail;
class SendRemindMail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'send:remindMail';
/**
* The console command description.
*
* @var string
*/
protected $description = '发送提醒邮件';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$users = User::all();
foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user);
if ($user->remind_traffic) $this->remindTraffic($user);
}
}
private function remindExpire($user)
{
if (($user->expired_at - 86400) < time() && $user->expired_at > time()) {
SendEmail::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期',
'template_name' => 'mail.sendRemindExpire',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
}
private function remindTraffic($user)
{
if ($this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) {
$sendCount = MailLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('template_name', 'mail.sendRemindTraffic')
->count();
if ($sendCount > 0) return;
SendEmail::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'mail.sendRemindTraffic',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
}
private function remindTrafficIsWarnValue($ud, $transfer_enable)
{
if ($ud <= 0) return false;
if (($ud / $transfer_enable * 100) < 80) return false;
return true;
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Order;
use App\Models\Server;
use App\Models\ServerLog;
use App\Utils\Helper;
use Illuminate\Support\Facades\Redis;
class SystemCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'system:cache';
/**
* The console command description.
*
* @var string
*/
protected $description = '系统缓存任务';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->setMonthIncome();
$this->setMonthRegisterTotal();
}
private function setMonthIncome() {
Redis::set(
'month_income',
Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->where('status', '3')
->sum('total_amount')
);
}
private function setMonthRegisterTotal() {
Redis::set(
'month_register_total',
User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count()
);
}
}

View File

@ -4,23 +4,27 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Order;
use App\Models\Server;
use App\Models\ServerLog;
use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
class ImportReset extends Command
class V2boardCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:reset';
protected $signature = 'v2board:cache';
/**
* The console command description.
*
* @var string
*/
protected $description = '为导入用户重置所有uuid及token';
protected $description = '缓存任务';
/**
* Create a new command instance.
@ -39,11 +43,5 @@ class ImportReset extends Command
*/
public function handle()
{
$user = User::all();
foreach ($user as $item) {
$item->v2ray_uuid = Helper::guid(true);
$item->token = Helper::guid();
$item->save();
}
}
}

View File

@ -46,41 +46,46 @@ class V2boardInstall extends Command
\Artisan::call('key:generate');
sleep(2);
\Artisan::call('config:cache');
DB::connection()->getPdo();
$file = \File::get(base_path() . '/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
DB::connection()->getPdo();
$file = \File::get(base_path() . '/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {
}
}
$email = '';
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
$email = $this->ask('请输入管理员邮箱?');
}
$password = '';
while (!$password) {
$password = $this->ask('请输入管理员密码?');
$password = $this->ask('请输入管理员密码?');
}
if (!$this->registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
abort(500, '管理员账号注册失败,请重试');
}
$this->info('一切就绪');
$this->info('一切就绪');
\File::put(base_path() . '/.lock', time());
}
private function registerAdmin ($email, $password) {
private function registerAdmin($email, $password)
{
$user = new User();
$user->email = $email;
if (strlen($password) < 8) {
abort(500, '管理员密码长度最小为8位字符');
}
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();

View File

@ -39,22 +39,23 @@ class V2boardUpdate extends Command
public function handle()
{
\Artisan::call('config:cache');
DB::connection()->getPdo();
$file = \File::get(base_path() . '/update.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
DB::connection()->getPdo();
$file = \File::get(base_path() . '/update.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
}
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {
}
}
$this->info('更新完毕');
}
}

View File

@ -19,22 +19,22 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// check order
// v2board
$schedule->command('v2board:cache')->hourly();
// check
$schedule->command('check:order')->everyMinute();
// check expire
$schedule->command('check:expire')->everyMinute();
// check commission
$schedule->command('check:commission')->everyMinute();
// system cache
$schedule->command('system:cache')->hourly();
// reset
$schedule->command('reset:traffic')->monthlyOn(1, '00:00');
$schedule->command('reset:serverLog')->monthlyOn(1, '00:00');
$schedule->command('reset:traffic')->monthly();
$schedule->command('reset:serverLog')->monthly();
// send
$schedule->command('send:remindMail')->dailyAt('11:30');
}
/**
@ -44,7 +44,7 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}

View File

@ -29,7 +29,7 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* @param \Exception $exception
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
@ -40,8 +40,8 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)

View File

@ -5,17 +5,16 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ConfigSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
class ConfigController extends Controller
{
public function init () {
public function init()
{
}
public function fetch () {
public function fetch()
{
return response([
'data' => [
'invite' => [
@ -30,8 +29,11 @@ class ConfigController extends Controller
'app_name' => config('v2board.app_name', 'V2Board'),
'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'),
'plan_update_fee' => config('v2board.plan_update_fee', 0.5),
'plan_is_update' => (int)config('v2board.plan_is_update', 1)
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'plan_transfer_hour' => config('v2board.plan_transfer_hour', 12),
'try_out_enable' => (int)config('v2board.try_out_enable', 0),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id'),
'try_out_hour' => (int)config('v2board.try_out_hour', 1)
],
'pay' => [
// alipay
@ -47,10 +49,19 @@ class ConfigController extends Controller
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
// bitpayx
'bitpayx_enable' => config('v2board.bitpayx_enable'),
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret')
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
// paytaro
'paytaro_enable' => config('v2board.paytaro_enable'),
'paytaro_app_id' => config('v2board.paytaro_app_id'),
'paytaro_app_secret' => config('v2board.paytaro_app_secret')
],
'frontend' => [
'frontend_theme' => config('v2board.frontend_theme', 1),
'frontend_background_url' => config('v2board.frontend_background_url')
],
'server' => [
'server_token' => config('v2board.server_token')
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license')
],
'tutorial' => [
'apple_id' => config('v2board.apple_id')
@ -58,18 +69,19 @@ class ConfigController extends Controller
]
]);
}
public function save (ConfigSave $request) {
public function save(ConfigSave $request)
{
$data = $request->input();
$array = \Config::get('v2board');
foreach ($data as $k => $v) {
if (!in_array($k, ConfigSave::filter())) {
abort(500, '禁止修改');
abort(500, '参数' . $k . '不在规则内,禁止修改');
}
$array[$k] = $v;
}
$data = var_export($array, 1);
if(!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
abort(500, '修改失败');
}
\Artisan::call('config:cache');

View File

@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\CouponSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Coupon;
use App\Utils\Helper;
class CouponController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Coupon::all()
]);
}
public function save(CouponSave $request)
{
$params = $request->only([
'name',
'type',
'value',
'started_at',
'ended_at',
'limit_use'
]);
if (!$request->input('id')) {
$params['code'] = Helper::randomChar(8);
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
if (!Coupon::find($request->input('id'))->update($params)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$coupon = Coupon::find($request->input('id'));
if (!$coupon) {
abort(500, '优惠券不存在');
}
if (!$coupon->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Jobs\SendEmail;
class MailController extends Controller
{
public function send(MailSend $request)
{
if ($request->input('type') == 2 && empty($request->input('receiver'))) {
abort(500, '收件人不能为空');
}
if ($request->input('receiver')) {
$users = User::whereIn('id', $request->input('receiver'))->get();
} else {
$users = User::all();
}
foreach ($users as $user) {
SendEmail::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'mail.sendEmailCustom',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
])->onQueue('other_mail');
}
return response([
'data' => true
]);
}
}

View File

@ -6,45 +6,40 @@ use App\Http\Requests\Admin\NoticeSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class NoticeController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('id', 'DESC')->get()
]);
}
public function save (NoticeSave $request) {
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!Notice::create($data)) {
abort(500, '保存失败');
if (!$request->input('id')) {
if (!Notice::create($data)) {
abort(500, '保存失败');
}
} else {
if (!Notice::find($request->input('id'))->update($data)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function update (NoticeSave $request) {
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!Notice::where('id', $request->input('id'))->update($data)) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}

View File

@ -11,7 +11,8 @@ use App\Models\Plan;
class OrderController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$orderModel = Order::orderBy('created_at', 'DESC');
@ -42,7 +43,8 @@ class OrderController extends Controller
]);
}
public function update (OrderUpdate $request) {
public function update(OrderUpdate $request)
{
$updateData = $request->only([
'status',
'commission_status'
@ -63,7 +65,8 @@ class OrderController extends Controller
]);
}
public function repair (Request $request) {
public function repair(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数错误');
}

View File

@ -12,13 +12,15 @@ use App\Models\User;
class PlanController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Plan::get()
]);
}
public function save (PlanSave $request) {
public function save(PlanSave $request)
{
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
@ -29,22 +31,20 @@ class PlanController extends Controller
}
$plan->name = $request->input('name');
$plan->content = $request->input('content');
if ($plan->content) {
$plan->content = str_replace(PHP_EOL, '', $plan->content);
}
$plan->transfer_enable = $request->input('transfer_enable');
$plan->group_id = $request->input('group_id');
$plan->month_price = $request->input('month_price');
$plan->quarter_price = $request->input('quarter_price');
$plan->half_year_price = $request->input('half_year_price');
$plan->year_price = $request->input('year_price');
return response([
'data' => $plan->save()
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if (Order::where('plan_id', $request->input('id'))->first()) {
abort(500, '该订阅下存在订单无法删除');
}
@ -62,12 +62,13 @@ class PlanController extends Controller
]);
}
public function update (PlanUpdate $request) {
public function update(PlanUpdate $request)
{
$updateData = $request->only([
'show',
'renew'
]);
$plan = Plan::find($request->input('id'));
if (!$plan) {
abort(500, '该订阅不存在');

View File

@ -10,54 +10,80 @@ use App\Models\ServerGroup;
use App\Models\Server;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class ServerController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$server = Server::get();
for ($i = 0; $i < count($server); $i++) {
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']);
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
}
}
return response([
'data' => $server
]);
}
public function save (ServerSave $request) {
public function save(ServerSave $request)
{
$params = $request->only([
'show',
'group_id',
'parent_id',
'name',
'host',
'port',
'server_port',
'tls',
'tags',
'rate',
'network',
'settings'
]);
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['settings'])) {
if (!is_object(json_decode($params['settings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
} else {
$server = new Server();
}
$server->group_id = json_encode($request->input('group_id'));
$server->name = $request->input('name');
$server->host = $request->input('host');
$server->port = $request->input('port');
$server->server_port = $request->input('server_port');
$server->tls = $request->input('tls');
$server->tags = $request->input('tags') ? json_encode($request->input('tags')) : NULL;
$server->rate = $request->input('rate');
$server->network = $request->input('network');
if ($request->input('settings')) {
if (!is_object(json_decode($request->input('settings')))) {
abort(500, '传输协议配置格式不正确');
if (!$server->update($params)) {
abort(500, '保存失败');
}
$server->settings = $request->input('settings');
return response([
'data' => true
]);
}
if (!Server::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => $server->save()
'data' => true
]);
}
public function groupFetch (Request $request) {
public function groupFetch(Request $request)
{
if ($request->input('group_id')) {
return response([
'data' => [ServerGroup::find($request->input('group_id'))]
@ -68,11 +94,12 @@ class ServerController extends Controller
]);
}
public function groupSave (Request $request) {
public function groupSave(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
} else {
@ -85,7 +112,8 @@ class ServerController extends Controller
]);
}
public function groupDrop (Request $request) {
public function groupDrop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
@ -111,8 +139,9 @@ class ServerController extends Controller
'data' => $serverGroup->delete()
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
@ -124,16 +153,18 @@ class ServerController extends Controller
]);
}
public function update (ServerUpdate $request) {
$updateData = $request->only([
public function update(ServerUpdate $request)
{
$params = $request->only([
'show',
]);
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
if (!$server->update($updateData)) {
if (!$server->update($params)) {
abort(500, '保存失败');
}

View File

@ -10,22 +10,27 @@ use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class StatController extends Controller
{
public function getOverride (Request $request) {
public function getOverride(Request $request)
{
return response([
'data' => [
'month_income' => Redis::get('month_income'),
'month_register_total' => Redis::get('month_register_total'),
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->where('status', '3')
->sum('total_amount'),
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count(),
'ticket_pendding_total' => Ticket::where('status', 0)
->count(),
'commission_pendding_total' => Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->where('status', 3)
->count(),
]
]);
}

View File

@ -10,7 +10,8 @@ use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
@ -43,7 +44,8 @@ class TicketController extends Controller
]);
}
public function reply (Request $request) {
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -75,7 +77,8 @@ class TicketController extends Controller
]);
}
public function close (Request $request) {
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\TutorialSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Tutorial;
class TutorialController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Tutorial::all()
]);
}
public function save(TutorialSave $request)
{
$params = $request->only([
'title',
'description',
'steps',
'icon'
]);
if (!$request->input('id')) {
if (!Tutorial::create($params)) {
abort(500, '创建失败');
}
} else {
if (!Tutorial::find($request->input('id'))->update($params)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
$tutorial->show = $tutorial->show ? 0 : 1;
if (!$tutorial->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
if (!$tutorial->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -11,7 +11,8 @@ use App\Models\Plan;
class UserController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$userModel = User::orderBy('created_at', 'DESC');
@ -35,7 +36,8 @@ class UserController extends Controller
]);
}
public function id2UserInfo ($id) {
public function id2UserInfo($id)
{
if (empty($id)) {
abort(500, '参数错误');
}
@ -50,17 +52,18 @@ class UserController extends Controller
]);
}
public function update (UserUpdate $request) {
$updateData = $request->only([
'email',
'password',
'transfer_enable',
'expired_at',
public function update(UserUpdate $request)
{
$updateData = $request->only([
'email',
'password',
'transfer_enable',
'expired_at',
'banned',
'plan_id',
'commission_rate',
'is_admin'
]);
'is_admin'
]);
$user = User::find($request->input('id'));
if (!$user) {
abort(500, '用户不存在');
@ -69,9 +72,9 @@ class UserController extends Controller
abort(500, '邮箱已被使用');
}
if (isset($updateData['password'])) {
$updateData['password'] = password_hash($updateData['password'], PASSWORD_DEFAULT);
$updateData['password'] = password_hash($updateData['password'], PASSWORD_DEFAULT);
} else {
unset($updateData['password']);
unset($updateData['password']);
}
$updateData['transfer_enable'] = $updateData['transfer_enable'] * 1073741824;
if (isset($updateData['plan_id'])) {

View File

@ -12,11 +12,12 @@ use App\Utils\Helper;
class AppController extends Controller
{
CONST CLIENT_CONFIG = '{"policy":{"levels":{"0":{"uplinkOnly":0}}},"dns":{"servers":["114.114.114.114","8.8.8.8"]},"outboundDetour":[{"protocol":"freedom","tag":"direct","settings":{}}],"inbound":{"listen":"0.0.0.0","port":31211,"protocol":"socks","settings":{"auth":"noauth","udp":true,"ip":"127.0.0.1"}},"inboundDetour":[{"listen":"0.0.0.0","allocate":{"strategy":"always","refresh":5,"concurrency":3},"port":31210,"protocol":"http","tag":"httpDetour","domainOverride":["http","tls"],"streamSettings":{},"settings":{"timeout":0}}],"routing":{"strategy":"rules","settings":{"domainStrategy":"IPIfNonMatch","rules":[{"port":"1-52","type":"field","outboundTag":"direct"},{"port":"54-79","type":"field","outboundTag":"direct"},{"port":"81-442","type":"field","outboundTag":"direct"},{"port":"444-65535","type":"field","outboundTag":"direct"},{"type":"field","ip":["0.0.0.0/8","10.0.0.0/8","100.64.0.0/10","127.0.0.0/8","169.254.0.0/16","172.16.0.0/12","192.0.0.0/24","192.0.2.0/24","192.168.0.0/16","198.18.0.0/15","198.51.100.0/24","203.0.113.0/24","::1/128","fc00::/7","fe80::/10"],"outboundTag":"direct"},{"type":"field","ip":["geoip:cn"],"outboundTag":"direct"}]}},"outbound":{"tag":"proxy","sendThrough":"0.0.0.0","mux":{"enabled":false,"concurrency":8},"protocol":"vmess","settings":{"vnext":[{"address":"server","port":443,"users":[{"id":"uuid","alterId":2,"security":"auto","level":0}],"remark":"remark"}]},"streamSettings":{"network":"tcp","tcpSettings":{"header":{"type":"none"}},"security":"none","tlsSettings":{"allowInsecure":true,"allowInsecureCiphers":true},"kcpSettings":{"header":{"type":"none"},"mtu":1350,"congestion":false,"tti":20,"uplinkCapacity":5,"writeBufferSize":1,"readBufferSize":1,"downlinkCapacity":20},"wsSettings":{"path":"","headers":{"Host":"server.cc"}}}}}';
CONST CLIENT_CONFIG = '{"policy":{"levels":{"0":{"uplinkOnly":0}}},"dns":{"servers":["114.114.114.114","8.8.8.8"]},"outboundDetour":[{"protocol":"freedom","tag":"direct","settings":{}}],"inbound":{"listen":"0.0.0.0","port":31211,"protocol":"socks","settings":{"auth":"noauth","udp":true,"ip":"127.0.0.1"}},"inboundDetour":[{"listen":"0.0.0.0","allocate":{"strategy":"always","refresh":5,"concurrency":3},"port":31210,"protocol":"http","tag":"httpDetour","domainOverride":["http","tls"],"streamSettings":{},"settings":{"timeout":0}}],"routing":{"strategy":"rules","settings":{"domainStrategy":"IPIfNonMatch","rules":[{"type":"field","ip":["geoip:cn"],"outboundTag":"direct"},{"type":"field","ip":["0.0.0.0/8","10.0.0.0/8","100.64.0.0/10","127.0.0.0/8","169.254.0.0/16","172.16.0.0/12","192.0.0.0/24","192.0.2.0/24","192.168.0.0/16","198.18.0.0/15","198.51.100.0/24","203.0.113.0/24","::1/128","fc00::/7","fe80::/10"],"outboundTag":"direct"}]}},"outbound":{"tag":"proxy","sendThrough":"0.0.0.0","mux":{"enabled":false,"concurrency":8},"protocol":"vmess","settings":{"vnext":[{"address":"server","port":443,"users":[{"id":"uuid","alterId":2,"security":"auto","level":0}],"remark":"remark"}]},"streamSettings":{"network":"tcp","tcpSettings":{"header":{"type":"none"}},"security":"none","tlsSettings":{"allowInsecure":true,"allowInsecureCiphers":true},"kcpSettings":{"header":{"type":"none"},"mtu":1350,"congestion":false,"tti":20,"uplinkCapacity":5,"writeBufferSize":1,"readBufferSize":1,"downlinkCapacity":20},"wsSettings":{"path":"","headers":{"Host":"server.cc"}}}}}';
CONST SOCKS_PORT = 10010;
CONST HTTP_PORT = 10011;
public function data (Request $request) {
public function data(Request $request)
{
$user = $request->user;
$nodes = [];
if ($user->plan_id) {
@ -49,7 +50,8 @@ class AppController extends Controller
]);
}
public function config (Request $request) {
public function config(Request $request)
{
if (empty($request->input('server_id'))) {
abort(500, '参数错误');
}
@ -77,22 +79,28 @@ class AppController extends Controller
$json->outbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp': $json->outbound->streamSettings->tcpSettings = json_decode($server->settings);
case 'tcp':
$json->outbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp': $json->outbound->streamSettings->kcpSettings = json_decode($server->settings);
case 'kcp':
$json->outbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws': $json->outbound->streamSettings->wsSettings = json_decode($server->settings);
case 'ws':
$json->outbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http': $json->outbound->streamSettings->httpSettings = json_decode($server->settings);
case 'http':
$json->outbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket': $json->outbound->streamSettings->dsSettings = json_decode($server->settings);
case 'domainsocket':
$json->outbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic': $json->outbound->streamSettings->quicSettings = json_decode($server->settings);
case 'quic':
$json->outbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ($request->input('is_global')) {
$json->routing->settings->rules[5]->outboundTag = 'proxy';
$json->routing->settings->rules[0]->outboundTag = 'proxy';
}
if ($server->tls) {
$json->outbound->streamSettings->security = "tls";

View File

@ -12,132 +12,138 @@ use Symfony\Component\Yaml\Yaml;
class ClientController extends Controller
{
public function subscribe (Request $request) {
public function subscribe(Request $request)
{
$user = $request->user;
$server = [];
if ($user->expired_at > time()) {
$servers = Server::where('show', 1)
->orderBy('name')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
$servers = Server::where('show', 1)
->orderBy('name')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
}
if(isset($_SERVER['HTTP_USER_AGENT'])) {
if(strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
die($this->quantumultX($user, $server));
}
if(strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
die($this->quantumult($user, $server));
}
if(strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
die($this->clash($user, $server));
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
die($this->quantumultX($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
die($this->quantumult($user, $server));
}
if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
die($this->clash($user, $server));
}
}
die($this->origin($user, $server));
}
private function quantumultX ($user, $server) {
$uri = '';
foreach($server as $item) {
$uri .= "vmess=".$item->host.":".$item->port.", method=none, password=".$user->v2ray_uuid.", fast-open=false, udp-relay=false, tag=".$item->name;
if ($item->network == 'ws') {
$uri .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $uri .= ', obfs-uri='.$wsSettings->path;
}
private function quantumultX($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= "vmess=" . $item->host . ":" . $item->port . ", method=none, password=" . $user->v2ray_uuid . ", fast-open=false, udp-relay=false, tag=" . $item->name;
if ($item->network == 'ws') {
$uri .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
}
}
$uri .= "\r\n";
}
$uri .= "\r\n";
}
return base64_encode($uri);
return base64_encode($uri);
}
private function quantumult ($user, $server) {
$uri = '';
header('subscription-userinfo: upload='.$user->u.'; download='.$user->d.';total='.$user->transfer_enable);
foreach($server as $item) {
$str = '';
$str .= $item->name.'= vmess, '.$item->host.', '.$item->port.', chacha20-ietf-poly1305, "'.$user->v2ray_uuid.'", over-tls='.($item->tls?"true":"false").', certificate=0, group='.config('v2board.app_name', 'V2Board');
if ($item->network === 'ws') {
$str .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $str .= ', obfs-path="'.$wsSettings->path.'"';
if ($wsSettings->headers->Host) $str .= ', obfs-header="Host:'.$wsSettings->headers->Host.'"';
}
private function quantumult($user, $server)
{
$uri = '';
header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
foreach ($server as $item) {
$str = '';
$str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->v2ray_uuid . '", over-tls=' . ($item->tls ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
if ($item->network === 'ws') {
$str .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $str .= ', obfs-path="' . $wsSettings->path . '"';
if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:' . $wsSettings->headers->Host . '"';
}
}
$uri .= "vmess://" . base64_encode($str) . "\r\n";
}
$uri .= "vmess://".base64_encode($str)."\r\n";
}
return base64_encode($uri);
return base64_encode($uri);
}
private function origin ($user, $server) {
$uri = '';
foreach($server as $item) {
$uri .= Helper::buildVmessLink($item, $user);
}
return base64_encode($uri);
private function origin($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= Helper::buildVmessLink($item, $user);
}
return base64_encode($uri);
}
private function clash ($user, $server) {
$proxy = [];
$proxyGroup = [];
$proxies = [];
foreach ($server as $item) {
$array = [];
$array['name'] = $item->name;
$array['type'] = 'vmess';
$array['server'] = $item->host;
$array['port'] = $item->port;
$array['uuid'] = $user->v2ray_uuid;
$array['alterId'] = $user->v2ray_alter_id;
$array['cipher'] = 'auto';
if ($item->tls) {
$array['tls'] = true;
private function clash($user, $server)
{
$proxy = [];
$proxyGroup = [];
$proxies = [];
foreach ($server as $item) {
$array = [];
$array['name'] = $item->name;
$array['type'] = 'vmess';
$array['server'] = $item->host;
$array['port'] = $item->port;
$array['uuid'] = $user->v2ray_uuid;
$array['alterId'] = $user->v2ray_alter_id;
$array['cipher'] = 'auto';
if ($item->tls) {
$array['tls'] = true;
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
'Host' => $wsSettings->headers->Host
];
}
}
array_push($proxy, $array);
array_push($proxies, $item->name);
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $array['ws-path'] = $wsSettings->path;
if ($wsSettings->headers->Host) $array['ws-headers'] = [
'Host' => $wsSettings->headers->Host
];
}
}
array_push($proxy, $array);
array_push($proxies, $item->name);
}
array_push($proxyGroup, [
'name' => config('v2board.app_name', 'V2Board'),
'type' => 'select',
'proxies' => $proxies
]);
$config = [
'port' => 7890,
'socks-port' => 0,
'allow-lan' => false,
'mode' => 'Rule',
'log-level' => 'info',
'external-controller' => '0.0.0.0:9090',
'secret' => '',
'Proxy' => $proxy,
'Proxy Group' => $proxyGroup,
'Rule' => [
'DOMAIN-SUFFIX,google.com,'.config('v2board.app_name', 'V2Board'),
'DOMAIN-KEYWORD,google,'.config('v2board.app_name', 'V2Board'),
'DOMAIN,google.com,'.config('v2board.app_name', 'V2Board'),
'DOMAIN-SUFFIX,ad.com,REJECT',
'IP-CIDR,127.0.0.0/8,DIRECT',
'GEOIP,CN,DIRECT',
'MATCH,'.config('v2board.app_name', 'V2Board')
]
];
return Yaml::dump($config);
array_push($proxyGroup, [
'name' => config('v2board.app_name', 'V2Board'),
'type' => 'select',
'proxies' => $proxies
]);
$config = [
'port' => 7890,
'socks-port' => 0,
'allow-lan' => false,
'mode' => 'Rule',
'log-level' => 'info',
'external-controller' => '0.0.0.0:9090',
'secret' => '',
'Proxy' => $proxy,
'Proxy Group' => $proxyGroup,
'Rule' => [
'DOMAIN-SUFFIX,google.com,' . config('v2board.app_name', 'V2Board'),
'DOMAIN-KEYWORD,google,' . config('v2board.app_name', 'V2Board'),
'DOMAIN,google.com,' . config('v2board.app_name', 'V2Board'),
'DOMAIN-SUFFIX,ad.com,REJECT',
'IP-CIDR,127.0.0.0/8,DIRECT',
'GEOIP,CN,DIRECT',
'MATCH,' . config('v2board.app_name', 'V2Board')
]
];
return Yaml::dump($config);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Coupon;
class CouponController extends Controller
{
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, '优惠券码不能为空');
}
$coupon = Coupon::where('code', $request->input('code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
}
return response([
'data' => $coupon
]);
}
}

View File

@ -7,12 +7,14 @@ use App\Http\Controllers\Controller;
use App\Models\Order;
use Omnipay\Omnipay;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function alipayNotify (Request $request) {
public function alipayNotify(Request $request)
{
Log::info('alipayNotifyData: ' . json_encode($_POST));
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
@ -24,8 +26,8 @@ class OrderController extends Controller
try {
/** @var \Omnipay\Alipay\Responses\AopCompletePurchaseResponse $response */
$response = $request->send();
if($response->isPaid()){
if ($response->isPaid()) {
/**
* Payment is successful
*/
@ -34,7 +36,7 @@ class OrderController extends Controller
}
die('success'); //The response should be 'success' only
}else{
} else {
/**
* Payment is not successful
*/
@ -48,7 +50,8 @@ class OrderController extends Controller
}
}
public function stripeNotify (Request $request) {
public function stripeNotify(Request $request)
{
Log::info('stripeNotifyData: ' . json_encode($request->input()));
\Stripe\Stripe::setApiKey(config('v2board.stripe_sk_live'));
@ -70,14 +73,14 @@ class OrderController extends Controller
'source' => $source['id'],
]);
if ($charge['status'] == 'succeeded') {
$trade_no = Redis::get($source['id']);
$trade_no = Cache::get($source['id']);
if (!$trade_no) {
abort(500, 'redis is not found trade no by stripe source id');
}
if (!$this->handle($trade_no, $source['id'])) {
abort(500, 'fail');
}
Redis::del($source['id']);
Cache::forget($source['id']);
die('success');
}
break;
@ -86,7 +89,8 @@ class OrderController extends Controller
}
}
public function bitpayXNotify (Request $request) {
public function bitpayXNotify(Request $request)
{
$inputString = file_get_contents('php://input', 'r');
Log::info('bitpayXNotifyData: ' . $inputString);
$inputStripped = str_replace(array("\r", "\n", "\t", "\v"), '', $inputString);
@ -94,46 +98,50 @@ class OrderController extends Controller
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'status' => $inputJSON['status'],
'order_id' => $inputJSON['order_id'],
'merchant_order_id' => $inputJSON['merchant_order_id'],
'price_amount' => $inputJSON['price_amount'],
'price_currency' => $inputJSON['price_currency'],
'pay_amount' => $inputJSON['pay_amount'],
'pay_currency' => $inputJSON['pay_currency'],
'created_at_t' => $inputJSON['created_at_t']
'status' => $inputJSON['status'],
'order_id' => $inputJSON['order_id'],
'merchant_order_id' => $inputJSON['merchant_order_id'],
'price_amount' => $inputJSON['price_amount'],
'price_currency' => $inputJSON['price_currency'],
'pay_amount' => $inputJSON['pay_amount'],
'pay_currency' => $inputJSON['pay_currency'],
'created_at_t' => $inputJSON['created_at_t']
];
$strToSign = $bitpayX->prepareSignId($inputJSON['merchant_order_id']);
if (!$bitpayX->verify($strToSign, $inputJSON['token'])) {
die([
'status' => 400,
'error' => 'sign error'
]);
abort(500, 'sign error');
}
if ($params['status'] !== 'PAID') {
die([
'status' => 400,
'error' => 'order is not paid'
]);
abort(500, 'order is not paid');
}
if (!$this->handle($params['merchant_order_id'], $params['order_id'])) {
die([
'status' => 400,
'error' => 'order process fail'
]);
abort(500, 'order process fail');
}
die([
'status' => 200
]);
die('success');
}
private function handle ($tradeNo, $callbackNo) {
public function payTaroNotify(Request $request)
{
Log::info('payTaroNotify: ' . json_encode($request->input()));
$payTaro = new PayTaro(config('v2board.paytaro_app_id'), config('v2board.paytaro_app_secret'));
if (!$payTaro->verify($request->input())) {
abort(500, 'fail');
}
if (!$this->handle($request->input('out_trade_no'), $request->input('trade_no'))) {
abort(500, 'fail');
}
die('success');
}
private function handle($tradeNo, $callbackNo)
{
$order = Order::where('trade_no', $tradeNo)->first();
if (!$order) {
abort(500, 'order is not found');
}
if ($order->status !== 0) {
abort(500, 'order is paid');
return true;
}
$order->status = 1;
$order->callback_no = $callbackNo;

View File

@ -8,7 +8,8 @@ use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$plan = Plan::where('show', 1)->get();
return response([
'data' => $plan

View File

@ -11,7 +11,8 @@ use App\Utils\Helper;
class InviteController extends Controller
{
public function save (Request $request) {
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
abort(500, '已达到创建数量上限');
}
@ -23,7 +24,8 @@ class InviteController extends Controller
]);
}
public function details (Request $request) {
public function details(Request $request)
{
return response([
'data' => Order::where('invite_user_id', $request->session()->get('id'))
->where('status', 3)
@ -38,7 +40,8 @@ class InviteController extends Controller
]);
}
public function fetch (Request $request) {
public function fetch(Request $request)
{
$codes = InviteCode::where('user_id', $request->session()->get('id'))
->where('status', 0)
->get();

View File

@ -9,7 +9,8 @@ use App\Utils\Helper;
class NoticeController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('created_at', 'DESC')->first()
]);

View File

@ -3,28 +3,32 @@
namespace App\Http\Controllers;
use App\Http\Requests\OrderSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redis;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Models\Coupon;
use App\Utils\Helper;
use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC')
->get();
$plan = Plan::get();
for($i = 0; $i < count($order); $i++) {
for($x = 0; $x < count($plan); $x++) {
for ($i = 0; $i < count($order); $i++) {
for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
@ -34,8 +38,9 @@ class OrderController extends Controller
'data' => $order
]);
}
public function details (Request $request) {
public function details(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
@ -43,7 +48,8 @@ class OrderController extends Controller
abort(500, '订单不存在');
}
$order['plan'] = Plan::find($order->plan_id);
$order['update_fee'] = config('v2board.plan_update_fee', 0.5);
$order['plan_transfer_hour'] = config('v2board.plan_transfer_hour', 12);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) {
abort(500, '订阅不存在');
}
@ -51,15 +57,31 @@ class OrderController extends Controller
'data' => $order
]);
}
public function save (OrderSave $request) {
private function isExistNotPayOrderByUserId($userId)
{
$order = Order::where('status', 0)
->where('user_id', $userId)
->first();
if (!$order) {
return false;
}
return true;
}
public function save(OrderSave $request)
{
if ($this->isExistNotPayOrderByUserId($request->session()->get('id'))) {
abort(500, '存在未付款订单,请取消后再试');
}
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
if (!($plan->show || $user->plan_id == $plan->id)) {
abort(500, '该订阅已售罄');
}
@ -68,26 +90,68 @@ class OrderController extends Controller
abort(500, '该订阅无法续费,请更换其他订阅');
}
if (!(int)$plan[$request->input('cycle')]) {
if ($plan[$request->input('cycle')] === NULL) {
abort(500, '该订阅周期无法进行购买,请选择其他周期');
}
if ($request->input('coupon_code')) {
$coupon = Coupon::where('code', $request->input('coupon_code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
}
}
DB::beginTransaction();
$order = new Order();
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
// renew and change subscribe process
if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
$order->type = 3;
if (!(int)config('v2board.plan_is_update', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
$order->total_amount = $order->total_amount + (ceil(($user->expired_at - time()) / 86400) * config('v2board.plan_update_fee', 0.5) * 100);
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
if ($user->invite_user_id) {
// coupon process
if (isset($coupon)) {
switch ($coupon->type) {
case 1:
$order->discount_amount = $coupon->value;
break;
case 2:
$order->discount_amount = $order->total_amount * ($coupon->value / 100);
break;
}
$order->total_amount = $order->total_amount - $order->discount_amount;
if ($coupon->limit_use !== NULL) {
$coupon->limit_use = $coupon->limit_use - 1;
if (!$coupon->save()) {
DB::rollback();
abort(500, '优惠券使用失败');
}
}
}
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
}
// invite process
if ($user->invite_user_id && $order->total_amount > 0) {
$order->invite_user_id = $user->invite_user_id;
$inviter = User::find($user->invite_user_id);
if ($inviter && $inviter->commission_rate) {
@ -97,15 +161,19 @@ class OrderController extends Controller
}
}
if (!$order->save()) {
DB::rollback();
abort(500, '订单创建失败');
}
DB::commit();
return response([
'data' => $order->trade_no
]);
}
public function checkout (Request $request) {
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
@ -153,12 +221,21 @@ class OrderController extends Controller
'type' => 1,
'data' => $this->bitpayX($order)
]);
case 5:
if (!(int)config('v2board.paytaro_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->payTaro($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check (Request $request) {
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
@ -171,7 +248,8 @@ class OrderController extends Controller
]);
}
public function getPaymentMethod () {
public function getPaymentMethod()
{
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
@ -199,18 +277,50 @@ class OrderController extends Controller
if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass();
$bitpayX->name = '虚拟货币';
$bitpayX->name = '聚合支付';
$bitpayX->method = 4;
$bitpayX->icon = 'bitcoin';
$bitpayX->icon = 'wallet';
array_push($data, $bitpayX);
}
if ((int)config('v2board.paytaro_enable')) {
$obj = new \StdClass();
$obj->name = '聚合支付';
$obj->method = 5;
$obj->icon = 'wallet';
array_push($data, $obj);
}
return response([
'data' => $data
]);
}
private function alipayF2F ($tradeNo, $totalAmount) {
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数有误');
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) {
abort(500, '只可以取消待支付订单');
}
$order->status = 2;
if (!$order->save()) {
abort(500, '取消失败');
}
return response([
'data' => true
]);
}
private function alipayF2F($tradeNo, $totalAmount)
{
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
@ -219,7 +329,7 @@ class OrderController extends Controller
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase();
$request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100
]);
@ -227,13 +337,14 @@ class OrderController extends Controller
$response = $request->send();
$result = $response->getAlipayResponse();
if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']);
abort(500, $result['sub_msg']);
}
// 获取收款二维码内容
return $response->getQrCode();
}
private function stripeAlipay ($order) {
private function stripeAlipay($order)
{
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
@ -250,15 +361,15 @@ class OrderController extends Controller
if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['redirect']['url'];
}
private function stripeWepay ($order) {
private function stripeWepay($order)
{
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
@ -275,29 +386,42 @@ class OrderController extends Controller
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['wechat']['qr_code_url'];
}
private function bitpayX ($order) {
private function bitpayX($order)
{
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'merchant_order_id' => 'V2Board_' . $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
$params = [
'merchant_order_id' => $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
];
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign);
$params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params);
Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false;
}
private function payTaro($order)
{
$payTaro = new PayTaro(config('v2board.paytaro_app_id'), config('v2board.paytaro_app_secret'));
$result = $payTaro->pay([
'app_id' => config('v2board.paytaro_app_id'),
'out_trade_no' => $order->trade_no,
'total_amount' => $order->total_amount,
'notify_url' => url('/api/v1/guest/order/payTaroNotify'),
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]);
return $result;
}
}

View File

@ -7,11 +7,14 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
use App\Jobs\SendEmail;
class CommController extends Controller
{
public function config () {
public function config()
{
return response([
'data' => [
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
@ -20,37 +23,35 @@ class CommController extends Controller
]);
}
private function isEmailVerify () {
private function isEmailVerify()
{
return response([
'data' => (int)config('v2board.email_verify', 0) ? 1 : 0
]);
}
public function sendEmailVerify (CommSendEmailVerify $request) {
public function sendEmailVerify(CommSendEmailVerify $request)
{
$email = $request->input('email');
$redisKey = 'sendEmailVerify:' . $email;
if (Redis::get($redisKey)) {
$cacheKey = 'sendEmailVerify:' . $email;
if (Cache::get($cacheKey)) {
abort(500, '验证码已发送,请过一会在请求');
}
$code = rand(100000, 999999);
$code = Helper::randomChar(6);
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
Mail::send(
'mail.sendEmailVerify',
[
'code' => $code,
'name' => config('v2board.app_name', 'V2Board')
],
function ($message) use($email, $subject) {
$message->to($email)->subject($subject);
}
);
if (count(Mail::failures()) >= 1) {
// 发送失败
abort(500, '发送失败');
}
Redis::set($redisKey, $code);
Redis::expire($redisKey, 600);
SendEmail::dispatch([
'email' => $email,
'subject' => $subject,
'template_name' => 'mail.sendEmailVerify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'code' => $code,
'url' => config('v2board.app_url')
]
])->onQueue('verify_mail');
Cache::put($cacheKey, $code, 60);
return response([
'data' => true
]);

View File

@ -7,13 +7,14 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class ForgetController extends Controller
{
public function index (ForgetIndex $request) {
public function index(ForgetIndex $request)
{
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (Redis::get($redisKey) !== $request->input('email_code')) {
if (Cache::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
$user = User::where('email', $request->input('email'))->first();
@ -21,7 +22,7 @@ class ForgetController extends Controller
if (!$user->save()) {
abort(500, '重置失败');
}
Redis::del($redisKey);
Cache::forget($redisKey);
return response([
'data' => true
]);

View File

@ -6,13 +6,16 @@ use Illuminate\Http\Request;
use App\Http\Requests\Passport\LoginIndex;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use App\Utils\Helper;
class LoginController extends Controller
{
public function index (LoginIndex $request) {
public function index(LoginIndex $request)
{
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
@ -20,7 +23,11 @@ class LoginController extends Controller
if (!password_verify($password, $user->password)) {
abort(500, '用户名或密码错误');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
@ -34,28 +41,52 @@ class LoginController extends Controller
]);
}
public function token2Login (Request $request) {
if (empty($request->input('token'))) {
abort(500, '参数错误');
public function token2Login(Request $request)
{
if ($request->input('token')) {
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
return header('Location:' . config('v2board.app_url'));
}
$code = Helper::guid();
$key = 'token2Login_' . $code;
Cache::put($key, $user->id, 600);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . $redirect;
} else {
$location = url($redirect);
}
return header('Location:' . $location);
}
$redirect = $request->input('redirect') ? $request->input('redirect') : 'dashboard';
$user = User::where('token', $request->input('token'))->first();
if ($user) {
if ($request->input('verify')) {
$key = 'token2Login_' . $request->input('verify');
$userId = Cache::get($key);
if (!$userId) {
abort(500, '令牌有误');
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
Cache::forget($key);
return response([
'data' => true
]);
}
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . '/#/' . $redirect;
} else {
$location = url('/#/' . $redirect);
}
header('Location:' . $location);
}
public function check (Request $request) {
public function check(Request $request)
{
return response([
'data' => $request->session()->get('id') ? true : false
]);

View File

@ -3,18 +3,22 @@
namespace App\Http\Controllers\Passport;
use App\Http\Requests\Passport\RegisterIndex;
use App\Http\Requests\Passport\RegisterSendEmailVerify;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Redis;
use App\Models\Plan;
use Illuminate\Support\Facades\Cache;
use App\Utils\Helper;
use App\Models\InviteCode;
class RegisterController extends Controller
{
public function index (RegisterIndex $request) {
private function setTryOut()
{
}
public function index(RegisterIndex $request)
{
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
}
@ -28,7 +32,7 @@ class RegisterController extends Controller
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
}
if (Redis::get($redisKey) !== $request->input('email_code')) {
if (Cache::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
}
@ -60,11 +64,22 @@ class RegisterController extends Controller
}
}
// try out
if ((int)config('v2board.try_out_enable', 0)) {
$plan = Plan::find(config('v2board.try_out_plan_id'));
if ($plan) {
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Redis::del($redisKey);
Cache::forget($redisKey);
}
return response()->json([
'data' => true

View File

@ -8,14 +8,20 @@ use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
if (empty($request->input('plan_id'))) {
abort(500, '参数错误');
}
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '该订阅不存在');
public function fetch(Request $request)
{
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))
->first();
if (!$plan) {
abort(500, '该订阅不存在');
}
return response([
'data' => $plan
]);
}
$plan = Plan::where('show', 1)
->get();
return response([
'data' => $plan
]);

View File

@ -1,12 +1,14 @@
<?php
namespace App\Http\Controllers\Server;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
public function __construct(Request $request) {
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');

View File

@ -9,19 +9,21 @@ use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class DeepbworkController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
// 后端获取用户
public function user (Request $request) {
public function user(Request $request)
{
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Redis::set('server_last_check_at_' . $server->id, time());
Cache::put('server_last_check_at_' . $server->id, time());
$users = User::whereIn('group_id', json_decode($server->group_id))
->select([
'id',
@ -56,26 +58,27 @@ class DeepbworkController extends Controller
}
// 后端提交数据
public function submit (Request $request) {
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
public function submit(Request $request)
{
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 1,
'msg' => 'ok'
]);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$user = User::find($item['user_id']);
$user->t = time();
$user->u = $user->u + $u;
$user->d = $user->d + $d;
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$user = User::find($item['user_id']);
$user->t = time();
$user->u = $user->u + $u;
$user->d = $user->d + $d;
$user->save();
$serverLog = new ServerLog();
$serverLog->user_id = $item['user_id'];
$serverLog->server_id = $request->input('node_id');
@ -84,15 +87,16 @@ class DeepbworkController extends Controller
$serverLog->rate = $server->rate;
$serverLog->save();
}
return response([
'ret' => 1,
'msg' => 'ok'
]);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
// 后端获取配置
public function config (Request $request) {
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
@ -108,26 +112,32 @@ class DeepbworkController extends Controller
$json->inbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp': $json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
case 'tcp':
$json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp': $json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
case 'kcp':
$json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws': $json->inbound->streamSettings->wsSettings = json_decode($server->settings);
case 'ws':
$json->inbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http': $json->inbound->streamSettings->httpSettings = json_decode($server->settings);
case 'http':
$json->inbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket': $json->inbound->streamSettings->dsSettings = json_decode($server->settings);
case 'domainsocket':
$json->inbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic': $json->inbound->streamSettings->quicSettings = json_decode($server->settings);
case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ((int)$server->tls) {
$json->inbound->streamSettings->security = "tls";
$tls = (object) array("certificateFile" => "/home/v2ray.crt", "keyFile" => "/home/v2ray.key");
$tls = (object)array("certificateFile" => "/home/v2ray.crt", "keyFile" => "/home/v2ray.key");
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
}
die(json_encode($json, JSON_UNESCAPED_UNICODE));
}
}

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
use App\Models\Server;
use App\Models\ServerLog;
@ -11,8 +11,10 @@ use App\Models\User;
use App\Utils\Helper;
class ServerController extends Controller {
public function fetch (Request $request) {
class ServerController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->session()->get('id'));
$server = [];
if ($user->expired_at > time()) {
@ -28,30 +30,38 @@ class ServerController extends Controller {
}
for ($i = 0; $i < count($server); $i++) {
$server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']);
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
}
}
return response([
'data' => $server
]);
}
public function logFetch (Request $request) {
$type = $request->input('type') ? $request->input('type') : 0;
public function logFetch(Request $request)
{
$type = $request->input('type') ? $request->input('type') : 0;
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$serverLogModel = ServerLog::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
switch ($type) {
case 0: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
break;
case 1: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
break;
case 2: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
}
$sum = [
'u' => $serverLogModel->sum('u'),
'd' => $serverLogModel->sum('d')
];
switch ($type) {
case 0:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
break;
case 1:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
break;
case 2:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
}
$sum = [
'u' => $serverLogModel->sum('u'),
'd' => $serverLogModel->sum('d')
];
$total = $serverLogModel->count();
$res = $serverLogModel->forPage($current, $pageSize)
->get();
@ -61,4 +71,4 @@ class ServerController extends Controller {
'sum' => $sum
]);
}
}
}

View File

@ -12,7 +12,8 @@ use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
@ -47,7 +48,8 @@ class TicketController extends Controller
]);
}
public function save (TicketSave $request) {
public function save(TicketSave $request)
{
DB::beginTransaction();
$ticket = Ticket::create(array_merge($request->only([
'subject',
@ -75,7 +77,8 @@ class TicketController extends Controller
]);
}
public function reply (Request $request) {
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -112,7 +115,8 @@ class TicketController extends Controller
}
public function close (Request $request) {
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -131,7 +135,8 @@ class TicketController extends Controller
]);
}
private function getLastMessage ($ticketId) {
private function getLastMessage($ticketId)
{
return TicketMessage::where('ticket_id', $ticketId)
->orderBy('id', 'DESC')
->first();

View File

@ -5,10 +5,12 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Tutorial;
class TutorialController extends Controller
{
public function getSubscribeUrl (Request $request) {
public function getSubscribeUrl(Request $request)
{
$user = User::find($request->session()->get('id'));
return response([
'data' => [
@ -17,7 +19,8 @@ class TutorialController extends Controller
]);
}
public function getAppleID (Request $request) {
public function getAppleID(Request $request)
{
$user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) {
return response([
@ -32,4 +35,42 @@ class TutorialController extends Controller
]
]);
}
public function fetch(Request $request)
{
if ($request->input('id')) {
$tutorial = Tutorial::where('show', 1)
->where('id', $request->input('id'))
->first();
if (!$tutorial) {
abort(500, '教程不存在');
}
return response([
'data' => $tutorial
]);
}
$tutorial = Tutorial::select(['id', 'title', 'description', 'icon'])
->where('show', 1)
->get();
$user = User::find($request->session()->get('id'));
$response = [
'data' => [
'tutorials' => $tutorial,
'safe_area_var' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'],
'app_name' => config('v2board.app_name', 'V2board'),
'apple_id' => $user->expired_at > time() ? config('v2board.apple_id', '管理员暂无提供AppleID信息') : '账号过期或未订阅',
'apple_id_password' => $user->expired_at > time() ? config('v2board.apple_id_password', '管理员暂无提供AppleID信息') : '账号过期或未订阅'
]
]
];
// fuck support shadowrocket urlsafeb64 subscribe
$response['data']['safe_area_var']['b64_subscribe_url'] = str_replace(
array('+', '/', '='),
array('-', '_', ''),
base64_encode($response['data']['safe_area_var']['subscribe_url'])
);
// end
return response($response);
}
}

View File

@ -15,13 +15,15 @@ use App\Models\ServerLog;
class UserController extends Controller
{
public function logout (Request $request) {
public function logout(Request $request)
{
return response([
'data' => $request->session()->flush()
]);
}
public function changePassword (Request $request) {
public function changePassword(Request $request)
{
if (empty($request->input('old_password'))) {
abort(500, '旧密码不能为空');
}
@ -41,11 +43,13 @@ class UserController extends Controller
'data' => true
]);
}
public function info (Request $request) {
public function info(Request $request)
{
$user = User::where('id', $request->session()->get('id'))
->select([
'email',
'transfer_enable',
'last_login_at',
'created_at',
'enable',
@ -54,7 +58,8 @@ class UserController extends Controller
'remind_traffic',
'expired_at',
'balance',
'commission_balance'
'commission_balance',
'plan_id'
])
->first();
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
@ -63,7 +68,8 @@ class UserController extends Controller
]);
}
public function getStat (Request $request) {
public function getStat(Request $request)
{
$stat = [
Order::where('status', 0)
->where('user_id', $request->session()->get('id'))
@ -79,7 +85,8 @@ class UserController extends Controller
]);
}
public function getSubscribe (Request $request) {
public function getSubscribe(Request $request)
{
$user = User::find($request->session()->get('id'));
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
@ -92,8 +99,9 @@ class UserController extends Controller
'data' => $user
]);
}
public function resetSecurity (Request $request) {
public function resetSecurity(Request $request)
{
$user = User::find($request->session()->get('id'));
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
@ -105,12 +113,13 @@ class UserController extends Controller
]);
}
public function update (UserUpdate $request) {
public function update(UserUpdate $request)
{
$updateData = $request->only([
'remind_expire',
'remind_traffic'
]);
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, '该用户不存在');

View File

@ -9,8 +9,8 @@ class Admin
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)

View File

@ -9,12 +9,12 @@ class Authenticate extends Middleware
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
if (!$request->expectsJson()) {
return route('login');
}
}

View File

@ -9,9 +9,9 @@ class CORS
public function handle($request, Closure $next)
{
$origin = $request->header('origin');
if(empty($origin)){
if (empty($origin)) {
$referer = $request->header('referer');
if(!empty($referer)&&preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)){
if (!empty($referer) && preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)) {
$origin = $matches[0];
}
}
@ -21,7 +21,7 @@ class CORS
$response->header('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With');
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', 10080);
return $response;
}
}
}

View File

@ -10,8 +10,8 @@ class Client
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)

View File

@ -9,9 +9,9 @@ class ForceJson
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)

View File

@ -10,9 +10,9 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)

View File

@ -10,8 +10,8 @@ class Server
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)

View File

@ -9,8 +9,8 @@ class User
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)

View File

@ -6,40 +6,55 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest
{
public static function filter() {
return [
'invite_force',
'invite_commission',
'invite_gen_limit',
'invite_never_expire',
'stop_register',
'email_verify',
'app_name',
'app_url',
'subscribe_url',
'plan_update_fee',
'plan_is_update',
// server
'server_token',
// alipay
'alipay_enable',
'alipay_appid',
'alipay_pubkey',
'alipay_privkey',
// stripe
'stripe_sk_live',
'stripe_pk_live',
'stripe_alipay_enable',
'stripe_wepay_enable',
'stripe_webhook_key',
// bitpayx,
'bitpayx_enable',
'bitpayx_appsecret',
// tutorial
'apple_id',
'apple_id_password'
];
CONST RULES = [
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_transfer_hour' => 'numeric',
'plan_change_enable' => 'in:0,1',
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
// server
'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
// bitpayx
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// paytaro
'paytaro_enable' => 'in:0,1',
'paytaro_app_id' => '',
'paytaro_app_secret' => '',
// frontend
'frontend_theme' => 'in:1,2',
'frontend_background_url' => 'nullable|url',
// tutorial
'apple_id' => 'email',
'apple_id_password' => ''
];
public static function filter()
{
return array_keys(self::RULES);
}
/**
* Get the validation rules that apply to the request.
*
@ -47,34 +62,9 @@ class ConfigSave extends FormRequest
*/
public function rules()
{
return [
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_update_fee' => 'numeric',
'plan_is_update' => 'in:0,1',
// server
'server_token' => 'min:16',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
// bitpayx
'bitpayx_enable' => 'in:0,1',
// tutorial
'apple_id' => 'email'
];
return self::RULES;
}
public function messages()
{
return [

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class CouponSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'type' => 'required|in:1,2',
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer'
];
}
public function messages()
{
return [
'name.required' => '名称不能为空',
'type.required' => '类型不能为空',
'type.in' => '类型格式有误',
'value.required' => '金额或比例不能为空',
'value.integer' => '金额或比例格式有误',
'started_at.required' => '开始时间不能为空',
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误'
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class MailSend extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => 'required|in:1,2',
'subject' => 'required',
'content' => 'required',
'receiver' => 'array'
];
}
public function messages()
{
return [
'type.required' => '发送类型不能为空',
'type.in' => '发送类型格式有误',
'subject.required' => '主题不能为空',
'content.required' => '内容不能为空',
'receiver.array' => '收件人格式有误'
];
}
}

View File

@ -19,7 +19,7 @@ class NoticeSave extends FormRequest
'img_url' => 'url'
];
}
public function messages()
{
return [

View File

@ -18,7 +18,7 @@ class OrderUpdate extends FormRequest
'commission_status' => 'in:0,1'
];
}
public function messages()
{
return [

View File

@ -17,23 +17,23 @@ class PlanSave extends FormRequest
'name' => 'required',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'required|numeric',
'quarter_price' => 'required|numeric',
'half_year_price' => 'required|numeric',
'year_price' => 'required|numeric'
'month_price' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer'
];
}
public function messages()
{
return [
'name.required' => '套餐名称不能为空',
'group_id.required' => '权限组不能为空',
'transfer_enable.required' => '流量不能为空',
'month_price.required' => '月付金额不能为空',
'quarter_price.required' => '季付金额不能为空',
'half_year_price.required' => '半年付金额不能为空',
'year_price.required' => '年付金额不能为空'
'month_price.integer' => '月付金额格式有误',
'quarter_price.integer' => '季付金额格式有误',
'half_year_price.integer' => '半年付金额格式有误',
'year_price.integer' => '年付金额格式有误'
];
}
}

View File

@ -18,7 +18,7 @@ class PlanUpdate extends FormRequest
'renew' => 'in:0,1'
];
}
public function messages()
{
return [

View File

@ -16,6 +16,7 @@ class ServerSave extends FormRequest
return [
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
@ -25,13 +26,14 @@ class ServerSave extends FormRequest
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic'
];
}
public function messages()
{
return [
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空',

View File

@ -11,14 +11,14 @@ class ServerUpdate extends FormRequest
*
* @return array
*/
public function rules()
{
return [
'show' => 'in:0,1'
];
}
public function messages()
{
return [

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'description' => 'required',
'icon' => 'required'
];
}
public function messages()
{
return [
'title.required' => '标题不能为空',
'description.required' => '描述不能为空',
'icon.required' => '图标不能为空'
];
}
}

View File

@ -23,7 +23,7 @@ class UserUpdate extends FormRequest
'commission_rate' => 'nullable|integer|min:0|max:100'
];
}
public function messages()
{
return [

View File

@ -18,7 +18,7 @@ class OrderSave extends FormRequest
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price'
];
}
public function messages()
{
return [

View File

@ -17,12 +17,12 @@ class CommSendEmailVerify extends FormRequest
'email' => 'required|email'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确'
'email.email' => '邮箱格式不正确'
];
}
}

View File

@ -19,12 +19,12 @@ class ForgetIndex extends FormRequest
'email_code' => 'required'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数',
'email_code.required' => '邮箱验证码不能为空'

View File

@ -18,12 +18,12 @@ class LoginIndex extends FormRequest
'password' => 'required|min:8'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
];

View File

@ -18,12 +18,12 @@ class RegisterIndex extends FormRequest
'password' => 'required|min:8'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
];

View File

@ -19,7 +19,7 @@ class TicketSave extends FormRequest
'message' => 'required'
];
}
public function messages()
{
return [

View File

@ -18,7 +18,7 @@ class UserUpdate extends FormRequest
'remind_traffic' => 'in:0,1'
];
}
public function messages()
{
return [

View File

@ -8,11 +8,13 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use App\Models\MailLog;
class SendEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params;
/**
* Create a new job instance.
*
@ -33,12 +35,23 @@ class SendEmail implements ShouldQueue
$params = $this->params;
$email = $params['email'];
$subject = $params['subject'];
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use($email, $subject) {
$message->to($email)->subject($subject);
}
);
try {
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use ($email, $subject) {
$message->to($email)->subject($subject);
}
);
} catch (\Exception $e) {
$error = $e->getMessage();
}
MailLog::create([
'email' => $params['email'],
'subject' => $params['subject'],
'template_name' => $params['template_name'],
'error' => isset($error) ? $error : NULL
]);
}
}

12
app/Models/Coupon.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Coupon extends Model
{
protected $table = 'v2_coupon';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

12
app/Models/MailLog.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MailLog extends Model
{
protected $table = 'v2_mail_log';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

12
app/Models/Tutorial.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tutorial extends Model
{
protected $table = 'v2_tutorial';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

View File

@ -52,8 +52,8 @@ class RouteServiceProvider extends ServiceProvider
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
@ -66,8 +66,8 @@ class RouteServiceProvider extends ServiceProvider
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}

8
app/Utils/CacheKey.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace App\Utils;
class CacheKey
{
}

View File

@ -4,7 +4,8 @@ namespace App\Utils;
class Helper
{
public static function guid ($format = false) {
public static function guid($format = false)
{
if (function_exists('com_create_guid') === true) {
return md5(trim(com_create_guid(), '{}'));
}
@ -14,16 +15,18 @@ class Helper
if ($format) {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)).'-'.time());
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . '-' . time());
}
public static function exchange ($from, $to) {
public static function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangeratesapi.io/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
public static function randomChar($len, $special=false){
public static function randomChar($len, $special = false)
{
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
@ -32,40 +35,44 @@ class Helper
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9"
);
if($special){
if ($special) {
$chars = array_merge($chars, array(
"!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
"%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
"}", "<", ">", "~", "+", "=", ",", "."
));
}
$charsLen = count($chars) - 1;
shuffle($chars);
$str = '';
for($i=0; $i<$len; $i++){
for ($i = 0; $i < $len; $i++) {
$str .= $chars[mt_rand(0, $charsLen)];
}
return $str;
}
public static function buildVmessLink($item, $user) {
public static function buildVmessLink($item, $user)
{
$config = [
"v" => "2",
"ps" => $item->name,
"add" => $item->host,
"port" => $item->port,
"id" => $user->v2ray_uuid,
"aid" => "2",
"net" => $item->network,
"type" => "chacha20-poly1305",
"type" => "none",
"host" => "",
"tls" => $item->tls?"tls":"",
"path" => "",
"tls" => $item->tls ? "tls" : ""
];
if ($item->network == 'ws') {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $config['path'] = $wsSettings->path;
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
}
return "vmess://".base64_encode(json_encode($config))."\r\n";
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
}

View File

@ -14,6 +14,7 @@
"laravel/framework": "^6.0",
"laravel/tinker": "^1.0",
"lokielse/omnipay-alipay": "^3.0",
"php-curl-class/php-curl-class": "^8.6",
"stripe/stripe-php": "^7.5",
"symfony/yaml": "^4.3"
},

View File

@ -98,6 +98,6 @@ return [
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
];

View File

@ -123,7 +123,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View File

@ -51,7 +51,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],

View File

@ -126,7 +126,7 @@ return [
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
),
/*

0
database/.gitignore vendored Executable file → Normal file
View File

1
database/factories/UserFactory.php Executable file → Normal file
View File

@ -1,6 +1,7 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

View File

View File

View File

0
database/seeds/DatabaseSeeder.php Executable file → Normal file
View File

View File

@ -5,6 +5,24 @@ SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET NAMES utf8mb4;
DROP TABLE IF EXISTS `v2_coupon`;
CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` char(8) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`type` tinyint(1) NOT NULL,
`value` int(11) NOT NULL,
`limit_use` int(11) DEFAULT NULL,
`started_at` int(11) NOT NULL,
`ended_at` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_invite_code`;
CREATE TABLE `v2_invite_code` (
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -17,6 +35,19 @@ CREATE TABLE `v2_invite_code` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_mail_log`;
CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(64) NOT NULL,
`subject` varchar(255) NOT NULL,
`template_name` varchar(255) NOT NULL,
`error` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_notice`;
CREATE TABLE `v2_notice` (
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -40,6 +71,7 @@ CREATE TABLE `v2_order` (
`trade_no` varchar(36) NOT NULL,
`callback_no` varchar(255) DEFAULT NULL,
`total_amount` int(11) NOT NULL,
`discount_amount` int(11) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`commission_status` tinyint(1) NOT NULL DEFAULT '0',
`commission_balance` int(11) NOT NULL DEFAULT '0',
@ -58,23 +90,22 @@ CREATE TABLE `v2_plan` (
`show` tinyint(1) NOT NULL DEFAULT '0',
`renew` tinyint(1) NOT NULL DEFAULT '1',
`content` text,
`month_price` int(11) NOT NULL DEFAULT '0',
`quarter_price` int(11) NOT NULL DEFAULT '0',
`half_year_price` int(11) NOT NULL DEFAULT '0',
`year_price` int(11) NOT NULL DEFAULT '0',
`month_price` int(11) DEFAULT '0',
`quarter_price` int(11) DEFAULT '0',
`half_year_price` int(11) DEFAULT '0',
`year_price` int(11) DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET NAMES utf8mb4;
DROP TABLE IF EXISTS `v2_server`;
CREATE TABLE `v2_server` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
@ -106,7 +137,7 @@ CREATE TABLE `v2_server_log` (
`server_id` int(11) NOT NULL,
`u` varchar(255) NOT NULL,
`d` varchar(255) NOT NULL,
`rate` int(11) NOT NULL,
`rate` decimal(10,2) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -138,6 +169,25 @@ CREATE TABLE `v2_ticket_message` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_tutorial`;
CREATE TABLE `v2_tutorial` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`icon` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`steps` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
(1, 'Windows', '兼容 Windows 7 以上的版本', 'fab fa-2x fa-windows', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577984396),
(2, 'Android', '兼容 Android 6 以上的版本', 'fab fa-2x fa-android', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577984397),
(3, 'macOS', '兼容 Yosemite 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577984397),
(4, 'iOS', '兼容 iOS 9 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]', 1, 1577982016, 1577983283);
DROP TABLE IF EXISTS `v2_user`;
CREATE TABLE `v2_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -172,4 +222,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2019-12-27 07:14:40
-- 2020-01-20 15:33:23

View File

@ -1,18 +1,22 @@
<?php
namespace Library;
class BitpayX {
class BitpayX
{
private $bitpayxAppSecret;
private $bitpayxGatewayUri;
/**
* 签名初始化
* @param merKey 签名密钥
*/
public function __construct($bitpayxAppSecret) {
$this->bitpayxAppSecret = $bitpayxAppSecret;
$this->bitpayxGatewayUri = 'https://api.mugglepay.com/v1/';
}
* 签名初始化
* @param merKey 签名密钥
*/
public function __construct($bitpayxAppSecret)
{
$this->bitpayxAppSecret = $bitpayxAppSecret;
$this->bitpayxGatewayUri = 'https://api.mugglepay.com/v1/';
}
public function prepareSignId($tradeno)
{
$data_sign = array();
@ -22,15 +26,18 @@ class BitpayX {
ksort($data_sign);
return http_build_query($data_sign);
}
public function sign($data)
{
return strtolower(md5(md5($data) . $this->bitpayxAppSecret));
}
public function verify($data, $signature)
{
$mySign = $this->sign($data);
return $mySign === $signature;
}
public function mprequest($data)
{
$headers = array('content-type: application/json', 'token: ' . $this->bitpayxAppSecret);
@ -48,6 +55,7 @@ class BitpayX {
curl_close($curl);
return json_decode($data, true);
}
public function mpcheckout($orderId, $data)
{
$headers = array('content-type: application/json', 'token: ' . $this->bitpayxAppSecret);
@ -65,17 +73,21 @@ class BitpayX {
curl_close($curl);
return json_decode($data, true);
}
public function refund($merchantTradeNo) {
public function refund($merchantTradeNo)
{
// TODO
return true;
}
public function buildHtml($params, $method = 'post', $target = '_self'){
public function buildHtml($params, $method = 'post', $target = '_self')
{
// var_dump($params);exit;
$html = "<form id='submit' name='submit' action='".$this->gatewayUri."' method='$method' target='$target'>";
foreach ($params as $key => $value) {
$html .= "<input type='hidden' name='$key' value='$value'/>";
}
$html .= "</form><script>document.forms['submit'].submit();</script>";
return $html;
$html = "<form id='submit' name='submit' action='" . $this->gatewayUri . "' method='$method' target='$target'>";
foreach ($params as $key => $value) {
$html .= "<input type='hidden' name='$key' value='$value'/>";
}
$html .= "</form><script>document.forms['submit'].submit();</script>";
return $html;
}
}
}

48
library/PayTaro.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace Library;
use \Curl\Curl;
class PayTaro
{
private $appId;
private $appSecret;
public function __construct($appId, $appSecret)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
}
public function pay($params)
{
ksort($params);
$str = http_build_query($params) . $this->appSecret;
$params['sign'] = md5($str);
$curl = new Curl();
$curl->post('http://api.paytaro.com/v1/gateway/fetch', http_build_query($params));
if ($curl->error) {
abort(500, '接口请求失败');
}
$result = $curl->response;
$curl->close();
if (!isset($result->data->trade_no)) {
abort(500, '接口请求失败');
}
return $result->data->pay_url;
}
public function verify($params)
{
$sign = $params['sign'];
unset($params['sign']);
ksort($params);
reset($params);
$str = http_build_query($params) . $this->appSecret;
if ($sign !== md5($str)) {
return false;
}
return true;
}
}

63
library/TomatoPay.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace Library;
class TomatoPay
{
private $mchid;
private $account;
private $key;
public function __construct($mchid, $account, $key)
{
$this->mchid = $mchid;
$this->account = $account;
$this->key = $key;
}
public function alipay($cny, $trade)
{
$params = [
'mchid' => $this->mchid,
'account' => $this->account,
'cny' => $cny,
'type' => '1',
'trade' => $trade
];
$params['signs'] = $this->sign($params);
return $this->buildHtml('https://b.fanqieui.com/gateways/alipay.php', $params);
}
public function wxpay($cny, $trade)
{
$params = [
'mchid' => $this->mchid,
'account' => $this->account,
'cny' => $cny,
'type' => '1',
'trade' => $trade
];
$params['signs'] = $this->sign($params);
return $this->buildHtml('https://b.fanqieui.com/gateways/wxpay.php', $params);
}
public function sign($params)
{
$o = '';
foreach ($params as $k => $v) {
$o .= "$k=" . ($v) . "&";
}
return md5(substr($o, 0, -1) . $this->key);
}
public function buildHtml($url, $params, $method = 'post', $target = '_self')
{
// return var_dump($params);
$html = "<form id='submit' name='submit' action='" . $url . "' method='$method' target='$target'>";
foreach ($params as $key => $value) {
$html .= "<input type='hidden' name='$key' value='$value'/>";
}
$html .= "</form><script>document.forms['submit'].submit();</script>";
return $html;
}
}

5
pm2.yaml Normal file
View File

@ -0,0 +1,5 @@
apps:
- name : 'V2Board Queue'
script : 'php artisan queue:work --queue=verify_mail,other_mail'
instances: 4
out_file : './storage/logs/queue/queue.log'

1
public/antd.async.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,10 @@
window.v2board = {
// 站点标题
title: 'V2Board',
// API
host: '',
// 主题
theme: '1',
host: ''
// 背景
background_url: ''
}

View File

@ -21,7 +21,7 @@ define('LARAVEL_START', microtime(true));
|
*/
require __DIR__.'/../vendor/autoload.php';
require __DIR__ . '/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
@ -35,7 +35,7 @@ require __DIR__.'/../vendor/autoload.php';
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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