563 Commits
1.0.1 ... 1.2.5

Author SHA1 Message Date
b020f2c196 Merge pull request #173 from v2board/dev
1.2.5
2020-04-07 01:36:38 +08:00
e1b16ef7e6 1.2.5 2020-04-07 01:35:03 +08:00
887aad7737 Merge pull request #172 from v2board/dev
#170
2020-04-07 01:20:20 +08:00
a1f2290ff2 #170 2020-04-07 01:19:52 +08:00
d23daf4a68 Merge pull request #171 from v2board/dev
1.2.5
2020-04-07 01:14:10 +08:00
c4868a9c48 send mail delay && fetch admin 2020-04-07 01:10:14 +08:00
6050e6e4a9 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-04-07 00:41:19 +08:00
7c69e19304 #170 2020-04-07 00:40:52 +08:00
fd42a855cf Merge pull request #166 from betaxab/patch-1
rules: fixes surge ws host headers & allow insecure tls
2020-04-07 00:36:13 +08:00
e73cbe9597 fix dns not active 2020-04-06 18:05:58 +08:00
276b040581 rules: fixes surge ws host headers & allow insecure tls
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-04-06 12:46:16 +08:00
495a5f89c5 Merge pull request #165 from v2board/dev
1.2.4
2020-04-05 15:32:38 +08:00
7c3309164b fix remindexpire 2020-04-05 15:30:58 +08:00
674b31675a fix remindtraffic 2020-04-05 15:29:45 +08:00
b2c33cd31b Merge pull request #149 from SquidFerry/hotfix/password
fix:change password
2020-04-05 15:18:28 +08:00
4240e8355a fix stripe not active 2020-04-05 15:17:34 +08:00
7770bf6b99 Merge pull request #162 from betaxab/patch-1
rules: fix HTTPS variable not set for some servers
2020-04-05 15:12:35 +08:00
55118d7706 rules: fix HTTPS variable not set for some servers
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-04-05 01:22:56 +08:00
2b34f5ec82 1.2.4 2020-04-03 13:28:44 +08:00
7dff7ddfc7 update 2020-04-02 22:32:35 +08:00
ed3e468a0a 1.2.4 2020-04-02 22:00:24 +08:00
901d89b5d7 1.2.4 2020-04-02 21:59:57 +08:00
402b9e0c3f 1.2.4 2020-04-02 17:18:47 +08:00
ae543d1c2c 1.2.4 2020-04-02 15:01:33 +08:00
5d9b98f383 Merge pull request #154 from betaxab/patch-1
修复换行
2020-04-02 15:01:01 +08:00
a2183b7143 rules: fix surge config text wrapping
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-04-02 12:42:07 +08:00
a2278487ee 1.2.4 2020-04-01 02:15:07 +08:00
075f7b39a8 1.2.4 2020-04-01 00:51:45 +08:00
k
3db93b4739 fix:change password 2020-03-31 10:47:14 +08:00
1fc9f94dad 1.2.4 2020-03-31 01:46:33 +08:00
bdfa1ff0d5 1.2.4 2020-03-31 01:27:04 +08:00
98e4aca61f 1.2.4 2020-03-31 01:18:46 +08:00
139aeb3f48 update 2020-03-31 00:36:01 +08:00
dfdf995ddb update 2020-03-31 00:34:25 +08:00
4b4d777a4e update 2020-03-31 00:23:09 +08:00
42607a789d update 2020-03-31 00:22:49 +08:00
79f53f2836 update 2020-03-31 00:22:07 +08:00
d1bf743316 update 2020-03-30 23:47:54 +08:00
39fadd8a63 update 2020-03-30 23:40:55 +08:00
4831c9f194 Merge pull request #143 from betaxab/patch-1
rules: add surge/surfboard support [1/2]
2020-03-30 22:44:29 +08:00
ee80e0f2ff update 2020-03-30 16:11:08 +08:00
1be7151b6c update 2020-03-30 00:28:10 +08:00
bb1ad02cf8 update 2020-03-30 00:27:52 +08:00
64f379d99d update 2020-03-30 00:10:15 +08:00
c271647ecc update 2020-03-29 23:57:34 +08:00
e8b6f1b481 update 2020-03-29 23:49:06 +08:00
1c6907fe33 rules: add surge/surfboard support [1/2]
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-03-28 11:27:16 +08:00
68f7cdeed8 Merge pull request #130 from v2board/dev
Dev
2020-03-21 19:58:19 +08:00
40e5ee62b1 Merge pull request #129 from DesperadoJ/dev
Minor bug fixes
2020-03-21 19:57:54 +08:00
9f9bb14e9d Fix email template typo 2020-03-21 19:50:49 +08:00
bcc80581d3 Fix Quantumult sub 2020-03-21 19:50:29 +08:00
288f4aba18 Merge pull request #127 from v2board/dev
fix quantumult sub
2020-03-21 16:02:07 +08:00
cc673cdbd1 fix quantumult sub 2020-03-21 16:00:15 +08:00
0781a0740b Merge pull request #126 from v2board/dev
1.2.3
2020-03-21 15:34:35 +08:00
8d56db2bf6 update user ver 2020-03-21 15:33:36 +08:00
56fd3f5b99 Merge pull request #125 from v2board/dev
1.2.3
2020-03-21 15:09:16 +08:00
5a84e412c4 fix ticket 2020-03-21 15:04:38 +08:00
3216a90235 fix ticket 2020-03-21 00:27:58 +08:00
59fa3a3316 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-03-20 23:15:01 +08:00
06465b3eb3 update email template 2020-03-20 23:13:53 +08:00
cc12605984 Merge pull request #123 from ColetteContreras/dev
Use ServerService
2020-03-20 17:51:15 +08:00
8fa9d60c4d update email template 2020-03-20 13:57:09 +08:00
1c3eff241a update email template 2020-03-20 13:51:29 +08:00
79aa942d5b update email template 2020-03-19 15:54:08 +08:00
f4688b6d50 update email template 2020-03-19 15:52:39 +08:00
d9c0d18689 update email template 2020-03-19 15:41:21 +08:00
7e88a39249 Use ServerService 2020-03-19 15:25:38 +08:00
064834001d update 2020-03-18 18:16:33 +08:00
49d5b407bc update 2020-03-18 18:12:19 +08:00
1291bf47be update 2020-03-18 17:33:58 +08:00
e15d5961f0 update 2020-03-18 16:01:10 +08:00
04c6b865b4 update sql 2020-03-17 22:16:12 +08:00
5b33bf7a0b update 2020-03-17 22:09:06 +08:00
2235a5e7c5 fix 2020-03-17 21:21:35 +08:00
6fba0b6dab add balance payment 2020-03-17 20:16:55 +08:00
3075f0d411 add balance payment 2020-03-17 20:04:00 +08:00
111d2720bd add balance payment 2020-03-17 20:00:46 +08:00
03e0b5d087 fix client onetime 2020-03-17 19:00:33 +08:00
96f562a9e7 fix elq update try catch 2020-03-17 14:28:47 +08:00
7bb4852cca update 2020-03-17 01:56:44 +08:00
8986ba1d42 update 2020-03-17 01:50:50 +08:00
f193f35642 update 2020-03-15 21:32:12 +08:00
2a92ee8b41 update 2020-03-15 21:28:57 +08:00
56cabbdc00 update 2020-03-15 21:26:56 +08:00
5f4d02dde3 fix 2020-03-15 20:30:00 +08:00
00c2dee361 opt 2020-03-15 20:21:31 +08:00
35917ad199 fix reset traffic 2020-03-15 20:12:52 +08:00
e4cb6458c0 update 2020-03-14 15:33:31 +08:00
93c1031078 update 2020-03-13 14:42:47 +08:00
26252aee02 update 2020-03-13 14:37:28 +08:00
8d10b52a35 update 2020-03-13 14:37:15 +08:00
6e7da97fcd update 2020-03-13 14:33:48 +08:00
13dbb143f8 update send email verify ttl 300 sec 2020-03-13 14:32:36 +08:00
01da63f82e pr #109 2020-03-13 13:47:20 +08:00
d2a0422f64 pr #109 2020-03-13 13:47:02 +08:00
c81cb8acca Is the commission only paid at the first time 2020-03-13 01:52:34 +08:00
6bf0d2d94e opt 1.2.3 2020-03-11 19:25:07 +08:00
260c1d7361 opt 1.2.3 2020-03-10 21:31:53 +08:00
6a6de2dc22 opt 1.2.3 2020-03-10 21:31:18 +08:00
ba9ec7006b opt 1.2.3 2020-03-10 21:13:41 +08:00
f17b5d04a8 opt 1.2.3 2020-03-10 21:10:44 +08:00
cccd8f36ee opt 1.2.3 2020-03-10 14:03:48 +08:00
4d7ebe4aea opt 1.2.3 2020-03-10 13:56:24 +08:00
8ac8427c2f opt 1.2.3 2020-03-10 13:47:50 +08:00
97056be8c3 opt 1.2.3 2020-03-10 13:45:48 +08:00
68d44e7657 opt 1.2.3 2020-03-10 13:39:05 +08:00
b07511f01b opt 1.2.3 2020-03-10 13:30:30 +08:00
a13809ac02 opt 1.2.3 2020-03-10 13:27:04 +08:00
5b317478c6 opt 1.2.3 2020-03-10 13:11:31 +08:00
57fd282024 fix client not use token login 2020-03-08 16:52:09 +08:00
27ccf9869d Merge pull request #95 from v2board/dev
Dev
2020-03-07 11:50:15 +08:00
5e5e3fbb08 1.2.2 2020-03-07 11:48:24 +08:00
f5132abad1 1.2.2 2020-03-07 11:37:39 +08:00
4f709bf1f6 1.2.2 2020-03-06 21:58:50 +08:00
e62ef5cb0a 1.2.2 2020-03-06 16:39:37 +08:00
e5c207ccff 1.2.2 2020-03-06 16:23:49 +08:00
6b7cea671d 1.2.2 2020-03-06 16:21:08 +08:00
dbf73f3a38 Merge pull request #81 from betaxab/patch-1
clash: Update default clash rules from latest SS-Rule-Snippet
2020-03-06 13:52:59 +08:00
a3b7130857 Merge pull request #80 from v2board/dev
Dev
2020-03-06 01:22:12 +08:00
c0bae87556 Merge branch 'master' of https://github.com/v2board/v2board into dev 2020-03-06 01:20:19 +08:00
f18715ed09 update 2020-03-06 01:19:51 +08:00
7f3e0d9fb9 clash: Update default clash rules from latest SS-Rule-Snippet
Complete the telegram IP address
2020-03-06 01:19:41 +08:00
e5c8e18206 Merge pull request #79 from v2board/dev
Dev
2020-03-06 01:10:17 +08:00
796a5ba55e Merge pull request #75 from daydaypy/dev
Stripe invoice description
2020-03-06 01:07:06 +08:00
9bf6f64f71 1.2.1 2020-03-06 01:06:04 +08:00
Aaa
66957eff6d Stripe invoice description 2020-03-05 23:41:44 +08:00
4e03662e8f fix pay form verify 2020-03-05 23:05:36 +08:00
fc48f5553f 1.2.1 2020-03-05 22:11:12 +08:00
c4ddde1c94 1.2.1 2020-03-05 22:10:35 +08:00
92f44d7a2e 1.2.1 2020-03-05 22:04:18 +08:00
3bfd64d8ca 1.2.1 2020-03-05 16:55:01 +08:00
cb2efac160 fix 2020-03-05 16:28:20 +08:00
c3898ec795 fix 2020-03-05 16:19:32 +08:00
6dc5dd0edb Merge pull request #73 from v2board/dev
Dev
2020-03-05 03:35:46 +08:00
e97f6c64a0 fix 2020-03-05 03:34:38 +08:00
89630d889d fix 2020-03-05 03:33:21 +08:00
3becb71a5a fix 2020-03-05 03:32:25 +08:00
d361ff80ed Merge pull request #72 from v2board/dev
Dev
2020-03-05 03:20:14 +08:00
3fe442313d fix 2020-03-05 03:19:46 +08:00
f76609a38d fix 2020-03-05 03:07:17 +08:00
51f4ad417e fix tags 2020-03-05 02:30:02 +08:00
516b2626ae Merge pull request #71 from v2board/dev
1.2
2020-03-05 02:16:29 +08:00
b9312f362c pre 1.2 2020-03-05 02:03:50 +08:00
102f18bef1 pre 1.2 2020-03-05 00:10:52 +08:00
1fc36d8e29 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-03-05 00:07:10 +08:00
a3837f83cb support stripe currency 2020-03-05 00:06:40 +08:00
47f27f4852 Merge pull request #66 from ColetteContreras/dev
Update poseidon
2020-03-04 22:14:09 +08:00
e73eaea66c pre 1.2 2020-03-04 21:52:31 +08:00
bc5ddbf40a update 2020-03-04 21:45:06 +08:00
a2c674f00c update 2020-03-04 19:34:47 +08:00
218338960b update 2020-03-04 19:33:33 +08:00
d9dcea4e4f update 2020-03-03 01:26:14 +08:00
fad12a62ce update 2020-03-03 01:21:11 +08:00
1ee104ec88 fix 2020-03-03 01:00:44 +08:00
19dcd0ffba fix 2020-03-03 00:56:31 +08:00
ab2e39f7dd fix 2020-03-02 23:36:19 +08:00
8f4ebe1764 fix 2020-03-02 23:21:27 +08:00
3763320261 fix 2020-03-02 20:47:52 +08:00
376c79aa91 fix 2020-03-02 20:32:15 +08:00
47686d50c5 fix 2020-03-02 20:29:17 +08:00
c8cb1f8e83 fix 2020-03-02 17:58:07 +08:00
620ed8e04c update 2020-03-02 15:40:19 +08:00
c28a6ec1d3 fix user plan not nullable 2020-03-02 15:08:37 +08:00
3c78bed800 fix user plan not nullable 2020-03-02 15:05:43 +08:00
72fb0d131d update 2020-03-02 00:36:15 +08:00
378c96d4c7 remove 2020-03-02 00:33:10 +08:00
ca2a744b10 opt config && add renew reset traffic 2020-03-02 00:27:06 +08:00
0f72e9a091 add renew reset traffic switch 2020-03-02 00:14:58 +08:00
bb49fb15d1 update reset traffic method 2020-03-02 00:04:07 +08:00
47d8dfd7c8 update onetime package 2020-03-01 23:29:49 +08:00
60b197410c update onetime package 2020-03-01 23:18:45 +08:00
e2b73094c0 fix rules message 2020-03-01 17:57:50 +08:00
7d8b8ad8ac update frontend 2020-03-01 16:25:15 +08:00
1992b0a9e9 Update poseidon 2020-02-29 17:00:28 +08:00
b4bede869d fix 2020-02-29 15:22:01 +08:00
a11e65f84a fix null is not post 2020-02-28 20:06:50 +08:00
e1a8ffe7c4 update admin frontend 2020-02-28 17:18:07 +08:00
2217286d03 fix mail send 2020-02-28 17:16:05 +08:00
dd924d95c6 update user manage 2020-02-28 16:38:44 +08:00
53710f2e01 update user manage 2020-02-28 16:35:40 +08:00
f03c53815c rewrite banned logic 2020-02-28 16:25:25 +08:00
6e98e42f51 rewrite banned logic 2020-02-28 16:21:16 +08:00
9932e34634 fix replace the negative subscription problem 2020-02-28 16:14:25 +08:00
6ba4702539 fix replace the negative subscription problem 2020-02-28 16:11:26 +08:00
b29ac8ac2c fix plan manage price not set 2020-02-28 15:30:06 +08:00
866fda84ed add onetime plan 2020-02-28 15:16:23 +08:00
95c7c48cc7 add onetime plan 2020-02-28 15:06:30 +08:00
010f0cbc03 add onetime plan 2020-02-28 15:04:13 +08:00
0d2b8bc976 add onetime plan 2020-02-28 14:53:33 +08:00
3b9e9cbe7f add onetime plan 2020-02-28 14:51:06 +08:00
a7b47d8f77 add onetime plan 2020-02-28 12:52:29 +08:00
dea26121fc add onetime plan 2020-02-28 01:22:16 +08:00
10e4de39de add onetime plan 2020-02-28 01:21:14 +08:00
e7fd81bf4c add onetime plan 2020-02-28 01:12:55 +08:00
0f9cb9696d add onetime plan 2020-02-28 01:03:05 +08:00
a7b3d6e778 add onetime plan 2020-02-27 19:39:26 +08:00
637bacd62f add onetime plan 2020-02-27 19:36:30 +08:00
2171ef59d7 fix tutorial 2020-02-25 09:56:06 +08:00
0a92308ad2 update plan 2020-02-24 02:04:42 +08:00
8ff53673d7 Merge pull request #55 from DesperadoJ/patch-1
Refine code
2020-02-24 01:46:16 +08:00
07772ceb66 fix f2f show qrcode 2020-02-23 22:45:06 +08:00
fc8333d757 Refine code
Remove duplicates
2020-02-23 22:15:31 +08:00
64dbd11e62 support clash for android subscribe 2020-02-23 20:02:34 +08:00
bfeab8eae2 fix server rules empty 2020-02-23 17:20:24 +08:00
5ee6fc2996 opt 2020-02-23 02:37:52 +08:00
018e4aa810 fix commission status update 2020-02-23 02:05:23 +08:00
17d6d04cc6 update frontend models && throttle 120:1 2020-02-22 20:23:45 +08:00
e174ef193b update tutorial 2020-02-22 14:56:13 +08:00
1887259554 reconsitution frontend && update tutorial 2020-02-22 14:39:33 +08:00
8b804913f5 update tutorial 2020-02-19 14:59:38 +08:00
a2ea88beb5 update tutorial 2020-02-19 14:33:35 +08:00
d969220654 update tutorial 2020-02-19 14:32:22 +08:00
5154993887 update tutorial 2020-02-19 14:31:02 +08:00
61b31c1925 update tutorial 2020-02-18 22:48:00 +08:00
a0454577cb update tutorial 2020-02-18 22:46:44 +08:00
f7fc6b9dfc update tutorial 2020-02-18 22:44:56 +08:00
1b7fc9529f update tutorial 2020-02-18 22:28:17 +08:00
a56faa7e8e update tutorial 2020-02-18 22:06:54 +08:00
0a2c626f89 update tutorial 2020-02-18 21:46:00 +08:00
19df032b57 fix email whitelist 2020-02-18 18:17:12 +08:00
de8f95ee3d fix ui 2020-02-18 17:57:25 +08:00
568648dc2f update change order process 2020-02-18 14:52:26 +08:00
9f5be4e83a update ui 2020-02-18 14:45:05 +08:00
14d0de18ec update change order process 2020-02-18 13:52:10 +08:00
64ae39aa2d update change order process 2020-02-18 12:42:05 +08:00
fb09baa3c1 update change order process 2020-02-18 03:28:25 +08:00
153bdcaad1 remove downgrade 2020-02-18 02:55:44 +08:00
f3ac8a37be update downgrade 2020-02-18 02:40:00 +08:00
9925ab6b47 update downgrade 2020-02-18 02:35:42 +08:00
84ff8d3ab3 update config 2020-02-18 02:33:57 +08:00
fcec3af75f Merge pull request #45 from v2board/dev
Dev
2020-02-17 23:13:56 +08:00
2a9a8805a8 update sql 2020-02-17 23:12:00 +08:00
72eafb9698 Merge pull request #44 from DesperadoJ/dev
Fix typo, update Clash config & v2ray server config
2020-02-17 23:10:16 +08:00
e837e774ef Update v2ray server config
Enable "sniffing".
Refine "routing".
2020-02-17 22:39:51 +08:00
00fcff5f0d Update Clash config
Add "skip-cert-verify: true" for self-signed certifcate
2020-02-17 22:17:16 +08:00
6b43597eaf Fix typo 2020-02-17 22:12:47 +08:00
c576299a60 Merge pull request #43 from v2board/dev
1.1.2
2020-02-17 22:06:10 +08:00
4926264160 update ui 2020-02-17 22:03:00 +08:00
0c1a53faab update ui 2020-02-17 21:34:06 +08:00
0f0b092f86 update ui 2020-02-17 21:29:53 +08:00
3beb938cbc update ui 2020-02-17 14:01:30 +08:00
c45578417f Merge pull request #42 from ColetteContreras/master
Update poseidon
2020-02-17 13:46:33 +08:00
088dc2cfa6 update ui 2020-02-17 13:41:19 +08:00
0b4c884824 update ui 2020-02-17 12:42:41 +08:00
7b9390a6cb update ui 2020-02-17 12:35:50 +08:00
2b69e83132 update order change process 2020-02-17 12:25:19 +08:00
56820346d0 Update poseidon 2020-02-17 11:30:58 +08:00
1ea307854f update theme 2020-02-17 03:55:38 +08:00
290ef77c03 update theme 2020-02-17 03:53:33 +08:00
172a639f59 update theme 2020-02-17 03:51:06 +08:00
bff92180b0 update theme 2020-02-17 03:37:04 +08:00
e094a2f4d1 update theme 2020-02-17 03:35:03 +08:00
b852180b4d update order change process 2020-02-17 01:43:18 +08:00
a0a5327f42 update order change process 2020-02-17 01:31:39 +08:00
874648a4c0 update order change process 2020-02-17 01:27:07 +08:00
15cd13c26c update order change process 2020-02-17 01:00:12 +08:00
76c50e01bf update order change process 2020-02-17 00:42:56 +08:00
9ae3994705 update order change process 2020-02-17 00:39:22 +08:00
cab7cc9d19 update order change process 2020-02-17 00:35:21 +08:00
dbac9d9dac Merge pull request #38 from v2board/dev
1.1.1
2020-02-16 18:39:47 +08:00
2e18b1b108 1.1.1 2020-02-16 18:39:10 +08:00
c1583a1014 Merge pull request #37 from v2board/dev
1.1.1
2020-02-16 17:54:29 +08:00
01c5901c71 1.1.1 2020-02-16 17:52:14 +08:00
0446e7d99f fix server rules maybe not defined 2020-02-16 16:51:25 +08:00
557f6bc0c2 fix admin login 2020-02-16 14:05:38 +08:00
e49dc87fda fix admin login 2020-02-16 13:55:48 +08:00
edf88cbccf fix admin login 2020-02-16 13:52:15 +08:00
19940f599d fix admin login 2020-02-16 13:48:31 +08:00
ace06630d8 fix commission frontent 2020-02-16 00:28:39 +08:00
50582137e4 update frontend 2020-02-15 23:27:59 +08:00
ab8da6bb02 update user fetch 2020-02-15 23:16:44 +08:00
0a58930619 compatibility old client 2020-02-15 23:04:50 +08:00
9d9866689d update order filter 2020-02-15 22:58:31 +08:00
2d21fa3fb7 update user update 2020-02-15 22:40:41 +08:00
5cd020ea7a update mailsend,frontend 2020-02-15 20:32:38 +08:00
43e7bb6c9f update mailsend,frontend 2020-02-15 17:24:00 +08:00
2b8456fdf5 update discount 2020-02-15 00:55:04 +08:00
b34fa70165 update userinfo fetch 2020-02-14 22:11:13 +08:00
d845439f14 add user discount 2020-02-14 21:54:16 +08:00
d356722482 update frontend 2020-02-14 18:24:59 +08:00
72ac20d387 update frontend 2020-02-13 17:09:11 +08:00
878a7da62c update frontend 2020-02-13 17:02:37 +08:00
4fd0c9ce51 update frontend 2020-02-13 16:58:48 +08:00
18ed6947b3 update frontend 2020-02-13 16:54:15 +08:00
b8b1e1043b update frontend 2020-02-13 16:45:54 +08:00
705ceb80b2 update frontend 2020-02-13 16:24:12 +08:00
47be10f274 update frontend 2020-02-13 16:22:30 +08:00
540b331f16 fix forget 2020-02-13 13:44:58 +08:00
e9ba149c0f update sql 2020-02-13 13:43:55 +08:00
895e0f177b fix description not show 2020-02-12 23:36:40 +08:00
9dc64114f5 opt paytaro error report 2020-02-12 14:01:40 +08:00
168b0ad72e opt paytaro error report 2020-02-12 14:01:00 +08:00
c82acafdf8 update api 2020-02-11 21:06:15 +08:00
e14489cf66 update api 2020-02-11 21:04:47 +08:00
96a36b1afb update ui 2020-02-11 17:12:57 +08:00
df9d99c271 update ui 2020-02-11 17:09:27 +08:00
79bd46c0da update ui 2020-02-11 16:35:12 +08:00
e638660bf7 update sql 2020-02-11 16:23:38 +08:00
880b8e8ad9 update ui 2020-02-11 16:21:36 +08:00
ba40e3a1bf fix order renew 2020-02-11 15:45:30 +08:00
8fafdc9bb5 fix order renew 2020-02-11 14:03:23 +08:00
e07b97b14e fix order renew 2020-02-11 13:31:58 +08:00
c817710556 fix order renew 2020-02-11 13:11:15 +08:00
19fd19fbcf add server rules 2020-02-10 21:24:49 +08:00
26e63cbe1c add server rules 2020-02-10 18:54:17 +08:00
5bd524fbf1 add server rules 2020-02-10 18:48:06 +08:00
3a7a0c6e62 remove web tls config 2020-02-10 16:11:38 +08:00
23bfb20307 opt tls 2020-02-10 15:52:07 +08:00
7f9ad68e1b opt tls 2020-02-10 15:51:01 +08:00
cb8cdb2e0e fix user sorter 2020-02-09 18:53:45 +08:00
5db26d862b fix get email whitelist suffix 2020-02-09 18:24:24 +08:00
13bc683faf opt register auto login 2020-02-09 18:21:58 +08:00
e9229781bc add email whitelist config 2020-02-09 18:20:33 +08:00
6b9e424b74 add email whitelist config 2020-02-09 18:09:48 +08:00
078e1e7bd6 add email whitelist config 2020-02-09 18:04:40 +08:00
d417e70475 add email whitelist config 2020-02-09 18:02:13 +08:00
214fb2a2bf add email whitelist config 2020-02-09 18:01:06 +08:00
f61e37aeaf add email whitelist config 2020-02-09 17:17:21 +08:00
98e23be297 add email blacklist config 2020-02-09 01:29:26 +08:00
5bc4bff8f9 add sorter 2020-02-09 00:32:39 +08:00
ab97db552d optimization try out 2020-02-08 23:03:48 +08:00
2e2a0bf273 fix try out 2020-02-08 22:26:38 +08:00
1f471545d4 add safe mode 2020-02-08 16:38:03 +08:00
ffe05a5467 optimization install 2020-02-08 16:19:22 +08:00
ec73224671 optimization install 2020-02-08 16:16:12 +08:00
3f4e3d46fb optimization install 2020-02-08 16:09:33 +08:00
6920352c0e optimization install 2020-02-08 16:07:20 +08:00
e57a70970f optimization install 2020-02-08 15:49:59 +08:00
406f1ee80d optimization install 2020-02-08 15:48:25 +08:00
f7558fecc7 optimization install 2020-02-08 15:45:23 +08:00
43d98a485d optimization install 2020-02-08 15:44:30 +08:00
be2caad604 optimization install 2020-02-08 15:43:13 +08:00
984abba50d optimization install 2020-02-08 15:26:18 +08:00
62f7d5b3e8 fix forget 2020-02-07 19:34:24 +08:00
37844c1bb4 fix 2020-02-05 21:09:33 +08:00
87e0d34961 fix tls 2020-02-05 18:46:10 +08:00
ce6bd80702 fix 2020-02-04 00:33:02 +08:00
79bb987c37 fix 2020-02-03 23:02:08 +08:00
ec8357aa3e fix api 2020-02-03 02:47:29 +08:00
24f5324c2a fix client route 2020-02-02 22:06:51 +08:00
0299cd63ad fix client route 2020-02-02 22:02:55 +08:00
bb566f01d9 fix client route 2020-02-02 22:01:50 +08:00
c03bd0098d fix client route 2020-02-02 21:59:12 +08:00
073f7611e1 update 2020-02-02 21:51:49 +08:00
517f7d0122 update clash rule 2020-02-02 21:50:58 +08:00
32d6a983a3 update changepassword 2020-02-02 20:44:52 +08:00
a8db24492b update frontend 2020-02-01 21:54:08 +08:00
1b23998248 split router 2020-02-01 20:58:36 +08:00
d744fba683 split router 2020-02-01 20:57:15 +08:00
3f6799aa4b split router 2020-02-01 20:54:47 +08:00
1d704a4424 split router 2020-02-01 20:51:45 +08:00
ab5aeb545e support multi password hash verify 2020-02-01 00:54:37 +08:00
5e7e42e52c support multi password hash verify 2020-01-31 21:57:43 +08:00
241cbd3016 support multi password hash verify 2020-01-31 21:54:17 +08:00
9ff815f853 update 2020-01-31 21:39:32 +08:00
b61e641dad update 2020-01-31 21:30:44 +08:00
a9eff7bea9 update 2020-01-31 20:04:47 +08:00
fc3c4b3b73 update 2020-01-31 17:54:41 +08:00
b75104475c update 2020-01-31 17:33:55 +08:00
6c606527b1 update 2020-01-31 17:18:00 +08:00
34892d9c34 Merge branch 'master' of https://github.com/v2board/v2board 2020-01-31 17:13:45 +08:00
51bb2779b9 update 2020-01-31 17:13:30 +08:00
45a257778c Merge pull request #25 from ColetteContreras/master
Add Poseidon
2020-01-31 15:18:13 +08:00
4286bbb988 Add Poseidon 2020-01-31 12:50:14 +08:00
2f8f3afebf update 2020-01-31 00:16:00 +08:00
15fc60d3c7 update 2020-01-31 00:03:18 +08:00
b8218f2c51 update 2020-01-30 20:16:14 +08:00
9080de4d0a update 2020-01-30 20:02:12 +08:00
50ffdd9db9 update 2020-01-30 19:59:06 +08:00
6cf3b73da2 update 2020-01-30 19:57:18 +08:00
e963a61910 update 2020-01-30 17:18:25 +08:00
038effdd42 update 2020-01-30 16:59:52 +08:00
5a4b208aab update 2020-01-29 17:10:10 +08:00
af400b3a0b update 2020-01-29 17:08:09 +08:00
9a4b2027f6 update 2020-01-29 17:01:12 +08:00
9ae2fc3f1d update 2020-01-29 16:58:45 +08:00
ba9d00f088 update 2020-01-29 16:49:32 +08:00
7fc34fa2b8 update 2020-01-29 16:42:47 +08:00
7cb5eb03b9 update 2020-01-29 16:37:57 +08:00
d7063191f4 update 2020-01-29 16:22:39 +08:00
deb7e4ed04 update 2020-01-29 16:10:56 +08:00
602e12d745 update 2020-01-29 16:10:21 +08:00
f2ffb184d4 update 2020-01-29 16:09:39 +08:00
6dd562df8c update 2020-01-29 16:08:50 +08:00
5fae1f2bd8 update 2020-01-27 15:51:05 +08:00
a60d68a13f update 2020-01-27 15:49:44 +08:00
dad9e43114 update 2020-01-25 23:04:52 +08:00
aac4ce8098 update 2020-01-20 23:34:53 +08:00
52b9ed47b0 update 2020-01-20 23:04:42 +08:00
6d76e30299 update 2020-01-20 20:12:13 +08:00
0675268a16 update 2020-01-20 17:13:14 +08:00
aaf94e61a2 update 2020-01-20 17:02:19 +08:00
47d5031fe6 update 2020-01-20 17:01:02 +08:00
8d4575ede9 update 2020-01-20 16:47:53 +08:00
e9a69cd0f6 update 2020-01-20 16:43:34 +08:00
625bcc59e8 update 2020-01-20 16:38:34 +08:00
34533ae10c update 2020-01-20 16:28:28 +08:00
b4fc90c133 update 2020-01-20 16:18:12 +08:00
12278e143e update 2020-01-20 16:16:59 +08:00
10510164e4 update 2020-01-20 15:24:48 +08:00
75e91b281c update 2020-01-20 14:47:36 +08:00
c027c1127b update 2020-01-20 14:04:03 +08:00
0dbdb55ed2 update 2020-01-20 14:01:53 +08:00
66042904e8 update 2020-01-20 13:58:50 +08:00
508961c4fb update 2020-01-20 13:57:30 +08:00
f03dbd1dcb update 2020-01-18 18:41:02 +08:00
e576d82955 update 2020-01-17 00:20:54 +08:00
88d7e2d35e update 2020-01-14 19:48:18 +08:00
9b6919e9c9 update 2020-01-14 19:45:19 +08:00
3e86cdcbbf update 2020-01-14 01:16:16 +08:00
9fc4a6129c update 2020-01-14 00:29:59 +08:00
db9743a67f update 2020-01-12 22:38:29 +08:00
0f5942bc03 update 2020-01-12 19:48:13 +08:00
28cc21e09b update 2020-01-12 02:35:40 +08:00
fc7f73b558 update 2020-01-12 02:32:15 +08:00
fb0ebea836 update 2020-01-12 02:16:05 +08:00
7d7cef5f37 update 2020-01-12 02:15:48 +08:00
abd488d247 update 2020-01-12 02:14:43 +08:00
e3916aaa06 update 2020-01-12 02:13:27 +08:00
32795cf541 update 2020-01-12 02:08:06 +08:00
415850184e update 2020-01-12 02:02:45 +08:00
afa3c0aa52 update 2020-01-12 01:53:05 +08:00
82875ae6f0 update 2020-01-12 01:48:01 +08:00
bcd85ef71a update 2020-01-12 01:47:54 +08:00
8f35924dee update 2020-01-12 01:41:49 +08:00
4a01b8f11e update 2020-01-12 01:27:36 +08:00
fc18168aa2 update 2020-01-12 00:49:13 +08:00
abdb6f80af update 2020-01-11 19:18:45 +08:00
fbf3a83104 update 2020-01-11 17:17:25 +08:00
515e23762f update 2020-01-11 15:39:07 +08:00
f7fdfadfb0 format 2020-01-11 13:36:52 +08:00
35f954cd84 update 2020-01-09 12:58:01 +08:00
ed0fe84687 update 2020-01-09 12:57:39 +08:00
a96ca5a363 update 2020-01-08 22:28:16 +08:00
6a051f469e update 2020-01-08 17:40:06 +08:00
5e9bc09396 Merge pull request #12 from ColetteContreras/refactor-cache
Use Cache for varieties of cache server
2020-01-08 17:38:44 +08:00
1ef8eab552 Use Cache for varieties of cache server 2020-01-08 01:34:48 +08:00
4d66941ef6 update 2020-01-06 13:48:58 +08:00
2e505a9759 update 2020-01-06 00:06:34 +08:00
85e0eb2760 update 2020-01-05 23:47:23 +08:00
c68440ce32 update 2020-01-05 23:30:07 +08:00
1cd0dbdd31 update 2020-01-05 23:14:42 +08:00
d268f6ddec Merge pull request #8 from chen-ran/master
Support mailgun
2020-01-05 22:07:02 +08:00
b75c33b1d9 support mailgun 2020-01-05 21:21:54 +08:00
0ee67ef270 update 2020-01-05 16:48:18 +08:00
44e57b7073 update 2020-01-05 15:48:51 +08:00
0de4a137bf update 2020-01-05 15:39:28 +08:00
392c849241 update 2020-01-05 15:35:12 +08:00
2f3f457ad9 update 2020-01-05 02:27:29 +08:00
776e866b3c update 2020-01-05 02:23:26 +08:00
9541bc8cd0 update 2020-01-05 02:16:41 +08:00
dc52c3191c update 2020-01-05 02:07:41 +08:00
85a29d3a8b update 2020-01-05 00:22:41 +08:00
7a0c9ce4c4 update 2020-01-04 18:11:13 +08:00
b4bb93e23e update 2020-01-04 18:08:05 +08:00
11d9654010 update 2020-01-04 17:46:53 +08:00
52163329da update 2020-01-04 17:36:31 +08:00
45b03b4fba update 2020-01-04 17:27:21 +08:00
0749372f34 update 2020-01-04 17:25:44 +08:00
588577d513 update 2020-01-04 17:21:46 +08:00
e3431c6ae7 update 2020-01-03 23:05:14 +08:00
2346b1a2dc update 2020-01-03 23:00:31 +08:00
c61f64d623 update 2020-01-03 22:42:27 +08:00
25fbdf0013 update 2020-01-03 21:29:25 +08:00
3a9b3ab32d update 2020-01-03 21:06:04 +08:00
a524fb6f76 update 2020-01-03 21:04:44 +08:00
2172f088a2 update 2020-01-03 20:55:54 +08:00
93fa074358 update 2020-01-03 20:09:21 +08:00
27e417a5f2 update 2020-01-03 20:04:20 +08:00
97e1d9b3bd update 2020-01-03 20:01:46 +08:00
1a0d8e9c55 update 2020-01-03 19:39:32 +08:00
b49b941e50 update 2020-01-03 19:13:04 +08:00
b9cee36641 update 2020-01-03 19:12:05 +08:00
2ecdc27921 update 2020-01-03 19:09:24 +08:00
2d58744de4 update 2020-01-03 12:38:25 +08:00
869a6a920b update 2020-01-03 12:36:44 +08:00
c82a189d76 update 2020-01-03 12:17:57 +08:00
7543819ef2 update 2020-01-03 01:47:15 +08:00
4ceca1957f update 2020-01-03 01:45:58 +08:00
44bd189259 update 2020-01-03 01:12:23 +08:00
b6752e3952 update 2020-01-03 00:45:33 +08:00
45ba9b0c15 update 2020-01-03 00:29:24 +08:00
52e7925cac update 2020-01-03 00:29:15 +08:00
54273a8f16 update 2020-01-03 00:24:45 +08:00
c6461f0bdf update 2020-01-02 23:32:31 +08:00
145f55ae29 update 2020-01-02 23:22:49 +08:00
a201b19940 update 2020-01-02 22:05:27 +08:00
f4166bed45 update 2020-01-02 22:03:24 +08:00
8f8be2ea33 update 2020-01-02 21:55:50 +08:00
df02973756 update 2020-01-02 21:53:02 +08:00
9e55cb2f5d update 2020-01-02 21:52:20 +08:00
5699fe09e9 update 2020-01-02 21:49:25 +08:00
34fa75b4cc update 2020-01-02 21:49:04 +08:00
96d3a27a5b update 2020-01-02 21:39:52 +08:00
b8c8335542 update 2020-01-02 21:38:32 +08:00
aceff450ec update 2020-01-02 13:09:59 +08:00
5d7b5eb8f6 update 2020-01-02 00:43:25 +08:00
afc9d64aab update 2020-01-02 00:21:39 +08:00
09e31dc70b update 2020-01-02 00:20:31 +08:00
d653541ef3 update 2020-01-02 00:19:04 +08:00
dc37499df9 update 2020-01-02 00:11:42 +08:00
2911680eaf update 2020-01-02 00:07:30 +08:00
6ff24f77b8 update 2020-01-02 00:06:23 +08:00
9f4c19bcab update 2020-01-02 00:05:54 +08:00
453a078cd5 update 2020-01-01 23:54:20 +08:00
20be5c3182 update 2020-01-01 23:50:50 +08:00
7639f07b83 update 2020-01-01 23:48:23 +08:00
2a693e4911 update 2020-01-01 23:42:25 +08:00
89d67279c6 update 2020-01-01 23:40:14 +08:00
8bc5004654 update 2020-01-01 18:05:44 +08:00
ec4c0ba339 update 2020-01-01 18:00:45 +08:00
b4e5eb26e3 update 2020-01-01 17:57:06 +08:00
1bf03b28c7 update 2020-01-01 17:55:16 +08:00
aa8f5bfa79 update 2020-01-01 16:35:24 +08:00
495aa04273 update 2020-01-01 16:35:14 +08:00
202a21c17a update 2020-01-01 16:20:44 +08:00
9eefb32a4c update 2020-01-01 15:59:53 +08:00
2eb594a4fa update 2020-01-01 02:25:49 +08:00
34e71ff049 update 2020-01-01 01:50:05 +08:00
63c1faba5e update 2020-01-01 01:36:22 +08:00
5b2e18f702 update 2019-12-31 18:16:43 +08:00
784b53c9f3 update 2019-12-31 18:13:42 +08:00
01d0f6b29d update 2019-12-31 17:49:52 +08:00
2d7d5a564e update 2019-12-31 17:49:24 +08:00
70c1d5c874 update 2019-12-31 15:58:53 +08:00
10029c8362 update 2019-12-31 15:57:12 +08:00
9cd377f8ed update 2019-12-31 15:54:24 +08:00
f5db443668 update 2019-12-31 15:53:27 +08:00
e1fce3ae37 update 2019-12-31 13:45:50 +08:00
fae5ef21e6 update 2019-12-31 01:06:48 +08:00
4c976b1cbe update 2019-12-31 01:05:41 +08:00
6de46f5c36 update 2019-12-31 01:04:55 +08:00
4992ea635c update 2019-12-30 22:52:02 +08:00
928e59a977 update 2019-12-30 22:45:42 +08:00
2be5b8e357 update 2019-12-30 22:45:10 +08:00
76ce89c17f update 2019-12-30 22:40:39 +08:00
8c5b32de90 update 2019-12-30 21:05:48 +08:00
0b89446b63 update 2019-12-30 19:03:15 +08:00
0d4a86c9e9 update 2019-12-30 17:54:50 +08:00
aa1d54137c update 2019-12-30 17:51:18 +08:00
eee6351e35 update 2019-12-30 17:48:26 +08:00
4b43bd2b9e update 2019-12-30 17:43:56 +08:00
ff6aeb92ec update 2019-12-30 16:57:21 +08:00
a9abdcd9d3 update 2019-12-30 16:48:59 +08:00
7965a020b6 update 2019-12-30 16:37:47 +08:00
17e626493a update 2019-12-30 16:26:25 +08:00
0de055a36b update 2019-12-30 16:25:32 +08:00
5070c851ed update 2019-12-30 16:21:50 +08:00
157a97b35d update 2019-12-30 15:13:11 +08:00
8e6ee35efb update 2019-12-29 13:33:42 +08:00
ec284241ee update 2019-12-29 13:29:52 +08:00
a3f59aac0a update 2019-12-29 13:21:36 +08:00
35c71be4a9 update 2019-12-29 12:12:33 +08:00
abf6dbf73c update 2019-12-29 01:07:38 +08:00
037ecc0a2a update 2019-12-28 17:46:47 +08:00
5a87d59d30 update 2019-12-28 17:34:08 +08:00
1351ec583e update 2019-12-28 17:31:56 +08:00
5b29257227 update 2019-12-28 12:31:47 +08:00
551073cb83 update 2019-12-28 11:41:47 +08:00
a1b466e2c2 update 2019-12-28 11:40:00 +08:00
0b3cf4a2a4 update 2019-12-27 18:01:19 +08:00
0dccfd9f09 update 2019-12-27 17:57:07 +08:00
988f088a58 update 2019-12-27 17:36:02 +08:00
237 changed files with 20667 additions and 7107 deletions

