222 Commits
1.5.1 ... 1.5.3

Author SHA1 Message Date
32c539d2c0 Merge pull request #484 from v2board/dev
1.5.3
2021-10-03 22:19:10 +09:00
52fa1ce6af update: language 2021-10-01 20:18:40 +09:00
82d2d91582 update: user 2021-10-01 19:30:49 +09:00
8085c2ba6a update: schedule 2021-10-01 17:37:49 +09:00
b60fde5762 update: fix charts 2021-09-27 14:58:58 +09:00
3b51f12ab1 update: rollback app rule 2021-09-26 22:09:19 +09:00
ab4e66a5b6 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-09-26 22:07:34 +09:00
1856e0e87e update: fix support safari 15 theme color 2021-09-26 22:07:24 +09:00
e20ae29fb1 Merge pull request #483 from betaxab/fix-clash-rule-for-gp
rules: clash: Solve Google Play app download issue on China phones
2021-09-26 20:57:06 +09:00
cfd528739a update: fix en-us 2021-09-26 20:44:47 +09:00
dd23609658 update: change charts component 2021-09-25 00:33:11 +09:00
c7f3cf9e67 update: support safari theme 2021-09-24 01:50:58 +09:00
ddf2f45d6c update: user filter 2021-09-22 21:13:20 +09:00
ba3de90733 update: fix commission 2021-09-22 18:04:16 +09:00
43f4ecce93 update: fix 2021-09-21 22:30:00 +09:00
30b3587771 update: fix 2021-09-21 19:11:14 +09:00
6ab9a4d54d update: email notice default off 2021-09-21 19:07:53 +09:00
7a4bd468a2 update: add plan reset method 2021-09-21 18:51:53 +09:00
c97d37a070 rules: Solve Google Play app download issue on China phones
Update: app.clash.yaml up-to-date
2021-09-20 10:52:37 +08:00
25b3b11efd update: add commission distribution 2021-09-18 21:04:35 +09:00
edfc4043e8 update: add commission distribution 2021-09-18 20:53:34 +09:00
ab9abf5b93 update: crisp more data 2021-09-14 20:31:56 +09:00
6dd199631b update: crisp more data 2021-09-14 20:07:57 +09:00
4b863d681e update: crisp more data 2021-09-14 19:47:57 +09:00
5d6010045d update: support md5 with sha256 2021-09-14 13:12:44 +09:00
0374a03892 update: support md5 with sha256 2021-09-14 13:10:29 +09:00
ec00fc4496 update: support md5 with sha256 2021-09-14 02:38:24 +09:00
a365357770 update: fix stat 2021-09-07 12:49:35 +09:00
14579d1eea update: update alert 2021-09-07 03:49:04 +09:00
9f6d1ada93 update: v2board install 2021-09-07 03:18:45 +09:00
895976b830 update: horizon config 2021-09-07 01:47:29 +09:00
4b011225db Merge pull request #482 from betaxab/subs-domain-force-direct 2021-09-07 01:33:37 +09:00
243aed3f55 Merge pull request #480 from betaxab/horizon-metrics 2021-09-07 01:33:24 +09:00
d25d7ba69b Merge pull request #479 from betaxab/update 2021-09-07 01:33:16 +09:00
c0c84efb7c Protocols: force the current subscription domain to be a direct rule 2021-09-06 18:26:03 +08:00
91c7dfc0ed update: add no reset method 2021-09-06 02:31:45 +09:00
6830e6af38 update: push tag 2021-09-06 01:24:41 +09:00
cb75579772 update: fix push tag 2021-09-06 01:19:43 +09:00
e980c2d8f3 update: set horizon with mem 2021-09-05 18:05:25 +09:00
b6195494d3 Kernel: add horizon snapshot schedule 2021-09-05 10:32:33 +08:00
ccf3497241 update: register api 2021-09-04 21:41:57 +09:00
c80d93fa25 update: order queue 2021-09-04 16:31:25 +09:00
c2577e37c4 update: server log 2021-09-03 00:31:12 +09:00
c183462ef3 update: server log 2021-09-02 21:09:04 +09:00
2a5e9ef079 update: rollback 2021-09-02 20:28:24 +09:00
decbae1413 update: traffic fetch job 2021-09-02 19:58:04 +09:00
0c14652ff7 update: server log fetch 2021-09-02 18:42:36 +09:00
91418caf04 update: log record 2021-09-01 03:32:15 +09:00
607aa82f88 update: horizon config 2021-09-01 03:08:31 +09:00
adc2d02c49 update: horizon config 2021-09-01 03:08:07 +09:00
fabb49baea update: horizon auth 2021-09-01 02:11:19 +09:00
35e11a6816 update: add horizon 2021-09-01 01:56:16 +09:00
1d87a1b99a update: traffic fetch queue 2021-09-01 00:55:07 +09:00
fbced4d09b update: fix order assign modal 2021-08-31 20:59:56 +09:00
52914e354e update: order assign notice 2021-08-31 20:54:08 +09:00
d392d29b50 update: reset text 2021-08-30 19:11:16 +09:00
347a3bb4b0 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-08-30 01:18:17 +09:00
7ac1f69a71 update: fix css 2021-08-30 01:18:06 +09:00
6317c4a4f3 Protocols: Clash: update 2021-08-29 21:44:51 +08:00
3da4de02d0 Merge pull request #478 from betaxab/fix-clash-appname
Protocols: Clash: fix Clash app_name not fould
2021-08-29 22:38:38 +09:00
85686df2e6 Protocols: Clash: fix Clash app_name not found 2021-08-29 21:32:46 +08:00
adb5d041e6 fix: remind expire 2021-08-29 12:56:55 +09:00
7ba0e9a4e0 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-08-29 12:55:08 +09:00
99a64f41e1 Merge pull request #477 from betaxab/abandon-clash-provider
Protocols: Clash: Rollback the previous config
2021-08-29 12:54:58 +09:00
62053bc30f Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-08-29 12:54:32 +09:00
e5e7a06514 update: fix coupon 2021-08-29 12:54:15 +09:00
905e2dacd1 Protocols: Clash: Rollback the previous configuration
because the provider cannot test the delay manually
2021-08-29 11:20:55 +08:00
8189a442bc Merge pull request #475 from WayChan/update_clash_subscribe
update Clash Subscribe
2021-08-29 02:52:51 +09:00
b38f7979d6 update: coupon 2021-08-28 16:34:22 +09:00
5a3b897c57 update: add coupon per user limit 2021-08-28 16:32:55 +09:00
2d5fb03937 update: remind mail i18n 2021-08-28 15:39:10 +09:00
982c47d0b4 update: remind mail i18n 2021-08-28 15:32:38 +09:00
4cebeed2d7 update: server log 2021-08-28 15:22:51 +09:00
c95d374cc0 update: order manual 2021-08-28 15:11:59 +09:00
23462e2753 update: add more tips 2021-08-27 00:54:35 +09:00
b26a3e0e70 update: fix order update 2021-08-25 18:06:08 +09:00
3600c9a166 update: rollback traffic fetch 2021-08-24 16:53:53 +09:00
8a58a1ad88 update 2021-08-24 16:50:45 +09:00
968d55e2e3 update: readme 2021-08-22 15:46:49 +09:00
98ac5cb680 Fix Clash Json decode error 2021-08-22 00:09:37 +08:00
0288d2df4b update: order success handle 2021-08-21 20:52:00 +09:00
cb8e34fa2e change profile-update-interval time 2021-08-21 17:59:58 +08:00
b0c818c661 update 2021-08-21 16:20:16 +09:00
303d4a1c66 update: short payment generate key 2021-08-20 23:42:07 +09:00
e6416712f0 Merge pull request #471 from betaxab/add-trojan-surfboard
Protocols: Surfboard: add trojan protocol support
2021-08-20 22:32:08 +09:00
5c6236366c Merge pull request #472 from betaxab/change-anxray-flag
Protocols: AnXray: change AnXray flag to axxray
2021-08-20 22:31:49 +09:00
9b0a487c69 update: add knowledge jump func 2021-08-20 22:30:18 +09:00
27a6cc98d1 update Clash Subscribe 2021-08-20 00:54:00 +08:00
e05f6116b6 Protocols: AnXray: change AnXray flag to axxray 2021-08-19 15:47:18 +08:00
a9db11877f Protocols: Surfboard: add trojan protocol support 2021-08-19 08:10:36 +08:00
abe1ebccae update: new generate order number method 2021-08-19 00:58:51 +09:00
c957a4ca83 update: fix ticket message length support utf8mb4 2021-08-18 21:23:45 +09:00
c9cd307cc4 update: auto open knowledge by id 2021-08-18 18:54:40 +09:00
31cdcef3aa update: remove apple id config 2021-08-12 17:28:15 +09:00
9f75d4cbde update: default theme add green color 2021-08-12 17:17:36 +09:00
de5f80b5a3 update: script 2021-08-10 13:37:37 +09:00
474df5e18f update: payment config format 2021-08-08 16:56:50 +09:00
dfa75f49bd update: fix tg 2021-08-07 19:00:33 +09:00
f2c7d092ac update: fix model 2021-08-06 23:41:41 +09:00
2c389ebe8c update: v2board update 2021-08-06 23:20:10 +09:00
25e19446e6 update: user tags 2021-08-06 17:33:16 +09:00
a8761e9d4d update: opt editor 2021-08-06 14:02:47 +09:00
5f4e9c0301 update: cache version 2021-08-06 13:51:55 +09:00
b6e9260464 update: fix editor 2021-08-06 13:51:30 +09:00
6aa96fe856 update: fix editor 2021-08-06 13:01:17 +09:00
ab02935fd7 update: fix v2board udpate 2021-08-06 02:40:31 +09:00
b275ad469b update: cache version 2021-08-06 01:53:51 +09:00
e6a7c2c11c update: opt code 2021-08-06 01:43:01 +09:00
0f0f726269 update: fix code support php8 2021-08-05 15:22:15 +09:00
00cd3e26be update: fix admin editor 2021-08-05 14:48:24 +09:00
d95974019a update: fix 2021-08-02 03:00:01 +09:00
7a80950ab5 update: laravel 7 2021-08-01 23:56:11 +09:00
73a6d3236a update: fix reset traffic maybe failure issue 2021-08-01 16:48:00 +09:00
59dd34674e update: rollback ui 2021-07-31 16:24:12 +09:00
5dda531c2b update: ui 2021-07-31 15:15:05 +09:00
7234ccf4c1 update: ui 2021-07-31 14:17:48 +09:00
448b5382b9 Merge pull request #468 from betaxab/fix-grpc-protocol 2021-07-31 13:17:06 +09:00
bd2b056fbf protocols: fix gRPC protocol serviceName field
fix anXray serverName filed
2021-07-31 12:14:40 +08:00
ad8e2b8e80 update: api 2021-07-31 03:36:49 +09:00
8a20a70513 update: fix ui 2021-07-31 02:34:58 +09:00
06adaa2124 update: fix order statistics 2021-07-31 02:21:49 +09:00
ebf98d42a8 update: new feature 2021-07-31 02:05:39 +09:00
1adb1bcfa0 update: fix default app_url with subscribe_url 2021-07-30 23:53:37 +09:00
bb8da6e2c5 update: fix payment verify app_url 2021-07-30 16:00:20 +09:00
c426aabbcf update: theme 2021-07-29 21:29:53 +09:00
18bb1bd962 update: theme 2021-07-29 21:24:37 +09:00
07e9377417 update: theme 2021-07-29 21:22:07 +09:00
c0d3150461 update: add payment app_url alert 2021-07-29 21:12:11 +09:00
1a9b8b09bb Merge pull request #467 from v2board/dev
1.5.2
2021-07-29 20:59:52 +09:00
cb621a93ae update: version 2021-07-29 20:56:28 +09:00
fb449b490e Merge pull request #466 from v2board/dev
1.5.2
2021-07-29 20:15:19 +09:00
e35ec76f81 update: add subscribe anxray 2021-07-29 20:13:01 +09:00
5a60380765 update: add theme config 2021-07-29 13:38:13 +09:00
f409d89c4a update: add config 2021-07-29 02:41:31 +09:00
de045c79f5 update: custom theme 2021-07-29 02:36:51 +09:00
6a336d4253 update: web route 2021-07-29 02:29:15 +09:00
0ce3948c12 update: theme directory 2021-07-29 02:21:09 +09:00
7338c4a294 update: user 2021-07-27 19:16:49 +09:00
9417623fcf fix: email suffix select 2021-07-27 18:32:29 +09:00
4f29264de9 update: fix email suffix 2021-07-26 13:50:04 +09:00
34d8f0d5f0 update: statistics check time 2021-07-24 01:31:23 +09:00
b585038916 update: user 2021-07-23 20:43:09 +09:00
5abb642277 update: user 2021-07-22 22:36:48 +09:00
01cf486137 update: comm config 2021-07-22 22:32:42 +09:00
cb811bda5a update: user 2021-07-22 21:12:09 +09:00
2306e38d0c update: remove aliyun repo 2021-07-22 01:22:14 +09:00
2798c1df06 update: user language 2021-07-20 21:16:46 +09:00
a727745b43 update: order ui 2021-07-20 18:22:26 +09:00
af901291a5 update: quick login url 2021-07-19 21:17:34 +09:00
42d755c7d9 update: get comm config api 2021-07-19 18:44:40 +09:00
56a4ce5fe4 fix: surplus 2021-07-18 22:27:23 +09:00
67ab0d1f22 update: user 2021-07-16 18:06:11 +09:00
230470c037 fix: knowledge sort 2021-07-16 17:46:36 +09:00
77aec7d553 update: commission type and opt knowledge sort timestamp 2021-07-14 20:08:30 +09:00
88948eb8ee fix: change plan refund surplus amount 2021-07-11 01:13:09 +09:00
4d38ce3968 Merge pull request #463 from betaxab/fix-vmss-tls 2021-07-09 20:24:25 +09:00
a88121626b fix: knowledge subscribe url 2021-07-09 00:01:13 +09:00
90409b1107 update: add default subscribe, 1.5.3 remove this 2021-07-07 19:54:31 +09:00
04615688f7 update: subscribe url random 2021-07-06 18:04:59 +09:00
f48de9f07d Protocols: fix vmess tls sni field 2021-07-05 21:01:28 +08:00
4722379e79 update: add new client protocol 2021-07-04 13:21:10 +09:00
9aed2554ee Merge pull request #462 from betaxab/routerprotocol
Protocols: add Passwall & SSRPlus subscription support
2021-07-04 13:08:07 +09:00
7eb3d9fed7 Protocols: add Passwall & SSRPlus subscription support
Usage:
add &flag=passwall at the end of subscription link for OpenWRT Passwall Luci Plugin
add &flag=ssrplus at the end of subscription link for OpenWRT ShadowsocksR Plus+ Luci Plugin
2021-07-03 23:02:47 +08:00
567acdd03b Merge pull request #461 from betaxab/v2rayprotocol
Protocols: add V2ray Client Protocol
2021-07-03 23:56:00 +09:00
a0d18d93d3 Protocols: add V2ray Client Protocol
Usage:
add &flag=v2rayng at the end of subscription link for V2rayNG Client
add &flag=v2rayn at the end of subscription link for V2rayN Client
2021-07-03 22:54:13 +08:00
1c419283c0 Merge pull request #460 from betaxab/p3
Protocols: fix QuantumultX subscription
2021-07-03 14:26:42 +09:00
d25f6bff65 Protocols: fix QuantumultX subscription 2021-07-03 12:53:22 +08:00
0b97dd0995 fix: app subscribe 2021-07-03 03:31:28 +09:00
6f90c6b878 update: code 2021-07-02 23:35:10 +09:00
5b8591fde9 update: code 2021-07-02 23:34:42 +09:00
dfeec044ea fix: statsistics 2021-07-02 22:43:22 +09:00
70b47ec4b0 update: remove origin subscribe method 2021-07-02 22:24:39 +09:00
cd85fba9c7 restore: origin subscribe method 2021-07-02 22:14:07 +09:00
e01e951f7f update: code 2021-07-02 22:07:54 +09:00
0284e47155 fix: shadowrocket grpc 2021-07-02 22:05:12 +09:00
a4e1ba4016 fix: shadowrocket grpc 2021-07-02 21:57:00 +09:00
f95deb3f16 fix: shadowrocket grpc 2021-07-02 21:52:13 +09:00
dfef6d2d94 update: remove origin subscribe method 2021-07-02 21:25:26 +09:00
efc8419eb5 update: grpc 2021-07-02 20:46:16 +09:00
aecfa85efd update 2021-07-01 23:25:59 +09:00
9db5de09f2 update: order process event 2021-07-01 22:04:22 +09:00
b174403a2a remove: soft delete 2021-07-01 20:06:09 +09:00
0f488540f4 update: middleware 2021-07-01 18:16:27 +09:00
d00ad94c46 update: sql 2021-07-01 18:14:49 +09:00
5fa7c534ff update: middleware 2021-07-01 18:11:23 +09:00
386c1339f5 fix: plan update 2021-07-01 18:02:13 +09:00
6509091e4f update: check order php env memory limit 2021-07-01 12:41:34 +09:00
078dfbf339 fix: safe delete sql 2021-07-01 03:01:26 +09:00
de6ff1dca9 update: add user softdelete api 2021-07-01 00:35:22 +09:00
044d1e9b7f update: message box 2021-06-30 00:48:28 +09:00
9c711b4ea6 update: middleware 2021-06-25 22:35:14 +09:00
760d248e4e update: ui 2021-06-23 19:36:40 +09:00
1a2e8e2966 fix: ui event 2021-06-23 19:24:30 +09:00
b00b58d73d update: ui 2021-06-23 19:15:19 +09:00
43027660be update: ui 2021-06-23 19:11:56 +09:00
fe69a5976b update: ui 2021-06-23 19:06:48 +09:00
ecebba1d51 fix: language change 2021-06-23 18:02:35 +09:00
efc43dc45e update: ui 2021-06-23 16:32:25 +09:00
c5d88bfbfc update: ui 2021-06-23 16:25:55 +09:00
6928fd3fef update readme 2021-06-23 02:31:50 +09:00
e4f178e167 add: wechatpay native 2021-06-14 22:29:31 +09:00
343c93ad8e add: wechatpay native 2021-06-14 22:27:29 +09:00
f80af8efdc update: dev version 2021-06-12 16:49:34 +09:00
e5b998ee8d fix: user update 2021-06-12 16:46:58 +09:00
d510064732 update: support grpc for clash & shadowrocket 2021-06-12 15:42:44 +09:00
c5e2ec1d12 update: gitignore 2021-06-12 01:57:49 +09:00
ee2ca23487 update: language more 2021-06-12 01:56:39 +09:00
a5532490ba update: telegram ticket replay notify 2021-06-10 22:24:54 +09:00
2f175323a3 update: css 2021-06-08 20:30:36 +09:00
f896370e64 update: sort leave alert 2021-06-08 20:07:10 +09:00
0313c35dbe update: user edit 2021-06-04 00:56:28 +09:00
232cb18a25 update: frontend 2021-06-02 21:10:16 +09:00
04a05a6ba4 Merge pull request #445 from betaxab/p1
ClientController: clash: add subscription-userinfo header
2021-05-26 23:26:35 +09:00
e2b4df592d update: frontend 2021-05-26 23:15:18 +09:00
668663f978 update: user frontend 2021-05-26 22:11:03 +09:00
2e7544c71c ClientController: clash: add subscription-userinfo header 2021-05-26 17:07:38 +08:00
1fb3f62cff update: user frontend 2021-05-26 17:35:57 +09:00
02a1728bff update: payment service 2021-05-26 01:38:57 +09:00
4ea71d85be update: user frontend 2021-05-24 22:49:57 +09:00
afe8bb3171 update: check order 2021-05-24 21:10:54 +09:00
ef17be2046 fix: ticket filter 2021-05-22 14:18:27 +09:00
53dea06a6c update: user 2021-05-21 02:40:31 +09:00
190 changed files with 3770 additions and 2398 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ composer.phar
composer.lock
yarn.lock
docker-compose.yml
.DS_Store

