mirror of
https://github.com/v2board/v2board.git
synced 2025-08-02 21:38:49 +08:00
Compare commits
459 Commits
Author | SHA1 | Date | |
---|---|---|---|
e0927de030 | |||
b706b8b3a1 | |||
f3dee5c230 | |||
b984b8dd98 | |||
d2589d340d | |||
b5376c9c1e | |||
7c69031db8 | |||
43f1dbaedb | |||
b083c4fe78 | |||
b2c53804d7 | |||
92e6947525 | |||
91bf999162 | |||
4f8ba2b59d | |||
001f0ced41 | |||
58ad896e45 | |||
6e789037d1 | |||
365775970c | |||
971637ffd6 | |||
a905a5ad27 | |||
565072e333 | |||
dc42e82dc0 | |||
4c457d183f | |||
2d67446ce3 | |||
4c73d55342 | |||
ee762fe69a | |||
2782bd1a2c | |||
2a237abade | |||
75588d46f1 | |||
37449b6640 | |||
0248a562b3 | |||
13cfb11a85 | |||
015798accd | |||
f10fc4c13e | |||
b5a9b3e68c | |||
ffd13fbc64 | |||
8bff334758 | |||
a63a154ee2 | |||
976dfd2d98 | |||
4f277097f6 | |||
cfd72ac515 | |||
6324645f08 | |||
80e6fb7186 | |||
13f17d41f0 | |||
a45f8e121d | |||
b6f2a034ec | |||
d3d18d2390 | |||
65a4abf51d | |||
0c7d27e331 | |||
ac13d1a8e1 | |||
0f43aee0e3 | |||
bbb42c0d46 | |||
3cdfc69b5d | |||
bf915214dd | |||
2fc77a38f2 | |||
e733a53e85 | |||
ff3451d0e0 | |||
d418443404 | |||
172af72761 | |||
2414ad5d96 | |||
796de25e2d | |||
9874ef2f72 | |||
da98dcad9c | |||
0e4d5c9e99 | |||
066fd96fb5 | |||
f24b38a4ba | |||
a1ae4caee3 | |||
747a3c5c06 | |||
943304eb02 | |||
9b82df98f5 | |||
3df4e04605 | |||
eace40eb08 | |||
49b60f7c23 | |||
47a6c4077a | |||
3200c427ce | |||
a40a1ab674 | |||
397436761f | |||
0fc9b1d867 | |||
d6b22011ba | |||
78f8bd6906 | |||
2e64b3873c | |||
022df26134 | |||
54e59f1178 | |||
5982f3349c | |||
beb2c90321 | |||
036bb00c08 | |||
b001f54b84 | |||
15a3f16b9f | |||
5d413fac55 | |||
136cffcf13 | |||
ca04634537 | |||
288bd43d44 | |||
26df97a862 | |||
95e698dbc8 | |||
c3e924036a | |||
8dc19ee67b | |||
d020ddf926 | |||
334f70f19e | |||
49e155797a | |||
53e1e41902 | |||
afc9b462e5 | |||
5c2c7e502c | |||
c3eac30c66 | |||
f667f5ee41 | |||
8838381432 | |||
a6bcc3f153 | |||
593066f9de | |||
73cbfba19b | |||
17e3905f18 | |||
b7e8db727c | |||
7464466b85 | |||
417d5255e7 | |||
9885661795 | |||
bade7e2cf3 | |||
88c0e75937 | |||
7eb5532c30 | |||
eb49e29cd0 | |||
6a5c3f6206 | |||
ccd52546c8 | |||
d9b4a872ff | |||
6c3148bdb9 | |||
afcd0d9c10 | |||
b851f1207a | |||
2e13bab3d8 | |||
bd1b339db8 | |||
0a32b0b085 | |||
c90aa538bc | |||
506d662ae9 | |||
dab9afca53 | |||
6e509dab73 | |||
9ea13bb00f | |||
59672a6f2c | |||
71c42765fc | |||
c5b56da958 | |||
83592d2f3f | |||
941289c641 | |||
e3c5466c0a | |||
c5b5abab1a | |||
03eb8d0724 | |||
1ed5a278da | |||
108d54f3cb | |||
c648308634 | |||
12db88b998 | |||
11ca911d02 | |||
de77170bdc | |||
f030023ec0 | |||
fddd816129 | |||
fa6aea6e2d | |||
ce19ebc97f | |||
ca650dd067 | |||
1acfd84d4b | |||
29d7228861 | |||
422b18ca66 | |||
871291e02d | |||
f26d9495e3 | |||
a7d6b615ed | |||
8afa3c8f09 | |||
503ac97a8c | |||
ade3770d50 | |||
83cbe86192 | |||
22643f04b1 | |||
af71ab8e27 | |||
c29bd836eb | |||
0c2090cb3c | |||
f7959dcd93 | |||
9158697546 | |||
c6cfa2d31c | |||
aaa04f12a3 | |||
a4df1416de | |||
d1a2e7a29e | |||
a3b400ed32 | |||
0d6d10421b | |||
deb12ae707 | |||
bf3b7bb66f | |||
15a28e7bd3 | |||
c46b8b1b40 | |||
98b9ca62f6 | |||
6857967eec | |||
9fed8b8f9d | |||
c5d74f8b38 | |||
3167306bc8 | |||
51fee8892e | |||
25d4c5b31d | |||
aaad8a7f7e | |||
5630066aa4 | |||
998ac1d500 | |||
890626d892 | |||
dae5d2a2a3 | |||
c7a45c9d3d | |||
71a8daf271 | |||
8fd0592139 | |||
153a0e9fdf | |||
9f3aaac614 | |||
ba1c4ffa00 | |||
99117cca58 | |||
42ad99065e | |||
1da49f7f9f | |||
16bdafc952 | |||
ecca13911c | |||
8a0ac687cf | |||
2e4de78923 | |||
867f1760d3 | |||
e2a3a1e72d | |||
c17b614e13 | |||
45a76b25ef | |||
3f2c8266de | |||
94158cb6e3 | |||
467f33c71d | |||
2076dded41 | |||
9e07861c9b | |||
6e2379cb6b | |||
295b4552d7 | |||
4ccd41e197 | |||
3b486e4693 | |||
50b5ed6b8e | |||
39ae037080 | |||
89b6fe119f | |||
bb56b581be | |||
1cc0dea454 | |||
4301d7e4ab | |||
2523253637 | |||
1b3833173d | |||
54a8542e0f | |||
3e550142cd | |||
b020f2c196 | |||
e1b16ef7e6 | |||
887aad7737 | |||
a1f2290ff2 | |||
d23daf4a68 | |||
c4868a9c48 | |||
6050e6e4a9 | |||
7c69e19304 | |||
fd42a855cf | |||
e73cbe9597 | |||
276b040581 | |||
495a5f89c5 | |||
7c3309164b | |||
674b31675a | |||
b2c33cd31b | |||
4240e8355a | |||
7770bf6b99 | |||
55118d7706 | |||
2b34f5ec82 | |||
7dff7ddfc7 | |||
ed3e468a0a | |||
901d89b5d7 | |||
402b9e0c3f | |||
ae543d1c2c | |||
5d9b98f383 | |||
a2183b7143 | |||
a2278487ee | |||
075f7b39a8 | |||
3db93b4739 | |||
1fc9f94dad | |||
bdfa1ff0d5 | |||
98e4aca61f | |||
139aeb3f48 | |||
dfdf995ddb | |||
4b4d777a4e | |||
42607a789d | |||
79f53f2836 | |||
d1bf743316 | |||
39fadd8a63 | |||
4831c9f194 | |||
ee80e0f2ff | |||
1be7151b6c | |||
bb1ad02cf8 | |||
64f379d99d | |||
c271647ecc | |||
e8b6f1b481 | |||
1c6907fe33 | |||
68f7cdeed8 | |||
40e5ee62b1 | |||
9f9bb14e9d | |||
bcc80581d3 | |||
288f4aba18 | |||
cc673cdbd1 | |||
0781a0740b | |||
8d56db2bf6 | |||
56fd3f5b99 | |||
5a84e412c4 | |||
3216a90235 | |||
59fa3a3316 | |||
06465b3eb3 | |||
cc12605984 | |||
8fa9d60c4d | |||
1c3eff241a | |||
79aa942d5b | |||
f4688b6d50 | |||
d9c0d18689 | |||
7e88a39249 | |||
064834001d | |||
49d5b407bc | |||
1291bf47be | |||
e15d5961f0 | |||
04c6b865b4 | |||
5b33bf7a0b | |||
2235a5e7c5 | |||
6fba0b6dab | |||
3075f0d411 | |||
111d2720bd | |||
03e0b5d087 | |||
96f562a9e7 | |||
7bb4852cca | |||
8986ba1d42 | |||
f193f35642 | |||
2a92ee8b41 | |||
56cabbdc00 | |||
5f4d02dde3 | |||
00c2dee361 | |||
35917ad199 | |||
e4cb6458c0 | |||
93c1031078 | |||
26252aee02 | |||
8d10b52a35 | |||
6e7da97fcd | |||
13dbb143f8 | |||
01da63f82e | |||
d2a0422f64 | |||
c81cb8acca | |||
6bf0d2d94e | |||
260c1d7361 | |||
6a6de2dc22 | |||
ba9ec7006b | |||
f17b5d04a8 | |||
cccd8f36ee | |||
4d7ebe4aea | |||
8ac8427c2f | |||
97056be8c3 | |||
68d44e7657 | |||
b07511f01b | |||
a13809ac02 | |||
5b317478c6 | |||
57fd282024 | |||
27ccf9869d | |||
5e5e3fbb08 | |||
f5132abad1 | |||
4f709bf1f6 | |||
e62ef5cb0a | |||
e5c207ccff | |||
6b7cea671d | |||
dbf73f3a38 | |||
a3b7130857 | |||
c0bae87556 | |||
f18715ed09 | |||
7f3e0d9fb9 | |||
e5c8e18206 | |||
796a5ba55e | |||
9bf6f64f71 | |||
66957eff6d | |||
4e03662e8f | |||
fc48f5553f | |||
c4ddde1c94 | |||
92f44d7a2e | |||
3bfd64d8ca | |||
cb2efac160 | |||
c3898ec795 | |||
6dc5dd0edb | |||
e97f6c64a0 | |||
89630d889d | |||
3becb71a5a | |||
d361ff80ed | |||
3fe442313d | |||
f76609a38d | |||
51f4ad417e | |||
516b2626ae | |||
b9312f362c | |||
102f18bef1 | |||
1fc36d8e29 | |||
a3837f83cb | |||
47f27f4852 | |||
e73eaea66c | |||
bc5ddbf40a | |||
a2c674f00c | |||
218338960b | |||
d9dcea4e4f | |||
fad12a62ce | |||
1ee104ec88 | |||
19dcd0ffba | |||
ab2e39f7dd | |||
8f4ebe1764 | |||
3763320261 | |||
376c79aa91 | |||
47686d50c5 | |||
c8cb1f8e83 | |||
620ed8e04c | |||
c28a6ec1d3 | |||
3c78bed800 | |||
72fb0d131d | |||
378c96d4c7 | |||
ca2a744b10 | |||
0f72e9a091 | |||
bb49fb15d1 | |||
47d8dfd7c8 | |||
60b197410c | |||
e2b73094c0 | |||
7d8b8ad8ac | |||
1992b0a9e9 | |||
b4bede869d | |||
a11e65f84a | |||
e1a8ffe7c4 | |||
2217286d03 | |||
dd924d95c6 | |||
53710f2e01 | |||
f03c53815c | |||
6e98e42f51 | |||
9932e34634 | |||
6ba4702539 | |||
b29ac8ac2c | |||
866fda84ed | |||
95c7c48cc7 | |||
010f0cbc03 | |||
0d2b8bc976 | |||
3b9e9cbe7f | |||
a7b47d8f77 | |||
dea26121fc | |||
10e4de39de | |||
e7fd81bf4c | |||
0f9cb9696d | |||
a7b3d6e778 | |||
637bacd62f | |||
2171ef59d7 | |||
0a92308ad2 | |||
8ff53673d7 | |||
07772ceb66 | |||
fc8333d757 | |||
64dbd11e62 | |||
bfeab8eae2 | |||
5ee6fc2996 | |||
018e4aa810 | |||
17d6d04cc6 | |||
e174ef193b | |||
1887259554 | |||
8b804913f5 | |||
a2ea88beb5 | |||
d969220654 | |||
5154993887 | |||
61b31c1925 | |||
a0454577cb | |||
f7fc6b9dfc | |||
1b7fc9529f | |||
a56faa7e8e | |||
0a2c626f89 | |||
19df032b57 | |||
de8f95ee3d | |||
568648dc2f | |||
9f5be4e83a | |||
14d0de18ec | |||
64ae39aa2d | |||
fb09baa3c1 | |||
153bdcaad1 | |||
f3ac8a37be | |||
9925ab6b47 | |||
84ff8d3ab3 | |||
fcec3af75f | |||
2a9a8805a8 | |||
72eafb9698 | |||
e837e774ef | |||
00fcff5f0d | |||
6b43597eaf |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
.env.backup
|
||||
.phpunit.result.cache
|
||||
.idea
|
||||
.lock
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
|
@ -38,6 +38,24 @@ class CheckCommission extends Command
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->autoCheck();
|
||||
$this->autoPayCommission();
|
||||
}
|
||||
|
||||
public function autoCheck()
|
||||
{
|
||||
if ((int)config('v2board.commission_auto_check_enable', 1)) {
|
||||
Order::where('commission_status', 0)
|
||||
->where('status', 3)
|
||||
->where('updated_at', '<=', strtotime('-3 day', time()))
|
||||
->update([
|
||||
'commission_status' => 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function autoPayCommission()
|
||||
{
|
||||
$order = Order::where('commission_status', 1)
|
||||
->where('status', 3)
|
||||
|
@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\OrderService;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
use App\Models\Plan;
|
||||
use App\Utils\Helper;
|
||||
use App\Models\Coupon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CheckOrder extends Command
|
||||
{
|
||||
@ -42,14 +44,14 @@ class CheckOrder extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$order = Order::get();
|
||||
foreach ($order as $item) {
|
||||
$orders = Order::get();
|
||||
foreach ($orders as $item) {
|
||||
switch ($item->status) {
|
||||
// cancel
|
||||
case 0:
|
||||
if (strtotime($item->created_at) <= (time() - 1800)) {
|
||||
$item->status = 2;
|
||||
$item->save();
|
||||
$orderService = new OrderService($item);
|
||||
$orderService->cancel();
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@ -60,33 +62,79 @@ class CheckOrder extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function orderHandle($order)
|
||||
private function orderHandle(Order $order)
|
||||
{
|
||||
$user = User::find($order->user_id);
|
||||
return $this->buy($order, $user);
|
||||
}
|
||||
|
||||
private function buy($order, $user)
|
||||
{
|
||||
$plan = Plan::find($order->plan_id);
|
||||
// change plan process
|
||||
if ($order->type == 3) {
|
||||
$user->expired_at = time();
|
||||
}
|
||||
|
||||
if ($order->refund_amount) {
|
||||
$user->balance = $user->balance + $order->refund_amount;
|
||||
}
|
||||
DB::beginTransaction();
|
||||
if ($order->surplus_order_ids) {
|
||||
try {
|
||||
Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
|
||||
'status' => 4
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
abort(500, '开通失败');
|
||||
}
|
||||
}
|
||||
switch ((string)$order->cycle) {
|
||||
case 'onetime_price':
|
||||
$this->buyByOneTime($order, $user, $plan);
|
||||
break;
|
||||
case 'reset_price':
|
||||
$this->buyReset($user);
|
||||
break;
|
||||
default:
|
||||
$this->buyByCycle($order, $user, $plan);
|
||||
}
|
||||
if (!$user->save()) {
|
||||
DB::rollBack();
|
||||
abort(500, '开通失败');
|
||||
}
|
||||
$order->status = 3;
|
||||
if (!$order->save()) {
|
||||
DB::rollBack();
|
||||
abort(500, '开通失败');
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
}
|
||||
|
||||
private function buyReset(User $user)
|
||||
{
|
||||
$user->u = 0;
|
||||
$user->d = 0;
|
||||
}
|
||||
|
||||
private function buyByCycle(Order $order, User $user, Plan $plan)
|
||||
{
|
||||
// change plan process
|
||||
if ((int)$order->type === 3) {
|
||||
$user->expired_at = time();
|
||||
}
|
||||
$user->transfer_enable = $plan->transfer_enable * 1073741824;
|
||||
// 当续费清空流量或用户先前是一次性订阅
|
||||
if ((int)config('v2board.renew_reset_traffic_enable', 1) || $user->expired_at === NULL) {
|
||||
$user->u = 0;
|
||||
$user->d = 0;
|
||||
}
|
||||
$user->plan_id = $plan->id;
|
||||
$user->group_id = $plan->group_id;
|
||||
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
|
||||
}
|
||||
|
||||
private function buyByOneTime(Order $order, User $user, Plan $plan)
|
||||
{
|
||||
$user->transfer_enable = $plan->transfer_enable * 1073741824;
|
||||
$user->enable = 1;
|
||||
$user->u = 0;
|
||||
$user->d = 0;
|
||||
$user->plan_id = $plan->id;
|
||||
$user->group_id = $plan->group_id;
|
||||
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
|
||||
if ($user->save()) {
|
||||
$order->status = 3;
|
||||
$order->save();
|
||||
}
|
||||
$user->expired_at = NULL;
|
||||
}
|
||||
|
||||
private function getTime($str, $timestamp)
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\User;
|
||||
|
||||
class ResetTraffic extends Command
|
||||
{
|
||||
@ -38,7 +38,47 @@ class ResetTraffic extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
DB::table('v2_user')->update([
|
||||
$user = User::where('expired_at', '!=', NULL)
|
||||
->where('expired_at', '>', time());
|
||||
$resetTrafficMethod = config('v2board.reset_traffic_method', 0);
|
||||
switch ((int)$resetTrafficMethod) {
|
||||
// 1 a month
|
||||
case 0:
|
||||
$this->resetByMonthFirstDay($user);
|
||||
break;
|
||||
// expire day
|
||||
case 1:
|
||||
$this->resetByExpireDay($user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function resetByMonthFirstDay($user):void
|
||||
{
|
||||
if ((string)date('d') === '01') {
|
||||
$user->update([
|
||||
'u' => 0,
|
||||
'd' => 0
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function resetByExpireDay($user):void
|
||||
{
|
||||
$lastDay = date('d', strtotime('last day of +0 months'));
|
||||
$users = [];
|
||||
foreach ($user->get() as $item) {
|
||||
$expireDay = date('d', $item->expired_at);
|
||||
$today = date('d');
|
||||
if ($expireDay === $today) {
|
||||
array_push($users, $item->id);
|
||||
}
|
||||
|
||||
if (($today === $lastDay) && $expireDay >= $lastDay) {
|
||||
array_push($users, $item->id);
|
||||
}
|
||||
}
|
||||
User::whereIn('id', $users)->update([
|
||||
'u' => 0,
|
||||
'd' => 0
|
||||
]);
|
||||
|
@ -5,7 +5,7 @@ namespace App\Console\Commands;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use App\Models\MailLog;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Jobs\SendEmailJob;
|
||||
|
||||
class SendRemindMail extends Command
|
||||
{
|
||||
@ -49,11 +49,11 @@ class SendRemindMail extends Command
|
||||
|
||||
private function remindExpire($user)
|
||||
{
|
||||
if (($user->expired_at - 86400) < time() && $user->expired_at > time()) {
|
||||
SendEmail::dispatch([
|
||||
if ($user->expired_at !== NULL && ($user->expired_at - 86400) < time() && $user->expired_at > time()) {
|
||||
SendEmailJob::dispatch([
|
||||
'email' => $user->email,
|
||||
'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期',
|
||||
'template_name' => 'mail.sendRemindExpire',
|
||||
'template_name' => 'remindExpire',
|
||||
'template_value' => [
|
||||
'name' => config('v2board.app_name', 'V2Board'),
|
||||
'url' => config('v2board.app_url')
|
||||
@ -66,13 +66,13 @@ class SendRemindMail extends Command
|
||||
{
|
||||
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')
|
||||
->where('template_name', 'like', '%remindTraffic%')
|
||||
->count();
|
||||
if ($sendCount > 0) return;
|
||||
SendEmail::dispatch([
|
||||
SendEmailJob::dispatch([
|
||||
'email' => $user->email,
|
||||
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
|
||||
'template_name' => 'mail.sendRemindTraffic',
|
||||
'template_name' => 'remindTraffic',
|
||||
'template_value' => [
|
||||
'name' => config('v2board.app_name', 'V2Board'),
|
||||
'url' => config('v2board.app_url')
|
||||
|
@ -3,24 +3,22 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
|
||||
class CheckExpire extends Command
|
||||
class Test extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'check:expire';
|
||||
protected $signature = 'test';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '过期检查';
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
@ -39,15 +37,5 @@ class CheckExpire extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$users = User::all();
|
||||
foreach ($users as $user) {
|
||||
if ($user->expired_at < time() || $user->u + $user->d >= $user->transfer_enable) {
|
||||
$user->enable = 0;
|
||||
} else {
|
||||
$user->enable = 1;
|
||||
}
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Utils\CacheKey;
|
||||
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 App\Models\ServerStat;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class V2boardCache extends Command
|
||||
{
|
||||
@ -44,4 +43,26 @@ class V2boardCache extends Command
|
||||
public function handle()
|
||||
{
|
||||
}
|
||||
|
||||
private function cacheServerStat()
|
||||
{
|
||||
$serverLogs = ServerLog::select(
|
||||
'server_id',
|
||||
DB::raw("sum(u) as u"),
|
||||
DB::raw("sum(d) as d"),
|
||||
DB::raw("count(*) as online")
|
||||
)
|
||||
->where('updated_at', '>=', time() - 3600)
|
||||
->groupBy('server_id')
|
||||
->get();
|
||||
foreach ($serverLogs as $serverLog) {
|
||||
$data = [
|
||||
'server_id' => $serverLog->server_id,
|
||||
'u' => $serverLog->u,
|
||||
'd' => $serverLog->d,
|
||||
'online' => $serverLog->online
|
||||
];
|
||||
// ServerStat::create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class V2boardInstall extends Command
|
||||
abort(500, '管理员密码长度最小为8位字符');
|
||||
}
|
||||
$user->password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$user->v2ray_uuid = Helper::guid(true);
|
||||
$user->uuid = Helper::guid(true);
|
||||
$user->token = Helper::guid();
|
||||
$user->is_admin = 1;
|
||||
return $user->save();
|
||||
|
@ -28,10 +28,9 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command('v2board:cache')->hourly();
|
||||
// check
|
||||
$schedule->command('check:order')->everyMinute();
|
||||
$schedule->command('check:expire')->everyMinute();
|
||||
$schedule->command('check:commission')->everyMinute();
|
||||
// reset
|
||||
$schedule->command('reset:traffic')->monthly();
|
||||
$schedule->command('reset:traffic')->daily();
|
||||
$schedule->command('reset:serverLog')->monthly();
|
||||
// send
|
||||
$schedule->command('send:remindMail')->dailyAt('11:30');
|
||||
|
@ -46,6 +46,9 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function render($request, Exception $exception)
|
||||
{
|
||||
if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
|
||||
abort(429, '请求频繁,请稍后再试');
|
||||
}
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,38 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\ConfigSave;
|
||||
use App\Services\TelegramService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Utils\Dict;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ConfigController extends Controller
|
||||
{
|
||||
public function getEmailTemplate()
|
||||
{
|
||||
$path = resource_path('views/mail/');
|
||||
$files = array_map(function ($item) use ($path) {
|
||||
return str_replace($path, '', $item);
|
||||
}, glob($path . '*'));
|
||||
return response([
|
||||
'data' => $files
|
||||
]);
|
||||
}
|
||||
|
||||
public function setTelegramWebhook(Request $request)
|
||||
{
|
||||
$telegramService = new TelegramService($request->input('telegram_bot_token'));
|
||||
$telegramService->getMe();
|
||||
$telegramService->setWebhook(
|
||||
url(
|
||||
'/api/v1/guest/telegram/webhook?access_token=' . md5(config('v2board.telegram_bot_token', $request->input('telegram_bot_token')))
|
||||
)
|
||||
);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function fetch()
|
||||
{
|
||||
// TODO: default should be in Dict
|
||||
@ -18,7 +44,9 @@ class ConfigController extends Controller
|
||||
'invite_force' => (int)config('v2board.invite_force', 0),
|
||||
'invite_commission' => config('v2board.invite_commission', 10),
|
||||
'invite_gen_limit' => config('v2board.invite_gen_limit', 5),
|
||||
'invite_never_expire' => config('v2board.invite_never_expire', 0)
|
||||
'invite_never_expire' => config('v2board.invite_never_expire', 0),
|
||||
'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1),
|
||||
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1)
|
||||
],
|
||||
'site' => [
|
||||
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
|
||||
@ -28,11 +56,16 @@ class ConfigController extends Controller
|
||||
'app_description' => config('v2board.app_description', 'V2Board is best!'),
|
||||
'app_url' => config('v2board.app_url'),
|
||||
'subscribe_url' => config('v2board.subscribe_url'),
|
||||
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
|
||||
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
|
||||
'try_out_hour' => (int)config('v2board.try_out_hour', 1),
|
||||
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
|
||||
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
|
||||
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
|
||||
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0)
|
||||
],
|
||||
'subscribe' => [
|
||||
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
|
||||
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
|
||||
'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1)
|
||||
],
|
||||
'pay' => [
|
||||
// alipay
|
||||
@ -41,16 +74,20 @@ class ConfigController extends Controller
|
||||
'alipay_pubkey' => config('v2board.alipay_pubkey'),
|
||||
'alipay_privkey' => config('v2board.alipay_privkey'),
|
||||
// stripe
|
||||
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
|
||||
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
|
||||
'stripe_card_enable' => (int)config('v2board.stripe_card_enable', 0),
|
||||
'stripe_sk_live' => config('v2board.stripe_sk_live'),
|
||||
'stripe_pk_live' => config('v2board.stripe_pk_live'),
|
||||
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable'),
|
||||
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable'),
|
||||
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
|
||||
'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
|
||||
// bitpayx
|
||||
'bitpayx_enable' => config('v2board.bitpayx_enable'),
|
||||
'bitpayx_name' => config('v2board.bitpayx_name', '聚合支付'),
|
||||
'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0),
|
||||
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
|
||||
// paytaro
|
||||
'paytaro_enable' => config('v2board.paytaro_enable'),
|
||||
'paytaro_name' => config('v2board.paytaro_name', '聚合支付'),
|
||||
'paytaro_enable' => (int)config('v2board.paytaro_enable', 0),
|
||||
'paytaro_app_id' => config('v2board.paytaro_app_id'),
|
||||
'paytaro_app_secret' => config('v2board.paytaro_app_secret')
|
||||
],
|
||||
@ -62,10 +99,18 @@ class ConfigController extends Controller
|
||||
],
|
||||
'server' => [
|
||||
'server_token' => config('v2board.server_token'),
|
||||
'server_license' => config('v2board.server_license')
|
||||
'server_license' => config('v2board.server_license'),
|
||||
'server_log_level' => config('v2board.server_log_level', 'none')
|
||||
],
|
||||
'tutorial' => [
|
||||
'apple_id' => config('v2board.apple_id')
|
||||
],
|
||||
'email' => [
|
||||
'email_template' => config('v2board.email_template', 'default')
|
||||
],
|
||||
'telegram' => [
|
||||
'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0),
|
||||
'telegram_bot_token' => config('v2board.telegram_bot_token')
|
||||
]
|
||||
]
|
||||
]);
|
||||
@ -76,7 +121,7 @@ class ConfigController extends Controller
|
||||
$data = $request->input();
|
||||
$array = \Config::get('v2board');
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, ConfigSave::filter())) {
|
||||
if (!in_array($k, array_keys(ConfigSave::RULES))) {
|
||||
abort(500, '参数' . $k . '不在规则内,禁止修改');
|
||||
}
|
||||
$array[$k] = $v;
|
||||
@ -86,6 +131,9 @@ class ConfigController extends Controller
|
||||
abort(500, '修改失败');
|
||||
}
|
||||
\Artisan::call('config:cache');
|
||||
if (function_exists('opcache')) {
|
||||
opcache_reset();
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
|
@ -12,29 +12,32 @@ class CouponController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$coupons = Coupon::all();
|
||||
foreach ($coupons as $k => $v) {
|
||||
if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']);
|
||||
}
|
||||
return response([
|
||||
'data' => Coupon::all()
|
||||
'data' => $coupons
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(CouponSave $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'name',
|
||||
'type',
|
||||
'value',
|
||||
'started_at',
|
||||
'ended_at',
|
||||
'limit_use'
|
||||
]);
|
||||
|
||||
$params = $request->only(array_keys(CouponSave::RULES));
|
||||
if (isset($params['limit_plan_ids'])) {
|
||||
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
|
||||
}
|
||||
if (!$request->input('id')) {
|
||||
$params['code'] = Helper::randomChar(8);
|
||||
if (!$params['code']) {
|
||||
$params['code'] = Helper::randomChar(8);
|
||||
}
|
||||
if (!Coupon::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
} else {
|
||||
if (!Coupon::find($request->input('id'))->update($params)) {
|
||||
try {
|
||||
Coupon::find($request->input('id'))->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
|
@ -3,65 +3,45 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\MailSend;
|
||||
use App\Services\UserService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Jobs\SendEmailJob;
|
||||
|
||||
class MailController extends Controller
|
||||
{
|
||||
public function send(MailSend $request)
|
||||
{
|
||||
|
||||
$userService = new UserService();
|
||||
$users = [];
|
||||
switch ($request->input('type')) {
|
||||
case 1: $users = $this->getAllUser();
|
||||
case 1: $users = $userService->getAllUsers();
|
||||
break;
|
||||
case 2: $users = $this->getReceiver($request->input('receiver'));
|
||||
case 2: $users = $userService->getUsersByIds($request->input('receiver'));
|
||||
break;
|
||||
case 3: $users = $this->getSubscribeUser();
|
||||
// available users
|
||||
case 3: $users = $userService->getAvailableUsers();
|
||||
break;
|
||||
case 4: $users = $this->getExpireUser();
|
||||
// un available users
|
||||
case 4: $users = $userService->getUnAvailbaleUsers();
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
SendEmail::dispatch([
|
||||
SendEmailJob::dispatch([
|
||||
'email' => $user->email,
|
||||
'subject' => $request->input('subject'),
|
||||
'template_name' => 'mail.sendEmailCustom',
|
||||
'template_name' => 'notify',
|
||||
'template_value' => [
|
||||
'name' => config('v2board.app_name', 'V2Board'),
|
||||
'url' => config('v2board.app_url'),
|
||||
'content' => $request->input('content')
|
||||
]
|
||||
])->onQueue('other_mail');
|
||||
]);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
private function getAllUser()
|
||||
{
|
||||
return User::all();
|
||||
}
|
||||
|
||||
private function getReceiver($receiver)
|
||||
{
|
||||
if (empty($receiver)) {
|
||||
abort(500, '收件人不能为空');
|
||||
}
|
||||
return User::whereIn('id', $receiver)->get();
|
||||
}
|
||||
|
||||
private function getSubscribeUser()
|
||||
{
|
||||
return User::where('expired_at', '=>', time())->get();
|
||||
}
|
||||
|
||||
private function getExpireUser()
|
||||
{
|
||||
return User::where('expired_at', '<', time())->get();
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,9 @@ class NoticeController extends Controller
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
} else {
|
||||
if (!Notice::find($request->input('id'))->update($data)) {
|
||||
try {
|
||||
Notice::find($request->input('id'))->update($data);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,16 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\OrderAssign;
|
||||
use App\Http\Requests\Admin\OrderUpdate;
|
||||
use App\Services\OrderService;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
use App\Models\Plan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OrderController extends Controller
|
||||
{
|
||||
@ -22,6 +26,7 @@ class OrderController extends Controller
|
||||
if ($request->input('is_commission')) {
|
||||
$orderModel->where('invite_user_id', '!=', NULL);
|
||||
$orderModel->where('status', 3);
|
||||
$orderModel->where('commission_balance', '>', 0);
|
||||
}
|
||||
if ($request->input('id')) {
|
||||
$orderModel->where('id', $request->input('id'));
|
||||
@ -48,7 +53,7 @@ class OrderController extends Controller
|
||||
|
||||
public function update(OrderUpdate $request)
|
||||
{
|
||||
$updateData = $request->only([
|
||||
$params = $request->only([
|
||||
'status',
|
||||
'commission_status'
|
||||
]);
|
||||
@ -59,7 +64,19 @@ class OrderController extends Controller
|
||||
abort(500, '订单不存在');
|
||||
}
|
||||
|
||||
if (!$order->update($updateData)) {
|
||||
if (isset($params['status']) && (int)$params['status'] === 2) {
|
||||
$orderService = new OrderService($order);
|
||||
if (!$orderService->cancel()) {
|
||||
abort(500, '更新失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$order->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '更新失败');
|
||||
}
|
||||
|
||||
@ -87,4 +104,50 @@ class OrderController extends Controller
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function assign(OrderAssign $request)
|
||||
{
|
||||
$plan = Plan::find($request->input('plan_id'));
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
|
||||
if (!$user) {
|
||||
abort(500, '该用户不存在');
|
||||
}
|
||||
|
||||
if (!$plan) {
|
||||
abort(500, '该订阅不存在');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
$order = new Order();
|
||||
$orderService = new OrderService($order);
|
||||
$order->user_id = $user->id;
|
||||
$order->plan_id = $plan->id;
|
||||
$order->cycle = $request->input('cycle');
|
||||
$order->trade_no = Helper::guid();
|
||||
$order->total_amount = $request->input('total_amount');
|
||||
|
||||
if ($order->cycle === 'reset_price') {
|
||||
$order->type = 4;
|
||||
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
||||
$order->type = 3;
|
||||
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
|
||||
$order->type = 2;
|
||||
} else {
|
||||
$order->type = 1;
|
||||
}
|
||||
|
||||
$orderService->setInvite($user);
|
||||
|
||||
if (!$order->save()) {
|
||||
DB::rollback();
|
||||
abort(500, '订单创建失败');
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response([
|
||||
'data' => $order->trade_no
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -3,43 +3,73 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\PlanSave;
|
||||
use App\Http\Requests\Admin\PlanSort;
|
||||
use App\Http\Requests\Admin\PlanUpdate;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PlanController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
|
||||
$counts = User::select(
|
||||
DB::raw("plan_id"),
|
||||
DB::raw("count(*) as count")
|
||||
)
|
||||
->where('plan_id', '!=', NULL)
|
||||
->where(function ($query) {
|
||||
$query->where('expired_at', '>=', time())
|
||||
->orWhere('expired_at', NULL);
|
||||
})
|
||||
->groupBy("plan_id")
|
||||
->get();
|
||||
$plans = Plan::orderBy('sort', 'ASC')->get();
|
||||
foreach ($plans as $k => $v) {
|
||||
$plans[$k]->count = 0;
|
||||
foreach ($counts as $kk => $vv) {
|
||||
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => Plan::get()
|
||||
'data' => $plans
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(PlanSave $request)
|
||||
{
|
||||
$params = $request->only(array_keys(PlanSave::RULES));
|
||||
if ($request->input('id')) {
|
||||
$plan = Plan::find($request->input('id'));
|
||||
if (!$plan) {
|
||||
abort(500, '该订阅不存在');
|
||||
}
|
||||
} else {
|
||||
$plan = new Plan();
|
||||
DB::beginTransaction();
|
||||
// update user group id and transfer
|
||||
try {
|
||||
User::where('plan_id', $plan->id)->update([
|
||||
'group_id' => $plan->group_id,
|
||||
'transfer_enable' => $plan->transfer_enable * 1073741824
|
||||
]);
|
||||
$plan->update($params);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
if (!Plan::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
$plan->name = $request->input('name');
|
||||
$plan->content = $request->input('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()
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
@ -73,7 +103,10 @@ class PlanController extends Controller
|
||||
if (!$plan) {
|
||||
abort(500, '该订阅不存在');
|
||||
}
|
||||
if (!$plan->update($updateData)) {
|
||||
|
||||
try {
|
||||
$plan->update($updateData);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
@ -81,4 +114,19 @@ class PlanController extends Controller
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function sort(PlanSort $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
foreach ($request->input('plan_ids') as $k => $v) {
|
||||
if (!Plan::find($v)->update(['sort' => $k + 1])) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
71
app/Http/Controllers/Admin/Server/GroupController.php
Normal file
71
app/Http/Controllers/Admin/Server/GroupController.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Server;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerGroup;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class GroupController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
if ($request->input('group_id')) {
|
||||
return response([
|
||||
'data' => [ServerGroup::find($request->input('group_id'))]
|
||||
]);
|
||||
}
|
||||
return response([
|
||||
'data' => ServerGroup::get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
{
|
||||
if (empty($request->input('name'))) {
|
||||
abort(500, '组名不能为空');
|
||||
}
|
||||
|
||||
if ($request->input('id')) {
|
||||
$serverGroup = ServerGroup::find($request->input('id'));
|
||||
} else {
|
||||
$serverGroup = new ServerGroup();
|
||||
}
|
||||
|
||||
$serverGroup->name = $request->input('name');
|
||||
return response([
|
||||
'data' => $serverGroup->save()
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(Request $request)
|
||||
{
|
||||
if ($request->input('id')) {
|
||||
$serverGroup = ServerGroup::find($request->input('id'));
|
||||
if (!$serverGroup) {
|
||||
abort(500, '组不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
$groupId = json_decode($server->group_id);
|
||||
if (in_array($request->input('id'), $groupId)) {
|
||||
abort(500, '该组已被节点所使用,无法删除');
|
||||
}
|
||||
}
|
||||
|
||||
if (Plan::where('group_id', $request->input('id'))->first()) {
|
||||
abort(500, '该组已被订阅所使用,无法删除');
|
||||
}
|
||||
if (User::where('group_id', $request->input('id'))->first()) {
|
||||
abort(500, '该组已被用户所使用,无法删除');
|
||||
}
|
||||
return response([
|
||||
'data' => $serverGroup->delete()
|
||||
]);
|
||||
}
|
||||
}
|
144
app/Http/Controllers/Admin/Server/TrojanController.php
Normal file
144
app/Http/Controllers/Admin/Server/TrojanController.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Server;
|
||||
|
||||
use App\Http\Requests\Admin\ServerTrojanSave;
|
||||
use App\Http\Requests\Admin\ServerTrojanSort;
|
||||
use App\Http\Requests\Admin\ServerTrojanUpdate;
|
||||
use App\Services\ServerService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ServerTrojan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TrojanController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$server = ServerTrojan::orderBy('sort', 'ASC')->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]['online'] = Cache::get(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server[$i]['id']));
|
||||
if ($server[$i]['parent_id']) {
|
||||
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server[$i]['parent_id']));
|
||||
} else {
|
||||
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server[$i]['id']));
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $server
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(ServerTrojanSave $request)
|
||||
{
|
||||
$params = $request->only(array_keys(ServerTrojanSave::RULES));
|
||||
$params['group_id'] = json_encode($params['group_id']);
|
||||
if (isset($params['tags'])) {
|
||||
$params['tags'] = json_encode($params['tags']);
|
||||
}
|
||||
|
||||
if ($request->input('id')) {
|
||||
$server = ServerTrojan::find($request->input('id'));
|
||||
if (!$server) {
|
||||
abort(500, '服务器不存在');
|
||||
}
|
||||
try {
|
||||
$server->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
if (!ServerTrojan::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(Request $request)
|
||||
{
|
||||
if ($request->input('id')) {
|
||||
$server = ServerTrojan::find($request->input('id'));
|
||||
if (!$server) {
|
||||
abort(500, '节点ID不存在');
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $server->delete()
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ServerTrojanUpdate $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'show',
|
||||
]);
|
||||
|
||||
$server = ServerTrojan::find($request->input('id'));
|
||||
|
||||
if (!$server) {
|
||||
abort(500, '该服务器不存在');
|
||||
}
|
||||
try {
|
||||
$server->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function copy(Request $request)
|
||||
{
|
||||
$server = ServerTrojan::find($request->input('id'));
|
||||
$server->show = 0;
|
||||
if (!$server) {
|
||||
abort(500, '服务器不存在');
|
||||
}
|
||||
if (!ServerTrojan::create($server->toArray())) {
|
||||
abort(500, '复制失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function sort(ServerTrojanSort $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
foreach ($request->input('server_ids') as $k => $v) {
|
||||
if (!ServerTrojan::find($v)->update(['sort' => $k + 1])) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function viewConfig(Request $request)
|
||||
{
|
||||
$serverService = new ServerService();
|
||||
$config = $serverService->getTrojanConfig($request->input('node_id'), 23333);
|
||||
return response([
|
||||
'data' => $config
|
||||
]);
|
||||
}
|
||||
}
|
168
app/Http/Controllers/Admin/Server/V2rayController.php
Normal file
168
app/Http/Controllers/Admin/Server/V2rayController.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Server;
|
||||
|
||||
use App\Http\Requests\Admin\ServerV2raySave;
|
||||
use App\Http\Requests\Admin\ServerV2raySort;
|
||||
use App\Http\Requests\Admin\ServerV2rayUpdate;
|
||||
use App\Services\ServerService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class V2rayController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$server = Server::orderBy('sort', 'ASC')->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]['online'] = Cache::get(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server[$i]['parent_id'] ? $server[$i]['parent_id'] : $server[$i]['id']));
|
||||
if ($server[$i]['parent_id']) {
|
||||
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['parent_id']));
|
||||
} else {
|
||||
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['id']));
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $server
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(ServerV2raySave $request)
|
||||
{
|
||||
$params = $request->only(array_keys(ServerV2raySave::RULES));
|
||||
$params['group_id'] = json_encode($params['group_id']);
|
||||
if (isset($params['tags'])) {
|
||||
$params['tags'] = json_encode($params['tags']);
|
||||
}
|
||||
|
||||
if (isset($params['dnsSettings'])) {
|
||||
if (!is_object(json_decode($params['dnsSettings']))) {
|
||||
abort(500, 'DNS规则配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['ruleSettings'])) {
|
||||
if (!is_object(json_decode($params['ruleSettings']))) {
|
||||
abort(500, '审计规则配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['networkSettings'])) {
|
||||
if (!is_object(json_decode($params['networkSettings']))) {
|
||||
abort(500, '传输协议配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['tlsSettings'])) {
|
||||
if (!is_object(json_decode($params['tlsSettings']))) {
|
||||
abort(500, 'TLS配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->input('id')) {
|
||||
$server = Server::find($request->input('id'));
|
||||
if (!$server) {
|
||||
abort(500, '服务器不存在');
|
||||
}
|
||||
try {
|
||||
$server->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Server::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(Request $request)
|
||||
{
|
||||
if ($request->input('id')) {
|
||||
$server = Server::find($request->input('id'));
|
||||
if (!$server) {
|
||||
abort(500, '节点ID不存在');
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $server->delete()
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ServerV2rayUpdate $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'show',
|
||||
]);
|
||||
|
||||
$server = Server::find($request->input('id'));
|
||||
|
||||
if (!$server) {
|
||||
abort(500, '该服务器不存在');
|
||||
}
|
||||
try {
|
||||
$server->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function copy(Request $request)
|
||||
{
|
||||
$server = Server::find($request->input('id'));
|
||||
$server->show = 0;
|
||||
if (!$server) {
|
||||
abort(500, '服务器不存在');
|
||||
}
|
||||
if (!Server::create($server->toArray())) {
|
||||
abort(500, '复制失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function viewConfig(Request $request)
|
||||
{
|
||||
$serverService = new ServerService();
|
||||
$config = $serverService->getVmessConfig($request->input('node_id'), 23333);
|
||||
return response([
|
||||
'data' => $config
|
||||
]);
|
||||
}
|
||||
|
||||
public function sort(ServerV2raySort $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
foreach ($request->input('server_ids') as $k => $v) {
|
||||
if (!Server::find($v)->update(['sort' => $k + 1])) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\ServerSave;
|
||||
use App\Http\Requests\Admin\ServerUpdate;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ServerGroup;
|
||||
use App\Models\Server;
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServerController extends Controller
|
||||
{
|
||||
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']);
|
||||
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)
|
||||
{
|
||||
$params = $request->only(array_keys(ServerSave::RULES));
|
||||
$params['group_id'] = json_encode($params['group_id']);
|
||||
if (isset($params['tags'])) {
|
||||
$params['tags'] = json_encode($params['tags']);
|
||||
}
|
||||
if (isset($params['rules'])) {
|
||||
if (!is_object(json_decode($params['rules']))) {
|
||||
abort(500, '审计规则配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
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, '服务器不存在');
|
||||
}
|
||||
if (!$server->update($params)) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Server::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function groupFetch(Request $request)
|
||||
{
|
||||
if ($request->input('group_id')) {
|
||||
return response([
|
||||
'data' => [ServerGroup::find($request->input('group_id'))]
|
||||
]);
|
||||
}
|
||||
return response([
|
||||
'data' => ServerGroup::get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function groupSave(Request $request)
|
||||
{
|
||||
if (empty($request->input('name'))) {
|
||||
abort(500, '组名不能为空');
|
||||
}
|
||||
|
||||
if ($request->input('id')) {
|
||||
$serverGroup = ServerGroup::find($request->input('id'));
|
||||
} else {
|
||||
$serverGroup = new ServerGroup();
|
||||
}
|
||||
|
||||
$serverGroup->name = $request->input('name');
|
||||
return response([
|
||||
'data' => $serverGroup->save()
|
||||
]);
|
||||
}
|
||||
|
||||
public function groupDrop(Request $request)
|
||||
{
|
||||
if ($request->input('id')) {
|
||||
$serverGroup = ServerGroup::find($request->input('id'));
|
||||
if (!$serverGroup) {
|
||||
abort(500, '组不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
$groupId = json_decode($server->group_id);
|
||||
if (in_array($request->input('id'), $groupId)) {
|
||||
abort(500, '该组已被节点所使用,无法删除');
|
||||
}
|
||||
}
|
||||
|
||||
if (Plan::where('group_id', $request->input('id'))->first()) {
|
||||
abort(500, '该组已被订阅所使用,无法删除');
|
||||
}
|
||||
if (User::where('group_id', $request->input('id'))->first()) {
|
||||
abort(500, '该组已被用户所使用,无法删除');
|
||||
}
|
||||
return response([
|
||||
'data' => $serverGroup->delete()
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(Request $request)
|
||||
{
|
||||
if ($request->input('id')) {
|
||||
$server = Server::find($request->input('id'));
|
||||
if (!$server) {
|
||||
abort(500, '节点ID不存在');
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $server->delete()
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ServerUpdate $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'show',
|
||||
]);
|
||||
|
||||
$server = Server::find($request->input('id'));
|
||||
|
||||
if (!$server) {
|
||||
abort(500, '该服务器不存在');
|
||||
}
|
||||
if (!$server->update($params)) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ class StatController extends Controller
|
||||
'data' => [
|
||||
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
|
||||
->where('created_at', '<', time())
|
||||
->where('status', '3')
|
||||
->whereIn('status', [3, 4])
|
||||
->sum('total_amount'),
|
||||
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
|
||||
->where('created_at', '<', time())
|
||||
@ -30,7 +30,16 @@ class StatController extends Controller
|
||||
'commission_pendding_total' => Order::where('commission_status', 0)
|
||||
->where('invite_user_id', '!=', NULL)
|
||||
->where('status', 3)
|
||||
->where('commission_balance', '>', 0)
|
||||
->count(),
|
||||
'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
|
||||
->where('created_at', '<', time())
|
||||
->where('status', 3)
|
||||
->sum('total_amount'),
|
||||
'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
|
||||
->where('created_at', '<', strtotime(date('Y-m-1')))
|
||||
->where('status', 3)
|
||||
->sum('total_amount')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Jobs\SendEmailJob;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use App\Models\TicketMessage;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TicketController extends Controller
|
||||
@ -30,17 +33,25 @@ class TicketController extends Controller
|
||||
'data' => $ticket
|
||||
]);
|
||||
}
|
||||
$ticket = Ticket::orderBy('created_at', 'DESC')
|
||||
$current = $request->input('current') ? $request->input('current') : 1;
|
||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
||||
$model = Ticket::orderBy('created_at', 'DESC');
|
||||
if ($request->input('status') !== NULL) {
|
||||
$model->where('status', $request->input('status'));
|
||||
}
|
||||
$total = $model->count();
|
||||
$res = $model->forPage($current, $pageSize)
|
||||
->get();
|
||||
for ($i = 0; $i < count($ticket); $i++) {
|
||||
if ($ticket[$i]['last_reply_user_id'] == $request->session()->get('id')) {
|
||||
$ticket[$i]['reply_status'] = 0;
|
||||
for ($i = 0; $i < count($res); $i++) {
|
||||
if ($res[$i]['last_reply_user_id'] == $request->session()->get('id')) {
|
||||
$res[$i]['reply_status'] = 0;
|
||||
} else {
|
||||
$ticket[$i]['reply_status'] = 1;
|
||||
$res[$i]['reply_status'] = 1;
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $ticket
|
||||
'data' => $res,
|
||||
'total' => $total
|
||||
]);
|
||||
}
|
||||
|
||||
@ -72,6 +83,7 @@ class TicketController extends Controller
|
||||
abort(500, '工单回复失败');
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendEmailNotify($ticket, $ticketMessage);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
@ -95,4 +107,24 @@ class TicketController extends Controller
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
// 半小时内不再重复通知
|
||||
private function sendEmailNotify(Ticket $ticket, TicketMessage $ticketMessage)
|
||||
{
|
||||
$user = User::find($ticket->user_id);
|
||||
$cacheKey = 'ticket_sendEmailNotify_' . $ticket->user_id;
|
||||
if (!Cache::get($cacheKey)) {
|
||||
Cache::put($cacheKey, 1, 1800);
|
||||
SendEmailJob::dispatch([
|
||||
'email' => $user->email,
|
||||
'subject' => '您在' . config('v2board.app_name', 'V2Board') . '的工单得到了回复',
|
||||
'template_name' => 'notify',
|
||||
'template_value' => [
|
||||
'name' => config('v2board.app_name', 'V2Board'),
|
||||
'url' => config('v2board.app_url'),
|
||||
'content' => "主题:{$ticket->subject}\r\n回复内容:{$ticketMessage->message}"
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,34 +3,33 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\TutorialSave;
|
||||
use App\Http\Requests\Admin\TutorialSort;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tutorial;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TutorialController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
return response([
|
||||
'data' => Tutorial::all()
|
||||
'data' => Tutorial::orderBy('sort', 'ASC')->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(TutorialSave $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'title',
|
||||
'description',
|
||||
'steps',
|
||||
'icon'
|
||||
]);
|
||||
$params = $request->only(array_keys(TutorialSave::RULES));
|
||||
|
||||
if (!$request->input('id')) {
|
||||
if (!Tutorial::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
} else {
|
||||
if (!Tutorial::find($request->input('id'))->update($params)) {
|
||||
try {
|
||||
Tutorial::find($request->input('id'))->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
@ -59,6 +58,21 @@ class TutorialController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function sort(TutorialSort $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
foreach ($request->input('tutorial_ids') as $k => $v) {
|
||||
if (!Tutorial::find($v)->update(['sort' => $k + 1])) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(Request $request)
|
||||
{
|
||||
if (empty($request->input('id'))) {
|
||||
|
@ -47,53 +47,37 @@ class UserController extends Controller
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
return response([
|
||||
'data' => User::select([
|
||||
'email',
|
||||
'u',
|
||||
'd',
|
||||
'transfer_enable',
|
||||
'expired_at'
|
||||
])->find($request->input('id'))
|
||||
'data' => User::find($request->input('id'))
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UserUpdate $request)
|
||||
{
|
||||
$updateData = $request->only([
|
||||
'email',
|
||||
'password',
|
||||
'transfer_enable',
|
||||
'expired_at',
|
||||
'banned',
|
||||
'plan_id',
|
||||
'commission_rate',
|
||||
'discount',
|
||||
'is_admin',
|
||||
'u',
|
||||
'd',
|
||||
'balance',
|
||||
'commission_balance'
|
||||
]);
|
||||
$params = $request->only(array_keys(UserUpdate::RULES));
|
||||
$user = User::find($request->input('id'));
|
||||
if (!$user) {
|
||||
abort(500, '用户不存在');
|
||||
}
|
||||
if (User::where('email', $updateData['email'])->first() && $user->email !== $updateData['email']) {
|
||||
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
|
||||
abort(500, '邮箱已被使用');
|
||||
}
|
||||
if (isset($updateData['password'])) {
|
||||
$updateData['password'] = password_hash($updateData['password'], PASSWORD_DEFAULT);
|
||||
if (isset($params['password'])) {
|
||||
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
|
||||
$params['password_algo'] = NULL;
|
||||
} else {
|
||||
unset($updateData['password']);
|
||||
unset($params['password']);
|
||||
}
|
||||
if (isset($updateData['plan_id'])) {
|
||||
$plan = Plan::find($updateData['plan_id']);
|
||||
if (isset($params['plan_id'])) {
|
||||
$plan = Plan::find($params['plan_id']);
|
||||
if (!$plan) {
|
||||
abort(500, '订阅计划不存在');
|
||||
}
|
||||
$updateData['group_id'] = $plan->group_id;
|
||||
$params['group_id'] = $plan->group_id;
|
||||
}
|
||||
if (!$user->update($updateData)) {
|
||||
|
||||
try {
|
||||
$user->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
return response([
|
||||
|
@ -3,12 +3,12 @@
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\Clash;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Server;
|
||||
use App\Models\Notice;
|
||||
use App\Utils\Helper;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class AppController extends Controller
|
||||
{
|
||||
@ -16,38 +16,40 @@ class AppController extends Controller
|
||||
CONST SOCKS_PORT = 10010;
|
||||
CONST HTTP_PORT = 10011;
|
||||
|
||||
// TODO: 1.1.1 abolish
|
||||
public function data(Request $request)
|
||||
public function getConfig(Request $request)
|
||||
{
|
||||
$server = [];
|
||||
$user = $request->user;
|
||||
$nodes = [];
|
||||
if ($user->plan_id) {
|
||||
$user['plan'] = Plan::find($user->plan_id);
|
||||
if (!$user['plan']) {
|
||||
abort(500, '订阅计划不存在');
|
||||
}
|
||||
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($nodes, $item);
|
||||
}
|
||||
}
|
||||
}
|
||||
$userService = new UserService();
|
||||
if ($userService->isAvailable($user)) {
|
||||
$serverService = new ServerService();
|
||||
$servers = $serverService->getAllServers($user);
|
||||
}
|
||||
$config = Yaml::parseFile(base_path() . '/resources/rules/app.clash.yaml');
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
foreach ($servers['vmess'] as $item) {
|
||||
array_push($proxy, Clash::buildVmess($user->uuid, $item));
|
||||
array_push($proxies, $item->name);
|
||||
}
|
||||
|
||||
foreach ($servers['trojan'] as $item) {
|
||||
array_push($proxy, Clash::buildTrojan($user->uuid, $item));
|
||||
array_push($proxies, $item->name);
|
||||
}
|
||||
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
die(Yaml::dump($config));
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return response([
|
||||
'data' => [
|
||||
'nodes' => $nodes,
|
||||
'u' => $user->u,
|
||||
'd' => $user->d,
|
||||
'transfer_enable' => $user->transfer_enable,
|
||||
'expired_at' => $user->expired_at,
|
||||
'plan' => isset($user['plan']) ? $user['plan'] : false,
|
||||
'notice' => Notice::orderBy('created_at', 'DESC')->first()
|
||||
]
|
||||
'data' => '4.0.0'
|
||||
]);
|
||||
}
|
||||
|
||||
@ -57,7 +59,7 @@ class AppController extends Controller
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
$user = $request->user;
|
||||
if ($user->expired_at < time()) {
|
||||
if ($user->expired_at < time() && $user->expired_at !== NULL) {
|
||||
abort(500, '订阅计划已过期');
|
||||
}
|
||||
$server = Server::where('show', 1)
|
||||
@ -74,29 +76,29 @@ class AppController extends Controller
|
||||
//other
|
||||
$json->outbound->settings->vnext[0]->address = (string)$server->host;
|
||||
$json->outbound->settings->vnext[0]->port = (int)$server->port;
|
||||
$json->outbound->settings->vnext[0]->users[0]->id = (string)$user->v2ray_uuid;
|
||||
$json->outbound->settings->vnext[0]->users[0]->id = (string)$user->uuid;
|
||||
$json->outbound->settings->vnext[0]->users[0]->alterId = (int)$user->v2ray_alter_id;
|
||||
$json->outbound->settings->vnext[0]->remark = (string)$server->name;
|
||||
$json->outbound->streamSettings->network = $server->network;
|
||||
if ($server->settings) {
|
||||
if ($server->networkSettings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->outbound->streamSettings->tcpSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->outbound->streamSettings->kcpSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'ws':
|
||||
$json->outbound->streamSettings->wsSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->wsSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'http':
|
||||
$json->outbound->streamSettings->httpSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->httpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->outbound->streamSettings->dsSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->dsSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'quic':
|
||||
$json->outbound->streamSettings->quicSettings = json_decode($server->settings);
|
||||
$json->outbound->streamSettings->quicSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
259
app/Http/Controllers/Client/ClientController.php
Executable file → Normal file
259
app/Http/Controllers/Client/ClientController.php
Executable file → Normal file
@ -3,72 +3,60 @@
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ServerService;
|
||||
use App\Utils\Clash;
|
||||
use App\Utils\QuantumultX;
|
||||
use App\Utils\Surge;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Server;
|
||||
use App\Utils\Helper;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use App\Services\UserService;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
public function subscribe(Request $request)
|
||||
{
|
||||
$user = $request->user;
|
||||
$server = [];
|
||||
// account not expired and is not banned.
|
||||
if ($user->expired_at > time() && !$user->banned) {
|
||||
$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);
|
||||
$userService = new UserService();
|
||||
if ($userService->isAvailable($user)) {
|
||||
$serverService = new ServerService();
|
||||
$servers = $serverService->getAllServers($user);
|
||||
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$_SERVER['HTTP_USER_AGENT'] = strtolower($_SERVER['HTTP_USER_AGENT']);
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult%20x') !== false) {
|
||||
die($this->quantumultX($user, $servers['vmess'], $servers['trojan']));
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult') !== false) {
|
||||
die($this->quantumult($user, $servers['vmess']));
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'clash') !== false) {
|
||||
die($this->clash($user, $servers['vmess'], $servers['trojan']));
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'surfboard') !== false) {
|
||||
die($this->surfboard($user, $servers['vmess']));
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'surge') !== false) {
|
||||
die($this->surge($user, $servers['vmess'], $servers['trojan']));
|
||||
}
|
||||
}
|
||||
die($this->origin($user, $servers['vmess'], $servers['trojan']));
|
||||
}
|
||||
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 (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
|
||||
}
|
||||
}
|
||||
$uri .= "\r\n";
|
||||
}
|
||||
return base64_encode($uri);
|
||||
}
|
||||
|
||||
private function quantumult($user, $server)
|
||||
// TODO: Ready to stop support
|
||||
private function quantumult($user, $vmess = [])
|
||||
{
|
||||
$uri = '';
|
||||
header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
|
||||
foreach ($server as $item) {
|
||||
foreach ($vmess 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');
|
||||
$str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->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 ($item->networkSettings) {
|
||||
$wsSettings = json_decode($item->networkSettings);
|
||||
if (isset($wsSettings->path)) $str .= ', obfs-path="' . $wsSettings->path . '"';
|
||||
if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:' . $wsSettings->headers->Host . '"';
|
||||
}
|
||||
@ -78,84 +66,137 @@ class ClientController extends Controller
|
||||
return base64_encode($uri);
|
||||
}
|
||||
|
||||
private function origin($user, $server)
|
||||
private function quantumultX($user, $vmess = [], $trojan = [])
|
||||
{
|
||||
$uri = '';
|
||||
foreach ($server as $item) {
|
||||
$uri .= Helper::buildVmessLink($item, $user);
|
||||
foreach ($vmess as $item) {
|
||||
$uri .= QuantumultX::buildVmess($user->uuid, $item);
|
||||
}
|
||||
foreach ($trojan as $item) {
|
||||
$uri .= QuantumultX::buildTrojan($user->uuid, $item);
|
||||
}
|
||||
return base64_encode($uri);
|
||||
}
|
||||
|
||||
private function clash($user, $server)
|
||||
private function origin($user, $vmess = [], $trojan = [])
|
||||
{
|
||||
$proxy = [];
|
||||
$proxyGroup = [];
|
||||
$proxies = [];
|
||||
$rules = [];
|
||||
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';
|
||||
$uri = '';
|
||||
foreach ($vmess as $item) {
|
||||
$uri .= Helper::buildVmessLink($item, $user);
|
||||
}
|
||||
foreach ($trojan as $item) {
|
||||
$uri .= Helper::buildTrojanLink($item, $user);
|
||||
}
|
||||
return base64_encode($uri);
|
||||
}
|
||||
|
||||
private function surge($user, $vmess = [], $trojan = [])
|
||||
{
|
||||
$proxies = '';
|
||||
$proxyGroup = '';
|
||||
foreach ($vmess as $item) {
|
||||
// [Proxy]
|
||||
$proxies .= Surge::buildVmess($user->uuid, $item);
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item->name . ', ';
|
||||
}
|
||||
|
||||
foreach ($trojan as $item) {
|
||||
// [Proxy]
|
||||
$proxies .= Surge::buildTrojan($user->uuid, $item);
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item->name . ', ';
|
||||
}
|
||||
|
||||
$defaultConfig = base_path() . '/resources/rules/default.surge.conf';
|
||||
$customConfig = base_path() . '/resources/rules/custom.surge.conf';
|
||||
if (\File::exists($customConfig)) {
|
||||
$config = file_get_contents("$customConfig");
|
||||
} else {
|
||||
$config = file_get_contents("$defaultConfig");
|
||||
}
|
||||
|
||||
// Subscription link
|
||||
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
|
||||
|
||||
$config = str_replace('$subs_link', $subsURL, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function surfboard($user, $vmess = [])
|
||||
{
|
||||
$proxies = '';
|
||||
$proxyGroup = '';
|
||||
foreach ($vmess as $item) {
|
||||
// [Proxy]
|
||||
$proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->uuid;
|
||||
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
|
||||
];
|
||||
$tlsSettings = json_decode($item->tlsSettings);
|
||||
$proxies .= ', tls=' . ($item->tls ? "true" : "false");
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
|
||||
}
|
||||
}
|
||||
array_push($proxy, $array);
|
||||
if ($item->network == 'ws') {
|
||||
$proxies .= ', ws=true';
|
||||
if ($item->networkSettings) {
|
||||
$wsSettings = json_decode($item->networkSettings);
|
||||
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
|
||||
}
|
||||
}
|
||||
$proxies .= "\r\n";
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item->name . ', ';
|
||||
}
|
||||
|
||||
$defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
|
||||
$customConfig = base_path() . '/resources/rules/custom.surfboard.conf';
|
||||
if (\File::exists($customConfig)) {
|
||||
$config = file_get_contents("$customConfig");
|
||||
} else {
|
||||
$config = file_get_contents("$defaultConfig");
|
||||
}
|
||||
|
||||
// Subscription link
|
||||
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
|
||||
|
||||
$config = str_replace('$subs_link', $subsURL, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function clash($user, $vmess = [], $trojan = [])
|
||||
{
|
||||
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
|
||||
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
|
||||
if (\File::exists($customConfig)) {
|
||||
$config = Yaml::parseFile($customConfig);
|
||||
} else {
|
||||
$config = Yaml::parseFile($defaultConfig);
|
||||
}
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
foreach ($vmess as $item) {
|
||||
array_push($proxy, Clash::buildVmess($user->uuid, $item));
|
||||
array_push($proxies, $item->name);
|
||||
}
|
||||
|
||||
array_push($proxyGroup, [
|
||||
'name' => 'auto',
|
||||
'type' => 'url-test',
|
||||
'proxies' => $proxies,
|
||||
'url' => 'https://www.bing.com',
|
||||
'interval' => 300
|
||||
]);
|
||||
array_push($proxyGroup, [
|
||||
'name' => 'fallback-auto',
|
||||
'type' => 'fallback',
|
||||
'proxies' => $proxies,
|
||||
'url' => 'https://www.bing.com',
|
||||
'interval' => 300
|
||||
]);
|
||||
array_push($proxyGroup, [
|
||||
'name' => 'select',
|
||||
'type' => 'select',
|
||||
'proxies' => $proxies
|
||||
]);
|
||||
|
||||
try {
|
||||
$rules = Yaml::parseFile(base_path() . '/resources/rules/clash.rule.yaml')['Rule'];
|
||||
} catch (\Exception $e) {}
|
||||
foreach ($trojan as $item) {
|
||||
array_push($proxy, Clash::buildTrojan($user->uuid, $item));
|
||||
array_push($proxies, $item->name);
|
||||
}
|
||||
|
||||
$config = [
|
||||
'port' => 7890,
|
||||
'socks-port' => 7891,
|
||||
'allow-lan' => false,
|
||||
'mode' => 'Rule',
|
||||
'log-level' => 'info',
|
||||
'external-controller' => '0.0.0.0:9090',
|
||||
'secret' => '',
|
||||
'Proxy' => $proxy,
|
||||
'Proxy Group' => $proxyGroup,
|
||||
'Rule' => $rules
|
||||
];
|
||||
|
||||
return Yaml::dump($config);
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
$yaml = Yaml::dump($config);
|
||||
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
|
||||
return $yaml;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Guest;
|
||||
|
||||
use App\Services\OrderService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Order;
|
||||
@ -15,7 +16,7 @@ class OrderController extends Controller
|
||||
{
|
||||
public function alipayNotify(Request $request)
|
||||
{
|
||||
Log::info('alipayNotifyData: ' . json_encode($_POST));
|
||||
// Log::info('alipayNotifyData: ' . json_encode($_POST));
|
||||
$gateway = Omnipay::create('Alipay_AopF2F');
|
||||
$gateway->setSignType('RSA2'); //RSA/RSA2
|
||||
$gateway->setAppId(config('v2board.alipay_appid'));
|
||||
@ -52,7 +53,7 @@ class OrderController extends Controller
|
||||
|
||||
public function stripeNotify(Request $request)
|
||||
{
|
||||
Log::info('stripeNotifyData: ' . json_encode($request->input()));
|
||||
// Log::info('stripeNotifyData: ' . json_encode($request->input()));
|
||||
|
||||
\Stripe\Stripe::setApiKey(config('v2board.stripe_sk_live'));
|
||||
try {
|
||||
@ -66,21 +67,26 @@ class OrderController extends Controller
|
||||
}
|
||||
switch ($event->type) {
|
||||
case 'source.chargeable':
|
||||
$source = $event->data->object;
|
||||
$charge = \Stripe\Charge::create([
|
||||
'amount' => $source['amount'],
|
||||
'currency' => $source['currency'],
|
||||
'source' => $source['id'],
|
||||
$object = $event->data->object;
|
||||
\Stripe\Charge::create([
|
||||
'amount' => $object->amount,
|
||||
'currency' => $object->currency,
|
||||
'source' => $object->id,
|
||||
'metadata' => json_decode($object->metadata, true)
|
||||
]);
|
||||
if ($charge['status'] == 'succeeded') {
|
||||
$trade_no = Cache::get($source['id']);
|
||||
if (!$trade_no) {
|
||||
abort(500, 'redis is not found trade no by stripe source id');
|
||||
die('success');
|
||||
break;
|
||||
case 'charge.succeeded':
|
||||
$object = $event->data->object;
|
||||
if ($object->status === 'succeeded') {
|
||||
$metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
|
||||
$tradeNo = $metaData->out_trade_no;
|
||||
if (!$tradeNo) {
|
||||
abort(500, 'trade no is not found in metadata');
|
||||
}
|
||||
if (!$this->handle($trade_no, $source['id'])) {
|
||||
if (!$this->handle($tradeNo, $object->balance_transaction)) {
|
||||
abort(500, 'fail');
|
||||
}
|
||||
Cache::forget($source['id']);
|
||||
die('success');
|
||||
}
|
||||
break;
|
||||
@ -92,7 +98,7 @@ class OrderController extends Controller
|
||||
public function bitpayXNotify(Request $request)
|
||||
{
|
||||
$inputString = file_get_contents('php://input', 'r');
|
||||
Log::info('bitpayXNotifyData: ' . $inputString);
|
||||
// Log::info('bitpayXNotifyData: ' . $inputString);
|
||||
$inputStripped = str_replace(array("\r", "\n", "\t", "\v"), '', $inputString);
|
||||
$inputJSON = json_decode($inputStripped, true); //convert JSON into array
|
||||
|
||||
@ -117,12 +123,14 @@ class OrderController extends Controller
|
||||
if (!$this->handle($params['merchant_order_id'], $params['order_id'])) {
|
||||
abort(500, 'order process fail');
|
||||
}
|
||||
die('success');
|
||||
die(json_encode([
|
||||
'status' => 200
|
||||
]));
|
||||
}
|
||||
|
||||
public function payTaroNotify(Request $request)
|
||||
{
|
||||
Log::info('payTaroNotify: ' . json_encode($request->input()));
|
||||
// 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())) {
|
||||
@ -140,11 +148,7 @@ class OrderController extends Controller
|
||||
if (!$order) {
|
||||
abort(500, 'order is not found');
|
||||
}
|
||||
if ($order->status !== 0) {
|
||||
return true;
|
||||
}
|
||||
$order->status = 1;
|
||||
$order->callback_no = $callbackNo;
|
||||
return $order->save();
|
||||
$orderService = new OrderService($order);
|
||||
return $orderService->success($callbackNo);
|
||||
}
|
||||
}
|
||||
|
127
app/Http/Controllers/Guest/TelegramController.php
Normal file
127
app/Http/Controllers/Guest/TelegramController.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Guest;
|
||||
|
||||
use App\Services\TelegramService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Utils\Helper;
|
||||
|
||||
class TelegramController extends Controller
|
||||
{
|
||||
protected $msg;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
if ($request->input('access_token') !== md5(config('v2board.telegram_bot_token'))) {
|
||||
abort(500, 'authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
$this->msg = $this->getMessage($request->input());
|
||||
if (!$this->msg) return;
|
||||
try {
|
||||
switch($this->msg->command) {
|
||||
case '/bind': $this->bind();
|
||||
break;
|
||||
case '/traffic': $this->traffic();
|
||||
break;
|
||||
case '/getlatesturl': $this->getLatestUrl();
|
||||
break;
|
||||
default: $this->help();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$telegramService = new TelegramService();
|
||||
$telegramService->sendMessage($this->msg->chat_id, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getMessage(array $data)
|
||||
{
|
||||
if (!isset($data['message'])) return false;
|
||||
$obj = new \StdClass();
|
||||
$obj->is_private = $data['message']['chat']['type'] === 'private' ? true : false;
|
||||
if (!isset($data['message']['text'])) return false;
|
||||
$text = explode(' ', $data['message']['text']);
|
||||
$obj->command = $text[0];
|
||||
$obj->args = array_slice($text, 1);
|
||||
$obj->chat_id = $data['message']['chat']['id'];
|
||||
$obj->message_id = $data['message']['message_id'];
|
||||
return $obj;
|
||||
}
|
||||
|
||||
private function bind()
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if (!$msg->is_private) return;
|
||||
if (!isset($msg->args[0])) {
|
||||
abort(500, '参数有误,请携带订阅地址发送');
|
||||
}
|
||||
$subscribeUrl = $msg->args[0];
|
||||
$subscribeUrl = parse_url($subscribeUrl);
|
||||
parse_str($subscribeUrl['query'], $query);
|
||||
$token = $query['token'];
|
||||
if (!$token) {
|
||||
abort(500, '订阅地址无效');
|
||||
}
|
||||
$user = User::where('token', $token)->first();
|
||||
if (!$user) {
|
||||
abort(500, '用户不存在');
|
||||
}
|
||||
$user->telegram_id = $msg->chat_id;
|
||||
if (!$user->save()) {
|
||||
abort(500, '设置失败');
|
||||
}
|
||||
$telegramService = new TelegramService();
|
||||
$telegramService->sendMessage($msg->chat_id, '绑定成功');
|
||||
}
|
||||
|
||||
private function help()
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if (!$msg->is_private) return;
|
||||
$telegramService = new TelegramService();
|
||||
$commands = [
|
||||
'/bind 订阅地址 - 绑定你的' . config('v2board.app_name', 'V2Board') . '账号',
|
||||
'/traffic - 查询流量信息',
|
||||
'/getlatesturl - 获取最新的' . config('v2board.app_name', 'V2Board') . '网址'
|
||||
];
|
||||
$text = implode(PHP_EOL, $commands);
|
||||
$telegramService->sendMessage($msg->chat_id, "你可以使用以下命令进行操作:\n\n$text", 'markdown');
|
||||
}
|
||||
|
||||
private function traffic()
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if (!$msg->is_private) return;
|
||||
$user = User::where('telegram_id', $msg->chat_id)->first();
|
||||
$telegramService = new TelegramService();
|
||||
if (!$user) {
|
||||
$this->help();
|
||||
$telegramService->sendMessage($msg->chat_id, '没有查询到您的用户信息,请先绑定账号', 'markdown');
|
||||
return;
|
||||
}
|
||||
$transferEnable = Helper::trafficConvert($user->transfer_enable);
|
||||
$up = Helper::trafficConvert($user->u);
|
||||
$down = Helper::trafficConvert($user->d);
|
||||
$remaining = Helper::trafficConvert($user->transfer_enable - ($user->u + $user->d));
|
||||
$text = "🚥流量查询\n———————————————\n计划流量:`{$transferEnable}`\n已用上行:`{$up}`\n已用下行:`{$down}`\n剩余流量:`{$remaining}`";
|
||||
$telegramService->sendMessage($msg->chat_id, $text, 'markdown');
|
||||
}
|
||||
|
||||
private function getLatestUrl()
|
||||
{
|
||||
$msg = $this->msg;
|
||||
$user = User::where('telegram_id', $msg->chat_id)->first();
|
||||
$telegramService = new TelegramService();
|
||||
$text = sprintf(
|
||||
"%s的最新网址是:%s",
|
||||
config('v2board.app_name', 'V2Board'),
|
||||
config('v2board.app_url')
|
||||
);
|
||||
$telegramService->sendMessage($msg->chat_id, $text, 'markdown');
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ use App\Models\User;
|
||||
use App\Models\InviteCode;
|
||||
use App\Utils\Helper;
|
||||
use App\Utils\Dict;
|
||||
use App\Utils\CacheKey;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
@ -26,6 +27,12 @@ class AuthController extends Controller
|
||||
abort(500, '邮箱后缀不处于白名单中');
|
||||
}
|
||||
}
|
||||
if ((int)config('v2board.email_gmail_limit_enable', 0)) {
|
||||
$prefix = explode('@', $request->input('email'))[0];
|
||||
if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) {
|
||||
abort(500, '不支持Gmail别名邮箱');
|
||||
}
|
||||
}
|
||||
if ((int)config('v2board.stop_register', 0)) {
|
||||
abort(500, '本站已关闭注册');
|
||||
}
|
||||
@ -35,11 +42,10 @@ class AuthController extends Controller
|
||||
}
|
||||
}
|
||||
if ((int)config('v2board.email_verify', 0)) {
|
||||
$redisKey = 'sendEmailVerify:' . $request->input('email');
|
||||
if (empty($request->input('email_code'))) {
|
||||
abort(500, '邮箱验证码不能为空');
|
||||
}
|
||||
if (Cache::get($redisKey) !== $request->input('email_code')) {
|
||||
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
|
||||
abort(500, '邮箱验证码有误');
|
||||
}
|
||||
}
|
||||
@ -52,7 +58,7 @@ class AuthController extends Controller
|
||||
$user = new User();
|
||||
$user->email = $email;
|
||||
$user->password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$user->v2ray_uuid = Helper::guid(true);
|
||||
$user->uuid = Helper::guid(true);
|
||||
$user->token = Helper::guid();
|
||||
if ($request->input('invite_code')) {
|
||||
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
|
||||
@ -64,7 +70,7 @@ class AuthController extends Controller
|
||||
}
|
||||
} else {
|
||||
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
|
||||
if (!(int)config('v2board.invite_never_expire', env('V2BOARD_INVITE_NEVER_EXPIRE'))) {
|
||||
if (!(int)config('v2board.invite_never_expire', 0)) {
|
||||
$inviteCode->status = 1;
|
||||
$inviteCode->save();
|
||||
}
|
||||
@ -86,7 +92,7 @@ class AuthController extends Controller
|
||||
abort(500, '注册失败');
|
||||
}
|
||||
if ((int)config('v2board.email_verify', 0)) {
|
||||
Cache::forget($redisKey);
|
||||
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
|
||||
}
|
||||
$request->session()->put('email', $user->email);
|
||||
$request->session()->put('id', $user->id);
|
||||
@ -189,8 +195,7 @@ class AuthController extends Controller
|
||||
|
||||
public function forget(AuthForget $request)
|
||||
{
|
||||
$redisKey = 'sendEmailVerify:' . $request->input('email');
|
||||
if (Cache::get($redisKey) !== $request->input('email_code')) {
|
||||
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
|
||||
abort(500, '邮箱验证码有误');
|
||||
}
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
@ -202,7 +207,7 @@ class AuthController extends Controller
|
||||
if (!$user->save()) {
|
||||
abort(500, '重置失败');
|
||||
}
|
||||
Cache::forget($redisKey);
|
||||
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
|
@ -9,9 +9,10 @@ use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Jobs\SendEmailJob;
|
||||
use App\Models\InviteCode;
|
||||
use App\Utils\Dict;
|
||||
use App\Utils\CacheKey;
|
||||
|
||||
class CommController extends Controller
|
||||
{
|
||||
@ -38,25 +39,25 @@ class CommController extends Controller
|
||||
public function sendEmailVerify(CommSendEmailVerify $request)
|
||||
{
|
||||
$email = $request->input('email');
|
||||
$cacheKey = 'sendEmailVerify:' . $email;
|
||||
if (Cache::get($cacheKey)) {
|
||||
abort(500, '验证码已发送,请过一会在请求');
|
||||
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
|
||||
abort(500, '验证码已发送,请过一会再请求');
|
||||
}
|
||||
$code = Helper::randomChar(6);
|
||||
$code = rand(100000, 999999);
|
||||
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
|
||||
|
||||
SendEmail::dispatch([
|
||||
SendEmailJob::dispatch([
|
||||
'email' => $email,
|
||||
'subject' => $subject,
|
||||
'template_name' => 'mail.sendEmailVerify',
|
||||
'template_name' => 'verify',
|
||||
'template_value' => [
|
||||
'name' => config('v2board.app_name', 'V2Board'),
|
||||
'code' => $code,
|
||||
'url' => config('v2board.app_url')
|
||||
]
|
||||
])->onQueue('verify_mail');
|
||||
]);
|
||||
|
||||
Cache::put($cacheKey, $code, 60);
|
||||
Cache::put(CacheKey::get('EMAIL_VERIFY_CODE', $email), $code, 300);
|
||||
Cache::put(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email), time(), 60);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
|
@ -2,18 +2,24 @@
|
||||
|
||||
namespace App\Http\Controllers\Server;
|
||||
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/*
|
||||
* V2ray Aurora
|
||||
* Github: https://github.com/tokumeikoi/aurora
|
||||
*/
|
||||
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":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"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 __construct(Request $request)
|
||||
{
|
||||
$token = $request->input('token');
|
||||
@ -33,30 +39,18 @@ class DeepbworkController extends Controller
|
||||
if (!$server) {
|
||||
abort(500, 'fail');
|
||||
}
|
||||
Cache::put('server_last_check_at_' . $server->id, time());
|
||||
$users = User::whereIn('group_id', json_decode($server->group_id))
|
||||
->select([
|
||||
'id',
|
||||
'email',
|
||||
't',
|
||||
'u',
|
||||
'd',
|
||||
'transfer_enable',
|
||||
'enable',
|
||||
'v2ray_uuid',
|
||||
'v2ray_alter_id',
|
||||
'v2ray_level'
|
||||
])
|
||||
->get();
|
||||
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
|
||||
$serverService = new ServerService();
|
||||
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
|
||||
$result = [];
|
||||
foreach ($users as $user) {
|
||||
$user->v2ray_user = [
|
||||
"uuid" => $user->v2ray_uuid,
|
||||
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
|
||||
"uuid" => $user->uuid,
|
||||
"email" => sprintf("%s@v2board.user", $user->uuid),
|
||||
"alter_id" => $user->v2ray_alter_id,
|
||||
"level" => $user->v2ray_level,
|
||||
];
|
||||
unset($user['v2ray_uuid']);
|
||||
unset($user['uuid']);
|
||||
unset($user['v2ray_alter_id']);
|
||||
unset($user['v2ray_level']);
|
||||
array_push($result, $user);
|
||||
@ -70,32 +64,37 @@ class DeepbworkController extends Controller
|
||||
// 后端提交数据
|
||||
public function submit(Request $request)
|
||||
{
|
||||
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
|
||||
// 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'
|
||||
'ret' => 0,
|
||||
'msg' => 'server is not found'
|
||||
]);
|
||||
}
|
||||
$data = file_get_contents('php://input');
|
||||
$data = json_decode($data, true);
|
||||
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
|
||||
$serverService = new ServerService();
|
||||
$userService = new UserService();
|
||||
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;
|
||||
$user->save();
|
||||
if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
|
||||
return response([
|
||||
'ret' => 0,
|
||||
'msg' => 'user fetch fail'
|
||||
]);
|
||||
}
|
||||
|
||||
$serverLog = new ServerLog();
|
||||
$serverLog->user_id = $item['user_id'];
|
||||
$serverLog->server_id = $request->input('node_id');
|
||||
$serverLog->u = $item['u'];
|
||||
$serverLog->d = $item['d'];
|
||||
$serverLog->rate = $server->rate;
|
||||
$serverLog->save();
|
||||
$serverService->log(
|
||||
$item['user_id'],
|
||||
$request->input('node_id'),
|
||||
$item['u'],
|
||||
$item['d'],
|
||||
$server->rate,
|
||||
'vmess'
|
||||
);
|
||||
}
|
||||
|
||||
return response([
|
||||
@ -112,65 +111,11 @@ class DeepbworkController extends Controller
|
||||
if (empty($nodeId) || empty($localPort)) {
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
$server = Server::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
$json = json_decode(self::SERVER_CONFIG);
|
||||
$json->inboundDetour[0]->port = (int)$localPort;
|
||||
$json->inbound->port = (int)$server->server_port;
|
||||
$json->inbound->streamSettings->network = $server->network;
|
||||
if ($server->settings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'ws':
|
||||
$json->inbound->streamSettings->wsSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'http':
|
||||
$json->inbound->streamSettings->httpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->inbound->streamSettings->dsSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'quic':
|
||||
$json->inbound->streamSettings->quicSettings = json_decode($server->settings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($server->rules) {
|
||||
$rules = json_decode($server->rules);
|
||||
// domain
|
||||
if (isset($rules->domain)) {
|
||||
$domainObj = new \StdClass();
|
||||
$domainObj->type = 'field';
|
||||
$domainObj->domain = $rules->domain;
|
||||
$domainObj->outboundTag = 'block';
|
||||
array_push($json->routing->settings->rules, $domainObj);
|
||||
}
|
||||
// protocol
|
||||
if (isset($rules->protocol)) {
|
||||
$protocolObj = new \StdClass();
|
||||
$protocolObj->type = 'field';
|
||||
$protocolObj->protocol = $rules->protocol;
|
||||
$protocolObj->outboundTag = 'block';
|
||||
array_push($json->routing->settings->rules, $protocolObj);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)$server->tls) {
|
||||
$json->inbound->streamSettings->security = 'tls';
|
||||
$tls = (object)[
|
||||
'certificateFile' => '/home/v2ray.crt',
|
||||
'keyFile' => '/home/v2ray.key'
|
||||
];
|
||||
$json->inbound->streamSettings->tlsSettings = new \StdClass();
|
||||
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
|
||||
$serverService = new ServerService();
|
||||
try {
|
||||
$json = $serverService->getVmessConfig($nodeId, $localPort);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, $e->getMessage());
|
||||
}
|
||||
|
||||
die(json_encode($json, JSON_UNESCAPED_UNICODE));
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Server;
|
||||
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
@ -11,9 +14,18 @@ use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/*
|
||||
* V2ray Poseidon
|
||||
* Github: https://github.com/ColetteContreras/trojan-poseidon
|
||||
*/
|
||||
class PoseidonController 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":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"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 $poseidonVersion;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->poseidonVersion = $request->input('poseidon_version');
|
||||
}
|
||||
|
||||
// 后端获取用户
|
||||
public function user(Request $request)
|
||||
@ -25,33 +37,18 @@ class PoseidonController extends Controller
|
||||
if (!$server) {
|
||||
return $this->error("server could not be found", 404);
|
||||
}
|
||||
|
||||
Cache::put('server_last_check_at_' . $server->id, time());
|
||||
$users = User::whereIn('group_id', json_decode($server->group_id))
|
||||
->select([
|
||||
'id',
|
||||
'email',
|
||||
't',
|
||||
'u',
|
||||
'd',
|
||||
'transfer_enable',
|
||||
'enable',
|
||||
'v2ray_uuid',
|
||||
'v2ray_alter_id',
|
||||
'v2ray_level'
|
||||
])
|
||||
->whereRaw('u + d < transfer_enable')
|
||||
->where('enable', 1)
|
||||
->get();
|
||||
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
|
||||
$serverService = new ServerService();
|
||||
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
|
||||
$result = [];
|
||||
foreach ($users as $user) {
|
||||
$user->v2ray_user = [
|
||||
"uuid" => $user->v2ray_uuid,
|
||||
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
|
||||
"uuid" => $user->uuid,
|
||||
"email" => sprintf("%s@v2board.user", $user->uuid),
|
||||
"alter_id" => $user->v2ray_alter_id,
|
||||
"level" => $user->v2ray_level,
|
||||
];
|
||||
unset($user['v2ray_uuid']);
|
||||
unset($user['uuid']);
|
||||
unset($user['v2ray_alter_id']);
|
||||
unset($user['v2ray_level']);
|
||||
array_push($result, $user);
|
||||
@ -64,30 +61,30 @@ class PoseidonController extends Controller
|
||||
public function submit(Request $request)
|
||||
{
|
||||
if ($r = $this->verifyToken($request)) { return $r; }
|
||||
|
||||
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
|
||||
$server = Server::find($request->input('node_id'));
|
||||
if (!$server) {
|
||||
return $this->error("server could not be found", 404);
|
||||
}
|
||||
$data = file_get_contents('php://input');
|
||||
$data = json_decode($data, true);
|
||||
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
|
||||
$serverService = new ServerService();
|
||||
$userService = new UserService();
|
||||
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;
|
||||
$user->save();
|
||||
if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
|
||||
return $this->error("user fetch fail", 500);
|
||||
}
|
||||
|
||||
$serverLog = new ServerLog();
|
||||
$serverLog->user_id = $item['user_id'];
|
||||
$serverLog->server_id = $request->input('node_id');
|
||||
$serverLog->u = $item['u'];
|
||||
$serverLog->d = $item['d'];
|
||||
$serverLog->rate = $server->rate;
|
||||
$serverLog->save();
|
||||
$serverService->log(
|
||||
$item['user_id'],
|
||||
$request->input('node_id'),
|
||||
$item['u'],
|
||||
$item['d'],
|
||||
$server->rate,
|
||||
'vmess'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->success('');
|
||||
@ -103,72 +100,32 @@ class PoseidonController extends Controller
|
||||
if (empty($nodeId) || empty($localPort)) {
|
||||
return $this->error('invalid parameters', 400);
|
||||
}
|
||||
$server = Server::find($nodeId);
|
||||
if (!$server) {
|
||||
return $this->error("server could not be found", 404);
|
||||
}
|
||||
$json = json_decode(self::SERVER_CONFIG);
|
||||
$json->inboundDetour[0]->port = (int)$localPort;
|
||||
$json->inbound->port = (int)$server->server_port;
|
||||
$json->inbound->streamSettings->network = $server->network;
|
||||
if ($server->settings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'ws':
|
||||
$json->inbound->streamSettings->wsSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'http':
|
||||
$json->inbound->streamSettings->httpSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->inbound->streamSettings->dsSettings = json_decode($server->settings);
|
||||
break;
|
||||
case 'quic':
|
||||
$json->inbound->streamSettings->quicSettings = json_decode($server->settings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($server->rules) {
|
||||
$rules = json_decode($server->rules);
|
||||
// domain
|
||||
if (isset($rules->domain)) {
|
||||
$domainObj = new \StdClass();
|
||||
$domainObj->type = 'field';
|
||||
$domainObj->domain = $rules->domain;
|
||||
$domainObj->outboundTag = 'block';
|
||||
array_push($json->routing->settings->rules, $domainObj);
|
||||
}
|
||||
// protocol
|
||||
if (isset($rules->protocol)) {
|
||||
$protocolObj = new \StdClass();
|
||||
$protocolObj->type = 'field';
|
||||
$protocolObj->protocol = $rules->protocol;
|
||||
$protocolObj->outboundTag = 'block';
|
||||
array_push($json->routing->settings->rules, $protocolObj);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)$server->tls) {
|
||||
$json->inbound->streamSettings->security = 'tls';
|
||||
$tls = (object)[
|
||||
'certificateFile' => '/home/v2ray.crt',
|
||||
'keyFile' => '/home/v2ray.key'
|
||||
$serverService = new ServerService();
|
||||
try {
|
||||
$json = $serverService->getVmessConfig($nodeId, $localPort);
|
||||
$json->poseidon = [
|
||||
'license_key' => (string)config('v2board.server_license'),
|
||||
];
|
||||
$json->inbound->streamSettings->tlsSettings = new \StdClass();
|
||||
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
|
||||
if ($this->poseidonVersion >= 'v1.5.0') {
|
||||
// don't need it after v1.5.0
|
||||
unset($json->inboundDetour);
|
||||
unset($json->stats);
|
||||
unset($json->api);
|
||||
array_shift($json->routing->rules);
|
||||
}
|
||||
|
||||
foreach($json->policy->levels as &$level) {
|
||||
$level->handshake = 2;
|
||||
$level->uplinkOnly = 2;
|
||||
$level->downlinkOnly = 2;
|
||||
$level->connIdle = 60;
|
||||
}
|
||||
|
||||
return $this->success($json);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
$json->poseidon = [
|
||||
'license_key' => (string)config('v2board.server_license'),
|
||||
];
|
||||
|
||||
return $this->success($json);
|
||||
}
|
||||
|
||||
protected function verifyToken(Request $request)
|
||||
|
120
app/Http/Controllers/Server/TrojanTidalabController.php
Normal file
120
app/Http/Controllers/Server/TrojanTidalabController.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Server;
|
||||
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\ServerTrojan;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/*
|
||||
* Tidal Lab Trojan
|
||||
* Github: https://github.com/tokumeikoi/tidalab-trojan
|
||||
*/
|
||||
class TrojanTidalabController extends Controller
|
||||
{
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$token = $request->input('token');
|
||||
if (empty($token)) {
|
||||
abort(500, 'token is null');
|
||||
}
|
||||
if ($token !== config('v2board.server_token')) {
|
||||
abort(500, 'token is error');
|
||||
}
|
||||
}
|
||||
|
||||
// 后端获取用户
|
||||
public function user(Request $request)
|
||||
{
|
||||
$nodeId = $request->input('node_id');
|
||||
$server = ServerTrojan::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, 'fail');
|
||||
}
|
||||
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
|
||||
$serverService = new ServerService();
|
||||
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
|
||||
$result = [];
|
||||
foreach ($users as $user) {
|
||||
$user->trojan_user = [
|
||||
"password" => $user->uuid,
|
||||
];
|
||||
unset($user['uuid']);
|
||||
unset($user['v2ray_alter_id']);
|
||||
unset($user['v2ray_level']);
|
||||
array_push($result, $user);
|
||||
}
|
||||
return response([
|
||||
'msg' => 'ok',
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
// 后端提交数据
|
||||
public function submit(Request $request)
|
||||
{
|
||||
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
|
||||
$server = ServerTrojan::find($request->input('node_id'));
|
||||
if (!$server) {
|
||||
return response([
|
||||
'ret' => 0,
|
||||
'msg' => 'server is not found'
|
||||
]);
|
||||
}
|
||||
$data = file_get_contents('php://input');
|
||||
$data = json_decode($data, true);
|
||||
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
|
||||
$serverService = new ServerService();
|
||||
$userService = new UserService();
|
||||
foreach ($data as $item) {
|
||||
$u = $item['u'] * $server->rate;
|
||||
$d = $item['d'] * $server->rate;
|
||||
if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
|
||||
return response([
|
||||
'ret' => 0,
|
||||
'msg' => 'user fetch fail'
|
||||
]);
|
||||
}
|
||||
|
||||
$serverService->log(
|
||||
$item['user_id'],
|
||||
$request->input('node_id'),
|
||||
$item['u'],
|
||||
$item['d'],
|
||||
$server->rate,
|
||||
'trojan'
|
||||
);
|
||||
}
|
||||
|
||||
return response([
|
||||
'ret' => 1,
|
||||
'msg' => 'ok'
|
||||
]);
|
||||
}
|
||||
|
||||
// 后端获取配置
|
||||
public function config(Request $request)
|
||||
{
|
||||
$nodeId = $request->input('node_id');
|
||||
$localPort = $request->input('local_port');
|
||||
if (empty($nodeId) || empty($localPort)) {
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
$serverService = new ServerService();
|
||||
try {
|
||||
$json = $serverService->getTrojanConfig($nodeId, $localPort);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, $e->getMessage());
|
||||
}
|
||||
|
||||
die(json_encode($json, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
19
app/Http/Controllers/User/CommController.php
Normal file
19
app/Http/Controllers/User/CommController.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\User;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class CommController extends Controller
|
||||
{
|
||||
public function config()
|
||||
{
|
||||
return response([
|
||||
'data' => [
|
||||
'isTelegram' => (int)config('v2board.telegram_bot_enable', 0),
|
||||
'stripePk' => config('v2board.stripe_pk_live')
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
@ -26,6 +26,13 @@ class CouponController extends Controller
|
||||
if (time() > $coupon->ended_at) {
|
||||
abort(500, '优惠券已过期');
|
||||
}
|
||||
if ($coupon->limit_plan_ids) {
|
||||
$limitPlanIds = json_decode($coupon->limit_plan_ids);
|
||||
info($limitPlanIds);
|
||||
if (!in_array($request->input('plan_id'), $limitPlanIds)) {
|
||||
abort(500, '这个计划无法使用该优惠码');
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $coupon
|
||||
]);
|
||||
|
@ -28,6 +28,7 @@ class InviteController extends Controller
|
||||
{
|
||||
return response([
|
||||
'data' => Order::where('invite_user_id', $request->session()->get('id'))
|
||||
->where('commission_balance', '>', 0)
|
||||
->where('status', 3)
|
||||
->select([
|
||||
'id',
|
||||
@ -45,7 +46,7 @@ class InviteController extends Controller
|
||||
$codes = InviteCode::where('user_id', $request->session()->get('id'))
|
||||
->where('status', 0)
|
||||
->get();
|
||||
$commission_rate = config('v2board.invite_commission');
|
||||
$commission_rate = config('v2board.invite_commission', 10);
|
||||
$user = User::find($request->session()->get('id'));
|
||||
if ($user->commission_rate) {
|
||||
$commission_rate = $user->commission_rate;
|
||||
|
@ -4,14 +4,15 @@ namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\OrderSave;
|
||||
use App\Services\CouponService;
|
||||
use App\Services\OrderService;
|
||||
use App\Services\UserService;
|
||||
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;
|
||||
@ -60,39 +61,11 @@ class OrderController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
private function isNotCompleteOrderByUserId($userId)
|
||||
{
|
||||
$order = Order::whereIn('status', [0, 1])
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
if (!$order) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// surplus value
|
||||
private function getSurplusValue(User $user)
|
||||
{
|
||||
$plan = Plan::find($user->plan_id);
|
||||
$dayPrice = 0;
|
||||
if ($plan->month_price) {
|
||||
$dayPrice = $plan->month_price / 30;
|
||||
} else if ($plan->quarter_price) {
|
||||
$dayPrice = $plan->quarter_price / 91;
|
||||
} else if ($plan->half_year_price) {
|
||||
$dayPrice = $plan->half_year_price / 183;
|
||||
} else if ($plan->year_price) {
|
||||
$dayPrice = $plan->year_price / 365;
|
||||
}
|
||||
$remainingDay = ($user->expired_at - time()) / 86400;
|
||||
return $remainingDay * $dayPrice;
|
||||
}
|
||||
|
||||
public function save(OrderSave $request)
|
||||
{
|
||||
if ($this->isNotCompleteOrderByUserId($request->session()->get('id'))) {
|
||||
abort(500, '存在未付款订单,请取消后再试');
|
||||
$userService = new UserService();
|
||||
if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) {
|
||||
abort(500, '您有未付款或开通中的订单,请稍后或取消再试');
|
||||
}
|
||||
|
||||
$plan = Plan::find($request->input('plan_id'));
|
||||
@ -103,7 +76,9 @@ class OrderController extends Controller
|
||||
}
|
||||
|
||||
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
|
||||
abort(500, '该订阅已售罄');
|
||||
if ($request->input('cycle') !== 'reset_price') {
|
||||
abort(500, '该订阅已售罄');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$plan->renew && $user->plan_id == $plan->id) {
|
||||
@ -111,84 +86,57 @@ class OrderController extends Controller
|
||||
}
|
||||
|
||||
if ($plan[$request->input('cycle')] === NULL) {
|
||||
if ($request->input('cycle') === 'reset_price') {
|
||||
abort(500, '该订阅当前不支持重置流量');
|
||||
}
|
||||
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, '优惠券已过期');
|
||||
}
|
||||
if ($request->input('cycle') === 'reset_price' && !$user->plan_id) {
|
||||
abort(500, '必须存在订阅才可以购买流量重置包');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
$order = new Order();
|
||||
$orderService = new OrderService($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) {
|
||||
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
|
||||
$order->type = 3;
|
||||
$order->surplus_amount = $this->getSurplusValue($user);
|
||||
if ($order->surplus_amount >= $order->total_amount) {
|
||||
$order->refund_amount = $order->surplus_amount - $order->total_amount;
|
||||
|
||||
if ($request->input('coupon_code')) {
|
||||
$couponService = new CouponService($request->input('coupon_code'));
|
||||
if (!$couponService->use($order)) {
|
||||
DB::rollBack();
|
||||
abort(500, '优惠券使用失败');
|
||||
}
|
||||
}
|
||||
|
||||
$orderService->setVipDiscount($user);
|
||||
$orderService->setOrderType($user);
|
||||
$orderService->setInvite($user);
|
||||
|
||||
if ($user->balance && $order->total_amount > 0) {
|
||||
$remainingBalance = $user->balance - $order->total_amount;
|
||||
$userService = new UserService();
|
||||
if ($remainingBalance > 0) {
|
||||
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
|
||||
DB::rollBack();
|
||||
abort(500, '余额不足');
|
||||
}
|
||||
$order->balance_amount = $order->total_amount;
|
||||
$order->total_amount = 0;
|
||||
} else {
|
||||
$order->total_amount = $order->total_amount - $order->surplus_amount;
|
||||
}
|
||||
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
|
||||
$order->type = 2;
|
||||
} else {
|
||||
$order->type = 1;
|
||||
}
|
||||
// discount start
|
||||
// coupon
|
||||
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;
|
||||
}
|
||||
if ($coupon->limit_use !== NULL) {
|
||||
$coupon->limit_use = $coupon->limit_use - 1;
|
||||
if (!$coupon->save()) {
|
||||
DB::rollback();
|
||||
abort(500, '优惠券使用失败');
|
||||
if (!$userService->addBalance($order->user_id, - $user->balance)) {
|
||||
DB::rollBack();
|
||||
abort(500, '余额不足');
|
||||
}
|
||||
$order->balance_amount = $user->balance;
|
||||
$order->total_amount = $order->total_amount - $user->balance;
|
||||
}
|
||||
}
|
||||
// user
|
||||
if ($user->discount) {
|
||||
$order->discount_amount = $order->discount_amount + ($order->total_amount * ($user->discount / 100));
|
||||
}
|
||||
// discount complete
|
||||
$order->total_amount = $order->total_amount - $order->discount_amount;
|
||||
// discount end
|
||||
// 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) {
|
||||
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
|
||||
} else {
|
||||
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$order->save()) {
|
||||
DB::rollback();
|
||||
abort(500, '订单创建失败');
|
||||
@ -217,10 +165,13 @@ class OrderController extends Controller
|
||||
$order->total_amount = 0;
|
||||
$order->status = 1;
|
||||
$order->save();
|
||||
exit();
|
||||
return response([
|
||||
'type' => -1,
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
switch ($method) {
|
||||
// return type => 0: QRCode / 1: URL
|
||||
// return type => 0: QRCode / 1: URL / 2: No action
|
||||
case 0:
|
||||
// alipayF2F
|
||||
if (!(int)config('v2board.alipay_enable')) {
|
||||
@ -265,6 +216,14 @@ class OrderController extends Controller
|
||||
'type' => 1,
|
||||
'data' => $this->payTaro($order)
|
||||
]);
|
||||
case 6:
|
||||
if (!(int)config('v2board.stripe_card_enable')) {
|
||||
abort(500, '支付方式不可用');
|
||||
}
|
||||
return response([
|
||||
'type' => 2,
|
||||
'data' => $this->stripeCard($order, $request->input('token'))
|
||||
]);
|
||||
default:
|
||||
abort(500, '支付方式不存在');
|
||||
}
|
||||
@ -313,7 +272,7 @@ class OrderController extends Controller
|
||||
|
||||
if ((int)config('v2board.bitpayx_enable')) {
|
||||
$bitpayX = new \StdClass();
|
||||
$bitpayX->name = '聚合支付';
|
||||
$bitpayX->name = config('v2board.bitpayx_name', '在线支付');
|
||||
$bitpayX->method = 4;
|
||||
$bitpayX->icon = 'wallet';
|
||||
array_push($data, $bitpayX);
|
||||
@ -321,12 +280,20 @@ class OrderController extends Controller
|
||||
|
||||
if ((int)config('v2board.paytaro_enable')) {
|
||||
$obj = new \StdClass();
|
||||
$obj->name = '聚合支付';
|
||||
$obj->name = config('v2board.paytaro_name', '在线支付');
|
||||
$obj->method = 5;
|
||||
$obj->icon = 'wallet';
|
||||
array_push($data, $obj);
|
||||
}
|
||||
|
||||
if ((int)config('v2board.stripe_card_enable')) {
|
||||
$obj = new \StdClass();
|
||||
$obj->name = '信用卡';
|
||||
$obj->method = 6;
|
||||
$obj->icon = 'card';
|
||||
array_push($data, $obj);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => $data
|
||||
]);
|
||||
@ -346,8 +313,8 @@ class OrderController extends Controller
|
||||
if ($order->status !== 0) {
|
||||
abort(500, '只可以取消待支付订单');
|
||||
}
|
||||
$order->status = 2;
|
||||
if (!$order->save()) {
|
||||
$orderService = new OrderService($order);
|
||||
if (!$orderService->cancel()) {
|
||||
abort(500, '取消失败');
|
||||
}
|
||||
return response([
|
||||
@ -381,15 +348,22 @@ class OrderController extends Controller
|
||||
|
||||
private function stripeAlipay($order)
|
||||
{
|
||||
$exchange = Helper::exchange('CNY', 'HKD');
|
||||
$currency = config('v2board.stripe_currency', 'hkd');
|
||||
$exchange = Helper::exchange('CNY', strtoupper($currency));
|
||||
if (!$exchange) {
|
||||
abort(500, '货币转换超时,请稍后再试');
|
||||
}
|
||||
Stripe::setApiKey(config('v2board.stripe_sk_live'));
|
||||
$source = Source::create([
|
||||
'amount' => floor($order->total_amount * $exchange),
|
||||
'currency' => 'hkd',
|
||||
'currency' => $currency,
|
||||
'type' => 'alipay',
|
||||
'statement_descriptor' => $order->trade_no,
|
||||
'metadata' => [
|
||||
'user_id' => $order->user_id,
|
||||
'out_trade_no' => $order->trade_no,
|
||||
'identifier' => ''
|
||||
],
|
||||
'redirect' => [
|
||||
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
|
||||
]
|
||||
@ -397,24 +371,26 @@ class OrderController extends Controller
|
||||
if (!$source['redirect']['url']) {
|
||||
abort(500, '支付网关请求失败');
|
||||
}
|
||||
|
||||
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
|
||||
abort(500, '订单创建失败');
|
||||
}
|
||||
return $source['redirect']['url'];
|
||||
}
|
||||
|
||||
private function stripeWepay($order)
|
||||
{
|
||||
$exchange = Helper::exchange('CNY', 'HKD');
|
||||
$currency = config('v2board.stripe_currency', 'hkd');
|
||||
$exchange = Helper::exchange('CNY', strtoupper($currency));
|
||||
if (!$exchange) {
|
||||
abort(500, '货币转换超时,请稍后再试');
|
||||
}
|
||||
Stripe::setApiKey(config('v2board.stripe_sk_live'));
|
||||
$source = Source::create([
|
||||
'amount' => floor($order->total_amount * $exchange),
|
||||
'currency' => 'hkd',
|
||||
'currency' => $currency,
|
||||
'type' => 'wechat',
|
||||
'metadata' => [
|
||||
'user_id' => $order->user_id,
|
||||
'out_trade_no' => $order->trade_no,
|
||||
'identifier' => ''
|
||||
],
|
||||
'redirect' => [
|
||||
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
|
||||
]
|
||||
@ -422,12 +398,38 @@ class OrderController extends Controller
|
||||
if (!$source['wechat']['qr_code_url']) {
|
||||
abort(500, '支付网关请求失败');
|
||||
}
|
||||
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
|
||||
abort(500, '订单创建失败');
|
||||
}
|
||||
return $source['wechat']['qr_code_url'];
|
||||
}
|
||||
|
||||
private function stripeCard($order, string $token)
|
||||
{
|
||||
$currency = config('v2board.stripe_currency', 'hkd');
|
||||
$exchange = Helper::exchange('CNY', strtoupper($currency));
|
||||
if (!$exchange) {
|
||||
abort(500, '货币转换超时,请稍后再试');
|
||||
}
|
||||
Stripe::setApiKey(config('v2board.stripe_sk_live'));
|
||||
try {
|
||||
$charge = \Stripe\Charge::create([
|
||||
'amount' => floor($order->total_amount * $exchange),
|
||||
'currency' => $currency,
|
||||
'source' => $token,
|
||||
'metadata' => [
|
||||
'user_id' => $order->user_id,
|
||||
'out_trade_no' => $order->trade_no,
|
||||
'identifier' => ''
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '遇到了点问题,请刷新页面稍后再试');
|
||||
}
|
||||
info($charge);
|
||||
if (!$charge->paid) {
|
||||
abort(500, '扣款失败,请检查信用卡信息');
|
||||
}
|
||||
return $charge->paid;
|
||||
}
|
||||
|
||||
private function bitpayX($order)
|
||||
{
|
||||
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
|
||||
@ -444,7 +446,7 @@ class OrderController extends Controller
|
||||
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
|
||||
$params['token'] = $bitpayX->sign($strToSign);
|
||||
$result = $bitpayX->mprequest($params);
|
||||
Log::info('bitpayXSubmit: ' . json_encode($result));
|
||||
// Log::info('bitpayXSubmit: ' . json_encode($result));
|
||||
return isset($result['payment_url']) ? $result['payment_url'] : false;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ class PlanController extends Controller
|
||||
]);
|
||||
}
|
||||
$plan = Plan::where('show', 1)
|
||||
->orderBy('sort', 'ASC')
|
||||
->get();
|
||||
return response([
|
||||
'data' => $plan
|
||||
|
@ -3,6 +3,9 @@
|
||||
namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Models\Server;
|
||||
@ -16,28 +19,15 @@ class ServerController extends Controller
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$user = User::find($request->session()->get('id'));
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
for ($i = 0; $i < count($server); $i++) {
|
||||
$server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
|
||||
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']);
|
||||
}
|
||||
$servers = [];
|
||||
$userService = new UserService();
|
||||
if ($userService->isAvailable($user)) {
|
||||
$serverService = new ServerService();
|
||||
$servers = $serverService->getAllServers($user);
|
||||
$servers = array_merge($servers['vmess'], $servers['trojan']);
|
||||
}
|
||||
return response([
|
||||
'data' => $server
|
||||
'data' => $servers
|
||||
]);
|
||||
}
|
||||
|
||||
@ -58,17 +48,12 @@ class ServerController extends Controller
|
||||
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();
|
||||
return response([
|
||||
'data' => $res,
|
||||
'total' => $total,
|
||||
'sum' => $sum
|
||||
'total' => $total
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
20
app/Http/Controllers/User/TelegramController.php
Normal file
20
app/Http/Controllers/User/TelegramController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\TelegramService;
|
||||
|
||||
class TelegramController extends Controller
|
||||
{
|
||||
public function getBotInfo()
|
||||
{
|
||||
$telegramService = new TelegramService();
|
||||
$response = $telegramService->getMe();
|
||||
return response([
|
||||
'data' => [
|
||||
'username' => $response->result->username
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@ namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\TicketSave;
|
||||
use App\Http\Requests\User\TicketWithdraw;
|
||||
use App\Jobs\SendTelegramJob;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\TicketMessage;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TicketController extends Controller
|
||||
@ -51,6 +53,9 @@ class TicketController extends Controller
|
||||
public function save(TicketSave $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
|
||||
abort(500, '存在其他工单尚未处理');
|
||||
}
|
||||
$ticket = Ticket::create(array_merge($request->only([
|
||||
'subject',
|
||||
'level'
|
||||
@ -72,6 +77,7 @@ class TicketController extends Controller
|
||||
abort(500, '工单创建失败');
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
@ -109,6 +115,7 @@ class TicketController extends Controller
|
||||
abort(500, '工单回复失败');
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
@ -141,4 +148,53 @@ class TicketController extends Controller
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function withdraw(TicketWithdraw $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
$subject = '[提现申请]本工单由系统发出';
|
||||
$ticket = Ticket::create([
|
||||
'subject' => $subject,
|
||||
'level' => 2,
|
||||
'user_id' => $request->session()->get('id'),
|
||||
'last_reply_user_id' => $request->session()->get('id')
|
||||
]);
|
||||
if (!$ticket) {
|
||||
DB::rollback();
|
||||
abort(500, '工单创建失败');
|
||||
}
|
||||
$methodText = [
|
||||
'alipay' => '支付宝',
|
||||
'paypal' => '贝宝(Paypal)',
|
||||
'usdt' => 'USDT',
|
||||
'btc' => '比特币'
|
||||
];
|
||||
$message = "提现方式:{$methodText[$request->input('withdraw_method')]}\r\n提现账号:{$request->input('withdraw_account')}\r\n";
|
||||
$ticketMessage = TicketMessage::create([
|
||||
'user_id' => $request->session()->get('id'),
|
||||
'ticket_id' => $ticket->id,
|
||||
'message' => $message
|
||||
]);
|
||||
if (!$ticketMessage) {
|
||||
DB::rollback();
|
||||
abort(500, '工单创建失败');
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage)
|
||||
{
|
||||
if (!config('v2board.telegram_bot_enable', 0)) return;
|
||||
$users = User::where('is_admin', 1)
|
||||
->where('telegram_id', '!=', NULL)
|
||||
->get();
|
||||
foreach ($users as $user) {
|
||||
$text = "📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`";
|
||||
SendTelegramJob::dispatch($user->telegram_id, $text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use App\Models\Tutorial;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TutorialController extends Controller
|
||||
{
|
||||
@ -49,9 +50,11 @@ class TutorialController extends Controller
|
||||
'data' => $tutorial
|
||||
]);
|
||||
}
|
||||
$tutorial = Tutorial::select(['id', 'title', 'description', 'icon'])
|
||||
$tutorial = Tutorial::select(['id', 'category_id', 'title'])
|
||||
->where('show', 1)
|
||||
->get();
|
||||
->orderBy('sort', 'ASC')
|
||||
->get()
|
||||
->groupBy('category_id');
|
||||
$user = User::find($request->session()->get('id'));
|
||||
$response = [
|
||||
'data' => [
|
||||
@ -59,8 +62,8 @@ class TutorialController extends Controller
|
||||
'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信息') : '账号过期或未订阅'
|
||||
'apple_id' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id', '本站暂无提供AppleID信息') : '账号过期或未订阅',
|
||||
'apple_id_password' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id_password', '本站暂无提供AppleID信息') : '账号过期或未订阅'
|
||||
]
|
||||
]
|
||||
];
|
||||
@ -71,6 +74,9 @@ class TutorialController extends Controller
|
||||
base64_encode($response['data']['safe_area_var']['subscribe_url'])
|
||||
);
|
||||
// end
|
||||
// fuck support surge urlencode subscribe
|
||||
$response['data']['safe_area_var']['ue_subscribe_url'] = urlencode($response['data']['safe_area_var']['subscribe_url']);
|
||||
// end
|
||||
return response($response);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\UserUpdate;
|
||||
use App\Http\Requests\User\UserChangePassword;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use App\Models\Plan;
|
||||
@ -23,14 +24,8 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function changePassword(Request $request)
|
||||
public function changePassword(UserChangePassword $request)
|
||||
{
|
||||
if (empty($request->input('old_password'))) {
|
||||
abort(500, '旧密码不能为空');
|
||||
}
|
||||
if (empty($request->input('new_password'))) {
|
||||
abort(500, '新密码不能为空');
|
||||
}
|
||||
$user = User::find($request->session()->get('id'));
|
||||
if (!Helper::multiPasswordVerify(
|
||||
$user->password_algo,
|
||||
@ -58,7 +53,7 @@ class UserController extends Controller
|
||||
'transfer_enable',
|
||||
'last_login_at',
|
||||
'created_at',
|
||||
'enable',
|
||||
'banned',
|
||||
'is_admin',
|
||||
'remind_expire',
|
||||
'remind_traffic',
|
||||
@ -67,7 +62,8 @@ class UserController extends Controller
|
||||
'commission_balance',
|
||||
'plan_id',
|
||||
'discount',
|
||||
'commission_rate'
|
||||
'commission_rate',
|
||||
'telegram_id'
|
||||
])
|
||||
->first();
|
||||
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
|
||||
@ -111,7 +107,7 @@ class UserController extends Controller
|
||||
public function resetSecurity(Request $request)
|
||||
{
|
||||
$user = User::find($request->session()->get('id'));
|
||||
$user->v2ray_uuid = Helper::guid(true);
|
||||
$user->uuid = Helper::guid(true);
|
||||
$user->token = Helper::guid();
|
||||
if (!$user->save()) {
|
||||
abort(500, '重置失败');
|
||||
@ -132,7 +128,9 @@ class UserController extends Controller
|
||||
if (!$user) {
|
||||
abort(500, '该用户不存在');
|
||||
}
|
||||
if (!$user->update($updateData)) {
|
||||
try {
|
||||
$user->update($updateData);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
@ -140,4 +138,26 @@ class UserController extends Controller
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function transfer(Request $request)
|
||||
{
|
||||
$user = User::find($request->session()->get('id'));
|
||||
if (!$user) {
|
||||
abort(500, '该用户不存在');
|
||||
}
|
||||
if ($request->input('transfer_amount') <= 0) {
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
if ($request->input('transfer_amount') > $user->commission_balance) {
|
||||
abort(500, '推广佣金余额不足');
|
||||
}
|
||||
$user->commission_balance = $user->commission_balance - $request->input('transfer_amount');
|
||||
$user->balance = $user->balance + $request->input('transfer_amount');
|
||||
if (!$user->save()) {
|
||||
abort(500, '划转失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\App\Http\Middleware\ForceJson::class,
|
||||
\App\Http\Middleware\CORS::class,
|
||||
'throttle:60,1',
|
||||
'throttle:120,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
@ -7,26 +7,35 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
class ConfigSave extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
// invite & commission
|
||||
'safe_mode_enable' => 'in:0,1',
|
||||
'invite_force' => 'in:0,1',
|
||||
'invite_commission' => 'integer',
|
||||
'invite_gen_limit' => 'integer',
|
||||
'invite_never_expire' => 'in:0,1',
|
||||
'commission_first_time_enable' => 'in:0,1',
|
||||
'commission_auto_check_enable' => 'in:0,1',
|
||||
// site
|
||||
'stop_register' => 'in:0,1',
|
||||
'email_verify' => 'in:0,1',
|
||||
'app_name' => '',
|
||||
'app_description' => '',
|
||||
'app_url' => 'url',
|
||||
'subscribe_url' => 'url',
|
||||
'plan_change_enable' => 'in:0,1',
|
||||
'app_url' => 'nullable|url',
|
||||
'subscribe_url' => 'nullable|url',
|
||||
'try_out_enable' => 'in:0,1',
|
||||
'try_out_plan_id' => 'integer',
|
||||
'try_out_hour' => 'numeric',
|
||||
'email_whitelist_enable' => 'in:0,1',
|
||||
'email_whitelist_suffix' => '',
|
||||
'email_gmail_limit_enable' => 'in:0,1',
|
||||
// subscribe
|
||||
'plan_change_enable' => 'in:0,1',
|
||||
'reset_traffic_method' => 'in:0,1',
|
||||
'renew_reset_traffic_enable' => 'in:0,1',
|
||||
// server
|
||||
'server_token' => 'nullable|min:16',
|
||||
'server_license' => 'nullable',
|
||||
'server_log_level' => 'nullable|in:debug,info,warning,error,none',
|
||||
// alipay
|
||||
'alipay_enable' => 'in:0,1',
|
||||
'alipay_appid' => 'nullable|integer|min:16',
|
||||
@ -35,13 +44,17 @@ class ConfigSave extends FormRequest
|
||||
// stripe
|
||||
'stripe_alipay_enable' => 'in:0,1',
|
||||
'stripe_wepay_enable' => 'in:0,1',
|
||||
'stripe_card_enable' => 'in:0,1',
|
||||
'stripe_sk_live' => '',
|
||||
'stripe_pk_live' => '',
|
||||
'stripe_webhook_key' => '',
|
||||
'stripe_currency' => 'in:hkd,usd,sgd,eur,gbp',
|
||||
// bitpayx
|
||||
'bitpayx_name' => '',
|
||||
'bitpayx_enable' => 'in:0,1',
|
||||
'bitpayx_appsecret' => '',
|
||||
// paytaro
|
||||
'paytaro_name' => '',
|
||||
'paytaro_enable' => 'in:0,1',
|
||||
'paytaro_app_id' => '',
|
||||
'paytaro_app_secret' => '',
|
||||
@ -52,14 +65,16 @@ class ConfigSave extends FormRequest
|
||||
'frontend_background_url' => 'nullable|url',
|
||||
// tutorial
|
||||
'apple_id' => 'email',
|
||||
'apple_id_password' => ''
|
||||
'apple_id_password' => '',
|
||||
// email
|
||||
'email_template' => '',
|
||||
// telegram
|
||||
'telegram_bot_enable' => 'in:0,1',
|
||||
'telegram_bot_token' => '',
|
||||
'telegram_discuss_id' => '',
|
||||
'telegram_channel_id' => ''
|
||||
];
|
||||
|
||||
public static function filter()
|
||||
{
|
||||
return array_keys(self::RULES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -72,7 +87,10 @@ class ConfigSave extends FormRequest
|
||||
|
||||
public function messages()
|
||||
{
|
||||
// illiteracy prompt
|
||||
return [
|
||||
'app_url.url' => '站点URL格式不正确,必须携带http(s)://',
|
||||
'subscribe_url.url' => '订阅URL格式不正确,必须携带http(s)://'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,16 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CouponSave extends FormRequest
|
||||
{
|
||||
const RULES = [
|
||||
'name' => 'required',
|
||||
'type' => 'required|in:1,2',
|
||||
'value' => 'required|integer',
|
||||
'started_at' => 'required|integer',
|
||||
'ended_at' => 'required|integer',
|
||||
'limit_use' => 'nullable|integer',
|
||||
'limit_plan_ids' => 'nullable|array',
|
||||
'code' => ''
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -13,14 +23,7 @@ class CouponSave extends FormRequest
|
||||
*/
|
||||
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'
|
||||
];
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
@ -35,7 +38,8 @@ class CouponSave extends FormRequest
|
||||
'started_at.integer' => '开始时间格式有误',
|
||||
'ended_at.required' => '结束时间不能为空',
|
||||
'ended_at.integer' => '结束时间格式有误',
|
||||
'limit_use.integer' => '使用次数格式有误'
|
||||
'limit_use.integer' => '使用次数格式有误',
|
||||
'limit_plan_ids.array' => '指定订阅格式有误'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class NoticeSave extends FormRequest
|
||||
return [
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'img_url' => 'url'
|
||||
'img_url' => 'nullable|url'
|
||||
];
|
||||
}
|
||||
|
||||
|
34
app/Http/Requests/Admin/OrderAssign.php
Normal file
34
app/Http/Requests/Admin/OrderAssign.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class OrderAssign extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'plan_id' => 'required',
|
||||
'email' => 'required',
|
||||
'total_amount' => 'required',
|
||||
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'plan_id.required' => '订阅不能为空',
|
||||
'email.required' => '邮箱不能为空',
|
||||
'total_amount.required' => '支付金额不能为空',
|
||||
'cycle.required' => '订阅周期不能为空',
|
||||
'cycle.in' => '订阅周期格式有误'
|
||||
];
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ class OrderUpdate extends FormRequest
|
||||
{
|
||||
return [
|
||||
'status' => 'in:0,1,2,3',
|
||||
'commission_status' => 'in:0,1'
|
||||
'commission_status' => 'in:0,1,2'
|
||||
];
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ class OrderUpdate extends FormRequest
|
||||
{
|
||||
return [
|
||||
'status.in' => '销售状态格式不正确',
|
||||
'commission_status.in' => '续费状态格式不正确'
|
||||
'commission_status.in' => '佣金状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,18 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PlanSave extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
'name' => 'required',
|
||||
'content' => '',
|
||||
'group_id' => 'required',
|
||||
'transfer_enable' => 'required',
|
||||
'month_price' => 'nullable|integer',
|
||||
'quarter_price' => 'nullable|integer',
|
||||
'half_year_price' => 'nullable|integer',
|
||||
'year_price' => 'nullable|integer',
|
||||
'onetime_price' => 'nullable|integer',
|
||||
'reset_price' => 'nullable|integer'
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -13,27 +25,23 @@ class PlanSave extends FormRequest
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'group_id' => 'required',
|
||||
'transfer_enable' => 'required',
|
||||
'month_price' => 'nullable|integer',
|
||||
'quarter_price' => 'nullable|integer',
|
||||
'half_year_price' => 'nullable|integer',
|
||||
'year_price' => 'nullable|integer'
|
||||
];
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => '套餐名称不能为空',
|
||||
'type.required' => '套餐类型不能为空',
|
||||
'type.in' => '套餐类型格式有误',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'transfer_enable.required' => '流量不能为空',
|
||||
'month_price.integer' => '月付金额格式有误',
|
||||
'quarter_price.integer' => '季付金额格式有误',
|
||||
'half_year_price.integer' => '半年付金额格式有误',
|
||||
'year_price.integer' => '年付金额格式有误'
|
||||
'year_price.integer' => '年付金额格式有误',
|
||||
'onetime_price.integer' => '一次性金额有误',
|
||||
'reset_price.integer' => '流量重置包金额有误'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
28
app/Http/Requests/Admin/PlanSort.php
Normal file
28
app/Http/Requests/Admin/PlanSort.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PlanSort extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'plan_ids' => 'required|array'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'plan_ids.required' => '订阅计划ID不能为空',
|
||||
'plan_ids.array' => '订阅计划ID格式有误'
|
||||
];
|
||||
}
|
||||
}
|
48
app/Http/Requests/Admin/ServerTrojanSave.php
Normal file
48
app/Http/Requests/Admin/ServerTrojanSave.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerTrojanSave extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
'show' => '',
|
||||
'name' => 'required',
|
||||
'group_id' => 'required|array',
|
||||
'parent_id' => 'nullable|integer',
|
||||
'host' => 'required',
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'allow_insecure' => 'nullable|in:0,1',
|
||||
'server_name' => 'nullable',
|
||||
'tags' => 'nullable|array',
|
||||
'rate' => 'required|numeric'
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => '节点名称不能为空',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'group_id.array' => '权限组格式不正确',
|
||||
'parent_id.integer' => '父节点格式不正确',
|
||||
'host.required' => '节点地址不能为空',
|
||||
'port.required' => '连接端口不能为空',
|
||||
'server_port.required' => '后端服务端口不能为空',
|
||||
'allow_insecure.in' => '允许不安全格式不正确',
|
||||
'tags.array' => '标签格式不正确',
|
||||
'rate.required' => '倍率不能为空',
|
||||
'rate.numeric' => '倍率格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
28
app/Http/Requests/Admin/ServerTrojanSort.php
Normal file
28
app/Http/Requests/Admin/ServerTrojanSort.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerTrojanSort extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'server_ids' => 'required|array'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'server_ids.required' => '服务器ID不能为空',
|
||||
'server_ids.array' => '服务器ID格式有误'
|
||||
];
|
||||
}
|
||||
}
|
28
app/Http/Requests/Admin/ServerTrojanUpdate.php
Executable file
28
app/Http/Requests/Admin/ServerTrojanUpdate.php
Executable file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerTrojanUpdate extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => 'in:0,1'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'show.in' => '显示状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -4,10 +4,9 @@ namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerSave extends FormRequest
|
||||
class ServerV2raySave extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
'rules' => '',
|
||||
'show' => '',
|
||||
'name' => 'required',
|
||||
'group_id' => 'required|array',
|
||||
@ -16,10 +15,13 @@ class ServerSave extends FormRequest
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'tls' => 'required',
|
||||
'tags' => 'array',
|
||||
'tags' => 'nullable|array',
|
||||
'rate' => 'required|numeric',
|
||||
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
|
||||
'settings' => ''
|
||||
'networkSettings' => '',
|
||||
'ruleSettings' => '',
|
||||
'tlsSettings' => '',
|
||||
'dnsSettings' => ''
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
28
app/Http/Requests/Admin/ServerV2raySort.php
Normal file
28
app/Http/Requests/Admin/ServerV2raySort.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerV2raySort extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'server_ids' => 'required|array'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'server_ids.required' => '服务器ID不能为空',
|
||||
'server_ids.array' => '服务器ID格式有误'
|
||||
];
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerUpdate extends FormRequest
|
||||
class ServerV2rayUpdate extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
@ -6,6 +6,12 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TutorialSave extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
'title' => 'required',
|
||||
// 1:windows 2:macos 3:ios 4:android 5:linux 6:router
|
||||
'category_id' => 'required|in:1,2,3,4,5,6',
|
||||
'steps' => 'required'
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -13,19 +19,16 @@ class TutorialSave extends FormRequest
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'title' => 'required',
|
||||
'description' => 'required',
|
||||
'icon' => 'required'
|
||||
];
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'title.required' => '标题不能为空',
|
||||
'description.required' => '描述不能为空',
|
||||
'icon.required' => '图标不能为空'
|
||||
'category_id.required' => '分类不能为空',
|
||||
'category_id.in' => '分类格式不正确',
|
||||
'steps.required' => '教程步骤不能为空'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
28
app/Http/Requests/Admin/TutorialSort.php
Normal file
28
app/Http/Requests/Admin/TutorialSort.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TutorialSort extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'tutorial_ids' => 'required|array'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'tutorial_ids.required' => '教程ID不能为空',
|
||||
'tutorial_ids.array' => '教程ID格式有误'
|
||||
];
|
||||
}
|
||||
}
|
@ -6,6 +6,21 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserUpdate extends FormRequest
|
||||
{
|
||||
CONST RULES = [
|
||||
'email' => 'required|email',
|
||||
'password' => 'nullable',
|
||||
'transfer_enable' => 'numeric',
|
||||
'expired_at' => 'nullable|integer',
|
||||
'banned' => 'required|in:0,1',
|
||||
'plan_id' => 'nullable|integer',
|
||||
'commission_rate' => 'nullable|integer|min:0|max:100',
|
||||
'discount' => 'nullable|integer|min:0|max:100',
|
||||
'is_admin' => 'required|in:0,1',
|
||||
'u' => 'integer',
|
||||
'd' => 'integer',
|
||||
'balance' => 'integer',
|
||||
'commission_balance' => 'integer'
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -13,16 +28,7 @@ class UserUpdate extends FormRequest
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'transfer_enable' => 'numeric',
|
||||
'expired_at' => 'integer',
|
||||
'banned' => 'required|in:0,1',
|
||||
'is_admin' => 'required|in:0,1',
|
||||
'plan_id' => 'integer',
|
||||
'commission_rate' => 'nullable|integer|min:0|max:100',
|
||||
'discount' => 'nullable|integer|min:0|max:100'
|
||||
];
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
@ -44,7 +50,11 @@ class UserUpdate extends FormRequest
|
||||
'discount.integer' => '专属折扣比例格式不正确',
|
||||
'discount.nullable' => '专属折扣比例格式不正确',
|
||||
'discount.min' => '专属折扣比例最小为0',
|
||||
'discount.max' => '专属折扣比例最大为100'
|
||||
'discount.max' => '专属折扣比例最大为100',
|
||||
'u.integer' => '上行流量格式不正确',
|
||||
'd.integer' => '下行流量格式不正确',
|
||||
'balance.integer' => '余额格式不正确',
|
||||
'commission_balance.integer' => '佣金格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class OrderSave extends FormRequest
|
||||
{
|
||||
return [
|
||||
'plan_id' => 'required',
|
||||
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price'
|
||||
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price'
|
||||
];
|
||||
}
|
||||
|
||||
|
30
app/Http/Requests/User/TicketWithdraw.php
Normal file
30
app/Http/Requests/User/TicketWithdraw.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\User;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TicketWithdraw extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'withdraw_method' => 'required|in:alipay,paypal,usdt,btc',
|
||||
'withdraw_account' => 'required'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'withdraw_method.required' => '提现方式不能为空',
|
||||
'withdraw_method.in' => '提现方式不支持',
|
||||
'withdraw_account.required' => '提现账号不能为空'
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/User/UserChangePassword.php
Normal file
30
app/Http/Requests/User/UserChangePassword.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\User;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserChangePassword extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'old_password' => 'required',
|
||||
'new_password' => 'required|min:8'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'old_password.required' => '旧密码不能为空',
|
||||
'new_password.required' => '新密码不能为空',
|
||||
'new_password.min' => '密码必须大于8位数'
|
||||
];
|
||||
}
|
||||
}
|
@ -14,23 +14,45 @@ class AdminRoute
|
||||
// Config
|
||||
$router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
|
||||
$router->post('/config/save', 'Admin\\ConfigController@save');
|
||||
$router->get ('/config/getEmailTemplate', 'Admin\\ConfigController@getEmailTemplate');
|
||||
$router->post('/config/setTelegramWebhook', 'Admin\\ConfigController@setTelegramWebhook');
|
||||
// Plan
|
||||
$router->get ('/plan/fetch', 'Admin\\PlanController@fetch');
|
||||
$router->post('/plan/save', 'Admin\\PlanController@save');
|
||||
$router->post('/plan/drop', 'Admin\\PlanController@drop');
|
||||
$router->post('/plan/update', 'Admin\\PlanController@update');
|
||||
$router->post('/plan/sort', 'Admin\\PlanController@sort');
|
||||
// Server
|
||||
$router->get ('/server/fetch', 'Admin\\ServerController@fetch');
|
||||
$router->post('/server/save', 'Admin\\ServerController@save');
|
||||
$router->get ('/server/group/fetch', 'Admin\\ServerController@groupFetch');
|
||||
$router->post('/server/group/save', 'Admin\\ServerController@groupSave');
|
||||
$router->post('/server/group/drop', 'Admin\\ServerController@groupDrop');
|
||||
$router->post('/server/drop', 'Admin\\ServerController@drop');
|
||||
$router->post('/server/update', 'Admin\\ServerController@update');
|
||||
$router->get ('/server/group/fetch', 'Admin\\Server\\GroupController@fetch');
|
||||
$router->post('/server/group/save', 'Admin\\Server\\GroupController@save');
|
||||
$router->post('/server/group/drop', 'Admin\\Server\\GroupController@drop');
|
||||
$router->group([
|
||||
'prefix' => 'server/trojan'
|
||||
], function ($router) {
|
||||
$router->get ('fetch', 'Admin\\Server\\TrojanController@fetch');
|
||||
$router->post('save', 'Admin\\Server\\TrojanController@save');
|
||||
$router->post('drop', 'Admin\\Server\\TrojanController@drop');
|
||||
$router->post('update', 'Admin\\Server\\TrojanController@update');
|
||||
$router->post('copy', 'Admin\\Server\\TrojanController@copy');
|
||||
$router->post('sort', 'Admin\\Server\\TrojanController@sort');
|
||||
$router->post('viewConfig', 'Admin\\Server\\TrojanController@viewConfig');
|
||||
});
|
||||
$router->group([
|
||||
'prefix' => 'server/v2ray'
|
||||
], function ($router) {
|
||||
$router->get ('fetch', 'Admin\\Server\\V2rayController@fetch');
|
||||
$router->post('save', 'Admin\\Server\\V2rayController@save');
|
||||
$router->post('drop', 'Admin\\Server\\V2rayController@drop');
|
||||
$router->post('update', 'Admin\\Server\\V2rayController@update');
|
||||
$router->post('copy', 'Admin\\Server\\V2rayController@copy');
|
||||
$router->post('sort', 'Admin\\Server\\V2rayController@sort');
|
||||
$router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig');
|
||||
});
|
||||
// Order
|
||||
$router->get ('/order/fetch', 'Admin\\OrderController@fetch');
|
||||
$router->post('/order/repair', 'Admin\\OrderController@repair');
|
||||
$router->post('/order/update', 'Admin\\OrderController@update');
|
||||
$router->post('/order/assign', 'Admin\\OrderController@assign');
|
||||
// User
|
||||
$router->get ('/user/fetch', 'Admin\\UserController@fetch');
|
||||
$router->post('/user/update', 'Admin\\UserController@update');
|
||||
@ -57,6 +79,7 @@ class AdminRoute
|
||||
$router->post('/tutorial/save', 'Admin\\TutorialController@save');
|
||||
$router->post('/tutorial/show', 'Admin\\TutorialController@show');
|
||||
$router->post('/tutorial/drop', 'Admin\\TutorialController@drop');
|
||||
$router->post('/tutorial/sort', 'Admin\\TutorialController@sort');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,9 @@ class ClientRoute
|
||||
// Client
|
||||
$router->get('/subscribe', 'Client\\ClientController@subscribe');
|
||||
// App
|
||||
$router->get('/app/data', 'Client\\AppController@data');
|
||||
$router->get('/app/config', 'Client\\AppController@config');
|
||||
$router->get('/app/getConfig', 'Client\\AppController@getConfig');
|
||||
$router->get('/app/getVersion', 'Client\\AppController@getVersion');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ class GuestRoute
|
||||
$router->post('/order/stripeNotify', 'Guest\\OrderController@stripeNotify');
|
||||
$router->post('/order/bitpayXNotify', 'Guest\\OrderController@bitpayXNotify');
|
||||
$router->post('/order/payTaroNotify', 'Guest\\OrderController@payTaroNotify');
|
||||
// Telegram
|
||||
$router->post('/telegram/webhook', 'Guest\\TelegramController@webhook');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ class UserRoute
|
||||
$router->post('/update', 'User\\UserController@update');
|
||||
$router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
|
||||
$router->get ('/getStat', 'User\\UserController@getStat');
|
||||
$router->post('/transfer', 'User\\UserController@transfer');
|
||||
// Order
|
||||
$router->post('/order/save', 'User\\OrderController@save');
|
||||
$router->post('/order/checkout', 'User\\OrderController@checkout');
|
||||
@ -44,11 +45,16 @@ class UserRoute
|
||||
$router->post('/ticket/close', 'User\\TicketController@close');
|
||||
$router->post('/ticket/save', 'User\\TicketController@save');
|
||||
$router->get ('/ticket/fetch', 'User\\TicketController@fetch');
|
||||
$router->post('/ticket/withdraw', 'User\\TicketController@withdraw');
|
||||
// Server
|
||||
$router->get ('/server/fetch', 'User\\ServerController@fetch');
|
||||
$router->get ('/server/log/fetch', 'User\\ServerController@logFetch');
|
||||
// Coupon
|
||||
$router->post('/coupon/check', 'User\\CouponController@check');
|
||||
// Telegram
|
||||
$router->get ('/telegram/getBotInfo', 'User\\TelegramController@getBotInfo');
|
||||
// Comm
|
||||
$router->get ('/comm/config', 'User\\CommController@config');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Models\MailLog;
|
||||
|
||||
class SendEmail implements ShouldQueue
|
||||
class SendEmailJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
protected $params;
|
||||
@ -22,6 +22,8 @@ class SendEmail implements ShouldQueue
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
$this->delay(now()->addSecond(2));
|
||||
$this->onQueue('send_email');
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
@ -35,6 +37,7 @@ class SendEmail implements ShouldQueue
|
||||
$params = $this->params;
|
||||
$email = $params['email'];
|
||||
$subject = $params['subject'];
|
||||
$params['template_name'] = 'mail.' . config('v2board.email_template', 'default') . '.' . $params['template_name'];
|
||||
try {
|
||||
Mail::send(
|
||||
$params['template_name'],
|
43
app/Jobs/SendTelegramJob.php
Normal file
43
app/Jobs/SendTelegramJob.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Services\TelegramService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SendTelegramJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
protected $telegramId;
|
||||
protected $text;
|
||||
|
||||
public $tries = 3;
|
||||
public $timeout = 5;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $telegramId, string $text)
|
||||
{
|
||||
$this->onQueue('send_telegram');
|
||||
$this->telegramId = $telegramId;
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$telegramService = new TelegramService();
|
||||
$telegramService->sendMessage($this->telegramId, $this->text, 'markdown');
|
||||
}
|
||||
}
|
@ -3,11 +3,10 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ServerLog extends Model
|
||||
{
|
||||
protected $table = 'v2_server_log';
|
||||
protected $dateFormat = 'U';
|
||||
protected $dispatchesEvents = [
|
||||
];
|
||||
}
|
||||
|
12
app/Models/ServerStat.php
Normal file
12
app/Models/ServerStat.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ServerStat extends Model
|
||||
{
|
||||
protected $table = 'v2_server_stat';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
}
|
12
app/Models/ServerTrojan.php
Normal file
12
app/Models/ServerTrojan.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ServerTrojan extends Model
|
||||
{
|
||||
protected $table = 'v2_server_trojan';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
}
|
54
app/Services/CouponService.php
Normal file
54
app/Services/CouponService.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Coupon;
|
||||
use App\Models\Order;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CouponService
|
||||
{
|
||||
public $order;
|
||||
|
||||
public function __construct($code)
|
||||
{
|
||||
$this->coupon = Coupon::where('code', $code)->first();
|
||||
if (!$this->coupon) {
|
||||
abort(500, '优惠券无效');
|
||||
}
|
||||
if ($this->coupon->limit_use <= 0 && $this->coupon->limit_use !== NULL) {
|
||||
abort(500, '优惠券已无可用次数');
|
||||
}
|
||||
if (time() < $this->coupon->started_at) {
|
||||
abort(500, '优惠券还未到可用时间');
|
||||
}
|
||||
if (time() > $this->coupon->ended_at) {
|
||||
abort(500, '优惠券已过期');
|
||||
}
|
||||
}
|
||||
|
||||
public function use(Order $order)
|
||||
{
|
||||
switch ($this->coupon->type) {
|
||||
case 1:
|
||||
$order->discount_amount = $this->coupon->value;
|
||||
break;
|
||||
case 2:
|
||||
$order->discount_amount = $order->total_amount * ($this->coupon->value / 100);
|
||||
break;
|
||||
}
|
||||
if ($this->coupon->limit_use !== NULL) {
|
||||
$this->coupon->limit_use = $this->coupon->limit_use - 1;
|
||||
if (!$this->coupon->save()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($this->coupon->limit_plan_ids) {
|
||||
$limitPlanIds = json_decode($this->coupon->limit_plan_ids);
|
||||
if (!in_array($order->plan_id, $limitPlanIds)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
7
app/Services/MailService.php
Normal file
7
app/Services/MailService.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class MailService
|
||||
{
|
||||
}
|
161
app/Services/OrderService.php
Normal file
161
app/Services/OrderService.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OrderService
|
||||
{
|
||||
public $order;
|
||||
|
||||
public function __construct(Order $order)
|
||||
{
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
public function cancel():bool
|
||||
{
|
||||
$order = $this->order;
|
||||
DB::beginTransaction();
|
||||
$order->status = 2;
|
||||
if (!$order->save()) {
|
||||
DB::rollBack();
|
||||
return false;
|
||||
}
|
||||
if ($order->balance_amount) {
|
||||
$userService = new UserService();
|
||||
if (!$userService->addBalance($order->user_id, $order->balance_amount)) {
|
||||
DB::rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setOrderType(User $user)
|
||||
{
|
||||
$order = $this->order;
|
||||
if ($order->cycle === 'reset_price') {
|
||||
$order->type = 4;
|
||||
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
||||
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单操作');
|
||||
$order->type = 3;
|
||||
$this->getSurplusValue($user, $order);
|
||||
if ($order->surplus_amount >= $order->total_amount) {
|
||||
$order->refund_amount = $order->surplus_amount - $order->total_amount;
|
||||
$order->total_amount = 0;
|
||||
} else {
|
||||
$order->total_amount = $order->total_amount - $order->surplus_amount;
|
||||
}
|
||||
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
|
||||
$order->type = 2;
|
||||
} else {
|
||||
$order->type = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public function setVipDiscount(User $user)
|
||||
{
|
||||
$order = $this->order;
|
||||
if ($user->discount) {
|
||||
$order->discount_amount = $order->discount_amount + ($order->total_amount * ($user->discount / 100));
|
||||
}
|
||||
$order->total_amount = $order->total_amount - $order->discount_amount;
|
||||
}
|
||||
|
||||
public function setInvite(User $user)
|
||||
{
|
||||
$order = $this->order;
|
||||
if ($user->invite_user_id && $order->total_amount > 0) {
|
||||
$order->invite_user_id = $user->invite_user_id;
|
||||
$commissionFirstTime = (int)config('v2board.commission_first_time_enable', 1);
|
||||
if (!$commissionFirstTime || ($commissionFirstTime && !Order::where('user_id', $user->id)->where('status', 3)->first())) {
|
||||
$inviter = User::find($user->invite_user_id);
|
||||
if ($inviter && $inviter->commission_rate) {
|
||||
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
|
||||
} else {
|
||||
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getSurplusValue(User $user, Order $order)
|
||||
{
|
||||
if ($user->expired_at === NULL) {
|
||||
$this->getSurplusValueByOneTime($user, $order);
|
||||
} else {
|
||||
$this->getSurplusValueByCycle($user, $order);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getSurplusValueByOneTime(User $user, Order $order)
|
||||
{
|
||||
$plan = Plan::find($user->plan_id);
|
||||
$trafficUnitPrice = $plan->onetime_price / $plan->transfer_enable;
|
||||
if ($user->discount && $trafficUnitPrice) {
|
||||
$trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100);
|
||||
}
|
||||
$notUsedTrafficPrice = $plan->transfer_enable - (($user->u + $user->d) / 1073741824);
|
||||
$result = $trafficUnitPrice * $notUsedTrafficPrice;
|
||||
$orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3);
|
||||
$order->surplus_amount = $result > 0 ? $result : 0;
|
||||
$order->surplus_order_ids = json_encode(array_map(function ($v) { return $v['id'];}, $orderModel->get()->toArray()));
|
||||
}
|
||||
|
||||
private function getSurplusValueByCycle(User $user, Order $order)
|
||||
{
|
||||
$strToMonth = [
|
||||
'month_price' => 1,
|
||||
'quarter_price' => 3,
|
||||
'half_year_price' => 6,
|
||||
'year_price' => 12
|
||||
];
|
||||
$orderModel = Order::where('user_id', $user->id)
|
||||
->where('cycle', '!=', 'reset_price')
|
||||
->where('status', 3);
|
||||
$orderSurplusMonth = 0;
|
||||
$orderSurplusAmount = 0;
|
||||
$userSurplusMonth = ($user->expired_at - time()) / 2678400;
|
||||
foreach ($orderModel->get() as $item) {
|
||||
// 兼容历史余留问题
|
||||
if ($item->cycle === 'onetime_price') continue;
|
||||
$orderSurplusMonth = $orderSurplusMonth + $strToMonth[$item->cycle];
|
||||
$orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount']);
|
||||
}
|
||||
if (!$orderSurplusMonth || !$orderSurplusAmount) return;
|
||||
$monthUnitPrice = $orderSurplusAmount / $orderSurplusMonth;
|
||||
// 如果用户过期月大于订单过期月
|
||||
if ($userSurplusMonth > $orderSurplusMonth) {
|
||||
$orderSurplusAmount = $orderSurplusMonth * $monthUnitPrice;
|
||||
} else {
|
||||
$orderSurplusAmount = $userSurplusMonth * $monthUnitPrice;
|
||||
}
|
||||
if (!$orderSurplusAmount) {
|
||||
return;
|
||||
}
|
||||
$order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
|
||||
$order->surplus_order_ids = json_encode(array_map(function ($v) { return $v['id'];}, $orderModel->get()->toArray()));
|
||||
}
|
||||
|
||||
public function success(string $callbackNo)
|
||||
{
|
||||
$order = $this->order;
|
||||
if ($order->status !== 0) {
|
||||
return true;
|
||||
}
|
||||
$order->status = 1;
|
||||
$order->callback_no = $callbackNo;
|
||||
return $order->save();
|
||||
}
|
||||
}
|
244
app/Services/ServerService.php
Normal file
244
app/Services/ServerService.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ServerLog;
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerTrojan;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServerService
|
||||
{
|
||||
|
||||
CONST V2RAY_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"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":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
|
||||
CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
|
||||
public function getVmess(User $user, $all = false):array
|
||||
{
|
||||
$vmess = [];
|
||||
$model = Server::orderBy('sort', 'ASC');
|
||||
if (!$all) {
|
||||
$model->where('show', 1);
|
||||
}
|
||||
$vmesss = $model->get();
|
||||
foreach ($vmesss as $k => $v) {
|
||||
$groupId = json_decode($vmesss[$k]['group_id']);
|
||||
if (in_array($user->group_id, $groupId)) {
|
||||
$vmesss[$k]['link'] = Helper::buildVmessLink($vmesss[$k], $user);
|
||||
if ($vmesss[$k]['parent_id']) {
|
||||
$vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['parent_id']));
|
||||
} else {
|
||||
$vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['id']));
|
||||
}
|
||||
array_push($vmess, $vmesss[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $vmess;
|
||||
}
|
||||
|
||||
public function getTrojan(User $user, $all = false)
|
||||
{
|
||||
$trojan = [];
|
||||
$model = ServerTrojan::orderBy('sort', 'ASC');
|
||||
if (!$all) {
|
||||
$model->where('show', 1);
|
||||
}
|
||||
$trojans = $model->get();
|
||||
foreach ($trojans as $k => $v) {
|
||||
$groupId = json_decode($trojans[$k]['group_id']);
|
||||
if (in_array($user->group_id, $groupId)) {
|
||||
if ($trojans[$k]['parent_id']) {
|
||||
$trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['parent_id']));
|
||||
} else {
|
||||
$trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['id']));
|
||||
}
|
||||
array_push($trojan, $trojans[$k]);
|
||||
}
|
||||
|
||||
}
|
||||
return $trojan;
|
||||
}
|
||||
|
||||
public function getAllServers(User $user, $all = false)
|
||||
{
|
||||
return [
|
||||
'vmess' => $this->getVmess($user, $all),
|
||||
'trojan' => $this->getTrojan($user, $all)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function getAvailableUsers($groupId)
|
||||
{
|
||||
return User::whereIn('group_id', $groupId)
|
||||
->whereRaw('u + d < transfer_enable')
|
||||
->where(function ($query) {
|
||||
$query->where('expired_at', '>=', time())
|
||||
->orWhere('expired_at', NULL);
|
||||
})
|
||||
->where('banned', 0)
|
||||
->select([
|
||||
'id',
|
||||
'email',
|
||||
't',
|
||||
'u',
|
||||
'd',
|
||||
'transfer_enable',
|
||||
'uuid',
|
||||
'v2ray_alter_id',
|
||||
'v2ray_level'
|
||||
])
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getVmessConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = Server::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
$json = json_decode(self::V2RAY_CONFIG);
|
||||
$json->log->loglevel = config('v2board.server_log_level', 'none');
|
||||
$json->inboundDetour[0]->port = (int)$localPort;
|
||||
$json->inbound->port = (int)$server->server_port;
|
||||
$json->inbound->streamSettings->network = $server->network;
|
||||
$this->setDns($server, $json);
|
||||
$this->setNetwork($server, $json);
|
||||
$this->setRule($server, $json);
|
||||
$this->setTls($server, $json);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function getTrojanConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = ServerTrojan::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
|
||||
$json = json_decode(self::TROJAN_CONFIG);
|
||||
$json->local_port = $server->server_port;
|
||||
$json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
|
||||
$json->ssl->cert = "/root/.cert/server.crt";
|
||||
$json->ssl->key = "/root/.cert/server.key";
|
||||
$json->api->api_port = $localPort;
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function setDns(Server $server, object $json)
|
||||
{
|
||||
if ($server->dnsSettings) {
|
||||
$dns = json_decode($server->dnsSettings);
|
||||
if (isset($dns->servers)) {
|
||||
array_push($dns->servers, '1.1.1.1');
|
||||
array_push($dns->servers, 'localhost');
|
||||
}
|
||||
$json->dns = $dns;
|
||||
$json->outbound->settings->domainStrategy = 'UseIP';
|
||||
}
|
||||
}
|
||||
|
||||
private function setNetwork(Server $server, object $json)
|
||||
{
|
||||
if ($server->networkSettings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->inbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->inbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'ws':
|
||||
$json->inbound->streamSettings->wsSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'http':
|
||||
$json->inbound->streamSettings->httpSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->inbound->streamSettings->dsSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
case 'quic':
|
||||
$json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setRule(Server $server, object $json)
|
||||
{
|
||||
if ($server->ruleSettings) {
|
||||
$rules = json_decode($server->ruleSettings);
|
||||
// domain
|
||||
if (isset($rules->domain) && !empty($rules->domain)) {
|
||||
$rules->domain = array_filter($rules->domain);
|
||||
$domainObj = new \StdClass();
|
||||
$domainObj->type = 'field';
|
||||
$domainObj->domain = $rules->domain;
|
||||
$domainObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $domainObj);
|
||||
}
|
||||
// protocol
|
||||
if (isset($rules->protocol) && !empty($rules->protocol)) {
|
||||
$rules->protocol = array_filter($rules->protocol);
|
||||
$protocolObj = new \StdClass();
|
||||
$protocolObj->type = 'field';
|
||||
$protocolObj->protocol = $rules->protocol;
|
||||
$protocolObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $protocolObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setTls(Server $server, object $json)
|
||||
{
|
||||
if ((int)$server->tls) {
|
||||
$tlsSettings = json_decode($server->tlsSettings);
|
||||
$json->inbound->streamSettings->security = 'tls';
|
||||
$tls = (object)[
|
||||
'certificateFile' => '/root/.cert/server.crt',
|
||||
'keyFile' => '/root/.cert/server.key'
|
||||
];
|
||||
$json->inbound->streamSettings->tlsSettings = new \StdClass();
|
||||
if (isset($tlsSettings->serverName)) {
|
||||
$json->inbound->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
|
||||
}
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
$json->inbound->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
|
||||
}
|
||||
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
|
||||
}
|
||||
}
|
||||
|
||||
public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
|
||||
{
|
||||
if (($u + $d) <= 10240) return;
|
||||
$timestamp = strtotime(date('Y-m-d H:0'));
|
||||
$serverLog = ServerLog::where('log_at', '>=', $timestamp)
|
||||
->where('log_at', '<', $timestamp + 3600)
|
||||
->where('server_id', $serverId)
|
||||
->where('user_id', $userId)
|
||||
->where('rate', $rate)
|
||||
->where('method', $method)
|
||||
->first();
|
||||
if ($serverLog) {
|
||||
$serverLog->u = $serverLog->u + $u;
|
||||
$serverLog->d = $serverLog->d + $d;
|
||||
$serverLog->save();
|
||||
} else {
|
||||
$serverLog = new ServerLog();
|
||||
$serverLog->user_id = $userId;
|
||||
$serverLog->server_id = $serverId;
|
||||
$serverLog->u = $u;
|
||||
$serverLog->d = $d;
|
||||
$serverLog->rate = $rate;
|
||||
$serverLog->log_at = $timestamp;
|
||||
$serverLog->method = $method;
|
||||
$serverLog->save();
|
||||
}
|
||||
}
|
||||
}
|
46
app/Services/TelegramService.php
Normal file
46
app/Services/TelegramService.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use \Curl\Curl;
|
||||
|
||||
class TelegramService {
|
||||
protected $api;
|
||||
|
||||
public function __construct($token = '')
|
||||
{
|
||||
$this->api = 'https://api.telegram.org/bot' . config('v2board.telegram_bot_token', $token) . '/';
|
||||
}
|
||||
|
||||
public function sendMessage(int $chatId, string $text, string $parseMode = '')
|
||||
{
|
||||
$this->request('sendMessage', [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $text,
|
||||
'parse_mode' => $parseMode
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMe()
|
||||
{
|
||||
return $this->request('getMe');
|
||||
}
|
||||
|
||||
public function setWebhook(string $url)
|
||||
{
|
||||
return $this->request('setWebhook', [
|
||||
'url' => $url
|
||||
]);
|
||||
}
|
||||
|
||||
private function request(string $method, array $params = [])
|
||||
{
|
||||
$curl = new Curl();
|
||||
$curl->get($this->api . $method . '?' . http_build_query($params));
|
||||
$response = $curl->response;
|
||||
$curl->close();
|
||||
if (!$response->ok) {
|
||||
abort(500, '来自TG的错误:' . $response->description);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
93
app/Services/UserService.php
Normal file
93
app/Services/UserService.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public function isAvailable(User $user)
|
||||
{
|
||||
if (!$user->banned && $user->transfer_enable && ($user->expired_at > time() || $user->expired_at === NULL)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAvailableUsers()
|
||||
{
|
||||
return User::whereRaw('u + d < transfer_enable')
|
||||
->where(function ($query) {
|
||||
$query->where('expired_at', '>=', time())
|
||||
->orWhere('expired_at', NULL);
|
||||
})
|
||||
->where('banned', 0)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getUnAvailbaleUsers()
|
||||
{
|
||||
return User::where(function ($query) {
|
||||
$query->where('expired_at', '<', time())
|
||||
->orWhere('expired_at', 0);
|
||||
})
|
||||
->where(function ($query) {
|
||||
$query->where('plan_id', NULL)
|
||||
->orWhere('transfer_enable', 0);
|
||||
})
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getUsersByIds($ids)
|
||||
{
|
||||
return User::whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
public function getAllUsers()
|
||||
{
|
||||
return User::all();
|
||||
}
|
||||
|
||||
public function addBalance(int $userId, int $balance):bool
|
||||
{
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
$user->balance = $user->balance + $balance;
|
||||
if ($user->balance < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!$user->save()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isNotCompleteOrderByUserId(int $userId):bool
|
||||
{
|
||||
$order = Order::whereIn('status', [0, 1])
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
if (!$order) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function trafficFetch(int $u, int $d, int $userId):bool
|
||||
{
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
$user->t = time();
|
||||
$user->u = $user->u + $u;
|
||||
$user->d = $user->d + $d;
|
||||
if (!$user->save()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -4,5 +4,20 @@ namespace App\Utils;
|
||||
|
||||
class CacheKey
|
||||
{
|
||||
CONST KEYS = [
|
||||
'EMAIL_VERIFY_CODE' => '邮箱验证吗',
|
||||
'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间',
|
||||
'SERVER_V2RAY_ONLINE_USER' => '节点在线用户',
|
||||
'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',
|
||||
'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
|
||||
'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间'
|
||||
];
|
||||
|
||||
public static function get(string $key, $uniqueValue)
|
||||
{
|
||||
if (!in_array($key, array_keys(self::KEYS))) {
|
||||
abort(500, 'key is not in cache key list');
|
||||
}
|
||||
return $key . '_' . $uniqueValue;
|
||||
}
|
||||
}
|
||||
|
52
app/Utils/Clash.php
Normal file
52
app/Utils/Clash.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
|
||||
class Clash
|
||||
{
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$array = [];
|
||||
$array['name'] = $server->name;
|
||||
$array['type'] = 'vmess';
|
||||
$array['server'] = $server->host;
|
||||
$array['port'] = $server->port;
|
||||
$array['uuid'] = $uuid;
|
||||
$array['alterId'] = 2;
|
||||
$array['cipher'] = 'auto';
|
||||
if ($server->tls) {
|
||||
$tlsSettings = json_decode($server->tlsSettings);
|
||||
$array['tls'] = true;
|
||||
if (isset($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
|
||||
}
|
||||
if ($server->network == 'ws') {
|
||||
$array['network'] = $server->network;
|
||||
if ($server->networkSettings) {
|
||||
$wsSettings = json_decode($server->networkSettings);
|
||||
if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
|
||||
'Host' => $wsSettings->headers->Host
|
||||
];
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$array = [];
|
||||
$array['name'] = $server->name;
|
||||
$array['type'] = 'trojan';
|
||||
$array['server'] = $server->host;
|
||||
$array['port'] = $server->port;
|
||||
$array['password'] = $password;
|
||||
$array['sni'] = $server->server_name;
|
||||
if ($server->allow_insecure) {
|
||||
$array['skip-cert-verify'] = true;
|
||||
} else {
|
||||
$array['skip-cert-verify'] = false;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerTrojan;
|
||||
use App\Models\User;
|
||||
|
||||
class Helper
|
||||
{
|
||||
public static function guid($format = false)
|
||||
@ -53,23 +57,35 @@ class Helper
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function buildVmessLink($item, $user)
|
||||
public static function buildTrojanLink(ServerTrojan $server, User $user)
|
||||
{
|
||||
$server->name = rawurlencode($server->name);
|
||||
$query = http_build_query([
|
||||
'allowInsecure' => $server->allow_insecure,
|
||||
'peer' => $server->server_name
|
||||
]);
|
||||
$uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$server->name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVmessLink(Server $server, User $user)
|
||||
{
|
||||
$config = [
|
||||
"v" => "2",
|
||||
"ps" => $item->name,
|
||||
"add" => $item->host,
|
||||
"port" => $item->port,
|
||||
"id" => $user->v2ray_uuid,
|
||||
"ps" => $server->name,
|
||||
"add" => $server->host,
|
||||
"port" => $server->port,
|
||||
"id" => $user->uuid,
|
||||
"aid" => "2",
|
||||
"net" => $item->network,
|
||||
"net" => $server->network,
|
||||
"type" => "none",
|
||||
"host" => "",
|
||||
"path" => "",
|
||||
"tls" => $item->tls ? "tls" : ""
|
||||
"tls" => $server->tls ? "tls" : ""
|
||||
];
|
||||
if ($item->network == 'ws') {
|
||||
$wsSettings = json_decode($item->settings);
|
||||
if ((string)$server->network === 'ws') {
|
||||
$wsSettings = json_decode($server->networkSettings);
|
||||
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
|
||||
}
|
||||
@ -95,4 +111,22 @@ class Helper
|
||||
if (!in_array($suffix, $suffixs)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function trafficConvert(int $byte)
|
||||
{
|
||||
$kb = 1024;
|
||||
$mb = 1048576;
|
||||
$gb = 1073741824;
|
||||
if ($byte > $gb) {
|
||||
return round($byte / $gb, 2) . ' GB';
|
||||
} else if ($byte > $mb) {
|
||||
return round($byte / $mb, 2) . ' MB';
|
||||
} else if ($byte > $kb) {
|
||||
return round($byte / $kb, 2) . ' KB';
|
||||
} else if ($byte < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return round($byte, 2) . ' B';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
app/Utils/QuantumultX.php
Normal file
51
app/Utils/QuantumultX.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
|
||||
class QuantumultX
|
||||
{
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$uri = "vmess=" . $server->host . ":" . $server->port . ", method=none, password=" . $uuid . ", fast-open=false, udp-relay=false, tag=" . $server->name;
|
||||
if ($server->tls) {
|
||||
$tlsSettings = json_decode($server->tlsSettings);
|
||||
if ($server->network === 'tcp') $uri .= ', obfs=over-tls';
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
// Default: tls-verification=true
|
||||
$uri .= ', tls-verification=' . ($tlsSettings->allowInsecure ? "false" : "true");
|
||||
}
|
||||
if (isset($tlsSettings->serverName)) {
|
||||
$uri .= ', obfs-host=' . $tlsSettings->serverName;
|
||||
}
|
||||
}
|
||||
if ($server->network === 'ws') {
|
||||
$uri .= ', obfs=' . ($server->tls ? 'wss' : 'ws');
|
||||
if ($server->networkSettings) {
|
||||
$wsSettings = json_decode($server->networkSettings);
|
||||
if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
|
||||
}
|
||||
}
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$config = [
|
||||
"trojan={$server->host}:{$server->port}",
|
||||
"password={$password}",
|
||||
"over-tls=true",
|
||||
$server->server_name ? "tls-host={$server->server_name}" : "",
|
||||
$server->allow_insecure ? 'tls-verification=true' : 'tls-verification=false',
|
||||
"fast-open=false",
|
||||
"udp-relay=false",
|
||||
"tag={$server->name}"
|
||||
];
|
||||
$config = array_filter($config);
|
||||
$uri = implode($config, ',');
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
}
|
46
app/Utils/Surge.php
Normal file
46
app/Utils/Surge.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
|
||||
class Surge
|
||||
{
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$proxies = $server->name . ' = vmess, ' . $server->host . ', ' . $server->port . ', username=' . $uuid . ', tfo=true';
|
||||
if ($server->tls) {
|
||||
$tlsSettings = json_decode($server->tlsSettings);
|
||||
$proxies .= ', tls=' . ($server->tls ? "true" : "false");
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
|
||||
}
|
||||
}
|
||||
if ($server->network == 'ws') {
|
||||
$proxies .= ', ws=true';
|
||||
if ($server->networkSettings) {
|
||||
$wsSettings = json_decode($server->networkSettings);
|
||||
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
|
||||
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
|
||||
}
|
||||
}
|
||||
$proxies .= "\r\n";
|
||||
return $proxies;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$config = [
|
||||
"{$server->name}=trojan",
|
||||
"{$server->host}",
|
||||
"{$server->port}",
|
||||
"password={$password}",
|
||||
$server->allow_insecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false',
|
||||
$server->server_name ? "sni={$server->server_name}" : "",
|
||||
"tfo=true"
|
||||
];
|
||||
$config = array_filter($config);
|
||||
$uri = implode($config, ',');
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
}
|
@ -13,9 +13,9 @@
|
||||
"fideloper/proxy": "^4.0",
|
||||
"laravel/framework": "^6.0",
|
||||
"laravel/tinker": "^1.0",
|
||||
"lokielse/omnipay-alipay": "^3.0",
|
||||
"lokielse/omnipay-alipay": "3.0.6",
|
||||
"php-curl-class/php-curl-class": "^8.6",
|
||||
"stripe/stripe-php": "^7.5",
|
||||
"stripe/stripe-php": "^7.36.1",
|
||||
"symfony/yaml": "^4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
@ -63,5 +63,11 @@
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
]
|
||||
},
|
||||
"repositories": {
|
||||
"packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.aliyun.com/composer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
'timezone' => 'Asia/Shanghai',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -232,7 +232,9 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
| V2board version
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The only modification by laravel config
|
||||
|
|
||||
*/
|
||||
|
||||
'version' => '1.1.2'
|
||||
'version' => '1.3.1-r.2'
|
||||
];
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Adminer 4.7.3 MySQL dump
|
||||
-- Adminer 4.7.6 MySQL dump
|
||||
|
||||
SET NAMES utf8;
|
||||
SET time_zone = '+00:00';
|
||||
@ -27,6 +27,7 @@ CREATE TABLE `v2_coupon` (
|
||||
`type` tinyint(1) NOT NULL,
|
||||
`value` int(11) NOT NULL,
|
||||
`limit_use` int(11) DEFAULT NULL,
|
||||
`limit_plan_ids` varchar(255) DEFAULT NULL,
|
||||
`started_at` int(11) NOT NULL,
|
||||
`ended_at` int(11) NOT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
@ -54,7 +55,7 @@ CREATE TABLE `v2_mail_log` (
|
||||
`email` varchar(64) NOT NULL,
|
||||
`subject` varchar(255) NOT NULL,
|
||||
`template_name` varchar(255) NOT NULL,
|
||||
`error` varchar(255) DEFAULT NULL,
|
||||
`error` text,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@ -85,7 +86,11 @@ CREATE TABLE `v2_order` (
|
||||
`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',
|
||||
`surplus_amount` int(11) DEFAULT NULL COMMENT '剩余价值',
|
||||
`refund_amount` int(11) DEFAULT NULL COMMENT '退款金额',
|
||||
`balance_amount` int(11) DEFAULT NULL COMMENT '使用余额',
|
||||
`surplus_order_ids` text COMMENT '折抵订单',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付1开通中2已取消3已完成4已折抵',
|
||||
`commission_status` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`commission_balance` int(11) NOT NULL DEFAULT '0',
|
||||
`created_at` int(11) NOT NULL,
|
||||
@ -101,12 +106,15 @@ CREATE TABLE `v2_plan` (
|
||||
`transfer_enable` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`renew` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`content` text,
|
||||
`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',
|
||||
`month_price` int(11) DEFAULT NULL,
|
||||
`quarter_price` int(11) DEFAULT NULL,
|
||||
`half_year_price` int(11) DEFAULT NULL,
|
||||
`year_price` int(11) DEFAULT NULL,
|
||||
`onetime_price` int(11) DEFAULT NULL,
|
||||
`reset_price` int(11) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@ -125,10 +133,15 @@ CREATE TABLE `v2_server` (
|
||||
`tls` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`tags` varchar(255) DEFAULT NULL,
|
||||
`rate` varchar(11) NOT NULL,
|
||||
`network` varchar(11) NOT NULL,
|
||||
`network` text NOT NULL,
|
||||
`settings` text,
|
||||
`rules` text,
|
||||
`networkSettings` text,
|
||||
`tlsSettings` text,
|
||||
`ruleSettings` text,
|
||||
`dnsSettings` text,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@ -147,16 +160,55 @@ CREATE TABLE `v2_server_group` (
|
||||
|
||||
DROP TABLE IF EXISTS `v2_server_log`;
|
||||
CREATE TABLE `v2_server_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`server_id` int(11) NOT NULL,
|
||||
`u` varchar(255) NOT NULL,
|
||||
`d` varchar(255) NOT NULL,
|
||||
`rate` decimal(10,2) NOT NULL,
|
||||
`method` varchar(255) NOT NULL,
|
||||
`log_at` int(11) NOT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `log_at` (`log_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `v2_server_stat`;
|
||||
CREATE TABLE `v2_server_stat` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`server_id` int(11) NOT NULL,
|
||||
`u` varchar(255) NOT NULL,
|
||||
`d` varchar(255) NOT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `v2_server_trojan`;
|
||||
CREATE TABLE `v2_server_trojan` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '节点ID',
|
||||
`group_id` varchar(255) NOT NULL COMMENT '节点组',
|
||||
`parent_id` int(11) DEFAULT NULL COMMENT '父节点',
|
||||
`tags` varchar(255) DEFAULT NULL COMMENT '节点标签',
|
||||
`name` varchar(255) NOT NULL COMMENT '节点名称',
|
||||
`rate` varchar(11) NOT NULL COMMENT '倍率',
|
||||
`host` varchar(255) NOT NULL COMMENT '主机名',
|
||||
`port` int(11) NOT NULL COMMENT '连接端口',
|
||||
`server_port` int(11) NOT NULL COMMENT '服务端口',
|
||||
`allow_insecure` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否允许不安全',
|
||||
`server_name` varchar(255) DEFAULT NULL,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否显示',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表';
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `v2_ticket`;
|
||||
CREATE TABLE `v2_ticket` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
@ -186,26 +238,22 @@ CREATE TABLE `v2_ticket_message` (
|
||||
DROP TABLE IF EXISTS `v2_tutorial`;
|
||||
CREATE TABLE `v2_tutorial` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`category_id` int(11) NOT NULL,
|
||||
`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',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`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,
|
||||
`invite_user_id` int(11) DEFAULT NULL,
|
||||
`telegram_id` bigint(20) DEFAULT NULL,
|
||||
`email` varchar(64) NOT NULL,
|
||||
`password` varchar(64) NOT NULL,
|
||||
`password_algo` char(10) DEFAULT NULL,
|
||||
@ -222,7 +270,7 @@ CREATE TABLE `v2_user` (
|
||||
`is_admin` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`last_login_at` int(11) DEFAULT NULL,
|
||||
`last_login_ip` int(11) DEFAULT NULL,
|
||||
`v2ray_uuid` varchar(36) NOT NULL,
|
||||
`uuid` varchar(36) NOT NULL,
|
||||
`v2ray_alter_id` tinyint(4) NOT NULL DEFAULT '2',
|
||||
`v2ray_level` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`group_id` int(11) DEFAULT NULL,
|
||||
@ -230,7 +278,7 @@ CREATE TABLE `v2_user` (
|
||||
`remind_expire` tinyint(4) DEFAULT '1',
|
||||
`remind_traffic` tinyint(4) DEFAULT '1',
|
||||
`token` char(32) NOT NULL,
|
||||
`expired_at` bigint(20) NOT NULL DEFAULT '0',
|
||||
`expired_at` bigint(20) DEFAULT '0',
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
@ -238,4 +286,4 @@ CREATE TABLE `v2_user` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- 2020-02-14 10:22:53
|
||||
-- 2020-07-01 07:01:59
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePasswordResetsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('password_resets', function (Blueprint $table) {
|
||||
$table->string('email')->index();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('password_resets');
|
||||
}
|
||||
}
|
@ -118,14 +118,6 @@ CREATE TABLE `v2_tutorial` (
|
||||
`updated_at` int(11) NOT NULL
|
||||
);
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
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, 1577980882),
|
||||
(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, 1577981610),
|
||||
(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, 1577981646),
|
||||
(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);
|
||||
|
||||
ALTER TABLE `v2_server_log`
|
||||
CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
|
||||
|
||||
@ -162,3 +154,145 @@ ADD `surplus_amount` int(11) NULL COMMENT '剩余价值' AFTER `discount_amount`
|
||||
|
||||
ALTER TABLE `v2_order`
|
||||
ADD `refund_amount` int(11) NULL COMMENT '退款金额' AFTER `surplus_amount`;
|
||||
|
||||
ALTER TABLE `v2_tutorial`
|
||||
ADD `category_id` int(11) NOT NULL AFTER `id`;
|
||||
|
||||
ALTER TABLE `v2_tutorial`
|
||||
DROP `description`;
|
||||
|
||||
ALTER TABLE `v2_plan`
|
||||
CHANGE `month_price` `month_price` int(11) NULL AFTER `content`,
|
||||
CHANGE `quarter_price` `quarter_price` int(11) NULL AFTER `month_price`,
|
||||
CHANGE `half_year_price` `half_year_price` int(11) NULL AFTER `quarter_price`,
|
||||
CHANGE `year_price` `year_price` int(11) NULL AFTER `half_year_price`,
|
||||
ADD `onetime_price` int(11) NULL AFTER `year_price`;
|
||||
|
||||
ALTER TABLE `v2_user`
|
||||
DROP `enable`,
|
||||
ADD `banned` tinyint(1) NOT NULL DEFAULT '0' AFTER `transfer_enable`;
|
||||
|
||||
ALTER TABLE `v2_user`
|
||||
CHANGE `expired_at` `expired_at` bigint(20) NULL DEFAULT '0' AFTER `token`;
|
||||
|
||||
ALTER TABLE `v2_tutorial`
|
||||
DROP `icon`;
|
||||
|
||||
ALTER TABLE `v2_server`
|
||||
CHANGE `settings` `networkSettings` text COLLATE 'utf8_general_ci' NULL AFTER `network`,
|
||||
CHANGE `rules` `ruleSettings` text COLLATE 'utf8_general_ci' NULL AFTER `networkSettings`;
|
||||
|
||||
ALTER TABLE `v2_server`
|
||||
CHANGE `tags` `tags` varchar(255) COLLATE 'utf8_general_ci' NULL AFTER `server_port`,
|
||||
CHANGE `rate` `rate` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `tags`,
|
||||
CHANGE `network` `network` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `rate`,
|
||||
CHANGE `networkSettings` `networkSettings` text COLLATE 'utf8_general_ci' NULL AFTER `network`,
|
||||
CHANGE `tls` `tls` tinyint(4) NOT NULL DEFAULT '0' AFTER `networkSettings`,
|
||||
ADD `tlsSettings` text COLLATE 'utf8_general_ci' NULL AFTER `tls`;
|
||||
|
||||
ALTER TABLE `v2_order`
|
||||
ADD `balance_amount` int(11) NULL COMMENT '使用余额' AFTER `refund_amount`;
|
||||
|
||||
ALTER TABLE `v2_server`
|
||||
CHANGE `network` `network` text COLLATE 'utf8_general_ci' NOT NULL AFTER `rate`,
|
||||
ADD `dnsSettings` text COLLATE 'utf8_general_ci' NULL AFTER `ruleSettings`;
|
||||
|
||||
ALTER TABLE `v2_order`
|
||||
ADD `surplus_order_ids` text NULL COMMENT '折抵订单' AFTER `balance_amount`;
|
||||
|
||||
ALTER TABLE `v2_order`
|
||||
CHANGE `status` `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付1开通中2已取消3已完成4已折抵' AFTER `surplus_order_ids`;
|
||||
|
||||
CREATE TABLE `v2_server_stat` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`server_id` int(11) NOT NULL,
|
||||
`u` varchar(255) NOT NULL,
|
||||
`d` varchar(25) NOT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE `v2_tutorial`
|
||||
ADD `sort` int(11) NULL AFTER `show`;
|
||||
|
||||
ALTER TABLE `v2_server`
|
||||
ADD `sort` int(11) NULL AFTER `show`;
|
||||
|
||||
ALTER TABLE `v2_plan`
|
||||
ADD `sort` int(11) NULL AFTER `show`;
|
||||
|
||||
ALTER TABLE `v2_plan`
|
||||
CHANGE `month_price` `month_price` int(11) NULL AFTER `content`,
|
||||
CHANGE `quarter_price` `quarter_price` int(11) NULL AFTER `month_price`,
|
||||
CHANGE `half_year_price` `half_year_price` int(11) NULL AFTER `quarter_price`,
|
||||
CHANGE `year_price` `year_price` int(11) NULL AFTER `half_year_price`,
|
||||
ADD `reset_price` int(11) NULL AFTER `onetime_price`;
|
||||
|
||||
ALTER TABLE `v2_server_log`
|
||||
ADD `id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
|
||||
|
||||
ALTER TABLE `v2_server_log`
|
||||
ADD `log_at` int(11) NOT NULL AFTER `rate`;
|
||||
|
||||
ALTER TABLE `v2_mail_log`
|
||||
CHANGE `error` `error` text COLLATE 'utf8_general_ci' NULL AFTER `template_name`;
|
||||
|
||||
ALTER TABLE `v2_plan`
|
||||
CHANGE `month_price` `month_price` int(11) NULL AFTER `content`,
|
||||
CHANGE `quarter_price` `quarter_price` int(11) NULL AFTER `month_price`,
|
||||
CHANGE `half_year_price` `half_year_price` int(11) NULL AFTER `quarter_price`,
|
||||
CHANGE `year_price` `year_price` int(11) NULL AFTER `half_year_price`;
|
||||
|
||||
ALTER TABLE `v2_server_log`
|
||||
ADD INDEX log_at (`log_at`);
|
||||
|
||||
ALTER TABLE `v2_user`
|
||||
ADD `telegram_id` bigint NULL AFTER `invite_user_id`;
|
||||
|
||||
ALTER TABLE `v2_server_stat`
|
||||
ADD `online` int(11) NOT NULL AFTER `d`;
|
||||
|
||||
ALTER TABLE `v2_server_stat`
|
||||
ADD INDEX `created_at` (`created_at`);
|
||||
|
||||
CREATE TABLE `v2_server_trojan` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`group_id` varchar(255) NOT NULL,
|
||||
`tags` varchar(255) NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`host` varchar(255) NOT NULL,
|
||||
`port` int(11) NOT NULL,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL
|
||||
) COMMENT='trojan伺服器表' COLLATE 'utf8mb4_general_ci';
|
||||
|
||||
ALTER TABLE `v2_server_stat`
|
||||
CHANGE `d` `d` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `u`,
|
||||
DROP `online`;
|
||||
|
||||
ALTER TABLE `v2_user`
|
||||
CHANGE `v2ray_uuid` `uuid` varchar(36) COLLATE 'utf8_general_ci' NOT NULL AFTER `last_login_ip`;
|
||||
|
||||
ALTER TABLE `v2_server_trojan`
|
||||
ADD `rate` varchar(11) COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `name`;
|
||||
|
||||
ALTER TABLE `v2_server_log`
|
||||
ADD `method` varchar(255) NOT NULL AFTER `rate`;
|
||||
|
||||
ALTER TABLE `v2_coupon`
|
||||
ADD `limit_plan_ids` varchar(255) NULL AFTER `limit_use`;
|
||||
|
||||
ALTER TABLE `v2_server_trojan`
|
||||
ADD `server_port` int(11) NOT NULL AFTER `port`;
|
||||
|
||||
ALTER TABLE `v2_server_trojan`
|
||||
ADD `parent_id` int(11) NULL AFTER `group_id`;
|
||||
|
||||
ALTER TABLE `v2_server_trojan`
|
||||
ADD `allow_insecure` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否允许不安全' AFTER `server_port`,
|
||||
CHANGE `show` `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否显示' AFTER `allow_insecure`;
|
||||
|
||||
ALTER TABLE `v2_server_trojan`
|
||||
ADD `server_name` varchar(255) NULL AFTER `allow_insecure`;
|
||||
|
@ -1,25 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mysql
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=123456
|
||||
volumes:
|
||||
- ./docker/mysql:/var/lib/mysql
|
||||
- ./install.sql:/install.sql
|
||||
phpfpm:
|
||||
image: bitnami/php-fpm
|
||||
volumes:
|
||||
- .:/app
|
||||
nginx:
|
||||
image: nginx
|
||||
depends_on:
|
||||
- phpfpm
|
||||
volumes:
|
||||
- .:/app
|
||||
- ./docker/nginx:/etc/nginx/conf.d
|
||||
ports:
|
||||
- 8964:80
|
||||
|
2
docker/mysql/.gitignore
vendored
2
docker/mysql/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
3
docker/nginx/.gitignore
vendored
3
docker/nginx/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!nginx.conf
|
@ -1,52 +0,0 @@
|
||||
server {
|
||||
# 监听 HTTP 协议默认的 [80] 端口。
|
||||
listen 80;
|
||||
# 绑定主机名 [example.com]。
|
||||
server_name localhost;
|
||||
# 服务器站点根目录 [/example.com/public]。
|
||||
root /app/public;
|
||||
|
||||
# 添加几条有关安全的响应头;与 Google+ 的配置类似,详情参见文末。
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
# 站点默认页面;可指定多个,将顺序查找。
|
||||
# 例如,访问 http://example.com/ Nginx 将首先尝试「站点根目录/index.html」是否存在,不存在则继续尝试「站点根目录/index.htm」,以此类推...
|
||||
index index.html index.htm index.php;
|
||||
|
||||
# 指定字符集为 UTF-8
|
||||
charset utf-8;
|
||||
|
||||
# Laravel 默认重写规则;删除将导致 Laravel 路由失效且 Nginx 响应 404。
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
# 关闭 [/favicon.ico] 和 [/robots.txt] 的访问日志。
|
||||
# 并且即使它们不存在,也不写入错误日志。
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
# 将 [404] 错误交给 [/index.php] 处理,表示由 Laravel 渲染美观的错误页面。
|
||||
error_page 404 /index.php;
|
||||
|
||||
# URI 符合正则表达式 [\.php$] 的请求将进入此段配置
|
||||
location ~ \.php$ {
|
||||
# 配置 FastCGI 服务地址,可以为 IP:端口,也可以为 Unix socket。
|
||||
fastcgi_pass phpfpm:9000;
|
||||
# 配置 FastCGI 的主页为 index.php。
|
||||
fastcgi_index index.php;
|
||||
# 配置 FastCGI 参数 SCRIPT_FILENAME 为 $realpath_root$fastcgi_script_name。
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
# 引用更多默认的 FastCGI 参数。
|
||||
include fastcgi_params;
|
||||
}
|
||||
# 通俗地说,以上配置将所有 URI 以 .php 结尾的请求,全部交给 PHP-FPM 处理。
|
||||
|
||||
# 除符合正则表达式 [/\.(?!well-known).*] 之外的 URI,全部拒绝访问
|
||||
# 也就是说,拒绝公开以 [.] 开头的目录,[.well-known] 除外
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
5
init.sh
Executable file → Normal file
5
init.sh
Executable file → Normal file
@ -1,2 +1,3 @@
|
||||
php artisan key:generate
|
||||
php artisan config:cache
|
||||
wget https://getcomposer.org/download/1.9.0/composer.phar
|
||||
php composer.phar install -vvv
|
||||
php artisan v2board:install
|
||||
|
@ -23,6 +23,9 @@ class PayTaro
|
||||
$curl = new Curl();
|
||||
$curl->post('https://api.paytaro.com/v1/gateway/fetch', http_build_query($params));
|
||||
$result = $curl->response;
|
||||
if (!$result) {
|
||||
abort(500, '网络异常');
|
||||
}
|
||||
if ($curl->error) {
|
||||
$errors = (array)$result->errors;
|
||||
abort(500, $errors[array_keys($errors)[0]][0]);
|
||||
|
@ -1,63 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
14
library/V2ray.php
Normal file
14
library/V2ray.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Library;
|
||||
|
||||
|
||||
class V2ray
|
||||
{
|
||||
protected $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new \StdClass();
|
||||
}
|
||||
}
|
4
pm2.yaml
4
pm2.yaml
@ -1,5 +1,5 @@
|
||||
apps:
|
||||
- name : 'V2Board'
|
||||
script : 'php artisan queue:work --queue=verify_mail,other_mail'
|
||||
script : 'php artisan queue:work --queue=send_email,send_telegram'
|
||||
instances: 4
|
||||
out_file : './storage/logs/queue/queue.log'
|
||||
out_file : './storage/logs/queue/queue.log'
|
||||
|
2
public/assets/admin/antd.async.js
vendored
2
public/assets/admin/antd.async.js
vendored
File diff suppressed because one or more lines are too long
2
public/assets/admin/antd.chunk.css
vendored
2
public/assets/admin/antd.chunk.css
vendored
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
Reference in New Issue
Block a user