View File

@ -7,16 +7,16 @@ APP_URL=http://localhost
LOG_CHANNEL=stack LOG_CHANNEL=stack
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=db DB_HOST=localhost
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=laravel DB_DATABASE=laravel
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=123456 DB_PASSWORD=123456
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_DRIVER=redis
QUEUE_CONNECTION=sync QUEUE_CONNECTION=redis
SESSION_DRIVER=file SESSION_DRIVER=redis
SESSION_LIFETIME=120 SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1
@ -31,6 +31,8 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=null MAIL_FROM_NAME=null
MAILGUN_DOMAIN=
MAILGUN_SECRET=
AWS_ACCESS_KEY_ID= AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY= AWS_SECRET_ACCESS_KEY=
@ -43,4 +45,4 @@ PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1 PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

5
.gitignore vendored
View File

@ -1,5 +1,5 @@
/node_modules /node_modules
/config/v2panel.php /config/v2board.php
/public/hot /public/hot
/public/storage /public/storage
/public/env.example.js /public/env.example.js
@ -9,6 +9,7 @@
.env.backup .env.backup
.phpunit.result.cache .phpunit.result.cache
.idea .idea
.lock
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
npm-debug.log npm-debug.log
@ -16,4 +17,4 @@ yarn-error.log
composer.phar composer.phar
composer.lock composer.lock
yarn.lock yarn.lock
docker-compose.yml docker-compose.yml