View File

@ -1,16 +0,0 @@
php:
preset: laravel
enabled:
- alpha_ordered_imports
disabled:
- length_ordered_imports
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Models\CommissionLog;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\User;
@ -59,27 +60,66 @@ class CheckCommission extends Command
public function autoPayCommission()
{
$order = Order::where('commission_status', 1)
$orders = Order::where('commission_status', 1)
->where('invite_user_id', '!=', NULL)
->get();
foreach ($order as $item) {
$inviter = User::find($item->invite_user_id);
if (!$inviter) continue;
if ((int)config('v2board.withdraw_close_enable', 0)) {
$inviter->balance = $inviter->balance + $item->commission_balance;
} else {
$inviter->commission_balance = $inviter->commission_balance + $item->commission_balance;
}
foreach ($orders as $order) {
DB::beginTransaction();
if ($inviter->save()) {
$item->commission_status = 2;
if (!$item->save()) {
DB::rollBack();
continue;
}
DB::commit();
if (!$this->payHandle($order->invite_user_id, $order)) {
DB::rollBack();
continue;
}
$order->commission_status = 2;
if (!$order->save()) {
DB::rollBack();
continue;
}
DB::commit();
}
}
public function payHandle($inviteUserId, Order $order)
{
if ((int)config('v2board.commission_distribution_enable', 0)) {
$level = 3;
$commissionShareLevels = [
0 => (int)config('v2board.commission_distribution_l1'),
1 => (int)config('v2board.commission_distribution_l2'),
2 => (int)config('v2board.commission_distribution_l3')
];
} else {
$level = 3;
$commissionShareLevels = [
0 => 100
];
}
for ($l = 0; $l < $level; $l++) {
$inviter = User::find($inviteUserId);
if (!$inviter) continue;
if (!isset($commissionShareLevels[$l])) continue;
$commissionBalance = $order->commission_balance * ($commissionShareLevels[$l] / 100);
if ((int)config('v2board.withdraw_close_enable', 0)) {
$inviter->balance = $inviter->balance + $commissionBalance;
} else {
$inviter->commission_balance = $inviter->commission_balance + $commissionBalance;
}
if (!$inviter->save()) {
DB::rollBack();
return false;
}
if (!CommissionLog::create([
'invite_user_id' => $inviteUserId,
'user_id' => $order->user_id,
'trade_no' => $order->trade_no,
'order_amount' => $order->total_amount,
'get_amount' => $commissionBalance
])) {
DB::rollBack();
return false;
}
$inviteUserId = $inviter->invite_user_id;
}
return true;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Jobs\OrderHandleJob;
use App\Services\OrderService;
use Illuminate\Console\Command;
use App\Models\Order;
@ -42,21 +43,11 @@ class CheckOrder extends Command
*/
public function handle()
{
$orders = Order::get();
foreach ($orders as $item) {
$orderService = new OrderService($item);
switch ($item->status) {
// cancel
case 0:
if (strtotime($item->created_at) <= (time() - 1800)) {
$orderService->cancel();
}
break;
case 1:
$orderService->open();
break;
}
ini_set('memory_limit', -1);
$orders = Order::whereIn('status', [0, 1])
->get();
foreach ($orders as $order) {
OrderHandleJob::dispatch($order->trade_no);
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Console\Commands;
use App\Services\ServerService;
use App\Services\TelegramService;
use App\Utils\CacheKey;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class CheckServer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:server';
/**
* The console command description.
*
* @var string
*/
protected $description = '节点检查任务';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->checkOffline();
}
private function checkOffline()
{
$serverService = new ServerService();
$servers = $serverService->getAllServers();
foreach ($servers as $server) {
if ($server['parent_id']) continue;
if ($server['last_check_at'] && (time() - $server['last_check_at']) > 1800) {
$telegramService = new TelegramService();
$message = sprintf(
"节点掉线通知\r\n----\r\n节点名称:%s\r\n节点地址:%s\r\n",
$server['name'],
$server['host']
);
$telegramService->sendMessageWithAdmin($message);
Cache::forget(CacheKey::get(sprintf("SERVER_%s_LAST_CHECK_AT", strtoupper($server['type'])), $server->id));
}
}
}
}

View File

@ -2,8 +2,10 @@
namespace App\Console\Commands;
use App\Models\Plan;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class ResetTraffic extends Command
{
@ -41,22 +43,45 @@ class ResetTraffic extends Command
*/
public function handle()
{
$resetTrafficMethod = config('v2board.reset_traffic_method', 0);
switch ((int)$resetTrafficMethod) {
// 1 a month
case 0:
$this->resetByMonthFirstDay();
break;
// expire day
case 1:
$this->resetByExpireDay();
break;
ini_set('memory_limit', -1);
foreach (Plan::get() as $plan) {
switch ($plan->reset_traffic_method) {
case null: {
$resetTrafficMethod = config('v2board.reset_traffic_method', 0);
switch ((int)$resetTrafficMethod) {
// month first day
case 0:
$this->resetByMonthFirstDay($this->builder);
break;
// expire day
case 1:
$this->resetByExpireDay($this->builder);
break;
// no action
case 2:
break;
}
break;
}
case 0: {
$builder = $this->builder->where('plan_id', $plan->id);
$this->resetByMonthFirstDay($builder);
break;
}
case 1: {
$builder = $this->builder->where('plan_id', $plan->id);
$this->resetByExpireDay($builder);
break;
}
case 2: {
break;
}
}
}
}
private function resetByMonthFirstDay():void
private function resetByMonthFirstDay($builder):void
{
$builder = $this->builder;
if ((string)date('d') === '01') {
$builder->update([
'u' => 0,
@ -65,9 +90,8 @@ class ResetTraffic extends Command
}
}
private function resetByExpireDay():void
private function resetByExpireDay($builder):void
{
$builder = $this->builder;
$lastDay = date('d', strtotime('last day of +0 months'));
$users = [];
foreach ($builder->get() as $item) {

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Services\MailService;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\MailLog;
@ -41,23 +42,9 @@ class SendRemindMail extends Command
public function handle()
{
$users = User::all();
$mailService = new MailService();
foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user);
}
}
private function remindExpire($user)
{
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' => 'remindExpire',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
if ($user->remind_expire) $mailService->remindExpire($user);
}
}
}

View File

@ -2,8 +2,11 @@
namespace App\Console\Commands;
use App\Services\PaymentService;
use App\Models\Order;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Console\Command;
use Matriphe\Larinfo;
class Test extends Command
{
@ -38,7 +41,5 @@ class Test extends Command
*/
public function handle()
{
$paymentService = new PaymentService('MGate');
var_dump($paymentService->form());
}
}

View File

@ -47,13 +47,12 @@ class V2boardInstall extends Command
$this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | ");
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.lock')) {
if (\File::exists(base_path() . '/.env')) {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件');
}
if (!\File::exists(base_path() . '/.env')) {
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限');
}
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限');
}
$this->saveToEnv([
'APP_KEY' => 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')),

View File

@ -42,21 +42,20 @@ class V2boardStatistics extends Command
*/
public function handle()
{
$this->statOrder();
$this->statServer();
$this->statOrder();
$this->statServer();
}
private function statOrder()
{
$endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt);
$builder = Order::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
$builder = Order::where('paid_at', '>=', $startAt)
->where('paid_at', '<', $endAt)
->whereNotIn('status', [0, 2]);
$orderCount = $builder->count();
$orderAmount = $builder->sum('total_amount');
$builder = $builder->where('commission_balance', '!=', 0)
->where('commission_status', 0);
$builder = $builder->where('commission_balance', '!=', 0);
$commissionCount = $builder->count();
$commissionAmount = $builder->sum('commission_balance');
$data = [

View File

@ -51,11 +51,12 @@ class V2boardUpdate extends Command
}
$this->info('正在导入数据库请稍等...');
foreach ($sql as $item) {
if (!$item) continue;
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {
}
}
$this->info('更新完毕');
$this->info('更新完毕,请重新启动队列服务。');
}
}

6
app/Console/Kernel.php Executable file → Normal file
View File

@ -25,15 +25,17 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule)
{
// v2board
$schedule->command('v2board:statistics')->daily();
$schedule->command('v2board:statistics')->dailyAt('0:10');
// check
$schedule->command('check:order')->everyMinute();
$schedule->command('check:commission')->everyMinute();
// reset
$schedule->command('reset:traffic')->daily();
$schedule->command('reset:serverLog')->quarterly();
$schedule->command('reset:serverLog')->quarterly()->at('0:15');
// send
$schedule->command('send:remindMail')->dailyAt('11:30');
// horizon metrics
$schedule->command('horizon:snapshot')->everyFiveMinutes();
}
/**

View File

@ -2,8 +2,8 @@
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
@ -29,10 +29,12 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* @param \Exception $exception
* @param \Throwable $exception
* @return void
*
* @throws \Throwable
*/
public function report(Exception $exception)
public function report(Throwable $exception)
{
parent::report($exception);
}
@ -40,16 +42,14 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
* @param \Illuminate\Http\Request $request
* @param \Throwable $exception
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Throwable
*/
public function render($request, Exception $exception)
public function render($request, Throwable $exception)
{
if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
abort(429, '请求频繁,请稍后再试');
}
return parent::render($request, $exception);
}
}

View File

@ -21,6 +21,17 @@ class ConfigController extends Controller
]);
}
public function getThemeTemplate()
{
$path = public_path('theme/');
$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'));
@ -49,7 +60,11 @@ class ConfigController extends Controller
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1),
'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100),
'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0)
'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0),
'commission_distribution_enable' => config('v2board.commission_distribution_enable', 0),
'commission_distribution_l1' => config('v2board.commission_distribution_l1'),
'commission_distribution_l2' => config('v2board.commission_distribution_l2'),
'commission_distribution_l3' => config('v2board.commission_distribution_l3')
],
'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
@ -72,8 +87,10 @@ class ConfigController extends Controller
'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', 0),
'surplus_enable' => (int)config('v2board.surplus_enable', 1)
'surplus_enable' => (int)config('v2board.surplus_enable', 1),
'new_order_event_id' => (int)config('v2board.new_order_event_id', 0),
'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0),
'change_order_event_id' => (int)config('v2board.change_order_event_id', 0),
],
'pay' => [
// alipay
@ -107,6 +124,7 @@ class ConfigController extends Controller
'epay_key' => config('v2board.epay_key'),
],
'frontend' => [
'frontend_theme' => config('v2board.frontend_theme', 'v2board'),
'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),
@ -122,9 +140,6 @@ class ConfigController extends Controller
'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
],
'tutorial' => [
'apple_id' => config('v2board.apple_id')
],
'email' => [
'email_template' => config('v2board.email_template', 'default'),
'email_host' => config('v2board.email_host'),
@ -152,7 +167,7 @@ class ConfigController extends Controller
public function save(ConfigSave $request)
{
$data = $request->input();
$data = $request->validated();
$array = \Config::get('v2board');
foreach ($data as $k => $v) {
if (!in_array($k, array_keys($request->validated()))) {

View File

@ -24,42 +24,12 @@ class CouponController extends Controller
$total = $builder->count();
$coupons = $builder->forPage($current, $pageSize)
->get();
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' => $coupons,
'total' => $total
]);
}
public function save(CouponSave $request)
{
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) {
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
}
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Coupon::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function generate(CouponGenerate $request)
{
if ($request->input('generate_count')) {
@ -68,9 +38,6 @@ class CouponController extends Controller
}
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) {
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
@ -95,10 +62,8 @@ class CouponController extends Controller
{
$coupons = [];
$coupon = $request->validated();
if (isset($coupon['limit_plan_ids'])) {
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
}
$coupon['created_at'] = $coupon['updated_at'] = time();
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
unset($coupon['generate_count']);
for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon['code'] = Helper::randomChar(8);
@ -118,7 +83,7 @@ class CouponController extends Controller
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
$limitUse = $coupon['limit_use'] ?? '不限制';
$createTime = date('Y-m-d H:i:s', $coupon['created_at']);
$limitPlanIds = $coupon['limit_plan_ids'] ?? '不限制';
$limitPlanIds = implode("/", json_decode($coupon['limit_plan_ids'], true)) ?? '不限制';
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
}
echo $data;

View File

@ -77,11 +77,15 @@ class KnowledgeController extends Controller
public function sort(KnowledgeSort $request)
{
DB::beginTransaction();
foreach ($request->input('knowledge_ids') as $k => $v) {
if (!Knowledge::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
try {
foreach ($request->input('knowledge_ids') as $k => $v) {
$knowledge = Knowledge::find($v);
$knowledge->timestamps = false;
$knowledge->update(['sort' => $k + 1]);
}
} catch (\Exception $e) {
DB::rollBack();
abort(500, '保存失败');
}
DB::commit();
return response([

View File

@ -6,6 +6,7 @@ use App\Http\Requests\Admin\OrderAssign;
use App\Http\Requests\Admin\OrderUpdate;
use App\Http\Requests\Admin\OrderFetch;
use App\Services\OrderService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
@ -63,10 +64,45 @@ class OrderController extends Controller
]);
}
public function paid(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->paid('manual_operation')) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function cancel(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function update(OrderUpdate $request)
{
$params = $request->only([
'status',
'commission_status'
]);
@ -76,16 +112,6 @@ class OrderController extends Controller
abort(500, '订单不存在');
}
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) {
@ -97,26 +123,6 @@ class OrderController extends Controller
]);
}
public function repair(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数错误');
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或订单已支付');
}
$order->status = 1;
if (!$order->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function assign(OrderAssign $request)
{
$plan = Plan::find($request->input('plan_id'));
@ -130,6 +136,11 @@ class OrderController extends Controller
abort(500, '该订阅不存在');
}
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($user->id)) {
abort(500, '该用户还有待支付的订单,无法分配');
}
DB::beginTransaction();
$order = new Order();
$orderService = new OrderService($order);

View File

@ -42,6 +42,9 @@ class PaymentController extends Controller
public function save(Request $request)
{
if (!config('v2board.app_url')) {
abort(500, '请在站点配置中配置站点地址');
}
if ($request->input('id')) {
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
@ -58,7 +61,7 @@ class PaymentController extends Controller
'name' => $request->input('name'),
'payment' => $request->input('payment'),
'config' => $request->input('config'),
'uuid' => Helper::guid()
'uuid' => Helper::randomChar(8)
])) {
abort(500, '保存失败');
}

View File

@ -52,8 +52,8 @@ class PlanController extends Controller
// 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
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824
]);
$plan->update($params);
} catch (\Exception $e) {

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers\Admin\Server;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\ServerGroup;
use App\Models\User;
use Illuminate\Http\Request;
@ -50,10 +50,9 @@ class GroupController extends Controller
}
}
$servers = Server::all();
$servers = ServerV2ray::all();
foreach ($servers as $server) {
$groupId = json_decode($server->group_id);
if (in_array($request->input('id'), $groupId)) {
if (in_array($request->input('id'), $server->group_id)) {
abort(500, '该组已被节点所使用,无法删除');
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Controllers\Admin\Server;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService;
@ -15,16 +15,8 @@ class ManageController extends Controller
public function getNodes(Request $request)
{
$serverService = new ServerService();
$servers = array_merge(
$serverService->getShadowsocksServers(),
$serverService->getV2rayServers(),
$serverService->getTrojanServers()
);
$serverService->mergeData($servers);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
return response([
'data' => $servers
'data' => $serverService->getAllServers()
]);
}
@ -40,7 +32,7 @@ class ManageController extends Controller
}
break;
case 'v2ray':
if (!Server::find($v['value'])->update(['sort' => $k + 1])) {
if (!ServerV2ray::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}

View File

@ -14,11 +14,6 @@ class ShadowsocksController extends Controller
public function save(ServerShadowsocksSave $request)
{
$params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {

View File

@ -14,11 +14,6 @@ class TrojanController extends Controller
public function save(ServerTrojanSave $request)
{
$params = $request->validated();
$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) {

View File

@ -7,44 +7,16 @@ use App\Http\Requests\Admin\ServerV2rayUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Server;
use App\Models\ServerV2ray;
class V2rayController extends Controller
{
public function save(ServerV2raySave $request)
{
$params = $request->validated();
$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'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
@ -58,7 +30,7 @@ class V2rayController extends Controller
]);
}
if (!Server::create($params)) {
if (!ServerV2ray::create($params)) {
abort(500, '创建失败');
}
@ -70,7 +42,7 @@ class V2rayController extends Controller
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
@ -86,7 +58,7 @@ class V2rayController extends Controller
'show',
]);
$server = Server::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
@ -104,12 +76,12 @@ class V2rayController extends Controller
public function copy(Request $request)
{
$server = Server::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!Server::create($server->toArray())) {
if (!ServerV2ray::create($server->toArray())) {
abort(500, '复制失败');
}

View File

@ -8,7 +8,7 @@ use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
@ -91,7 +91,7 @@ class StatController extends Controller
{
$servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'vmess' => Server::where('parent_id', null)->get()->toArray(),
'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
];
$timestamp = strtotime('-1 day', strtotime(date('Y-m-d')));

View File

@ -7,10 +7,10 @@ use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Services\UserService;
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;
@ -68,7 +68,7 @@ class UserController extends Controller
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
$res[$i]['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $res[$i]['token'];
$res[$i]['subscribe_url'] = Helper::getSubscribeHost() . '/api/v1/client/subscribe?token=' . $res[$i]['token'];
}
return response([
'data' => $res,
@ -81,8 +81,12 @@ class UserController extends Controller
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$user = User::find($request->input('id'));
if ($user->invite_user_id) {
$user['invite_user'] = User::find($user->invite_user_id);
}
return response([
'data' => User::find($request->input('id'))
'data' => $user
]);
}
@ -109,6 +113,14 @@ class UserController extends Controller
}
$params['group_id'] = $plan->group_id;
}
if ($request->input('invite_user_email')) {
$inviteUser = User::where('email', $request->input('invite_user_email'))->first();
if ($inviteUser) {
$params['invite_user_id'] = $inviteUser->id;
}
} else {
$params['invite_user_id'] = null;
}
try {
$user->update($params);
@ -265,30 +277,4 @@ class UserController extends Controller
'data' => true
]);
}
public function setInviteUser(Request $request)
{
$request->validate([
'user_id' => 'required|integer',
'invite_user' => 'required',
], [
'user_id.required' => '用户ID不能为空',
'user_id.integer' => '用户ID参数有误',
'invite_user.required' => '邀请人不能为空'
]);
$user = User::find($request->input('user_id'));
if (!$user) abort(500, '用户不存在');
if (strpos($request->input('invite_user'), '@') !== -1) {
$inviteUser = User::where('email', $request->input('invite_user'))->first();
} else {
$inviteUser = User::find($request->input('invite_user'));
}
if (!$inviteUser) abort(500, '邀请人不存在');
$user->invite_user_id = $inviteUser->id;
return response([
'data' => $user->save()
]);
}
}

View File

@ -7,7 +7,8 @@ use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\Clash;
use Illuminate\Http\Request;
use App\Models\Server;
use App\Models\ServerV2ray;
use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml;
class AppController extends Controller
@ -25,21 +26,27 @@ class AppController extends Controller
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$config = Yaml::parseFile(base_path() . '/resources/rules/app.clash.yaml');
$defaultConfig = base_path() . '/resources/rules/app.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.app.clash.yaml';
if (File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'v2ray') {
array_push($proxy, Clash::buildVmess($user['uuid'], $item));
array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, Clash::buildTrojan($user['uuid'], $item));
array_push($proxy, Protocols\Clash::buildTrojan($user['uuid'], $item));
array_push($proxies, $item['name']);
}
}
@ -84,62 +91,4 @@ class AppController extends Controller
]
]);
}
public function config(Request $request)
{
if (empty($request->input('server_id'))) {
abort(500, '参数错误');
}
$user = $request->user;
if ($user->expired_at < time() && $user->expired_at !== NULL) {
abort(500, '订阅计划已过期');
}
$server = Server::where('show', 1)
->where('id', $request->input('server_id'))
->first();
if (!$server) {
abort(500, '服务器不存在');
}
$json = json_decode(self::CLIENT_CONFIG);
//socks
$json->inbound->port = (int)self::SOCKS_PORT;
//http
$json->inboundDetour[0]->port = (int)self::HTTP_PORT;
//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->uuid;
$json->outbound->settings->vnext[0]->users[0]->alterId = (int)$server->alter_id;
$json->outbound->settings->vnext[0]->remark = (string)$server->name;
$json->outbound->streamSettings->network = $server->network;
if ($server->networkSettings) {
switch ($server->network) {
case 'tcp':
$json->outbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
break;
case 'kcp':
$json->outbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
break;
case 'ws':
$json->outbound->streamSettings->wsSettings = json_decode($server->networkSettings);
break;
case 'http':
$json->outbound->streamSettings->httpSettings = json_decode($server->networkSettings);
break;
case 'domainsocket':
$json->outbound->streamSettings->dsSettings = json_decode($server->networkSettings);
break;
case 'quic':
$json->outbound->streamSettings->quicSettings = json_decode($server->networkSettings);
break;
}
}
if ($request->input('is_global')) {
$json->routing->settings->rules[0]->outboundTag = 'proxy';
}
if ($server->tls) {
$json->outbound->streamSettings->security = "tls";
}
die(json_encode($json, JSON_UNESCAPED_UNICODE));
}
}

View File

@ -2,18 +2,10 @@
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Client\Protocols\V2rayN;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Utils\Clash;
use App\Utils\QuantumultX;
use App\Utils\Shadowrocket;
use App\Utils\Surge;
use App\Utils\Surfboard;
use App\Utils\URLSchemes;
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
@ -32,251 +24,18 @@ class ClientController extends Controller
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
if ($flag) {
if (strpos($flag, 'quantumult%20x') !== false) {
die($this->quantumultX($user, $servers));
}
if (strpos($flag, 'quantumult') !== false) {
die($this->quantumult($user, $servers));
}
if (strpos($flag, 'clash') !== false) {
die($this->clash($user, $servers));
}
if (strpos($flag, 'surfboard') !== false) {
die($this->surfboard($user, $servers));
}
if (strpos($flag, 'surge') !== false) {
die($this->surge($user, $servers));
}
if (strpos($flag, 'shadowrocket') !== false) {
die($this->shadowrocket($user, $servers));
}
if (strpos($flag, 'shadowsocks') !== false) {
die($this->shaodowsocksSIP008($user, $servers));
}
}
die($this->origin($user, $servers));
}
}
// TODO: Ready to stop support
private function quantumult($user, $servers = [])
{
$uri = '';
header('subscription-userinfo: upload=' . $user['u'] . '; download=' . $user['d'] . ';total=' . $user['transfer_enable']);
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$str = '';
$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['networkSettings']) {
$wsSettings = json_decode($item['networkSettings'], true);
if (isset($wsSettings['path'])) $str .= ', obfs-path="' . $wsSettings['path'] . '"';
if (isset($wsSettings['headers']['Host'])) $str .= ', obfs-header="Host:' . $wsSettings['headers']['Host'] . '"';
foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
$file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
$class = new $file($user, $servers);
if (strpos($flag, $class->flag) !== false) {
die($class->handle());
}
}
$uri .= "vmess://" . base64_encode($str) . "\r\n";
}
// todo 1.5.3 remove
$class = new V2rayN($user, $servers);
die($class->handle());
die('该客户端暂不支持进行订阅');
}
return base64_encode($uri);
}
private function shadowrocket($user, $servers = [])
{
$uri = '';
//display remaining traffic and expire date
$upload = round($user['u'] / (1024*1024*1024), 2);
$download = round($user['d'] / (1024*1024*1024), 2);
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$expiredDate = date('Y-m-d', $user['expired_at']);
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= Shadowrocket::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= Shadowrocket::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= Shadowrocket::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
private function quantumultX($user, $servers = [])
{
$uri = '';
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= QuantumultX::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= QuantumultX::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= QuantumultX::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
private function origin($user, $servers = [])
{
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= URLSchemes::buildShadowsocks($item, $user);
}
if ($item['type'] === 'v2ray') {
$uri .= URLSchemes::buildVmess($item, $user);
}
if ($item['type'] === 'trojan') {
$uri .= URLSchemes::buildTrojan($item, $user);
}
}
return base64_encode($uri);
}
private function shaodowsocksSIP008($user, $servers = [])
{
$configs = [];
$subs = [];
$subs['servers'] = [];
$subs['bytes_used'] = '';
$subs['bytes_remaining'] = '';
$bytesUsed = $user['u'] + $user['d'];
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($configs, URLSchemes::buildShadowsocksSIP008($item, $user));
}
}
$subs['version'] = 1;
$subs['bytes_used'] = $bytesUsed;
$subs['bytes_remaining'] = $bytesRemaining;
$subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
}
private function surge($user, $servers = [])
{
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= Surge::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= Surge::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'trojan') {
// [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, $servers = [])
{
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= Surfboard::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= Surfboard::buildVmess($user['uuid'], $item);
// [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, $servers = [])
{
$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 ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'v2ray') {
array_push($proxy, Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
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) {
if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
$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;
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class AnXray
{
public $flag = 'axxray';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($uuid, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$uuid}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildShadowsocksSIP008($uuid, $server)
{
$config = [
"id" => $server['id'],
"remarks" => $server['name'],
"server" => $server['host'],
"server_port" => $server['port'],
"password" => $uuid,
"method" => $server['cipher']
];
return $config;
}
public static function buildVmess($uuid, $server)
{
$config = [
"encryption" => "none",
"type" => urlencode($server['network']),
"security" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = urlencode($tlsSettings['serverName']);
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']);
if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']);
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['serviceName'] = urlencode($grpcSettings['serviceName']);
}
return "vmess://" . $uuid . "@" . $server['host'] . ":" . $server['port'] . "?" . http_build_query($config) . "#" . urlencode($server['name']) . "\r\n";
}
public static function buildTrojan($uuid, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$uuid}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
use Symfony\Component\Yaml\Yaml;
class Clash
{
public $flag = 'clash';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$appName = config('v2board.app_name', 'V2Board');
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
header('profile-update-interval: 24');
header("content-disposition: filename={$appName}");
$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 ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, self::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) {
if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['SERVER_NAME'];
$subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], $subsDomainRule);
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
public static function buildShadowsocks($uuid, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'ss';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['cipher'] = $server['cipher'];
$array['password'] = $uuid;
$array['udp'] = true;
return $array;
}
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'] = $server['alter_id'];
$array['cipher'] = 'auto';
$array['udp'] = true;
if ($server['tls']) {
$array['tls'] = true;
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$array['ws-path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
}
}
if ($server['network'] === 'grpc') {
$array['network'] = 'grpc';
if ($server['networkSettings']) {
$grpcObject = $server['networkSettings'];
$array['grpc-opts'] = [];
$array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName'];
}
}
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['udp'] = true;
if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
return $array;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Passwall
{
public $flag = 'passwall';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -1,10 +1,40 @@
<?php
namespace App\Utils;
namespace App\Http\Controllers\Client\Protocols;
class QuantumultX
{
public $flag = 'quantumult%20x';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$config = [
@ -36,7 +66,7 @@ class QuantumultX
if ($server['network'] === 'tcp')
array_push($config, 'obfs=over-tls');
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'tls-verification=' . ($tlsSettings['allowInsecure'] ? 'false' : 'true'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -49,7 +79,7 @@ class QuantumultX
else
array_push($config, 'obfs=ws');
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "obfs-uri={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']) && !isset($host))

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class SSRPlus
{
public $flag = 'ssrplus';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -1,10 +1,46 @@
<?php
namespace App\Utils;
namespace App\Http\Controllers\Client\Protocols;
class Shadowrocket
{
public $flag = 'shadowrocket';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
//display remaining traffic and expire date
$upload = round($user['u'] / (1024*1024*1024), 2);
$download = round($user['d'] / (1024*1024*1024), 2);
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$expiredDate = date('Y-m-d', $user['expired_at']);
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
@ -27,7 +63,7 @@ class Shadowrocket
if ($server['tls']) {
$config['tls'] = 1;
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$config['allowInsecure'] = (int)$tlsSettings['allowInsecure'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -37,13 +73,26 @@ class Shadowrocket
if ($server['network'] === 'ws') {
$config['obfs'] = "websocket";
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$config['obfsParam'] = $wsSettings['headers']['Host'];
}
}
if ($server['network'] === 'grpc') {
$config['obfs'] = "grpc";
if ($server['networkSettings']) {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName']) && !empty($grpcSettings['serviceName']))
$config['path'] = $grpcSettings['serviceName'];
}
if (isset($tlsSettings)) {
$config['host'] = $tlsSettings['serverName'];
} else {
$config['host'] = $server['host'];
}
}
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
$uri = "vmess://{$userinfo}?{$query}";
$uri .= "\r\n";

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Shadowsocks
{
public $flag = 'shadowsocks';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$configs = [];
$subs = [];
$subs['servers'] = [];
$subs['bytes_used'] = '';
$subs['bytes_remaining'] = '';
$bytesUsed = $user['u'] + $user['d'];
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($configs, self::SIP008($item, $user));
}
}
$subs['version'] = 1;
$subs['bytes_used'] = $bytesUsed;
$subs['bytes_remaining'] = $bytesRemaining;
$subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
}
public static function SIP008($server, $user)
{
$config = [
"id" => $server['id'],
"remarks" => $server['name'],
"server" => $server['host'],
"server_port" => $server['port'],
"password" => $user['uuid'],
"method" => $server['cipher']
];
return $config;
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Surfboard
{
public $flag = 'surfboard';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'trojan') {
// [Proxy]
$proxies .= self::buildTrojan($user['uuid'], $item);
// [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'];
$subsDomain = $_SERVER['SERVER_NAME'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config;
}
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server['name']}=custom",
"{$server['host']}",
"{$server['port']}",
"{$server['cipher']}",
"{$password}",
'https://raw.githubusercontent.com/Hackl0us/proxy-tool-backup/master/SSEncrypt.module',
'tfo=true',
'udp-relay=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"{$server['name']}=vmess",
"{$server['host']}",
"{$server['port']}",
"username={$uuid}",
'tfo=true',
'udp-relay=true'
];
if ($server['tls']) {
array_push($config, 'tls=true');
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
array_push($config, "sni={$tlsSettings['serverName']}");
}
}
if ($server['network'] === 'ws') {
array_push($config, 'ws=true');
if ($server['networkSettings']) {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$config = [
"{$server['name']}=trojan",
"{$server['host']}",
"{$server['port']}",
"password={$password}",
$server['server_name'] ? "sni={$server['server_name']}" : "",
'tfo=true',
'udp-relay=true'
];
if (!empty($server['allow_insecure'])) {
array_push($config, $server['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}

View File

@ -1,10 +1,68 @@
<?php
namespace App\Utils;
namespace App\Http\Controllers\Client\Protocols;
class Surge
{
public $flag = 'surge';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'trojan') {
// [Proxy]
$proxies .= self::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'];
$subsDomain = $_SERVER['SERVER_NAME'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config;
}
public static function buildShadowsocks($password, $server)
{
$config = [
@ -36,7 +94,7 @@ class Surge
if ($server['tls']) {
array_push($config, 'tls=true');
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -46,7 +104,7 @@ class Surge
if ($server['network'] === 'ws') {
array_push($config, 'ws=true');
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class V2rayN
{
public $flag = 'v2rayn';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class V2rayNG
{
public $flag = 'v2rayng';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -11,8 +11,26 @@ class CommController extends Controller
{
return response([
'data' => [
'tos_url' => config('v2board.tos_url')
'tos_url' => config('v2board.tos_url'),
'is_email_verify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'is_invite_force' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'email_whitelist_suffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0,
'is_recaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
'app_description' => config('v2board.app_description'),
'app_url' => config('v2board.app_url')
]
]);
}
private function getEmailSuffix()
{
$suffix = config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
if (!is_array($suffix)) {
return preg_split('/,/', $suffix);
}
return $suffix;
}
}

View File

@ -20,7 +20,7 @@ class PaymentController extends Controller
if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
abort(500, 'handle error');
}
die('success');
die(isset($paymentService->customResult) ? $paymentService->customResult : 'success');
} catch (\Exception $e) {
abort(500, 'fail');
}
@ -34,7 +34,7 @@ class PaymentController extends Controller
}
if ($order->status === 1) return true;
$orderService = new OrderService($order);
if (!$orderService->success($callbackNo)) {
if (!$orderService->paid($callbackNo)) {
return false;
}
$telegramService = new TelegramService();

View File

@ -193,6 +193,7 @@ class TelegramController extends Controller
}
$telegramService = new TelegramService();
$telegramService->sendMessage($msg->chat_id, "#`{$ticketId}` 的工单已回复成功", 'markdown');
$telegramService->sendMessageWithAdmin("#`{$ticketId}` 的工单已由 {$user->email} 进行回复", true);
}

View File

@ -24,7 +24,7 @@ class AuthController extends Controller
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, '验证码有误');
abort(500, __('Invalid code is incorrect'));
}
}
if ((int)config('v2board.email_whitelist_enable', 0)) {
@ -32,36 +32,36 @@ class AuthController extends Controller
$request->input('email'),
config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
) {
abort(500, '邮箱后缀不处于白名单中');
abort(500, __('Email suffix is not in the Whitelist'));
}
}
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别名邮箱');
abort(500, __('Gmail alias is not supported'));
}
}
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
abort(500, __('Registration has closed'));
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
abort(500, __('You must use the invitation code to register'));
}
}
if ((int)config('v2board.email_verify', 0)) {
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
abort(500, __('Email verification code cannot be empty'));
}
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
abort(500, __('Incorrect email verification code'));
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
abort(500, __('Email already exists'));
}
$user = new User();
$user->email = $email;
@ -74,7 +74,7 @@ class AuthController extends Controller
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
abort(500, __('Invalid invitation code'));
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
@ -97,15 +97,20 @@ class AuthController extends Controller
}
if (!$user->save()) {
abort(500, '注册失败');
abort(500, __('Register failed'));
}
if ((int)config('v2board.email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
return response()->json([
'data' => true
'data' => $data
]);
}
@ -116,18 +121,19 @@ class AuthController extends Controller
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
abort(500, __('Incorrect email or password'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$password,
$user->password)
) {
abort(500, '用户名或密码错误');
abort(500, __('Incorrect email or password'));
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
abort(500, __('Your account has been suspended'));
}
$data = [
@ -165,14 +171,14 @@ class AuthController extends Controller
$key = CacheKey::get('TEMP_TOKEN', $request->input('verify'));
$userId = Cache::get($key);
if (!$userId) {
abort(500, '令牌有误');
abort(500, __('Token error'));
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
abort(500, __('The user does not '));
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
abort(500, __('Your account has been suspended'));
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
@ -190,7 +196,7 @@ class AuthController extends Controller
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, '令牌有误');
abort(500, __('Token error'));
}
$code = Helper::guid();
@ -204,11 +210,12 @@ class AuthController extends Controller
public function getQuickLoginUrl(Request $request)
{
$authData = explode(':', base64_decode($request->input('auth_data')));
if (!isset($authData[0])) abort(403, __('Token error'));
$user = User::where('email', $authData[0])
->where('password', $authData[1])
->first();
if (!$user) {
abort(500, '令牌有误');
abort(500, __('Token error'));
}
$code = Helper::guid();
@ -241,16 +248,17 @@ class AuthController extends Controller
public function forget(AuthForget $request)
{
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
abort(500, __('Incorrect email verification code'));
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, '该邮箱不存在系统中');
abort(500, __('This email is not registered in the system'));
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
abort(500, '重置失败');
abort(500, __('Reset failed'));
}
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
return response([

View File

@ -17,6 +17,7 @@ use ReCaptcha\ReCaptcha;
class CommController extends Controller
{
// TODO: remove on 1.5.5
public function config()
{
return response([
@ -47,15 +48,15 @@ class CommController extends Controller
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, '验证码有误');
abort(500, __('Invalid code is incorrect'));
}
}
$email = $request->input('email');
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
abort(500, '验证码已发送,请过一会再请求');
abort(500, __('Email verification code has been sent, please request again later'));
}
$code = rand(100000, 999999);
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
$subject = config('v2board.app_name', 'V2Board') . __('Email verification code');
SendEmailJob::dispatch([
'email' => $email,

View File

@ -8,7 +8,7 @@ use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@ -35,13 +35,13 @@ class DeepbworkController extends Controller
public function user(Request $request)
{
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
$server = ServerV2ray::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [
@ -64,7 +64,7 @@ class DeepbworkController extends Controller
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
$server = ServerV2ray::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
@ -76,23 +76,11 @@ class DeepbworkController extends Controller
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess');
}
DB::commit();
return response([
'ret' => 1,

View File

@ -1,158 +0,0 @@
<?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\Plan;
use App\Models\Server;
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
{
public $poseidonVersion;
public function __construct(Request $request)
{
$this->poseidonVersion = $request->input('poseidon_version');
}
// 后端获取用户
public function user(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
if (!$server) {
return $this->error("server could not be found", 404);
}
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->uuid,
"email" => sprintf("%s@v2board.user", $user->uuid),
"alter_id" => $server->alter_id,
"level" => 0,
];
unset($user['uuid']);
unset($user['email']);
array_push($result, $user);
}
return $this->success($result);
}
// 后端提交数据
public function submit(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$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);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$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'], $server, 'vmess')) {
return $this->error("user fetch fail", 500);
}
}
return $this->success('');
}
// 后端获取配置
public function config(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
return $this->error('invalid parameters', 400);
}
$serverService = new ServerService();
try {
$json = $serverService->getV2RayConfig($nodeId, $localPort);
$json->poseidon = [
'license_key' => (string)config('v2board.server_license'),
];
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);
}
}
protected function verifyToken(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
return $this->error("token must be set");
}
if ($token !== config('v2board.server_token')) {
return $this->error("invalid token");
}
}
protected function error($msg, int $status = 400) {
return response([
'msg' => $msg,
], $status);
}
protected function success($data) {
$req = request();
// Only for "GET" method
if (!$req->isMethod('GET') || !$data) {
return response([
'msg' => 'ok',
'data' => $data,
]);
}
$etag = sha1(json_encode($data));
if ($etag == $req->header("IF-NONE-MATCH")) {
return response(null, 304);
}
return response([
'msg' => 'ok',
'data' => $data,
])->header('ETAG', $etag);
}
}

View File

@ -8,10 +8,6 @@ use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
@ -41,7 +37,7 @@ class ShadowsocksTidalabController extends Controller
}
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
array_push($result, [
@ -72,23 +68,11 @@ class ShadowsocksTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'], $server, 'shadowsocks')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$userService->trafficFetch($u, $d, $item['user_id'], $server, 'shadowsocks');
}
DB::commit();
return response([
'ret' => 1,

View File

@ -41,7 +41,7 @@ class TrojanTidalabController extends Controller
}
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->trojan_user = [
@ -73,23 +73,11 @@ class TrojanTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan');
}
DB::commit();
return response([
'ret' => 1,

View File

@ -27,9 +27,8 @@ class CommController extends Controller
->where('payment', 'StripeCredit')
->first();
if (!$payment) abort(500, 'payment is not found');
$config = json_decode($payment->config, true);
return response([
'data' => $config['stripe_pk_live']
'data' => $payment->config['stripe_pk_live']
]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Services\CouponService;
use Illuminate\Http\Request;
use App\Models\Coupon;
@ -11,29 +12,14 @@ class CouponController extends Controller
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, __('user.coupon.check.coupon_not_empty'));
}
$coupon = Coupon::where('code', $request->input('code'))->first();
if (!$coupon) {
abort(500, __('user.coupon.check.coupon_invalid'));
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, __('user.coupon.check.coupon_not_available_by_number'));
}
if (time() < $coupon->started_at) {
abort(500, __('user.coupon.check.coupon_not_available_by_time'));
}
if (time() > $coupon->ended_at) {
abort(500, __('user.coupon.check.coupon_expired'));
}
if ($coupon->limit_plan_ids) {
$limitPlanIds = json_decode($coupon->limit_plan_ids);
if (!in_array($request->input('plan_id'), $limitPlanIds)) {
abort(500, __('user.coupon.check.coupon_limit_plan'));
}
abort(500, __('Coupon cannot be empty'));
}
$couponService = new CouponService($request->input('code'));
$couponService->setPlanId($request->input('plan_id'));
$couponService->setUserId($request->session()->get('id'));
$couponService->check();
return response([
'data' => $coupon
'data' => $couponService->getCoupon()
]);
}
}

View File

@ -14,7 +14,7 @@ class InviteController extends Controller
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
abort(500, __('user.invite.save.invite_create_limit'));
abort(500, __('The maximum number of creations has been reached'));
}
$inviteCode = new InviteCode();
$inviteCode->user_id = $request->session()->get('id');

View File

@ -17,21 +17,24 @@ class KnowledgeController extends Controller
->where('show', 1)
->first()
->toArray();
if (!$knowledge) abort(500, __('user.knowledge.fetch.knowledge_not_exist'));
if (!$knowledge) abort(500, __('Article does not exist'));
$user = User::find($request->session()->get('id'));
$userService = new UserService();
if ($userService->isAvailable($user)) {
$appleId = config('v2board.apple_id');
$appleIdPassword = config('v2board.apple_id_password');
} else {
$appleId = __('user.knowledge.fetch.apple_id_must_be_plan');
$appleIdPassword = __('user.knowledge.fetch.apple_id_must_be_plan');
$appleId = __('No active subscription. Unable to use our provided Apple ID');
$appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
$this->formatAccessData($knowledge['body']);
}
$subscribeUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$subscribeUrl = config('v2board.app_url', env('APP_URL'));
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
if ($subscribeUrls) {
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
}
$subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
$knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
$knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
$knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $knowledge['body']);
$knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']);
$knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']);
$knowledge['body'] = str_replace(
@ -63,7 +66,7 @@ class KnowledgeController extends Controller
function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $substr;}
$accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
if ($accessData) {
$body = str_replace($accessData, '<div class="v2board-no-access">'. __('user.knowledge.formatAccessData.no_access') .'</div>', $body);
$body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body);
}
}
}