View File

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

View File

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

View File

@ -1,53 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\User;
class CheckExpire extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:expire';
/**
* 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()
{
$user = User::all();
foreach ($user as $item) {
if ($item->expired_at < time() || $item->u + $item->d >= $item->transfer_enable) {
$item->enable = 0;
} else {
$item->enable = 1;
}
$item->save();
}
}
}

View File

@ -2,11 +2,13 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Services\OrderService;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use App\Utils\Helper; use App\Utils\Helper;
use App\Models\Coupon;
class CheckOrder extends Command class CheckOrder extends Command
{ {
@ -41,33 +43,48 @@ class CheckOrder extends Command
*/ */
public function handle() public function handle()
{ {
$order = Order::get(); $orders = Order::get();
foreach ($order as $item) { foreach ($orders as $item) {
switch ($item->status) { switch ($item->status) {
// cancel // cancel
case 0: case 0:
if (strtotime($item->created_at) <= (time() - 1800)) { if (strtotime($item->created_at) <= (time() - 1800)) {
$item->status = 2; $orderService = new OrderService($item);
$item->save(); $orderService->cancel();
} }
break; break;
case 1: case 1:
$this->orderHandle($item); $this->orderHandle($item);
break; break;
} }
} }
} }
private function orderHandle ($order) { private function orderHandle(Order $order)
{
$user = User::find($order->user_id); $user = User::find($order->user_id);
return $this->buy($order, $user);
}
private function buy ($order, $user) {
$plan = Plan::find($order->plan_id); $plan = Plan::find($order->plan_id);
if ((string)$order->cycle === 'onetime_price') {
return $this->buyByOneTime($order, $user, $plan);
}
return $this->buyByCycle($order, $user, $plan);
}
private function buyByCycle(Order $order, User $user, Plan $plan)
{
// change plan process
if ((int)$order->type === 3) {
$user->expired_at = time();
}
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
$user->transfer_enable = $plan->transfer_enable * 1073741824; $user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->enable = 1; if ((int)config('v2board.renew_reset_traffic_enable', 1)) {
$user->u = 0;
$user->d = 0;
}
$user->plan_id = $plan->id; $user->plan_id = $plan->id;
$user->group_id = $plan->group_id; $user->group_id = $plan->group_id;
$user->expired_at = $this->getTime($order->cycle, $user->expired_at); $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
@ -76,16 +93,38 @@ class CheckOrder extends Command
$order->save(); $order->save();
} }
} }
private function getTime ($str, $timestamp) { private function buyByOneTime(Order $order, User $user, Plan $plan)
{
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->u = 0;
$user->d = 0;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = NULL;
if ($user->save()) {
$order->status = 3;
$order->save();
}
}
private function getTime($str, $timestamp)
{
if ($timestamp < time()) { if ($timestamp < time()) {
$timestamp = time(); $timestamp = time();
} }
switch ($str) { switch ($str) {
case 'month_price': return strtotime('+1 month', $timestamp); case 'month_price':
case 'quarter_price': return strtotime('+3 month', $timestamp); return strtotime('+1 month', $timestamp);
case 'half_year_price': return strtotime('+6 month', $timestamp); case 'quarter_price':
case 'year_price': return strtotime('+12 month', $timestamp); return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
} }
} }
} }

View File