View File

@ -52,12 +52,12 @@ class OrderController extends Controller
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, __('user.order.details.order_not_exist'));
abort(500, __('Order does not exist or has been paid'));
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) {
abort(500, __('user.order.details.plan_not_exist'));
abort(500, __('Subscription plan does not exist'));
}
return response([
'data' => $order
@ -68,38 +68,38 @@ class OrderController extends Controller
{
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) {
abort(500, __('user.order.save.exist_open_order'));
abort(500, __('You have an unpaid or pending order, please try again later or cancel it'));
}
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, __('user.order.save.plan_not_exist'));
abort(500, __('Subscription plan does not exist'));
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
if ($request->input('cycle') !== 'reset_price') {
abort(500, __('user.order.save.plan_stop_sell'));
abort(500, __('This subscription has been sold out, please choose another subscription'));
}
}
if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') {
abort(500, __('user.order.save.plan_stop_renew'));
abort(500, __('This subscription cannot be renewed, please change to another subscription'));
}
if ($plan[$request->input('cycle')] === NULL) {
abort(500, __('user.order.save.plan_stop'));
abort(500, __('This payment cycle cannot be purchased, please choose another cycle'));
}
if ($request->input('cycle') === 'reset_price') {
if ($user->expired_at <= time() || !$user->plan_id) {
abort(500, __('user.order.save.plan_exist_not_buy_package'));
abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
}
}
if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
abort(500, __('user.order.save.plan_expired'));
abort(500, __('This subscription has expired, please change to another subscription'));
}
DB::beginTransaction();
@ -108,14 +108,14 @@ class OrderController extends Controller
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->trade_no = Helper::generateOrderNo();
$order->total_amount = $plan[$request->input('cycle')];
if ($request->input('coupon_code')) {
$couponService = new CouponService($request->input('coupon_code'));
if (!$couponService->use($order)) {
DB::rollBack();
abort(500, __('user.order.save.coupon_use_failed'));
abort(500, __('Coupon failed'));
}
$order->coupon_id = $couponService->getId();
}
@ -130,14 +130,14 @@ class OrderController extends Controller
if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
DB::rollBack();
abort(500, __('user.order.save.insufficient_balance'));
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $order->total_amount;
$order->total_amount = 0;
} else {
if (!$userService->addBalance($order->user_id, - $user->balance)) {
DB::rollBack();
abort(500, __('user.order.save.insufficient_balance'));
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance;
@ -146,7 +146,7 @@ class OrderController extends Controller
if (!$order->save()) {
DB::rollback();
abort(500, __('user.order.save.order_create_failed'));
abort(500, __('Failed to create order'));
}
DB::commit();
@ -165,20 +165,19 @@ class OrderController extends Controller
->where('status', 0)
->first();
if (!$order) {
abort(500, __('user.order.checkout.order_not_exist_or_paid'));
abort(500, __('Order does not exist or has been paid'));
}
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
$order->save();
$orderService = new OrderService($order);
if (!$orderService->paid($order->trade_no)) abort(500, '');
return response([
'type' => -1,
'data' => true
]);
}
$payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('user.order.checkout.pay_method_not_use'));
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id);
$result = $paymentService->pay([
'trade_no' => $tradeNo,
@ -200,7 +199,7 @@ class OrderController extends Controller
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, __('user.order.check.order_not_exist'));
abort(500, __('Order does not exist'));
}
return response([
'data' => $order->status
@ -224,20 +223,20 @@ class OrderController extends Controller
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, __('user.order.cancel.params_wrong'));
abort(500, __('Invalid parameter'));
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, __('user.order.cancel.order_not_exist'));
abort(500, __('Order does not exist'));
}
if ($order->status !== 0) {
abort(500, __('user.order.cancel.only_cancel_pending_order'));
abort(500, __('You can only cancel pending orders'));
}
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, __('user.order.cancel.cancel_failed'));
abort(500, __('Cancel failed'));
}
return response([
'data' => true

View File

@ -14,7 +14,7 @@ class PlanController extends Controller
$plan = Plan::where('id', $request->input('id'))
->first();
if (!$plan) {
abort(500, __('user.plan.fetch.plan_not_exist'));
abort(500, __('Subscription plan does not exist'));
}
return response([
'data' => $plan

View File

@ -8,7 +8,7 @@ use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\ServerLog;
use App\Models\User;
@ -36,16 +36,16 @@ class ServerController extends Controller
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$serverLogModel = ServerLog::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
->orderBy('log_at', 'DESC');
switch ($type) {
case 0:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
$serverLogModel->where('log_at', '>=', strtotime(date('Y-m-d')));
break;
case 1:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
$serverLogModel->where('log_at', '>=', strtotime(date('Y-m-d')) - 604800);
break;
case 2:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
$serverLogModel->where('log_at', '>=', strtotime(date('Y-m-1')));
}
$total = $serverLogModel->count();
$res = $serverLogModel->forPage($current, $pageSize)

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller
{
@ -17,4 +19,9 @@ class TelegramController extends Controller
]
]);
}
public function unbind(Request $request)
{
$user = User::where('user_id', $request->session()->get('id'))->first();
}
}

View File

@ -23,7 +23,7 @@ class TicketController extends Controller
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, __('user.ticket.fetch.ticket_not_exist'));
abort(500, __('Ticket does not exist'));
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
@ -56,7 +56,7 @@ class TicketController extends Controller
{
DB::beginTransaction();
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
abort(500, __('user.ticket.save.exist_other_open_ticket'));
abort(500, __('There are other unresolved tickets'));
}
$ticket = Ticket::create(array_merge($request->only([
'subject',
@ -67,7 +67,7 @@ class TicketController extends Controller
]));
if (!$ticket) {
DB::rollback();
abort(500, __('user.ticket.save.ticket_create_failed'));
abort(500, __('Failed to open ticket'));
}
$ticketMessage = TicketMessage::create([
'user_id' => $request->session()->get('id'),
@ -76,7 +76,7 @@ class TicketController extends Controller
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, __('user.ticket.save.ticket_create_failed'));
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);
@ -88,22 +88,22 @@ class TicketController extends Controller
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, __('user.ticket.reply.params_wrong'));
abort(500, __('Invalid parameter'));
}
if (empty($request->input('message'))) {
abort(500, __('user.ticket.reply.message_not_empty'));
abort(500, __('Message cannot be empty'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, __('user.ticket.reply.ticket_not_exist'));
abort(500, __('Ticket does not exist'));
}
if ($ticket->status) {
abort(500, __('user.ticket.reply.ticket_close_not_reply'));
abort(500, __('The ticket is closed and cannot be replied'));
}
if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) {
abort(500, __('user.ticket.reply.wait_reply'));
abort(500, __('Please wait for the technical enginneer to reply'));
}
DB::beginTransaction();
$ticketMessage = TicketMessage::create([
@ -114,7 +114,7 @@ class TicketController extends Controller
$ticket->last_reply_user_id = $request->session()->get('id');
if (!$ticketMessage || !$ticket->save()) {
DB::rollback();
abort(500, __('user.ticket.reply.ticket_reply_failed'));
abort(500, __('Ticket reply failed'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);
@ -127,17 +127,17 @@ class TicketController extends Controller
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, __('user.ticket.close.params_wrong'));
abort(500, __('Invalid parameter'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, __('user.ticket.close.ticket_not_exist'));
abort(500, __('Ticket does not exist'));
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, __('user.ticket.close.close_failed'));
abort(500, __('Close failed'));
}
return response([
'data' => true
@ -163,15 +163,15 @@ class TicketController extends Controller
Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT
)
)) {
abort(500, __('user.ticket.withdraw.not_support_withdraw_method'));
abort(500, __('Unsupported withdrawal method'));
}
$user = User::find($request->session()->get('id'));
$limit = config('v2board.commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) {
abort(500, __('user.ticket.withdraw.system_require_withdraw_limit', ['limit' => $limit]));
abort(500, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit]));
}
DB::beginTransaction();
$subject = __('user.ticket.withdraw.ticket_subject');
$subject = __('[Commission Withdrawal Request] This ticket is opened by the system');
$ticket = Ticket::create([
'subject' => $subject,
'level' => 2,
@ -180,12 +180,12 @@ class TicketController extends Controller
]);
if (!$ticket) {
DB::rollback();
abort(500, __('user.ticket.withdraw.ticket_create_failed'));
abort(500, __('Failed to open ticket'));
}
$message = __('user.ticket.withdraw.ticket_message', [
'method' => $request->input('withdraw_method'),
'account' => $request->input('withdraw_account')
]);
$message = sprintf("%s\r\n%s",
__('Withdrawal method') . "" . $request->input('withdraw_method'),
__('Withdrawal account') . "" . $request->input('withdraw_account')
);
$ticketMessage = TicketMessage::create([
'user_id' => $request->session()->get('id'),
'ticket_id' => $ticket->id,
@ -193,7 +193,7 @@ class TicketController extends Controller
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, __('user.ticket.withdraw.ticket_create_failed'));
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);