@ -3,7 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB; use App\Models\User;
class ResetTraffic extends Command class ResetTraffic extends Command
{ {
@ -38,7 +38,47 @@ class ResetTraffic extends Command
*/ */
public function handle() public function handle()
{ {
DB::table('v2_user')->update([ $user = User::where('expired_at', '!=', NULL)
->where('expired_at', '>', time());
$resetTrafficMethod = config('v2board.reset_traffic_method', 0);
switch ((int)$resetTrafficMethod) {
// 1 a month
case 0:
$this->resetByMonthFirstDay($user);
break;
// expire day
case 1:
$this->resetByExpireDay($user);
break;
}
}
private function resetByMonthFirstDay($user):void
{
if ((string)date('d') === '01') {
$user->update([
'u' => 0,
'd' => 0
]);
}
}
private function resetByExpireDay($user):void
{
$lastDay = date('d', strtotime('last day of +0 months'));
$users = [];
foreach ($user->get() as $item) {
$expireDay = date('d', $item->expired_at);
$today = date('d');
if ($expireDay === $today) {
array_push($users, $item->id);
}
if (($today === $lastDay) && $expireDay >= $lastDay) {
array_push($users, $item->id);
}
}
User::whereIn('id', $users)->update([
'u' => 0, 'u' => 0,
'd' => 0 'd' => 0
]); ]);

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Encryption\Encrypter;
use App\Models\User; use App\Models\User;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -40,51 +41,114 @@ class V2boardInstall extends Command
*/ */
public function handle() public function handle()
{ {
if (\File::exists(base_path() . '/.lock')) { try {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件'); $this->info("__ ______ ____ _ ");
$this->info("\ \ / /___ \| __ ) ___ __ _ _ __ __| | ");
$this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | ");
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.lock')) {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件');
}
if (!\File::exists(base_path() . '/.env')) {
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限');
}
}
$this->saveToEnv([
'APP_KEY' => 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')),
'DB_HOST' => $this->ask('请输入数据库地址(默认:localhost', 'localhost'),
'DB_DATABASE' => $this->ask('请输入数据库名'),
'DB_USERNAME' => $this->ask('请输入数据库用户名'),
'DB_PASSWORD' => $this->ask('请输入数据库密码')
]);
\Artisan::call('config:clear');
\Artisan::call('config:cache');
try {
DB::connection()->getPdo();
} catch (\Exception $e) {
abort(500, '数据库连接失败');
}
$file = \File::get(base_path() . '/database/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {
}
}
$this->info('数据库导入完成');
$email = '';
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
}
$password = '';
while (!$password) {
$password = $this->ask('请输入管理员密码?');
}
if (!$this->registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
}
$this->info('一切就绪');
$this->info('访问 http(s)://你的站点/admin 进入管理面板');
\File::put(base_path() . '/.lock', time());
} catch (\Exception $e) {
$this->error($e->getMessage());
} }
\Artisan::call('key:generate');
sleep(2);
\Artisan::call('config:cache');
DB::connection()->getPdo();
$file = \File::get(base_path() . '/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
$sql = str_replace("\n", "", $file);
$sql = preg_split("/;/", $sql);
if (!is_array($sql)) {
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
}
$email = '';
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
}
$password = '';
while (!$password) {
$password = $this->ask('请输入管理员密码?');
}
if (!$this->registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
}
$this->info('一切就绪');
\File::put(base_path() . '/.lock', time());
} }
private function registerAdmin ($email, $password) { private function registerAdmin($email, $password)
{
$user = new User(); $user = new User();
$user->email = $email; $user->email = $email;
if (strlen($password) < 8) {
abort(500, '管理员密码长度最小为8位字符');
}
$user->password = password_hash($password, PASSWORD_DEFAULT); $user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true); $user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid(); $user->token = Helper::guid();
$user->is_admin = 1; $user->is_admin = 1;
return $user->save(); return $user->save();
} }
private function saveToEnv($data = [])
{
function set_env_var($key, $value)
{
if (! is_bool(strpos($value, ' '))) {
$value = '"' . $value . '"';
}
$key = strtoupper($key);
$envPath = app()->environmentFilePath();
$contents = file_get_contents($envPath);
preg_match("/^{$key}=[^\r\n]*/m", $contents, $matches);
$oldValue = count($matches) ? $matches[0] : '';
if ($oldValue) {
$contents = str_replace("{$oldValue}", "{$key}={$value}", $contents);
} else {
$contents = $contents . "\n{$key}={$value}\n";
}
$file = fopen($envPath, 'w');
fwrite($file, $contents);
return fclose($file);
}
foreach($data as $key => $value) {
set_env_var($key, $value);
}
return true;
}
} }

View File

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

View File

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

View File

@ -29,7 +29,7 @@ class Handler extends ExceptionHandler
/** /**
* Report or log an exception. * Report or log an exception.
* *
* @param \Exception $exception * @param \Exception $exception
* @return void * @return void
*/ */
public function report(Exception $exception) public function report(Exception $exception)
@ -40,12 +40,15 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Exception $exception * @param \Exception $exception
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function render($request, Exception $exception) public function render($request, Exception $exception)
{ {
if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
abort(429, '请求频繁,请稍后再试');
}
return parent::render($request, $exception); return parent::render($request, $exception);
} }

View File

@ -4,34 +4,51 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ConfigSave; use App\Http\Requests\Admin\ConfigSave;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Utils\Dict;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
class ConfigController extends Controller class ConfigController extends Controller
{ {
public function init () { public function getEmailTemplate()
{
$path = resource_path('views/mail/');
$files = array_map(function ($item) use ($path) {
return str_replace($path, '', $item);
}, glob($path . '*'));
return response([
'data' => $files
]);
} }
public function fetch () { public function fetch()
{
// TODO: default should be in Dict
return response([ return response([
'data' => [ 'data' => [
'invite' => [ 'invite' => [
'invite_force' => (int)config('v2board.invite_force', 0), 'invite_force' => (int)config('v2board.invite_force', 0),
'invite_commission' => config('v2board.invite_commission', 10), 'invite_commission' => config('v2board.invite_commission', 10),
'invite_gen_limit' => config('v2board.invite_gen_limit', 5), 'invite_gen_limit' => config('v2board.invite_gen_limit', 5),
'invite_never_expire' => config('v2board.invite_never_expire', 0) 'invite_never_expire' => config('v2board.invite_never_expire', 0),
'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1)
], ],
'site' => [ 'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
'stop_register' => (int)config('v2board.stop_register', 0), 'stop_register' => (int)config('v2board.stop_register', 0),
'email_verify' => (int)config('v2board.email_verify', 0), 'email_verify' => (int)config('v2board.email_verify', 0),
'app_name' => config('v2board.app_name', 'V2Board'), 'app_name' => config('v2board.app_name', 'V2Board'),
'app_description' => config('v2board.app_description', 'V2Board is best!'),
'app_url' => config('v2board.app_url'), 'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'), 'subscribe_url' => config('v2board.subscribe_url'),
'plan_update_fee' => config('v2board.plan_update_fee', 0.5), 'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
'plan_is_update' => (int)config('v2board.plan_is_update', 1) 'try_out_hour' => (int)config('v2board.try_out_hour', 1),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1)
], ],
'pay' => [ 'pay' => [
// alipay // alipay
@ -40,39 +57,58 @@ class ConfigController extends Controller
'alipay_pubkey' => config('v2board.alipay_pubkey'), 'alipay_pubkey' => config('v2board.alipay_pubkey'),
'alipay_privkey' => config('v2board.alipay_privkey'), 'alipay_privkey' => config('v2board.alipay_privkey'),
// stripe // stripe
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
'stripe_sk_live' => config('v2board.stripe_sk_live'), 'stripe_sk_live' => config('v2board.stripe_sk_live'),
'stripe_pk_live' => config('v2board.stripe_pk_live'), 'stripe_pk_live' => config('v2board.stripe_pk_live'),
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable'),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable'),
'stripe_webhook_key' => config('v2board.stripe_webhook_key'), 'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
// bitpayx // bitpayx
'bitpayx_enable' => config('v2board.bitpayx_enable'), 'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0),
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret') 'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
// paytaro
'paytaro_enable' => (int)config('v2board.paytaro_enable', 0),
'paytaro_app_id' => config('v2board.paytaro_app_id'),
'paytaro_app_secret' => config('v2board.paytaro_app_secret')
],
'frontend' => [
'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'),
'frontend_background_url' => config('v2board.frontend_background_url')
], ],
'server' => [ 'server' => [
'server_token' => config('v2board.server_token') 'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license')
], ],
'tutorial' => [ 'tutorial' => [
'apple_id' => config('v2board.apple_id') 'apple_id' => config('v2board.apple_id')
],
'email' => [
'email_template' => config('v2board.email_template', 'default')
] ]
] ]
]); ]);
} }
public function save (ConfigSave $request) { public function save(ConfigSave $request)
{
$data = $request->input(); $data = $request->input();
$array = \Config::get('v2board'); $array = \Config::get('v2board');
foreach ($data as $k => $v) { foreach ($data as $k => $v) {
if (!in_array($k, ConfigSave::filter())) { if (!in_array($k, array_keys(ConfigSave::RULES))) {
abort(500, '禁止修改'); abort(500, '参数' . $k . '不在规则内,禁止修改');
} }
$array[$k] = $v; $array[$k] = $v;
} }
$data = var_export($array, 1); $data = var_export($array, 1);
if(!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) { if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
abort(500, '修改失败'); abort(500, '修改失败');
} }
\Artisan::call('config:cache'); \Artisan::call('config:cache');
if (function_exists('opcache')) {
opcache_reset();
}
return response([ return response([
'data' => true 'data' => true
]); ]);

View File

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

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend;
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Jobs\SendEmailJob;
class MailController extends Controller
{
public function send(MailSend $request)
{
$userService = new UserService();
$users = [];
switch ($request->input('type')) {
case 1: $users = $userService->getAllUsers();
break;
case 2: $users = $userService->getUsersByIds($request->input('receiver'));
break;
// available users
case 3: $users = $userService->getAvailableUsers();
break;
// un available users
case 4: $users = $userService->getUnAvailbaleUsers();
break;
}
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
}

View File

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

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\OrderUpdate; use App\Http\Requests\Admin\OrderUpdate;
use App\Services\OrderService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Order; use App\Models\Order;
@ -11,7 +12,8 @@ use App\Models\Plan;
class OrderController extends Controller class OrderController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1; $current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$orderModel = Order::orderBy('created_at', 'DESC'); $orderModel = Order::orderBy('created_at', 'DESC');
@ -25,6 +27,9 @@ class OrderController extends Controller
if ($request->input('id')) { if ($request->input('id')) {
$orderModel->where('id', $request->input('id')); $orderModel->where('id', $request->input('id'));
} }
if ($request->input('user_id')) {
$orderModel->where('user_id', $request->input('user_id'));
}
$total = $orderModel->count(); $total = $orderModel->count();
$res = $orderModel->forPage($current, $pageSize) $res = $orderModel->forPage($current, $pageSize)
->get(); ->get();
@ -42,8 +47,9 @@ class OrderController extends Controller
]); ]);
} }
public function update (OrderUpdate $request) { public function update(OrderUpdate $request)
$updateData = $request->only([ {
$params = $request->only([
'status', 'status',
'commission_status' 'commission_status'
]); ]);
@ -54,7 +60,19 @@ class OrderController extends Controller
abort(500, '订单不存在'); abort(500, '订单不存在');
} }
if (!$order->update($updateData)) { if (isset($params['status']) && (int)$params['status'] === 2) {
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
try {
$order->update($params);
} catch (\Exception $e) {
abort(500, '更新失败'); abort(500, '更新失败');
} }
@ -63,7 +81,8 @@ class OrderController extends Controller
]); ]);
} }
public function repair (Request $request) { public function repair(Request $request)
{
if (empty($request->input('trade_no'))) { if (empty($request->input('trade_no'))) {
abort(500, '参数错误'); abort(500, '参数错误');
} }

View File

@ -9,42 +9,49 @@ use App\Http\Controllers\Controller;
use App\Models\Plan; use App\Models\Plan;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller class PlanController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
return response([ return response([
'data' => Plan::get() 'data' => Plan::get()
]); ]);
} }
public function save (PlanSave $request) { public function save(PlanSave $request)
{
$params = $request->only(array_keys(PlanSave::RULES));
if ($request->input('id')) { if ($request->input('id')) {
$plan = Plan::find($request->input('id')); $plan = Plan::find($request->input('id'));
if (!$plan) { if (!$plan) {
abort(500, '该订阅不存在'); abort(500, '该订阅不存在');
} }
} else { DB::beginTransaction();
$plan = new Plan(); // update user group id
try {
User::where('plan_id', $plan->id)->update(['group_id' => $plan->group_id]);
$plan->update($params);
} catch (\Exception $e) {
DB::rollBack();
abort(500, '保存失败');
}
DB::commit();
return response([
'data' => true
]);
} }
$plan->name = $request->input('name'); if (!Plan::create($params)) {
$plan->content = $request->input('content'); abort(500, '创建失败');
if ($plan->content) {
$plan->content = str_replace(PHP_EOL, '', $plan->content);
} }
$plan->transfer_enable = $request->input('transfer_enable');
$plan->group_id = $request->input('group_id');
$plan->month_price = $request->input('month_price');
$plan->quarter_price = $request->input('quarter_price');
$plan->half_year_price = $request->input('half_year_price');
$plan->year_price = $request->input('year_price');
return response([ return response([
'data' => $plan->save() 'data' => true
]); ]);
} }
public function drop (Request $request) { public function drop(Request $request)
{
if (Order::where('plan_id', $request->input('id'))->first()) { if (Order::where('plan_id', $request->input('id'))->first()) {
abort(500, '该订阅下存在订单无法删除'); abort(500, '该订阅下存在订单无法删除');
} }
@ -62,17 +69,21 @@ class PlanController extends Controller
]); ]);
} }
public function update (PlanUpdate $request) { public function update(PlanUpdate $request)
{
$updateData = $request->only([ $updateData = $request->only([
'show', 'show',
'renew' 'renew'
]); ]);
$plan = Plan::find($request->input('id')); $plan = Plan::find($request->input('id'));
if (!$plan) { if (!$plan) {
abort(500, '该订阅不存在'); abort(500, '该订阅不存在');
} }
if (!$plan->update($updateData)) {
try {
$plan->update($updateData);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }

View File

@ -4,60 +4,94 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ServerSave; use App\Http\Requests\Admin\ServerSave;
use App\Http\Requests\Admin\ServerUpdate; use App\Http\Requests\Admin\ServerUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ServerGroup; use App\Models\ServerGroup;
use App\Models\Server; use App\Models\Server;
use App\Models\Plan; use App\Models\Plan;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Cache;
class ServerController extends Controller class ServerController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
$server = Server::get(); $server = Server::get();
for ($i = 0; $i < count($server); $i++) { for ($i = 0; $i < count($server); $i++) {
if (!empty($server[$i]['tags'])) { if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']); $server[$i]['tags'] = json_decode($server[$i]['tags']);
} }
$server[$i]['group_id'] = json_decode($server[$i]['group_id']); $server[$i]['group_id'] = json_decode($server[$i]['group_id']);
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']); if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
}
} }
return response([ return response([
'data' => $server 'data' => $server
]); ]);
} }
public function save (ServerSave $request) { public function save(ServerSave $request)
{
$params = $request->only(array_keys(ServerSave::RULES));
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['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')) { if ($request->input('id')) {
$server = Server::find($request->input('id')); $server = Server::find($request->input('id'));
if (!$server) { if (!$server) {
abort(500, '服务器不存在'); abort(500, '服务器不存在');
} }
} else { try {
$server = new Server(); $server->update($params);
} } catch (\Exception $e) {
$server->group_id = json_encode($request->input('group_id')); abort(500, '保存失败');
$server->name = $request->input('name');
$server->host = $request->input('host');
$server->port = $request->input('port');
$server->server_port = $request->input('server_port');
$server->tls = $request->input('tls');
$server->tags = $request->input('tags') ? json_encode($request->input('tags')) : NULL;
$server->rate = $request->input('rate');
$server->network = $request->input('network');
if ($request->input('settings')) {
if (!is_object(json_decode($request->input('settings')))) {
abort(500, '传输协议配置格式不正确');
} }
$server->settings = $request->input('settings'); return response([
'data' => true
]);
} }
if (!Server::create($params)) {
abort(500, '创建失败');
}
return response([ return response([
'data' => $server->save() 'data' => true
]); ]);
} }
public function groupFetch (Request $request) { public function groupFetch(Request $request)
{
if ($request->input('group_id')) { if ($request->input('group_id')) {
return response([ return response([
'data' => [ServerGroup::find($request->input('group_id'))] 'data' => [ServerGroup::find($request->input('group_id'))]
@ -68,11 +102,12 @@ class ServerController extends Controller
]); ]);
} }
public function groupSave (Request $request) { public function groupSave(Request $request)
{
if (empty($request->input('name'))) { if (empty($request->input('name'))) {
abort(500, '组名不能为空'); abort(500, '组名不能为空');
} }
if ($request->input('id')) { if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id')); $serverGroup = ServerGroup::find($request->input('id'));
} else { } else {
@ -85,7 +120,8 @@ class ServerController extends Controller
]); ]);
} }
public function groupDrop (Request $request) { public function groupDrop(Request $request)
{
if ($request->input('id')) { if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id')); $serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) { if (!$serverGroup) {
@ -111,8 +147,9 @@ class ServerController extends Controller
'data' => $serverGroup->delete() 'data' => $serverGroup->delete()
]); ]);
} }
public function drop (Request $request) { public function drop(Request $request)
{
if ($request->input('id')) { if ($request->input('id')) {
$server = Server::find($request->input('id')); $server = Server::find($request->input('id'));
if (!$server) { if (!$server) {
@ -124,16 +161,20 @@ class ServerController extends Controller
]); ]);
} }
public function update (ServerUpdate $request) { public function update(ServerUpdate $request)
$updateData = $request->only([ {
$params = $request->only([
'show', 'show',
]); ]);
$server = Server::find($request->input('id')); $server = Server::find($request->input('id'));
if (!$server) { if (!$server) {
abort(500, '该服务器不存在'); abort(500, '该服务器不存在');
} }
if (!$server->update($updateData)) { try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }
@ -141,4 +182,28 @@ class ServerController extends Controller
'data' => true 'data' => true
]); ]);
} }
public function copy(Request $request)
{
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
if (!Server::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getConfig($request->input('node_id'), 23333);
return response([
'data' => $config
]);
}
} }