View File

@ -6,14 +6,16 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerV2ray;
use App\Models\Ticket;
use App\Utils\Helper;
use App\Models\Order;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
@ -29,19 +31,21 @@ class UserController extends Controller
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('user.user.changePassword.user_not_exist'));
abort(500, __('The user does not exist'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$request->input('old_password'),
$user->password)
) {
abort(500, __('user.user.changePassword.old_password_wrong'));
abort(500, __('The old password is wrong'));
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
abort(500, __('user.user.changePassword.save_failed'));
abort(500, __('Save failed'));
}
$request->session()->flush();
return response([
@ -70,7 +74,7 @@ class UserController extends Controller
])
->first();
if (!$user) {
abort(500, __('user.user.info.user_not_exist'));
abort(500, __('The user does not exist'));
}
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
return response([
@ -110,15 +114,15 @@ class UserController extends Controller
])
->first();
if (!$user) {
abort(500, __('user.user.getSubscribe.user_not_exist'));
abort(500, __('The user does not exist'));
}
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
if (!$user['plan']) {
abort(500, __('user.user.getSubscribe.plan_not_exist'));
abort(500, __('Subscription plan does not exist'));
}
}
$user['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$user['subscribe_url'] = Helper::getSubscribeHost() . "/api/v1/client/subscribe?token={$user['token']}";
$user['reset_day'] = $this->getResetDay($user);
return response([
'data' => $user
@ -129,12 +133,12 @@ class UserController extends Controller
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('user.user.resetSecurity.user_not_exist'));
abort(500, __('The user does not exist'));
}
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if (!$user->save()) {
abort(500, __('user.user.resetSecurity.reset_failed'));
abort(500, __('Reset failed'));
}
return response([
'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
@ -150,12 +154,12 @@ class UserController extends Controller
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('user.user.update.user_not_exist'));
abort(500, __('The user does not exist'));
}
try {
$user->update($updateData);
} catch (\Exception $e) {
abort(500, __('user.user.update.save_failed'));
abort(500, __('Save failed'));
}
return response([
@ -167,15 +171,15 @@ class UserController extends Controller
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('user.user.transfer.user_not_exist'));
abort(500, __('The user does not exist'));
}
if ($request->input('transfer_amount') > $user->commission_balance) {
abort(500, __('user.user.transfer.insufficient_commission_balance'));
abort(500, __('Insufficient commission balance'));
}
$user->commission_balance = $user->commission_balance - $request->input('transfer_amount');
$user->balance = $user->balance + $request->input('transfer_amount');
if (!$user->save()) {
abort(500, __('user.user.transfer.transfer_failed'));
abort(500, __('Transfer failed'));
}
return response([
'data' => true
@ -204,4 +208,26 @@ class UserController extends Controller
}
return null;
}
public function getQuickLoginUrl(Request $request)
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('The user does not exist'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
}

View File

@ -35,6 +35,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CORS::class,
],
'api' => [

View File

@ -15,22 +15,17 @@ class User
*/
public function handle($request, Closure $next)
{
if ($request->input('auth_data')) {
$authData = explode(':', base64_decode($request->input('auth_data')));
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if ($authorization) {
$authData = explode(':', base64_decode($authorization));
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->first();
if ($user) {
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
}
if (!$user) abort(403, '鉴权失败,请重新登入');
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
}
// if ($request->input('lang')) {
// $request->session()->put('lang', $request->input('lang'));
// }
// if ($request->session()->get('lang')) {
// App::setLocale($request->session()->get('lang'));
// }
if (!$request->session()->get('id')) {
abort(403, '未登录或登陆已过期');
}

View File

@ -25,13 +25,17 @@ class ConfigSave extends FormRequest
'commission_withdraw_limit' => 'nullable|numeric',
'commission_withdraw_method' => 'nullable|array',
'withdraw_close_enable' => 'in:0,1',
'commission_distribution_enable' => 'in:0,1',
'commission_distribution_l1' => 'nullable|numeric',
'commission_distribution_l2' => 'nullable|numeric',
'commission_distribution_l3' => 'nullable|numeric',
// site
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_description' => '',
'app_url' => 'nullable|url',
'subscribe_url' => 'nullable|url',
'subscribe_url' => 'nullable',
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
@ -44,9 +48,11 @@ class ConfigSave extends FormRequest
'tos_url' => 'nullable|url',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1',
'renew_reset_traffic_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1,2',
'surplus_enable' => 'in:0,1',
'new_order_event_id' => 'in:0,1',
'renew_order_event_id' => 'in:0,1',
'change_order_event_id' => 'in:0,1',
// server
'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
@ -83,16 +89,14 @@ class ConfigSave extends FormRequest
'epay_pid' => '',
'epay_key' => '',
// frontend
'frontend_theme' => '',
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black',
'frontend_theme_color' => 'in:default,darkblue,black,green',
'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '',
'frontend_customer_service_method' => '',
'frontend_customer_service_id' => '',
// tutorial
'apple_id' => 'nullable|email',
'apple_id_password' => '',
// email
'email_template' => '',
'email_host' => '',

View File

@ -21,6 +21,7 @@ class CouponGenerate extends FormRequest
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer',
'limit_use_with_user' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array',
'code' => ''
];
@ -40,7 +41,8 @@ class CouponGenerate extends FormRequest
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误',
'limit_use.integer' => '最大使用次数格式有误',
'limit_use_with_user.integer' => '限制用户使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误'
];
}