View File

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

View File

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

View File

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

View File

@ -11,13 +11,19 @@ use App\Models\Plan;
class UserController extends Controller class UserController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1; $current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$userModel = User::orderBy('created_at', 'DESC'); $sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$userModel = User::orderBy($sort, $sortType);
if ($request->input('email')) { if ($request->input('email')) {
$userModel->where('email', $request->input('email')); $userModel->where('email', $request->input('email'));
} }
if ($request->input('invite_user_id')) {
$userModel->where('invite_user_id', $request->input('invite_user_id'));
}
$total = $userModel->count(); $total = $userModel->count();
$res = $userModel->forPage($current, $pageSize) $res = $userModel->forPage($current, $pageSize)
->get(); ->get();
@ -35,8 +41,9 @@ class UserController extends Controller
]); ]);
} }
public function id2UserInfo ($id) { public function getUserInfoById(Request $request)
if (empty($id)) { {
if (empty($request->input('id'))) {
abort(500, '参数错误'); abort(500, '参数错误');
} }
return response([ return response([
@ -46,42 +53,36 @@ class UserController extends Controller
'd', 'd',
'transfer_enable', 'transfer_enable',
'expired_at' 'expired_at'
])->find($id) ])->find($request->input('id'))
]); ]);
} }
public function update (UserUpdate $request) { public function update(UserUpdate $request)
$updateData = $request->only([ {
'email', $params = $request->only(array_keys(UserUpdate::RULES));
'password',
'transfer_enable',
'expired_at',
'banned',
'plan_id',
'commission_rate',
'is_admin'
]);
$user = User::find($request->input('id')); $user = User::find($request->input('id'));
if (!$user) { if (!$user) {
abort(500, '用户不存在'); abort(500, '用户不存在');
} }
if (User::where('email', $updateData['email'])->first() && $user->email !== $updateData['email']) { if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
abort(500, '邮箱已被使用'); abort(500, '邮箱已被使用');
} }
if (isset($updateData['password'])) { if (isset($params['password'])) {
$updateData['password'] = password_hash($updateData['password'], PASSWORD_DEFAULT); $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
} else { } else {
unset($updateData['password']); unset($params['password']);
} }
$updateData['transfer_enable'] = $updateData['transfer_enable'] * 1073741824; if (isset($params['plan_id'])) {
if (isset($updateData['plan_id'])) { $plan = Plan::find($params['plan_id']);
$plan = Plan::find($updateData['plan_id']);
if (!$plan) { if (!$plan) {
abort(500, '订阅计划不存在'); abort(500, '订阅计划不存在');
} }
$updateData['group_id'] = $plan->group_id; $params['group_id'] = $plan->group_id;
} }
if (!$user->update($updateData)) {
try {
$user->update($params);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }
return response([ return response([

View File

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

View File

@ -0,0 +1,228 @@
<?php
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Server;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
use App\Services\UserService;
class ClientController extends Controller
{
public function subscribe(Request $request)
{
$user = $request->user;
$server = [];
// account not expired and is not banned.
$userService = new UserService();
if ($userService->isAvailable($user)) {
$servers = Server::where('show', 1)
->orderBy('name')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
die($this->quantumultX($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
die($this->quantumult($user, $server));
}
if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
die($this->clash($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surfboard') !== false) {
die($this->surge($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surge') !== false) {
die($this->surge($user, $server));
}
}
die($this->origin($user, $server));
}
private function quantumultX($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= "vmess=" . $item->host . ":" . $item->port . ", method=none, password=" . $user->v2ray_uuid . ", fast-open=false, udp-relay=false, tag=" . $item->name;
if ($item->network == 'ws') {
$uri .= ', obfs=' . ($item->tls ? 'wss' : 'ws');
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
}
}
$uri .= "\r\n";
}
return base64_encode($uri);
}
private function quantumult($user, $server)
{
$uri = '';
header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
foreach ($server as $item) {
$str = '';
$str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->v2ray_uuid . '", over-tls=' . ($item->tls ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
if ($item->network === 'ws') {
$str .= ', obfs=ws';
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $str .= ', obfs-path="' . $wsSettings->path . '"';
if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:' . $wsSettings->headers->Host . '"';
}
}
$uri .= "vmess://" . base64_encode($str) . "\r\n";
}
return base64_encode($uri);
}
private function origin($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= Helper::buildVmessLink($item, $user);
}
return base64_encode($uri);
}
private function surge($user, $server)
{
$proxies = '';
$proxyGroup = '';
foreach ($server as $item) {
// [Proxy]
$proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->v2ray_uuid;
if ($item->tls) {
$tlsSettings = json_decode($item->tlsSettings);
$proxies .= ', tls=' . ($item->tls ? "true" : "false");
if (isset($tlsSettings->allowInsecure)) {
$proxies .= ', skip-cert-verify=true';
}
}
if ($item->network == 'ws') {
$proxies .= ', ws=true';
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
}
}
$proxies .= "\r\n";
// [Proxy Group]
$proxyGroup .= $item->name . ', ';
}
try {
$rules = '';
foreach (glob(base_path() . '/resources/rules/' . '*.surge.conf') as $file) {
$rules = file_get_contents("$file");
}
} catch (\Exception $e) {}
// Subscription link
$subsURL = 'http';
if (isset( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) == 'on') {
$subsURL .= 's';
}
$subsURL .= '://';
if ($_SERVER['SERVER_PORT'] != ('80' || '443')) {
$subsURL .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
} else {
$subsURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
}
$rules = str_replace('{subs_link}',$subsURL,$rules);
$rules = str_replace('{proxies}',$proxies,$rules);
$rules = str_replace('{proxy_group}',rtrim($proxyGroup, ', '),$rules);
return $rules;
}
private function clash($user, $server)
{
$proxy = [];
$proxyGroup = [];
$proxies = [];
$rules = [];
foreach ($server as $item) {
$array = [];
$array['name'] = $item->name;
$array['type'] = 'vmess';
$array['server'] = $item->host;
$array['port'] = $item->port;
$array['uuid'] = $user->v2ray_uuid;
$array['alterId'] = $user->v2ray_alter_id;
$array['cipher'] = 'auto';
if ($item->tls) {
$array['tls'] = true;
$array['skip-cert-verify'] = true;
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
'Host' => $wsSettings->headers->Host
];
}
}
array_push($proxy, $array);
array_push($proxies, $item->name);
}
array_push($proxyGroup, [
'name' => 'auto',
'type' => 'url-test',
'proxies' => $proxies,
'url' => 'https://www.bing.com',
'interval' => 300
]);
array_push($proxyGroup, [
'name' => 'fallback-auto',
'type' => 'fallback',
'proxies' => $proxies,
'url' => 'https://www.bing.com',
'interval' => 300
]);
array_push($proxyGroup, [
'name' => 'select',
'type' => 'select',
'proxies' => array_merge($proxies, [
'auto',
'fallback-auto'
])
]);
try {
$rules = [];
foreach (glob(base_path() . '/resources/rules/' . '*.clash.yaml') as $file) {
$rules = array_merge($rules, Yaml::parseFile($file)['Rule']);
}
} catch (\Exception $e) {}
$config = [
'port' => 7890,
'socks-port' => 7891,
'allow-lan' => false,
'mode' => 'Rule',
'log-level' => 'info',
'external-controller' => '0.0.0.0:9090',
'secret' => '',
'Proxy' => $proxy,
'Proxy Group' => $proxyGroup,
'Rule' => $rules
];
return Yaml::dump($config);
}
}

View File

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

View File

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

View File

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

View File

@ -1,17 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use App\Utils\Helper;
class NoticeController extends Controller
{
public function fetch (Request $request) {
return response([
'data' => Notice::orderBy('created_at', 'DESC')->first()
]);
}
}

View File

@ -1,303 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\OrderSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Log;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Utils\Helper;
use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
use Library\BitpayX;
class OrderController extends Controller
{
public function fetch (Request $request) {
$order = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC')
->get();
$plan = Plan::get();
for($i = 0; $i < count($order); $i++) {
for($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
}
}
return response([
'data' => $order
]);
}
public function details (Request $request) {
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
$order['plan'] = Plan::find($order->plan_id);
$order['update_fee'] = config('v2board.plan_update_fee', 0.5);
if (!$order['plan']) {
abort(500, '订阅不存在');
}
return response([
'data' => $order
]);
}
public function save (OrderSave $request) {
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
if (!($plan->show || $user->plan_id == $plan->id)) {
abort(500, '该订阅已售罄');
}
if (!$plan->show && !$plan->renew) {
abort(500, '该订阅无法续费,请更换其他订阅');
}
if (!(int)$plan[$request->input('cycle')]) {
abort(500, '该订阅周期无法进行购买,请选择其他周期');
}
$order = new Order();
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
$order->type = 3;
if (!(int)config('v2board.plan_is_update', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
$order->total_amount = $order->total_amount + (ceil(($user->expired_at - time()) / 86400) * config('v2board.plan_update_fee', 0.5) * 100);
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
if ($user->invite_user_id) {
$order->invite_user_id = $user->invite_user_id;
$inviter = User::find($user->invite_user_id);
if ($inviter && $inviter->commission_rate) {
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
} else {
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
}
}
if (!$order->save()) {
abort(500, '订单创建失败');
}
return response([
'data' => $order->trade_no
]);
}
public function checkout (Request $request) {
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或已支付');
}
switch ($method) {
// return type => 0: QRCode / 1: URL
case 0:
// alipayF2F
if (!(int)config('v2board.alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->alipayF2F($tradeNo, $order->total_amount)
]);
case 2:
// stripeAlipay
if (!(int)config('v2board.stripe_alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->stripeAlipay($order)
]);
case 3:
// stripeWepay
if (!(int)config('v2board.stripe_wepay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->stripeWepay($order)
]);
case 4:
// bitpayX
if (!(int)config('v2board.bitpayx_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->bitpayX($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check (Request $request) {
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
return response([
'data' => $order->status
]);
}
public function getPaymentMethod () {
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
$alipayF2F->name = '支付宝';
$alipayF2F->method = 0;
$alipayF2F->icon = 'alipay';
array_push($data, $alipayF2F);
}
if ((int)config('v2board.stripe_alipay_enable')) {
$stripeAlipay = new \StdClass();
$stripeAlipay->name = '支付宝';
$stripeAlipay->method = 2;
$stripeAlipay->icon = 'alipay';
array_push($data, $stripeAlipay);
}
if ((int)config('v2board.stripe_wepay_enable')) {
$stripeWepay = new \StdClass();
$stripeWepay->name = '微信';
$stripeWepay->method = 3;
$stripeWepay->icon = 'wechat';
array_push($data, $stripeWepay);
}
if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass();
$bitpayX->name = '虚拟货币';
$bitpayX->method = 4;
$bitpayX->icon = 'bitcoin';
array_push($data, $bitpayX);
}
return response([
'data' => $data
]);
}
private function alipayF2F ($tradeNo, $totalAmount) {
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
$gateway->setPrivateKey(config('v2board.alipay_privkey')); // 可以是路径,也可以是密钥内容
$gateway->setAlipayPublicKey(config('v2board.alipay_pubkey')); // 可以是路径,也可以是密钥内容
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase();
$request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100
]);
/** @var \Omnipay\Alipay\Responses\AopTradePreCreateResponse $response */
$response = $request->send();
$result = $response->getAlipayResponse();
if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']);
}
// 获取收款二维码内容
return $response->getQrCode();
}
private function stripeAlipay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'alipay',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['redirect']['url'];
}
private function stripeWepay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'wechat',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['wechat']['qr_code_url'];
}
private function bitpayX ($order) {
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'merchant_order_id' => 'V2Board_' . $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
];
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params);
Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false;
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\AuthRegister;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Plan;
use App\Models\User;
use App\Models\InviteCode;
use App\Utils\Helper;
use App\Utils\Dict;
use App\Utils\CacheKey;
class AuthController extends Controller
{
public function register(AuthRegister $request)
{
if ((int)config('v2board.email_whitelist_enable', 0)) {
if (!Helper::emailSuffixVerify(
$request->input('email'),
config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
) {
abort(500, '邮箱后缀不处于白名单中');
}
}
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
}
}
if ((int)config('v2board.email_verify', 0)) {
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
}
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)config('v2board.invite_never_expire', 0)) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
// try out
if ((int)config('v2board.try_out_plan_id', 0)) {
$plan = Plan::find(config('v2board.try_out_plan_id'));
if ($plan) {
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
return response()->json([
'data' => true
]);
}
public function login(AuthLogin $request)
{
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$password,
$user->password)
) {
abort(500, '用户名或密码错误');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$data = [
'token' => $user->token
];
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
$data['is_admin'] = true;
}
return response([
'data' => $data
]);
}
public function token2Login(Request $request)
{
if ($request->input('token')) {
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
return header('Location:' . config('v2board.app_url'));
}
$code = Helper::guid();
$key = 'token2Login_' . $code;
Cache::put($key, $user->id, 600);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . $redirect;
} else {
$location = url($redirect);
}
return header('Location:' . $location);
}
if ($request->input('verify')) {
$key = 'token2Login_' . $request->input('verify');
$userId = Cache::get($key);
if (!$userId) {
abort(500, '令牌有误');
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
Cache::forget($key);
return response([
'data' => true
]);
}
}
public function check(Request $request)
{
$data = [
'is_login' => $request->session()->get('id') ? true : false
];
if ($request->session()->get('is_admin')) {
$data['is_admin'] = true;
}
return response([
'data' => $data
]);
}
public function forget(AuthForget $request)
{
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, '该邮箱不存在系统中');
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '重置失败');
}
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
return response([
'data' => true
]);
}
}

View File

@ -7,52 +7,81 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis; use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Utils\Dict;
use App\Utils\CacheKey;
class CommController extends Controller class CommController extends Controller
{ {
public function config () { public function config()
{
return response([ return response([
'data' => [ 'data' => [
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0, 'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0 'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0
] ]
]); ]);
} }
private function isEmailVerify () { private function isEmailVerify()
{
return response([ return response([
'data' => (int)config('v2board.email_verify', 0) ? 1 : 0 'data' => (int)config('v2board.email_verify', 0) ? 1 : 0
]); ]);
} }
public function sendEmailVerify (CommSendEmailVerify $request) { public function sendEmailVerify(CommSendEmailVerify $request)
{
$email = $request->input('email'); $email = $request->input('email');
$redisKey = 'sendEmailVerify:' . $email; if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
if (Redis::get($redisKey)) { abort(500, '验证码已发送,请过一会再请求');
abort(500, '验证码已发送,请过一会在请求');
} }
$code = rand(100000, 999999); $code = rand(100000, 999999);
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码'; $subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
Mail::send(
'mail.sendEmailVerify',
[
'code' => $code,
'name' => config('v2board.app_name', 'V2Board')
],
function ($message) use($email, $subject) {
$message->to($email)->subject($subject);
}
);
if (count(Mail::failures()) >= 1) {
// 发送失败
abort(500, '发送失败');
}
Redis::set($redisKey, $code); SendEmailJob::dispatch([
Redis::expire($redisKey, 600); 'email' => $email,
'subject' => $subject,
'template_name' => 'verify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'code' => $code,
'url' => config('v2board.app_url')
]
]);
Cache::put(CacheKey::get('EMAIL_VERIFY_CODE', $email), $code, 300);
Cache::put(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email), time(), 60);
return response([ return response([
'data' => true 'data' => true
]); ]);
} }
public function pv(Request $request)
{
$inviteCode = InviteCode::where('code', $request->input('invite_code'))->first();
if ($inviteCode) {
$inviteCode->pv = $inviteCode->pv + 1;
$inviteCode->save();
}
return response([
'data' => true
]);
}
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

@ -1,29 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Requests\Passport\ForgetIndex;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
class ForgetController extends Controller
{
public function index (ForgetIndex $request) {
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (Redis::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
$user = User::where('email', $request->input('email'))->first();
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
if (!$user->save()) {
abort(500, '重置失败');
}
Redis::del($redisKey);
return response([
'data' => true
]);
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use Illuminate\Http\Request;
use App\Http\Requests\Passport\LoginIndex;
use App\Http\Controllers\Controller;
use App\Models\User;
class LoginController extends Controller
{
public function index (LoginIndex $request) {
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
}
if (!password_verify($password, $user->password)) {
abort(500, '用户名或密码错误');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
return response([
'data' => [
'is_admin' => $user->is_admin ? 2 : 1,
'token' => $user->token
]
]);
}
public function token2Login (Request $request) {
if (empty($request->input('token'))) {
abort(500, '参数错误');
}
$redirect = $request->input('redirect') ? $request->input('redirect') : 'dashboard';
$user = User::where('token', $request->input('token'))->first();
if ($user) {
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
}
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . '/#/' . $redirect;
} else {
$location = url('/#/' . $redirect);
}
header('Location:' . $location);
}
public function check (Request $request) {
return response([
'data' => $request->session()->get('id') ? true : false
]);
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Requests\Passport\RegisterIndex;
use App\Http\Requests\Passport\RegisterSendEmailVerify;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use App\Models\InviteCode;
class RegisterController extends Controller
{
public function index (RegisterIndex $request) {
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
}
}
if ((int)config('v2board.email_verify', 0)) {
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
}
if (Redis::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)config('v2board.invite_never_expire', env('V2BOARD_INVITE_NEVER_EXPIRE'))) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Redis::del($redisKey);
}
return response()->json([
'data' => true
]);
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
if (empty($request->input('plan_id'))) {
abort(500, '参数错误');
}
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
return response([
'data' => $plan
]);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Http\Controllers\Server;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
public function __construct(Request $request) {
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
}
}

View File

@ -2,45 +2,46 @@
namespace App\Http\Controllers\Server; namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Server\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use App\Models\Plan;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog; use App\Models\ServerLog;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Cache;
class DeepbworkController extends Controller class DeepbworkController extends Controller
{ {
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}'; CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户 // 后端获取用户
public function user (Request $request) { public function user(Request $request)
{
$nodeId = $request->input('node_id'); $nodeId = $request->input('node_id');
$server = Server::find($nodeId); $server = Server::find($nodeId);
if (!$server) { if (!$server) {
abort(500, 'fail'); abort(500, 'fail');
} }
Redis::set('server_last_check_at_' . $server->id, time()); Cache::put('server_last_check_at_' . $server->id, time());
$users = User::whereIn('group_id', json_decode($server->group_id)) $serverService = new ServerService();
->select([ $users = $serverService->getAvailableUsers(json_decode($server->group_id));
'id',
'email',
't',
'u',
'd',
'transfer_enable',
'enable',
'v2ray_uuid',
'v2ray_alter_id',
'v2ray_level'
])
->get();
$result = []; $result = [];
foreach ($users as $user) { foreach ($users as $user) {
$user->v2ray_user = [ $user->v2ray_user = [
"uuid" => $user->v2ray_uuid, "uuid" => $user->v2ray_uuid,
"email" => sprintf("%s@v2panel.user", $user->v2ray_uuid), "email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
"alter_id" => $user->v2ray_alter_id, "alter_id" => $user->v2ray_alter_id,
"level" => $user->v2ray_level, "level" => $user->v2ray_level,
]; ];
@ -56,26 +57,27 @@ class DeepbworkController extends Controller
} }
// 后端提交数据 // 后端提交数据
public function submit (Request $request) { public function submit(Request $request)
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input')); {
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id')); $server = Server::find($request->input('node_id'));
if (!$server) { if (!$server) {
return response([ return response([
'ret' => 1, 'ret' => 1,
'msg' => 'ok' 'msg' => 'ok'
]); ]);
} }
$data = file_get_contents('php://input'); $data = file_get_contents('php://input');
$data = json_decode($data, true); $data = json_decode($data, true);
foreach ($data as $item) { foreach ($data as $item) {
$u = $item['u'] * $server->rate; $u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate; $d = $item['d'] * $server->rate;
$user = User::find($item['user_id']); $user = User::find($item['user_id']);
$user->t = time(); $user->t = time();
$user->u = $user->u + $u; $user->u = $user->u + $u;
$user->d = $user->d + $d; $user->d = $user->d + $d;
$user->save(); $user->save();
$serverLog = new ServerLog(); $serverLog = new ServerLog();
$serverLog->user_id = $item['user_id']; $serverLog->user_id = $item['user_id'];
$serverLog->server_id = $request->input('node_id'); $serverLog->server_id = $request->input('node_id');
@ -84,50 +86,28 @@ class DeepbworkController extends Controller
$serverLog->rate = $server->rate; $serverLog->rate = $server->rate;
$serverLog->save(); $serverLog->save();
} }
return response([ return response([
'ret' => 1, 'ret' => 1,
'msg' => 'ok' 'msg' => 'ok'
]); ]);
} }
// 后端获取配置 // 后端获取配置
public function config (Request $request) { public function config(Request $request)
{
$nodeId = $request->input('node_id'); $nodeId = $request->input('node_id');
$localPort = $request->input('local_port'); $localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) { if (empty($nodeId) || empty($localPort)) {
abort(1000, '参数错误'); abort(500, '参数错误');
} }
$server = Server::find($nodeId); $serverService = new ServerService();
if (!$server) { try {
abort(1001, '节点不存在'); $json = $serverService->getConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
} }
$json = json_decode(self::SERVER_CONFIG);
$json->inboundDetour[0]->port = (int)$localPort;
$json->inbound->port = (int)$server->server_port;
$json->inbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp': $json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp': $json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws': $json->inbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http': $json->inbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket': $json->inbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic': $json->inbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ((int)$server->tls) {
$json->inbound->streamSettings->security = "tls";
$tls = (object) array("certificateFile" => "/home/v2ray.crt", "keyFile" => "/home/v2ray.key");
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
}
die(json_encode($json, JSON_UNESCAPED_UNICODE)); die(json_encode($json, JSON_UNESCAPED_UNICODE));
} }
} }

View File

@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
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;
class PoseidonController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
// 后端获取用户
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('server_last_check_at_' . $server->id, time());
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [
"uuid" => $user->v2ray_uuid,
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
"alter_id" => $user->v2ray_alter_id,
"level" => $user->v2ray_level,
];
unset($user['v2ray_uuid']);
unset($user['v2ray_alter_id']);
unset($user['v2ray_level']);
array_push($result, $user);
}
return $this->success($result);
}
// 后端提交数据
public function submit(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
return $this->error("server could not be found", 404);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$user = User::find($item['user_id']);
$user->t = time();
$user->u = $user->u + $u;
$user->d = $user->d + $d;
$user->save();
$serverLog = new ServerLog();
$serverLog->user_id = $item['user_id'];
$serverLog->server_id = $request->input('node_id');
$serverLog->u = $item['u'];
$serverLog->d = $item['d'];
$serverLog->rate = $server->rate;
$serverLog->save();
}
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->getConfig($nodeId, $localPort);
$json->poseidon = [
'license_key' => (string)config('v2board.server_license'),
];
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) {
return response([
'msg' => 'ok',
'data' => $data,
]);
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
class TutorialController extends Controller
{
public function getSubscribeUrl (Request $request) {
$user = User::find($request->session()->get('id'));
return response([
'data' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']
]
]);
}
public function getAppleID (Request $request) {
$user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) {
return response([
'data' => [
]
]);
}
return response([
'data' => [
'apple_id' => config('v2board.apple_id'),
'apple_id_password' => config('v2board.apple_id_password')
]
]);
}
}

View File

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

View File

@ -1,9 +1,9 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use App\Models\Order; use App\Models\Order;
use App\Models\InviteCode; use App\Models\InviteCode;
@ -11,7 +11,8 @@ use App\Utils\Helper;
class InviteController extends Controller class InviteController extends Controller
{ {
public function save (Request $request) { public function save(Request $request)
{
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) { if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
abort(500, '已达到创建数量上限'); abort(500, '已达到创建数量上限');
} }
@ -23,7 +24,8 @@ class InviteController extends Controller
]); ]);
} }
public function details (Request $request) { public function details(Request $request)
{
return response([ return response([
'data' => Order::where('invite_user_id', $request->session()->get('id')) 'data' => Order::where('invite_user_id', $request->session()->get('id'))
->where('status', 3) ->where('status', 3)
@ -38,11 +40,12 @@ class InviteController extends Controller
]); ]);
} }
public function fetch (Request $request) { public function fetch(Request $request)
{
$codes = InviteCode::where('user_id', $request->session()->get('id')) $codes = InviteCode::where('user_id', $request->session()->get('id'))
->where('status', 0) ->where('status', 0)
->get(); ->get();
$commission_rate = config('v2board.invite_commission'); $commission_rate = config('v2board.invite_commission', 10);
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if ($user->commission_rate) { if ($user->commission_rate) {
$commission_rate = $user->commission_rate; $commission_rate = $user->commission_rate;
@ -52,7 +55,7 @@ class InviteController extends Controller
(int)User::where('invite_user_id', $request->session()->get('id'))->count(), (int)User::where('invite_user_id', $request->session()->get('id'))->count(),
//有效的佣金 //有效的佣金
(int)Order::where('status', 3) (int)Order::where('status', 3)
->where('commission_status', 1) ->where('commission_status', 2)
->where('invite_user_id', $request->session()->get('id')) ->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'), ->sum('commission_balance'),
//确认中的佣金 //确认中的佣金
@ -61,7 +64,9 @@ class InviteController extends Controller
->where('invite_user_id', $request->session()->get('id')) ->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'), ->sum('commission_balance'),
//佣金比例 //佣金比例
(int)$commission_rate (int)$commission_rate,
//可用佣金
(int)$user->commission_balance
]; ];
return response([ return response([
'data' => [ 'data' => [

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Notice;
use App\Utils\Helper;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = 5;
$model = Notice::orderBy('created_at', 'DESC');
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
}

View File

@ -0,0 +1,526 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use App\Services\OrderService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Models\Coupon;
use App\Utils\Helper;
use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function fetch(Request $request)
{
$model = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
if ($request->input('status') !== null) {
$model->where('status', $request->input('status'));
}
$order = $model->get();
$plan = Plan::get();
for ($i = 0; $i < count($order); $i++) {
for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
}
}
return response([
'data' => $order
]);
}
public function details(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) {
abort(500, '订阅不存在');
}
return response([
'data' => $order
]);
}
private function isNotCompleteOrderByUserId($userId)
{
$order = Order::whereIn('status', [0, 1])
->where('user_id', $userId)
->first();
if (!$order) {
return false;
}
return true;
}
// surplus value
private function getSurplusValue(User $user)
{
$plan = Plan::find($user->plan_id);
if ($user->expired_at === NULL) {
return $this->getSurplusValueByOneTime($user, $plan);
} else {
return $this->getSurplusValueByCycle($user, $plan);
}
}
private function getSurplusValueByOneTime(User $user, Plan $plan)
{
$trafficUnitPrice = $plan->onetime_price / $plan->transfer_enable;
if ($user->discount && $trafficUnitPrice) {
$trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100);
}
$notUsedTrafficPrice = $plan->transfer_enable - (($user->u + $user->d) / 1073741824);
$result = $trafficUnitPrice * $notUsedTrafficPrice;
return $result > 0 ? $result : 0;
}
private function getSurplusValueByCycle(User $user, Plan $plan)
{
$price = 0;
if ($plan->month_price) {
$price = $plan->month_price / (31536000 / 12);
} else if ($plan->quarter_price) {
$price = $plan->quarter_price / (31536000 / 4);
} else if ($plan->half_year_price) {
$price = $plan->half_year_price / (31536000 / 2);
} else if ($plan->year_price) {
$price = $plan->year_price / 31536000;
}
// exclude discount
if ($user->discount && $price) {
$price = $price - ($price * $user->discount / 100);
}
$remainingDay = $user->expired_at - time();
$result = $remainingDay * $price;
return $result > 0 ? $result : 0;
}
public function save(OrderSave $request)
{
if ($this->isNotCompleteOrderByUserId($request->session()->get('id'))) {
abort(500, '存在未付款订单,请取消后再试');
}
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
abort(500, '该订阅已售罄');
}
if (!$plan->renew && $user->plan_id == $plan->id) {
abort(500, '该订阅无法续费,请更换其他订阅');
}
if ($plan[$request->input('cycle')] === NULL) {
abort(500, '该订阅周期无法进行购买,请选择其他周期');
}
if ($request->input('coupon_code')) {
$coupon = Coupon::where('code', $request->input('coupon_code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
}
}
DB::beginTransaction();
$order = new Order();
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
// coupon start
if (isset($coupon)) {
switch ($coupon->type) {
case 1:
$order->discount_amount = $coupon->value;
break;
case 2:
$order->discount_amount = $order->total_amount * ($coupon->value / 100);
break;
}
if ($coupon->limit_use !== NULL) {
$coupon->limit_use = $coupon->limit_use - 1;
if (!$coupon->save()) {
DB::rollback();
abort(500, '优惠券使用失败');
}
}
}
// coupon complete
// discount start
if ($user->discount) {
$order->discount_amount = $order->discount_amount + ($order->total_amount * ($user->discount / 100));
}
// discount end
$order->total_amount = $order->total_amount - $order->discount_amount;
// renew and change subscribe process
if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单');
$order->type = 3;
$order->surplus_amount = $this->getSurplusValue($user);
if ($order->surplus_amount >= $order->total_amount) {
$order->refund_amount = $order->surplus_amount - $order->total_amount;
$order->total_amount = 0;
} else {
$order->total_amount = $order->total_amount - $order->surplus_amount;
}
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
// invite process
if ($user->invite_user_id && $order->total_amount > 0) {
$order->invite_user_id = $user->invite_user_id;
$commissionFirstTime = (int)config('v2board.commission_first_time_enable', 1);
if (!$commissionFirstTime || ($commissionFirstTime && !Order::where('user_id', $user->id)->where('status', 3)->first())) {
$inviter = User::find($user->invite_user_id);
if ($inviter && $inviter->commission_rate) {
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
} else {
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
}
}
}
// use balance
if ($user->balance && $order->total_amount > 0) {
$remainingBalance = $user->balance - $order->total_amount;
$userService = new UserService();
if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
DB::rollBack();
abort(500, '余额不足');
}
$order->balance_amount = $order->total_amount;
$order->total_amount = 0;
} else {
if (!$userService->addBalance($order->user_id, - $user->balance)) {
DB::rollBack();
abort(500, '余额不足');
}
$order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance;
}
}
if (!$order->save()) {
DB::rollback();
abort(500, '订单创建失败');
}
DB::commit();
return response([
'data' => $order->trade_no
]);
}
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或已支付');
}
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
$order->save();
exit();
}
switch ($method) {
// return type => 0: QRCode / 1: URL
case 0:
// alipayF2F
if (!(int)config('v2board.alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->alipayF2F($tradeNo, $order->total_amount)
]);
case 2:
// stripeAlipay
if (!(int)config('v2board.stripe_alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->stripeAlipay($order)
]);
case 3:
// stripeWepay
if (!(int)config('v2board.stripe_wepay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->stripeWepay($order)
]);
case 4:
// bitpayX
if (!(int)config('v2board.bitpayx_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->bitpayX($order)
]);
case 5:
if (!(int)config('v2board.paytaro_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->payTaro($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
return response([
'data' => $order->status
]);
}
public function getPaymentMethod()
{
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
$alipayF2F->name = '支付宝';
$alipayF2F->method = 0;
$alipayF2F->icon = 'alipay';
array_push($data, $alipayF2F);
}
if ((int)config('v2board.stripe_alipay_enable')) {
$stripeAlipay = new \StdClass();
$stripeAlipay->name = '支付宝';
$stripeAlipay->method = 2;
$stripeAlipay->icon = 'alipay';
array_push($data, $stripeAlipay);
}
if ((int)config('v2board.stripe_wepay_enable')) {
$stripeWepay = new \StdClass();
$stripeWepay->name = '微信';
$stripeWepay->method = 3;
$stripeWepay->icon = 'wechat';
array_push($data, $stripeWepay);
}
if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass();
$bitpayX->name = '聚合支付';
$bitpayX->method = 4;
$bitpayX->icon = 'wallet';
array_push($data, $bitpayX);
}
if ((int)config('v2board.paytaro_enable')) {
$obj = new \StdClass();
$obj->name = '聚合支付';
$obj->method = 5;
$obj->icon = 'wallet';
array_push($data, $obj);
}
return response([
'data' => $data
]);
}
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数有误');
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) {
abort(500, '只可以取消待支付订单');
}
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '取消失败');
}
return response([
'data' => true
]);
}
private function alipayF2F($tradeNo, $totalAmount)
{
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
$gateway->setPrivateKey(config('v2board.alipay_privkey')); // 可以是路径,也可以是密钥内容
$gateway->setAlipayPublicKey(config('v2board.alipay_pubkey')); // 可以是路径,也可以是密钥内容
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase();
$request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100
]);
/** @var \Omnipay\Alipay\Responses\AopTradePreCreateResponse $response */
$response = $request->send();
$result = $response->getAlipayResponse();
if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']);
}
// 获取收款二维码内容
return $response->getQrCode();
}
private function stripeAlipay($order)
{
$currency = config('v2board.stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => $currency,
'type' => 'alipay',
'statement_descriptor' => $order->trade_no,
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败');
}
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
return $source['redirect']['url'];
}
private function stripeWepay($order)
{
$currency = config('v2board.stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => $currency,
'type' => 'wechat',
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
return $source['wechat']['qr_code_url'];
}
private function bitpayX($order)
{
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'merchant_order_id' => $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
];
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params);
// Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false;
}
private function payTaro($order)
{
$payTaro = new PayTaro(config('v2board.paytaro_app_id'), config('v2board.paytaro_app_secret'));
$result = $payTaro->pay([
'app_id' => config('v2board.paytaro_app_id'),
'out_trade_no' => $order->trade_no,
'total_amount' => $order->total_amount,
'notify_url' => url('/api/v1/guest/order/payTaroNotify'),
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]);
return $result;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Plan;
class PlanController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))
->first();
if (!$plan) {
abort(500, '该订阅不存在');
}
return response([
'data' => $plan
]);
}
$plan = Plan::where('show', 1)
->get();
return response([
'data' => $plan
]);
}
}