View File

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

View File

@ -25,7 +25,8 @@ class PlanSave extends FormRequest
'two_year_price' => 'nullable|integer',
'three_year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer'
'reset_price' => 'nullable|integer',
'reset_traffic_method' => 'nullable|integer|in:0,1,2'
];
}
@ -44,7 +45,9 @@ class PlanSave extends FormRequest
'two_year_price.integer' => '两年付金额格式有误',
'three_year_price.integer' => '三年付金额格式有误',
'onetime_price.integer' => '一次性金额有误',
'reset_price.integer' => '流量重置包金额有误'
'reset_price.integer' => '流量重置包金额有误',
'reset_traffic_method.integer' => '流量重置方式格式有误',
'reset_traffic_method.in' => '流量重置方式格式有误'
];
}
}

View File

@ -25,11 +25,11 @@ class ServerV2raySave extends FormRequest
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'alter_id' => 'required|integer',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
'networkSettings' => '',
'ruleSettings' => '',
'tlsSettings' => '',
'dnsSettings' => ''
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
'networkSettings' => 'nullable|array',
'ruleSettings' => 'nullable|array',
'tlsSettings' => 'nullable|array',
'dnsSettings' => 'nullable|array'
];
}
@ -48,7 +48,11 @@ class ServerV2raySave extends FormRequest
'rate.required' => '倍率不能为空',
'rate.numeric' => '倍率格式不正确',
'network.required' => '传输协议不能为空',
'network.in' => '传输协议格式不正确'
'network.in' => '传输协议格式不正确',
'networkSettings.array' => '传输协议配置有误',
'ruleSettings.array' => '规则配置有误',
'tlsSettings.array' => 'tls配置有误',
'dnsSettings.array' => 'dns配置有误'
];
}
}