View File

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

View File

@ -1,10 +1,10 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Http\Requests\TicketSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\User\TicketSave;
use Illuminate\Http\Request;
use App\Models\Ticket; use App\Models\Ticket;
use App\Models\TicketMessage; use App\Models\TicketMessage;
use App\Utils\Helper; use App\Utils\Helper;
@ -12,7 +12,8 @@ use Illuminate\Support\Facades\DB;
class TicketController extends Controller class TicketController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
if ($request->input('id')) { if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id')) $ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id')) ->where('user_id', $request->session()->get('id'))
@ -47,8 +48,12 @@ class TicketController extends Controller
]); ]);
} }
public function save (TicketSave $request) { public function save(TicketSave $request)
{
DB::beginTransaction(); DB::beginTransaction();
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
abort(500, '存在其他工单尚未处理');
}
$ticket = Ticket::create(array_merge($request->only([ $ticket = Ticket::create(array_merge($request->only([
'subject', 'subject',
'level' 'level'
@ -75,7 +80,8 @@ class TicketController extends Controller
]); ]);
} }
public function reply (Request $request) { public function reply(Request $request)
{
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数错误'); abort(500, '参数错误');
} }
@ -112,7 +118,8 @@ class TicketController extends Controller
} }
public function close (Request $request) { public function close(Request $request)
{
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数错误'); abort(500, '参数错误');
} }
@ -131,7 +138,8 @@ class TicketController extends Controller
]); ]);
} }
private function getLastMessage ($ticketId) { private function getLastMessage($ticketId)
{
return TicketMessage::where('ticket_id', $ticketId) return TicketMessage::where('ticket_id', $ticketId)
->orderBy('id', 'DESC') ->orderBy('id', 'DESC')
->first(); ->first();

View File

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

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Http\Requests\UserUpdate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword;
use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use App\Models\Server; use App\Models\Server;
@ -15,24 +16,26 @@ use App\Models\ServerLog;
class UserController extends Controller class UserController extends Controller
{ {
public function logout (Request $request) { public function logout(Request $request)
{
$request->session()->flush();
return response([ return response([
'data' => $request->session()->flush() 'data' => true
]); ]);
} }
public function changePassword (Request $request) { public function changePassword(UserChangePassword $request)
if (empty($request->input('old_password'))) { {
abort(500, '旧密码不能为空');
}
if (empty($request->input('new_password'))) {
abort(500, '新密码不能为空');
}
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if (!password_verify($request->input('old_password'), $user->password)) { if (!Helper::multiPasswordVerify(
$user->password_algo,
$request->input('old_password'),
$user->password)
) {
abort(500, '旧密码有误'); abort(500, '旧密码有误');
} }
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT); $user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) { if (!$user->save()) {
abort(500, '保存失败'); abort(500, '保存失败');
} }
@ -41,20 +44,25 @@ class UserController extends Controller
'data' => true 'data' => true
]); ]);
} }
public function info (Request $request) { public function info(Request $request)
{
$user = User::where('id', $request->session()->get('id')) $user = User::where('id', $request->session()->get('id'))
->select([ ->select([
'email', 'email',
'transfer_enable',
'last_login_at', 'last_login_at',
'created_at', 'created_at',
'enable', 'banned',
'is_admin', 'is_admin',
'remind_expire', 'remind_expire',
'remind_traffic', 'remind_traffic',
'expired_at', 'expired_at',
'balance', 'balance',
'commission_balance' 'commission_balance',
'plan_id',
'discount',
'commission_rate'
]) ])
->first(); ->first();
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon'; $user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
@ -63,7 +71,8 @@ class UserController extends Controller
]); ]);
} }
public function getStat (Request $request) { public function getStat(Request $request)
{
$stat = [ $stat = [
Order::where('status', 0) Order::where('status', 0)
->where('user_id', $request->session()->get('id')) ->where('user_id', $request->session()->get('id'))
@ -79,7 +88,8 @@ class UserController extends Controller
]); ]);
} }
public function getSubscribe (Request $request) { public function getSubscribe(Request $request)
{
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if ($user->plan_id) { if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id); $user['plan'] = Plan::find($user->plan_id);
@ -92,8 +102,9 @@ class UserController extends Controller
'data' => $user 'data' => $user
]); ]);
} }
public function resetSecurity (Request $request) { public function resetSecurity(Request $request)
{
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
$user->v2ray_uuid = Helper::guid(true); $user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid(); $user->token = Helper::guid();
@ -101,21 +112,24 @@ class UserController extends Controller
abort(500, '重置失败'); abort(500, '重置失败');
} }
return response([ return response([
'data' => true 'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
]); ]);
} }
public function update (UserUpdate $request) { public function update(UserUpdate $request)
{
$updateData = $request->only([ $updateData = $request->only([
'remind_expire', 'remind_expire',
'remind_traffic' 'remind_traffic'
]); ]);
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if (!$user) { if (!$user) {
abort(500, '该用户不存在'); abort(500, '该用户不存在');
} }
if (!$user->update($updateData)) { try {
$user->update($updateData);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }

View File

@ -43,7 +43,7 @@ class Kernel extends HttpKernel
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\ForceJson::class, \App\Http\Middleware\ForceJson::class,
\App\Http\Middleware\CORS::class, \App\Http\Middleware\CORS::class,
'throttle:60,1', 'throttle:120,1',
'bindings', 'bindings',
], ],
]; ];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\User;
class Server
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
return $next($request);
}
}

View File

@ -9,12 +9,19 @@ class User
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if ($request->input('access_token')) {
$user = \App\Models\User::where('token', $request->input('access_token'))->first();
if ($user) {
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
}
}
if (!$request->session()->get('id')) { if (!$request->session()->get('id')) {
abort(403, '未登录或登陆已过期'); abort(403, '未登录或登陆已过期');
} }

View File

@ -6,40 +6,64 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest class ConfigSave extends FormRequest
{ {
public static function filter() { CONST RULES = [
return [ // invite & commission
'invite_force', 'safe_mode_enable' => 'in:0,1',
'invite_commission', 'invite_force' => 'in:0,1',
'invite_gen_limit', 'invite_commission' => 'integer',
'invite_never_expire', 'invite_gen_limit' => 'integer',
'stop_register', 'invite_never_expire' => 'in:0,1',
'email_verify', 'commission_first_time_enable' => 'in:0,1',
'app_name', // site
'app_url', 'stop_register' => 'in:0,1',
'subscribe_url', 'email_verify' => 'in:0,1',
'plan_update_fee', 'app_name' => '',
'plan_is_update', 'app_description' => '',
// server 'app_url' => 'nullable|url',
'server_token', 'subscribe_url' => 'nullable|url',
// alipay 'try_out_enable' => 'in:0,1',
'alipay_enable', 'try_out_plan_id' => 'integer',
'alipay_appid', 'try_out_hour' => 'numeric',
'alipay_pubkey', 'email_whitelist_enable' => 'in:0,1',
'alipay_privkey', 'email_whitelist_suffix' => '',
// stripe // subscribe
'stripe_sk_live', 'plan_change_enable' => 'in:0,1',
'stripe_pk_live', 'reset_traffic_method' => 'in:0,1',
'stripe_alipay_enable', 'renew_reset_traffic_enable' => 'in:0,1',
'stripe_wepay_enable', // server
'stripe_webhook_key', 'server_token' => 'nullable|min:16',
// bitpayx, 'server_license' => 'nullable',
'bitpayx_enable', // alipay
'bitpayx_appsecret', 'alipay_enable' => 'in:0,1',
// tutorial 'alipay_appid' => 'nullable|integer|min:16',
'apple_id', 'alipay_pubkey' => 'max:2048',
'apple_id_password' 'alipay_privkey' => 'max:2048',
]; // stripe
} 'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
'stripe_currency' => 'in:hkd,usd,sgd,eur,gbp',
// bitpayx
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// paytaro
'paytaro_enable' => 'in:0,1',
'paytaro_app_id' => '',
'paytaro_app_secret' => '',
// frontend
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black',
'frontend_background_url' => 'nullable|url',
// tutorial
'apple_id' => 'email',
'apple_id_password' => '',
// email
'email_template' => ''
];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -47,37 +71,15 @@ class ConfigSave extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_update_fee' => 'numeric',
'plan_is_update' => 'in:0,1',
// server
'server_token' => 'min:16',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
// bitpayx
'bitpayx_enable' => 'in:0,1',
// tutorial
'apple_id' => 'email'
];
} }
public function messages() public function messages()
{ {
// illiteracy prompt
return [ return [
'app_url.url' => '站点URL格式不正确必须携带http(s)://',
'subscribe_url.url' => '订阅URL格式不正确必须携带http(s)://'
]; ];
} }
} }

View File

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

View File

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

View File

@ -16,10 +16,10 @@ class NoticeSave extends FormRequest
return [ return [
'title' => 'required', 'title' => 'required',
'content' => 'required', 'content' => 'required',
'img_url' => 'url' 'img_url' => 'nullable|url'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

View File

@ -15,15 +15,15 @@ class OrderUpdate extends FormRequest
{ {
return [ return [
'status' => 'in:0,1,2,3', 'status' => 'in:0,1,2,3',
'commission_status' => 'in:0,1' 'commission_status' => 'in:0,1,2'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [
'status.in' => '销售状态格式不正确', 'status.in' => '销售状态格式不正确',
'commission_status.in' => '续费状态格式不正确' 'commission_status.in' => '佣金状态格式不正确'
]; ];
} }
} }

View File

@ -6,6 +6,17 @@ use Illuminate\Foundation\Http\FormRequest;
class PlanSave extends FormRequest class PlanSave extends FormRequest
{ {
CONST RULES = [
'name' => 'required',
'content' => '',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer'
];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -13,27 +24,22 @@ class PlanSave extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'name' => 'required',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'required|numeric',
'quarter_price' => 'required|numeric',
'half_year_price' => 'required|numeric',
'year_price' => 'required|numeric'
];
} }
public function messages() public function messages()
{ {
return [ return [
'name.required' => '套餐名称不能为空', 'name.required' => '套餐名称不能为空',
'type.required' => '套餐类型不能为空',
'type.in' => '套餐类型格式有误',
'group_id.required' => '权限组不能为空', 'group_id.required' => '权限组不能为空',
'transfer_enable.required' => '流量不能为空', 'transfer_enable.required' => '流量不能为空',
'month_price.required' => '月付金额不能为空', 'month_price.integer' => '月付金额格式有误',
'quarter_price.required' => '季付金额不能为空', 'quarter_price.integer' => '季付金额格式有误',
'half_year_price.required' => '半年付金额不能为空', 'half_year_price.integer' => '半年付金额格式有误',
'year_price.required' => '年付金额不能为空' 'year_price.integer' => '年付金额格式有误',
'onetime_price.integer' => '一次性金额有误'
]; ];
} }
} }

View File

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

View File