View File

@ -14,7 +14,7 @@ class UserFetch extends FormRequest
public function rules()
{
return [
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned',
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => 'required'
];

View File

@ -27,6 +27,7 @@ class UserUpdate extends FormRequest
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_type' => 'integer',
'commission_balance' => 'integer',
'remarks' => 'nullable'
];

View File

@ -23,11 +23,11 @@ class AuthForget extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数',
'email_code.required' => '邮箱验证码不能为空'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits'),
'email_code.required' => __('Email verification code cannot be empty')
];
}
}

View File

@ -22,10 +22,10 @@ class AuthLogin extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits')
];
}
}

View File

@ -22,10 +22,10 @@ class AuthRegister extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits')
];
}
}

View File

@ -21,8 +21,8 @@ class CommSendEmailVerify extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect')
];
}
}

View File

@ -22,9 +22,9 @@ class OrderSave extends FormRequest
public function messages()
{
return [
'plan_id.required' => '套餐ID不能为空',
'cycle.required' => '套餐周期不能为空',
'cycle.in' => '套餐周期有误'
'plan_id.required' => __('Plan ID cannot be empty'),
'cycle.required' => __('Plan cycle cannot be empty'),
'cycle.in' => __('Wrong plan cycle')
];
}
}

View File

@ -23,10 +23,10 @@ class TicketSave extends FormRequest
public function messages()
{
return [
'subject.required' => '工单主题不能为空',
'level.required' => '工单级别不能为空',
'level.in' => '工单级别格式不正确',
'message.required' => '消息不能为空'
'subject.required' => __('Ticket subject cannot be empty'),
'level.required' => __('Ticket level cannot be empty'),
'level.in' => __('Incorrect ticket level format'),
'message.required' => __('Message cannot be empty')
];
}
}

View File

@ -22,9 +22,8 @@ class TicketWithdraw extends FormRequest
public function messages()
{
return [
'withdraw_method.required' => '提现方式不能为空',
'withdraw_method.in' => '提现方式不支持',
'withdraw_account.required' => '提现账号不能为空'
'withdraw_method.required' => __('The withdrawal method cannot be empty'),
'withdraw_account.required' => __('The withdrawal account cannot be empty')
];
}
}

View File

@ -22,9 +22,9 @@ class UserChangePassword extends FormRequest
public function messages()
{
return [
'old_password.required' => '旧密码不能为空',
'new_password.required' => '新密码不能为空',
'new_password.min' => '密码必须大于8位数'
'old_password.required' => __('Old password cannot be empty'),
'new_password.required' => __('New password cannot be empty'),
'new_password.min' => __('Password must be greater than 8 digits')
];
}
}

View File

@ -21,9 +21,9 @@ class UserTransfer extends FormRequest
public function messages()
{
return [
'transfer_amount.required' => '划转金额不能为空',
'transfer_amount.integer' => __('user.user.transfer.params_wrong'),
'transfer_amount.min' => __('user.user.transfer.params_wrong')
'transfer_amount.required' => __('The transfer amount cannot be empty'),
'transfer_amount.integer' => __('The transfer amount parameter is wrong'),
'transfer_amount.min' => __('The transfer amount parameter is wrong')
];
}
}

View File

@ -22,8 +22,8 @@ class UserUpdate extends FormRequest
public function messages()
{
return [
'show.in' => '过期提醒格式不正确',
'renew.in' => '流量提醒格式不正确'
'show.in' => __('Incorrect format of expiration reminder'),
'renew.in' => __('Incorrect traffic alert format')
];
}
}

View File

@ -15,6 +15,7 @@ class AdminRoute
$router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
$router->post('/config/save', 'Admin\\ConfigController@save');
$router->get ('/config/getEmailTemplate', 'Admin\\ConfigController@getEmailTemplate');
$router->get ('/config/getThemeTemplate', 'Admin\\ConfigController@getThemeTemplate');
$router->post('/config/setTelegramWebhook', 'Admin\\ConfigController@setTelegramWebhook');
// Plan
$router->get ('/plan/fetch', 'Admin\\PlanController@fetch');
@ -62,9 +63,10 @@ class AdminRoute
});
// 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');
$router->post('/order/paid', 'Admin\\OrderController@paid');
$router->post('/order/cancel', 'Admin\\OrderController@cancel');
// User
$router->get ('/user/fetch', 'Admin\\UserController@fetch');
$router->post('/user/update', 'Admin\\UserController@update');

View File

@ -20,6 +20,7 @@ class UserRoute
$router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
$router->get ('/getStat', 'User\\UserController@getStat');
$router->post('/transfer', 'User\\UserController@transfer');
$router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
// Order
$router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout');

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use App\Services\OrderService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class OrderHandleJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($tradeNo)
{
$this->onQueue('order_handle');
$this->order = Order::where('trade_no', $tradeNo)
->lockForUpdate()
->first();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (!$this->order) return;
$orderService = new OrderService($this->order);
switch ($this->order->status) {
// cancel
case 0:
if ($this->order->created_at <= (time() - 1800)) {
$orderService->cancel();
}
break;
case 1:
$orderService->open();
break;
}
}
}

58
app/Jobs/ServerLogJob.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Jobs;
use App\Services\ServerService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerLogJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $userId;
protected $server;
protected $protocol;
public $tries = 3;
public $timeout = 3;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $userId, $server, $protocol)
{
$this->onQueue('server_log');
$this->u = $u;
$this->d = $d;
$this->userId = $userId;
$this->server = $server;
$this->protocol = $protocol;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$serverService = new ServerService();
if (!$serverService->log(
$this->userId,
$this->server->id,
$this->u,
$this->d,
$this->server->rate,
$this->protocol
)) {
throw new \Exception('日志记录失败');
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Jobs;
use App\Models\User;
use App\Services\MailService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TrafficFetchJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $userId;
protected $server;
protected $protocol;
public $tries = 3;
public $timeout = 3;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $userId, $server, $protocol)
{
$this->onQueue('traffic_fetch');
$this->u = $u;
$this->d = $d;
$this->userId = $userId;
$this->server = $server;
$this->protocol = $protocol;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$user = User::lockForUpdate()->find($this->userId);
if (!$user) return;
$user->t = time();
$user->u = $user->u + $this->u;
$user->d = $user->d + $this->d;
if (!$user->save()) throw new \Exception('流量更新失败');
$mailService = new MailService();
$mailService->remindTraffic($user);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class CommissionLog extends Model
{
protected $table = 'v2_commission_log';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,9 @@ class Coupon extends Model
protected $table = 'v2_coupon';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'limit_plan_ids' => 'array'
];
}

View File

@ -8,4 +8,8 @@ class InviteCode extends Model
{
protected $table = 'v2_invite_code';
protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,8 @@ class Knowledge extends Model
protected $table = 'v2_knowledge';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,8 @@ class MailLog extends Model
protected $table = 'v2_mail_log';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,8 @@ class Notice extends Model
protected $table = 'v2_notice';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,9 @@ class Order extends Model
protected $table = 'v2_order';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'surplus_order_ids' => 'array'
];
}

View File

@ -9,4 +9,9 @@ class Payment extends Model
protected $table = 'v2_payment';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'config' => 'array'
];
}

View File

@ -9,4 +9,8 @@ class Plan extends Model
protected $table = 'v2_plan';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

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

View File

@ -8,4 +8,8 @@ class ServerGroup extends Model
{
protected $table = 'v2_server_group';
protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,8 @@ class ServerLog extends Model
{
protected $table = 'v2_server_log';
protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,10 @@ class ServerShadowsocks extends Model
protected $table = 'v2_server_shadowsocks';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tags' => 'array'
];
}

View File

@ -9,4 +9,8 @@ class ServerStat extends Model
protected $table = 'v2_server_stat';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,10 @@ class ServerTrojan extends Model
protected $table = 'v2_server_trojan';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tags' => 'array'
];
}

22
app/Models/ServerV2ray.php Executable file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerV2ray extends Model
{
protected $table = 'v2_server_v2ray';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tlsSettings' => 'array',
'networkSettings' => 'array',
'dnsSettings' => 'array',
'ruleSettings' => 'array',
'tags' => 'array'
];
}

View File

@ -9,4 +9,8 @@ class StatOrder extends Model
protected $table = 'v2_stat_order';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,8 @@ class StatServer extends Model
protected $table = 'v2_stat_server';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

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