@ -6,6 +6,23 @@ use Illuminate\Foundation\Http\FormRequest;
class ServerSave extends FormRequest class ServerSave extends FormRequest
{ {
CONST RULES = [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
'networkSettings' => '',
'ruleSettings' => '',
'tlsSettings' => '',
'dnsSettings' => ''
];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -13,25 +30,16 @@ class ServerSave extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'name' => 'required',
'group_id' => 'required|array',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic'
];
} }
public function messages() public function messages()
{ {
return [ return [
'name.required' => '节点名称不能为空', 'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空', 'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确', 'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空', 'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空', 'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空', 'server_port.required' => '后端服务端口不能为空',

View File

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

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends FormRequest
{
CONST RULES = [
'title' => 'required',
// 1:windows 2:macos 3:ios 4:android 5:linux 6:router
'category_id' => 'required|in:1,2,3,4,5,6',
'steps' => 'required'
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return self::RULES;
}
public function messages()
{
return [
'title.required' => '标题不能为空',
'category_id.required' => '分类不能为空',
'category_id.in' => '分类格式不正确',
'steps.required' => '教程步骤不能为空'
];
}
}

View File

@ -6,6 +6,21 @@ use Illuminate\Foundation\Http\FormRequest;
class UserUpdate extends FormRequest class UserUpdate extends FormRequest
{ {
CONST RULES = [
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'is_admin' => 'required|in:0,1',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_balance' => 'integer'
];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -13,17 +28,9 @@ class UserUpdate extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'email' => 'required|email',
'transfer_enable' => 'numeric',
'expired_at' => 'integer',
'banned' => 'required|in:0,1',
'is_admin' => 'required|in:0,1',
'plan_id' => 'integer',
'commission_rate' => 'nullable|integer|min:0|max:100'
];
} }
public function messages() public function messages()
{ {
return [ return [
@ -39,7 +46,15 @@ class UserUpdate extends FormRequest
'commission_rate.integer' => '推荐返利比例格式不正确', 'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确', 'commission_rate.nullable' => '推荐返利比例格式不正确',
'commission_rate.min' => '推荐返利比例最小为0', 'commission_rate.min' => '推荐返利比例最小为0',
'commission_rate.max' => '推荐返利比例最大为100' 'commission_rate.max' => '推荐返利比例最大为100',
'discount.integer' => '专属折扣比例格式不正确',
'discount.nullable' => '专属折扣比例格式不正确',
'discount.min' => '专属折扣比例最小为0',
'discount.max' => '专属折扣比例最大为100',
'u.integer' => '上行流量格式不正确',
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确'
]; ];
} }
} }

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class ForgetIndex extends FormRequest class AuthForget extends FormRequest
{ {
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
@ -19,12 +19,12 @@ class ForgetIndex extends FormRequest
'email_code' => 'required' 'email_code' => 'required'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [
'email.required' => '邮箱不能为空', 'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确', 'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空', 'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数', 'password.min' => '密码必须大于8位数',
'email_code.required' => '邮箱验证码不能为空' 'email_code.required' => '邮箱验证码不能为空'

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class LoginIndex extends FormRequest class AuthLogin extends FormRequest
{ {
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
@ -18,12 +18,12 @@ class LoginIndex extends FormRequest
'password' => 'required|min:8' 'password' => 'required|min:8'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [
'email.required' => '邮箱不能为空', 'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确', 'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空', 'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数' 'password.min' => '密码必须大于8位数'
]; ];

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class RegisterIndex extends FormRequest class AuthRegister extends FormRequest
{ {
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
@ -18,12 +18,12 @@ class RegisterIndex extends FormRequest
'password' => 'required|min:8' 'password' => 'required|min:8'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [
'email.required' => '邮箱不能为空', 'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确', 'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空', 'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数' 'password.min' => '密码必须大于8位数'
]; ];

View File

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

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@ -15,10 +15,10 @@ class OrderSave extends FormRequest
{ {
return [ return [
'plan_id' => 'required', 'plan_id' => 'required',
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price' 'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@ -19,7 +19,7 @@ class TicketSave extends FormRequest
'message' => 'required' 'message' => 'required'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;
class UserChangePassword extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'old_password' => 'required',
'new_password' => 'required|min:8'
];
}
public function messages()
{
return [
'old_password.required' => '旧密码不能为空',
'new_password.required' => '新密码不能为空',
'new_password.min' => '密码必须大于8位数'
];
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@ -18,7 +18,7 @@ class UserUpdate extends FormRequest
'remind_traffic' => 'in:0,1' 'remind_traffic' => 'in:0,1'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class AdminRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'admin',
'middleware' => 'admin'
], function ($router) {
// Config
$router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
$router->post('/config/save', 'Admin\\ConfigController@save');
$router->get ('/config/getEmailTemplate', 'Admin\\ConfigController@getEmailTemplate');
// Plan
$router->get ('/plan/fetch', 'Admin\\PlanController@fetch');
$router->post('/plan/save', 'Admin\\PlanController@save');
$router->post('/plan/drop', 'Admin\\PlanController@drop');
$router->post('/plan/update', 'Admin\\PlanController@update');
// Server
$router->get ('/server/fetch', 'Admin\\ServerController@fetch');
$router->post('/server/save', 'Admin\\ServerController@save');
$router->get ('/server/group/fetch', 'Admin\\ServerController@groupFetch');
$router->post('/server/group/save', 'Admin\\ServerController@groupSave');
$router->post('/server/group/drop', 'Admin\\ServerController@groupDrop');
$router->post('/server/drop', 'Admin\\ServerController@drop');
$router->post('/server/update', 'Admin\\ServerController@update');
$router->post('/server/copy', 'Admin\\ServerController@copy');
$router->post('/server/viewConfig', 'Admin\\ServerController@viewConfig');
// Order
$router->get ('/order/fetch', 'Admin\\OrderController@fetch');
$router->post('/order/repair', 'Admin\\OrderController@repair');
$router->post('/order/update', 'Admin\\OrderController@update');
// User
$router->get ('/user/fetch', 'Admin\\UserController@fetch');
$router->post('/user/update', 'Admin\\UserController@update');
$router->get ('/user/getUserInfoById', 'Admin\\UserController@getUserInfoById');
// Stat
$router->get ('/stat/getOverride', 'Admin\\StatController@getOverride');
// Notice
$router->get ('/notice/fetch', 'Admin\\NoticeController@fetch');
$router->post('/notice/save', 'Admin\\NoticeController@save');
$router->post('/notice/update', 'Admin\\NoticeController@update');
$router->post('/notice/drop', 'Admin\\NoticeController@drop');
// Ticket
$router->get ('/ticket/fetch', 'Admin\\TicketController@fetch');
$router->post('/ticket/reply', 'Admin\\TicketController@reply');
$router->post('/ticket/close', 'Admin\\TicketController@close');
// Mail
$router->post('/mail/send', 'Admin\\MailController@send');
// Coupon
$router->get ('/coupon/fetch', 'Admin\\CouponController@fetch');
$router->post('/coupon/save', 'Admin\\CouponController@save');
$router->post('/coupon/drop', 'Admin\\CouponController@drop');
// Tutorial
$router->get ('/tutorial/fetch', 'Admin\\TutorialController@fetch');
$router->post('/tutorial/save', 'Admin\\TutorialController@save');
$router->post('/tutorial/show', 'Admin\\TutorialController@show');
$router->post('/tutorial/drop', 'Admin\\TutorialController@drop');
});
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class ClientRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'client',
'middleware' => 'client'
], function ($router) {
// Client
$router->get('/subscribe', 'Client\\ClientController@subscribe');
// App
$router->get('/app/data', 'Client\\AppController@data');
$router->get('/app/config', 'Client\\AppController@config');
});
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class GuestRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'guest'
], function ($router) {
// Plan
$router->get ('/plan/fetch', 'Guest\\PlanController@fetch');
// Order
$router->post('/order/alipayNotify', 'Guest\\OrderController@alipayNotify');
$router->post('/order/stripeNotify', 'Guest\\OrderController@stripeNotify');
$router->post('/order/bitpayXNotify', 'Guest\\OrderController@bitpayXNotify');
$router->post('/order/payTaroNotify', 'Guest\\OrderController@payTaroNotify');
});
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class PassportRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'passport'
], function ($router) {
// TODO: 1.1.1 abolish
$router->post('/login', 'Passport\\AuthController@login');
// Auth
$router->post('/auth/register', 'Passport\\AuthController@register');
$router->post('/auth/login', 'Passport\\AuthController@login');
$router->get ('/auth/token2Login', 'Passport\\AuthController@token2Login');
$router->get ('/auth/check', 'Passport\\AuthController@check');
$router->post('/auth/forget', 'Passport\\AuthController@forget');
// Comm
$router->get ('/comm/config', 'Passport\\CommController@config');
$router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');
$router->post('/comm/pv', 'Passport\\CommController@pv');
});
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class ServerRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'server'
], function ($router) {
$router->any('/{class}/{action}', function($class, $action) {
$ctrl = \App::make("\\App\\Http\\Controllers\\Server\\" . ucfirst($class) . "Controller");
return \App::call([$ctrl, $action]);
});
});
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class UserRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'user',
'middleware' => 'user'
], function ($router) {
// User
$router->get ('/resetSecurity', 'User\\UserController@resetSecurity');
$router->get ('/logout', 'User\\UserController@logout');
$router->get ('/info', 'User\\UserController@info');
$router->post('/changePassword', 'User\\UserController@changePassword');
$router->post('/update', 'User\\UserController@update');
$router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
$router->get ('/getStat', 'User\\UserController@getStat');
// Order
$router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout');
$router->get ('/order/check', 'User\\OrderController@check');
$router->get ('/order/details', 'User\\OrderController@details');
$router->get ('/order/fetch', 'User\\OrderController@fetch');
$router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');
$router->post('/order/cancel', 'User\\OrderController@cancel');
// Plan
$router->get ('/plan/fetch', 'User\\PlanController@fetch');
// Invite
$router->get ('/invite/save', 'User\\InviteController@save');
$router->get ('/invite/fetch', 'User\\InviteController@fetch');
$router->get ('/invite/details', 'User\\InviteController@details');
// Tutorial
$router->get ('/tutorial/getSubscribeUrl', 'User\\TutorialController@getSubscribeUrl');
$router->get ('/tutorial/getAppleID', 'User\\TutorialController@getAppleID');
$router->get ('/tutorial/fetch', 'User\\TutorialController@fetch');
// Notice
$router->get ('/notice/fetch', 'User\\NoticeController@fetch');
// Ticket
$router->post('/ticket/reply', 'User\\TicketController@reply');
$router->post('/ticket/close', 'User\\TicketController@close');
$router->post('/ticket/save', 'User\\TicketController@save');
$router->get ('/ticket/fetch', 'User\\TicketController@fetch');
// Server
$router->get ('/server/fetch', 'User\\ServerController@fetch');
$router->get ('/server/log/fetch', 'User\\ServerController@logFetch');
// Coupon
$router->post('/coupon/check', 'User\\CouponController@check');
});
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
class SendEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($params)
{
$this->params = $params;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$params = $this->params;
$email = $params['email'];
$subject = $params['subject'];
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use($email, $subject) {
$message->to($email)->subject($subject);
}
);
}
}

60
app/Jobs/SendEmailJob.php Normal file
View File

@ -0,0 +1,60 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use App\Models\MailLog;
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($params)
{
$this->delay(now()->addSecond(2));
$this->onQueue('send_email');
$this->params = $params;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$params = $this->params;
$email = $params['email'];
$subject = $params['subject'];
$params['template_name'] = 'mail.' . config('v2board.email_template', 'default') . '.' . $params['template_name'];
try {
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use ($email, $subject) {
$message->to($email)->subject($subject);
}
);
} catch (\Exception $e) {
$error = $e->getMessage();
}
MailLog::create([
'email' => $params['email'],
'subject' => $params['subject'],
'template_name' => $params['template_name'],
'error' => isset($error) ? $error : NULL
]);
}
}

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

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

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

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

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

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

View File

@ -36,7 +36,6 @@ class RouteServiceProvider extends ServiceProvider
public function map() public function map()
{ {
$this->mapApiRoutes(); $this->mapApiRoutes();
$this->mapWebRoutes(); $this->mapWebRoutes();
// //
@ -52,8 +51,8 @@ class RouteServiceProvider extends ServiceProvider
protected function mapWebRoutes() protected function mapWebRoutes()
{ {
Route::middleware('web') Route::middleware('web')
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/web.php')); ->group(base_path('routes/web.php'));
} }
/** /**
@ -65,9 +64,14 @@ class RouteServiceProvider extends ServiceProvider
*/ */
protected function mapApiRoutes() protected function mapApiRoutes()
{ {
Route::prefix('api') Route::group([
->middleware('api') 'prefix' => '/api/v1',
->namespace($this->namespace) 'middleware' => 'api',
->group(base_path('routes/api.php')); 'namespace' => $this->namespace
], function ($router) {
foreach (glob(app_path('Http//Routes') . '/*.php') as $file) {
$this->app->make('App\\Http\\Routes\\' . basename($file, '.php'))->map($router);
}
});
} }
} }

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderService
{
public $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function cancel():bool
{
$order = $this->order;
DB::beginTransaction();
$order->status = 2;
if (!$order->save()) {
DB::rollBack();
return false;
}
if ($order->balance_amount) {
$userService = new UserService();
if (!$userService->addBalance($order->user_id, $order->balance_amount)) {
DB::rollBack();
return false;
}
}
DB::commit();
return true;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Services;
use App\Models\User;
use App\Models\Server;
class ServerService
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public function getAvailableUsers($groupId)
{
return User::whereIn('group_id', $groupId)
->whereRaw('u + d < transfer_enable')
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->where('banned', 0)
->select([
'id',
'email',
't',
'u',
'd',
'transfer_enable',
'v2ray_uuid',
'v2ray_alter_id',
'v2ray_level'
])
->get();
}
public function getConfig(int $nodeId, int $localPort)
{
$server = Server::find($nodeId);
if (!$server) {
abort(500, '节点不存在');
}
$json = json_decode(self::SERVER_CONFIG);
$json->inboundDetour[0]->port = (int)$localPort;
$json->inbound->port = (int)$server->server_port;
$json->inbound->streamSettings->network = $server->network;
$this->setDns($server, $json);
$this->setNetwork($server, $json);
$this->setRule($server, $json);
$this->setTls($server, $json);
return $json;
}
private function setDns(Server $server, object $json)
{
if ($server->dnsSettings) {
$dns = json_decode($server->dnsSettings);
$json->dns = $dns;
$json->outbound->settings->domainStrategy = 'UseIP';
}
}
private function setNetwork(Server $server, object $json)
{
if ($server->networkSettings) {
switch ($server->network) {
case 'tcp':
$json->inbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
break;
case 'kcp':
$json->inbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
break;
case 'ws':
$json->inbound->streamSettings->wsSettings = json_decode($server->networkSettings);
break;
case 'http':
$json->inbound->streamSettings->httpSettings = json_decode($server->networkSettings);
break;
case 'domainsocket':
$json->inbound->streamSettings->dsSettings = json_decode($server->networkSettings);
break;
case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings);
break;
}
}
}
private function setRule(Server $server, object $json)
{
if ($server->ruleSettings) {
$rules = json_decode($server->ruleSettings);
// domain
if (isset($rules->domain) && !empty($rules->domain)) {
$domainObj = new \StdClass();
$domainObj->type = 'field';
$domainObj->domain = $rules->domain;
$domainObj->outboundTag = 'block';
array_push($json->routing->rules, $domainObj);
}
// protocol
if (isset($rules->protocol) && !empty($rules->protocol)) {
$protocolObj = new \StdClass();
$protocolObj->type = 'field';
$protocolObj->protocol = $rules->protocol;
$protocolObj->outboundTag = 'block';
array_push($json->routing->rules, $protocolObj);
}
}
}
private function setTls(Server $server, object $json)
{
if ((int)$server->tls) {
$tlsSettings = json_decode($server->tlsSettings);
$json->inbound->streamSettings->security = 'tls';
$tls = (object)[
'certificateFile' => '/home/v2ray.crt',
'keyFile' => '/home/v2ray.key'
];
$json->inbound->streamSettings->tlsSettings = new \StdClass();
if (isset($tlsSettings->serverName)) {
$json->inbound->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
}
if (isset($tlsSettings->allowInsecure)) {
$json->inbound->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
}
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
}
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Services;
use App\Models\User;
class UserService
{
public function isAvailable(User $user)
{
if (!$user->banned && $user->transfer_enable && ($user->expired_at > time() || $user->expired_at === NULL)) {
return true;
}
return false;
}
public function getAvailableUsers()
{
return User::whereRaw('u + d < transfer_enable')
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->where('banned', 0)
->get();
}
public function getUnAvailbaleUsers()
{
return User::where(function ($query) {
$query->where('expired_at', '<', time())
->orWhere('expired_at', 0);
})
->where(function ($query) {
$query->where('plan_id', NULL)
->orWhere('transfer_enable', 0);
})
->get();
}
public function getUsersByIds($ids)
{
return User::whereIn('id', $ids)->get();
}
public function getAllUsers()
{
return User::all();
}
public function addBalance(int $userId, int $balance):bool
{
$user = User::find($userId);
if (!$user) {
return false;
}
$user->balance = $user->balance + $balance;
if ($user->balance < 0) {
return false;
}
if (!$user->save()) {
return false;
}
return true;
}
}

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

@ -0,0 +1,19 @@
<?php
namespace App\Utils;
class CacheKey
{
CONST KEYS = [
'EMAIL_VERIFY_CODE' => '邮箱验证吗',
'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间'
];
public static function get(string $key, $uniqueValue)
{
if (!in_array($key, array_keys(self::KEYS))) {
abort(500, 'key is not in cache key list');
}
return $key . '_' . $uniqueValue;
}
}

18
app/Utils/Dict.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Utils;
class Dict
{
CONST EMAIL_WHITELIST_SUFFIX_DEFAULT = [
'gmail.com',
'qq.com',
'163.com',
'yahoo.com',
'sina.com',
'126.com',
'outlook.com',
'yeah.net',
'foxmail.com'
];
}

View File

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

View File

@ -13,7 +13,8 @@
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.0",
"laravel/framework": "^6.0", "laravel/framework": "^6.0",
"laravel/tinker": "^1.0", "laravel/tinker": "^1.0",
"lokielse/omnipay-alipay": "^3.0", "lokielse/omnipay-alipay": "3.0.6",
"php-curl-class/php-curl-class": "^8.6",
"stripe/stripe-php": "^7.5", "stripe/stripe-php": "^7.5",
"symfony/yaml": "^4.3" "symfony/yaml": "^4.3"
}, },
@ -62,5 +63,11 @@
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php artisan key:generate --ansi"
] ]
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
} }
} }

View File

@ -67,7 +67,7 @@ return [
| |
*/ */
'timezone' => 'UTC', 'timezone' => 'Asia/Shanghai',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -228,4 +228,13 @@ return [
], ],
/*
|--------------------------------------------------------------------------
| V2board version
|--------------------------------------------------------------------------
|
| The only modification by laravel config
|
*/
'version' => '1.2.5'
]; ];

View File

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

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