397 Commits
1.0.2 ... 1.2.3

Author SHA1 Message Date
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
193 changed files with 18437 additions and 6759 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=redis 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}"

4
.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
@ -16,4 +16,4 @@ yarn-error.log
composer.phar composer.phar
composer.lock composer.lock
yarn.lock yarn.lock
docker-compose.yml docker-compose.yml

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()
{
$users = User::all();
foreach ($users as $user) {
if ($user->expired_at < time() || $user->u + $user->d >= $user->transfer_enable) {
$user->enable = 0;
} else {
$user->enable = 1;
}
$user->save();
}
}
}

View File

@ -2,6 +2,7 @@
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;
@ -42,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);
@ -77,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,42 @@ 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);
if ($expireDay === date('d') || (string)$lastDay === '29' || (string)$lastDay === '30') {
array_push($users, $item->id);
}
}
$user->whereIn('id', $users)->update([
'u' => 0, 'u' => 0,
'd' => 0 'd' => 0
]); ]);

View File

@ -5,7 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\User; use App\Models\User;
use App\Models\MailLog; use App\Models\MailLog;
use App\Jobs\SendEmail; use App\Jobs\SendEmailJob;
class SendRemindMail extends Command class SendRemindMail extends Command
{ {
@ -46,13 +46,14 @@ class SendRemindMail extends Command
if ($user->remind_traffic) $this->remindTraffic($user); if ($user->remind_traffic) $this->remindTraffic($user);
} }
} }
private function remindExpire ($user) { private function remindExpire($user)
{
if (($user->expired_at - 86400) < time() && $user->expired_at > time()) { if (($user->expired_at - 86400) < time() && $user->expired_at > time()) {
SendEmail::dispatch([ SendEmailJob::dispatch([
'email' => $user->email, 'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期', 'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期',
'template_name' => 'mail.sendRemindExpire', 'template_name' => 'remindExpire',
'template_value' => [ 'template_value' => [
'name' => config('v2board.app_name', 'V2Board'), 'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url') 'url' => config('v2board.app_url')
@ -61,16 +62,17 @@ class SendRemindMail extends Command
} }
} }
private function remindTraffic ($user) { private function remindTraffic($user)
{
if ($this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) { if ($this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) {
$sendCount = MailLog::where('created_at', '>=', strtotime(date('Y-m-1'))) $sendCount = MailLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('template_name', 'mail.sendRemindTraffic') ->where('template_name', 'mail.sendRemindTraffic')
->count(); ->count();
if ($sendCount > 0) return; if ($sendCount > 0) return;
SendEmail::dispatch([ SendEmailJob::dispatch([
'email' => $user->email, 'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%', 'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'mail.sendRemindTraffic', 'template_name' => 'remindTraffic',
'template_value' => [ 'template_value' => [
'name' => config('v2board.app_name', 'V2Board'), 'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url') 'url' => config('v2board.app_url')
@ -78,8 +80,9 @@ class SendRemindMail extends Command
]); ]);
} }
} }
private function remindTrafficIsWarnValue ($ud, $transfer_enable) { private function remindTrafficIsWarnValue($ud, $transfer_enable)
{
if ($ud <= 0) return false; if ($ud <= 0) return false;
if (($ud / $transfer_enable * 100) < 80) return false; if (($ud / $transfer_enable * 100) < 80) return false;
return true; return true;

View File

@ -8,7 +8,7 @@ use App\Models\Order;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Cache;
class V2boardCache extends Command class V2boardCache extends Command
{ {
@ -43,26 +43,5 @@ class V2boardCache extends Command
*/ */
public function handle() 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

@ -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,7 +19,7 @@ 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)
@ -28,10 +28,9 @@ class Kernel extends ConsoleKernel
$schedule->command('v2board:cache')->hourly(); $schedule->command('v2board:cache')->hourly();
// check // check
$schedule->command('check:order')->everyMinute(); $schedule->command('check:order')->everyMinute();
$schedule->command('check:expire')->everyMinute();
$schedule->command('check:commission')->everyMinute(); $schedule->command('check:commission')->everyMinute();
// reset // reset
$schedule->command('reset:traffic')->monthly(); $schedule->command('reset:traffic')->daily();
$schedule->command('reset:serverLog')->monthly(); $schedule->command('reset:serverLog')->monthly();
// send // send
$schedule->command('send:remindMail')->dailyAt('11:30'); $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,37 +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),
'try_out_enable' => (int)config('v2board.try_out_enable', 0), 'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id'), 'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
'try_out_day' => (int)config('v2board.try_out_day', 1) ],
'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
@ -43,36 +57,52 @@ 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, '参数' . $k . '不在规则内,禁止修改'); 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');

View File

@ -10,13 +10,15 @@ use App\Utils\Helper;
class CouponController extends Controller class CouponController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
return response([ return response([
'data' => Coupon::all() 'data' => Coupon::all()
]); ]);
} }
public function save (CouponSave $request) { public function save(CouponSave $request)
{
$params = $request->only([ $params = $request->only([
'name', 'name',
'type', 'type',
@ -26,23 +28,26 @@ class CouponController extends Controller
'limit_use' 'limit_use'
]); ]);
if (!$request->input('id')) { if (!$request->input('id')) {
$params['code'] = Helper::randomChar(8); $params['code'] = Helper::randomChar(8);
if (!Coupon::create($params)) { if (!Coupon::create($params)) {
abort(500, '创建失败'); abort(500, '创建失败');
} }
} else { } else {
if (!Coupon::find($request->input('id'))->update($params)) { try {
abort(500, '保存失败'); Coupon::find($request->input('id'))->update($params);
} } catch (\Exception $e) {
} abort(500, '保存失败');
}
}
return response([ return response([
'data' => true 'data' => true
]); ]);
} }
public function drop (Request $request) { public function drop(Request $request)
{
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数有误'); abort(500, '参数有误');
} }
@ -58,4 +63,4 @@ class CouponController extends Controller
'data' => true 'data' => true
]); ]);
} }
} }

View File

@ -3,29 +3,35 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend; use App\Http\Requests\Admin\MailSend;
use App\Services\UserService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Jobs\SendEmailJob;
use App\Jobs\SendEmail;
class MailController extends Controller class MailController extends Controller
{ {
public function send (MailSend $request) { public function send(MailSend $request)
if ($request->input('type') == 2 && empty($request->input('receiver'))) { {
abort(500, '收件人不能为空'); $userService = new UserService();
} $users = [];
switch ($request->input('type')) {
if ($request->input('receiver')) { case 1: $users = $userService->getAllUsers();
$users = User::whereIn('id', $request->input('receiver'))->get(); break;
} else { case 2: $users = $userService->getUsersByIds($request->input('receiver'));
$users = User::all(); break;
// available users
case 3: $users = $userService->getAvailableUsers();
break;
// un available users
case 4: $users = $userService->getUnAvailbaleUsers();
break;
} }
foreach ($users as $user) { foreach ($users as $user) {
SendEmail::dispatch([ SendEmailJob::dispatch([
'email' => $user->email, 'email' => $user->email,
'subject' => $request->input('subject'), 'subject' => $request->input('subject'),
'template_name' => 'mail.sendEmailCustom', 'template_name' => 'notify',
'template_value' => [ 'template_value' => [
'name' => config('v2board.app_name', 'V2Board'), 'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'), 'url' => config('v2board.app_url'),
@ -38,4 +44,4 @@ class MailController extends Controller
'data' => true 'data' => true
]); ]);
} }
} }

View File

@ -6,17 +6,19 @@ 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',
@ -27,7 +29,9 @@ class NoticeController extends Controller
abort(500, '保存失败'); abort(500, '保存失败');
} }
} else { } else {
if (!Notice::find($request->input('id'))->update($data)) { try {
Notice::find($request->input('id'))->update($data);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }
} }
@ -36,7 +40,8 @@ class NoticeController extends Controller
]); ]);
} }
public function drop (Request $request) { 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

@ -10,11 +10,12 @@ 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'])) {
@ -22,9 +23,9 @@ class ServerController extends Controller
} }
$server[$i]['group_id'] = json_decode($server[$i]['group_id']); $server[$i]['group_id'] = json_decode($server[$i]['group_id']);
if ($server[$i]['parent_id']) { if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['parent_id']); $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else { } else {
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']); $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
} }
} }
return response([ return response([
@ -32,45 +33,47 @@ class ServerController extends Controller
]); ]);
} }
public function save (ServerSave $request) { public function save(ServerSave $request)
$params = $request->only([ {
'show', $params = $request->only(array_keys(ServerSave::RULES));
'group_id',
'parent_id',
'name',
'host',
'port',
'server_port',
'tls',
'tags',
'rate',
'network',
'settings'
]);
$params['group_id'] = json_encode($params['group_id']); $params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) { if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']); $params['tags'] = json_encode($params['tags']);
} }
if (isset($params['settings'])) { if (isset($params['ruleSettings'])) {
if (!is_object(json_decode($params['settings']))) { if (!is_object(json_decode($params['ruleSettings']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['networkSettings'])) {
if (!is_object(json_decode($params['networkSettings']))) {
abort(500, '传输协议配置格式不正确'); abort(500, '传输协议配置格式不正确');
} }
} }
if ($request->input('id')) { if (isset($params['tlsSettings'])) {
$server = Server::find($request->input('id')); if (!is_object(json_decode($params['tlsSettings']))) {
if (!$server) { abort(500, 'TLS配置格式不正确');
abort(500, '服务器不存在'); }
} }
if (!$server->update($params)) {
abort(500, '保存失败'); if ($request->input('id')) {
} $server = Server::find($request->input('id'));
return response([ if (!$server) {
'data' => true abort(500, '服务器不存在');
]); }
} try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!Server::create($params)) { if (!Server::create($params)) {
abort(500, '创建失败'); abort(500, '创建失败');
} }
@ -79,8 +82,9 @@ class ServerController extends Controller
'data' => true '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'))]
@ -91,11 +95,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 {
@ -108,7 +113,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) {
@ -134,8 +140,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) {
@ -147,17 +154,20 @@ class ServerController extends Controller
]); ]);
} }
public function update (ServerUpdate $request) { public function update(ServerUpdate $request)
{
$params = $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($params)) { try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败'); abort(500, '保存失败');
} }
@ -165,4 +175,19 @@ 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
]);
}
} }

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

@ -9,36 +9,36 @@ use App\Models\Tutorial;
class TutorialController extends Controller class TutorialController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
return response([ return response([
'data' => Tutorial::all() 'data' => Tutorial::get()
]); ]);
} }
public function save (TutorialSave $request) { public function save(TutorialSave $request)
$params = $request->only([ {
'title', $params = $request->only(array_keys(TutorialSave::RULES));
'description',
'steps',
'icon'
]);
if (!$request->input('id')) { if (!$request->input('id')) {
if (!Tutorial::create($params)) { if (!Tutorial::create($params)) {
abort(500, '创建失败'); abort(500, '创建失败');
} }
} else { } else {
if (!Tutorial::find($request->input('id'))->update($params)) { try {
abort(500, '保存失败'); Tutorial::find($request->input('id'))->update($params);
} } catch (\Exception $e) {
} abort(500, '保存失败');
}
}
return response([ return response([
'data' => true 'data' => true
]); ]);
} }
public function show (Request $request) { public function show(Request $request)
{
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数有误'); abort(500, '参数有误');
} }
@ -56,7 +56,8 @@ class TutorialController extends Controller
]); ]);
} }
public function drop (Request $request) { public function drop(Request $request)
{
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数有误'); abort(500, '参数有误');
} }
@ -72,4 +73,4 @@ class TutorialController extends Controller
'data' => true '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,170 @@
<?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));
}
}
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 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,144 +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 (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->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $str .= ', obfs-path="'.$wsSettings->path.'"';
if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:'.$wsSettings->headers->Host.'"';
}
}
$uri .= "vmess://".base64_encode($str)."\r\n";
}
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 (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' => 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

@ -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,53 +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'),
'url' => config('v2board.app_url')
],
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,85 +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;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
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 ($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;
Redis::set($key, $user->id);
Redis::expire($key, 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 = Redis::get($key);
if (!$userId) {
abort(500, '令牌有误');
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
Redis::del($key);
return response([
'data' => true
]);
}
}
public function check (Request $request) {
return response([
'data' => $request->session()->get('id') ? true : false
]);
}
}

View File

@ -1,88 +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 App\Models\Plan;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use App\Models\InviteCode;
class RegisterController extends Controller
{
private function setTryOut () {
}
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();
}
}
}
// try out
if ((int)config('v2board.try_out_enable', 0)) {
$plan = Plan::find(config('v2board.try_out_plan_id'));
if ($plan) {
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_day', 1) * 86400);
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Redis::del($redisKey);
}
return response()->json([
'data' => true
]);
}
}

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,13 +1,15 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Coupon; use App\Models\Coupon;
class CouponController extends Controller class CouponController extends Controller
{ {
public function check (Request $request) { public function check(Request $request)
{
if (empty($request->input('code'))) { if (empty($request->input('code'))) {
abort(500, '优惠券码不能为空'); abort(500, '优惠券码不能为空');
} }
@ -28,4 +30,4 @@ class CouponController extends Controller
'data' => $coupon '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

@ -1,11 +1,13 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Http\Requests\OrderSave;
use App\Http\Controllers\Controller; 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\Http\Request;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Models\Order; use App\Models\Order;
@ -17,16 +19,21 @@ use Omnipay\Omnipay;
use Stripe\Stripe; use Stripe\Stripe;
use Stripe\Source; use Stripe\Source;
use Library\BitpayX; use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller class OrderController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
$order = Order::where('user_id', $request->session()->get('id')) {
->orderBy('created_at', 'DESC') $model = Order::where('user_id', $request->session()->get('id'))
->get(); ->orderBy('created_at', 'DESC');
if ($request->input('status') !== null) {
$model->where('status', $request->input('status'));
}
$order = $model->get();
$plan = Plan::get(); $plan = Plan::get();
for($i = 0; $i < count($order); $i++) { for ($i = 0; $i < count($order); $i++) {
for($x = 0; $x < count($plan); $x++) { for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) { if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x]; $order[$i]['plan'] = $plan[$x];
} }
@ -36,8 +43,9 @@ class OrderController extends Controller
'data' => $order 'data' => $order
]); ]);
} }
public function details (Request $request) { public function details(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id')) $order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no')) ->where('trade_no', $request->input('trade_no'))
->first(); ->first();
@ -45,7 +53,7 @@ class OrderController extends Controller
abort(500, '订单不存在'); abort(500, '订单不存在');
} }
$order['plan'] = Plan::find($order->plan_id); $order['plan'] = Plan::find($order->plan_id);
$order['update_fee'] = config('v2board.plan_update_fee', 0.5); $order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) { if (!$order['plan']) {
abort(500, '订阅不存在'); abort(500, '订阅不存在');
} }
@ -54,8 +62,9 @@ class OrderController extends Controller
]); ]);
} }
private function isExistNotPayOrderByUserId ($userId) { private function isNotCompleteOrderByUserId($userId)
$order = Order::where('status', 0) {
$order = Order::whereIn('status', [0, 1])
->where('user_id', $userId) ->where('user_id', $userId)
->first(); ->first();
if (!$order) { if (!$order) {
@ -63,24 +72,68 @@ class OrderController extends Controller
} }
return true; return true;
} }
public function save (OrderSave $request) { // surplus value
if ($this->isExistNotPayOrderByUserId($request->session()->get('id'))) { private function getSurplusValue(User $user)
{
$plan = Plan::find($user->plan_id);
switch ($plan->type) {
case 0: return $this->getSurplusValueByCycle($user, $plan);
case 1: return $this->getSurplusValueByOneTime($user, $plan);
}
}
private function getSurplusValueByOneTime(User $user, Plan $plan)
{
$trafficUnitPrice = 0;
$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)
{
$dayPrice = 0;
if ($plan->month_price) {
$dayPrice = $plan->month_price / 2592000;
} else if ($plan->quarter_price) {
$dayPrice = $plan->quarter_price / 7862400;
} else if ($plan->half_year_price) {
$dayPrice = $plan->half_year_price / 15811200;
} else if ($plan->year_price) {
$dayPrice = $plan->year_price / 31536000;
}
// exclude discount
if ($user->discount && $dayPrice) {
$dayPrice = $dayPrice - ($dayPrice * $user->discount / 100);
}
$remainingDay = $user->expired_at - time();
$result = $remainingDay * $dayPrice;
return $result > 0 ? $result : 0;
}
public function save(OrderSave $request)
{
if ($this->isNotCompleteOrderByUserId($request->session()->get('id'))) {
abort(500, '存在未付款订单,请取消后再试'); abort(500, '存在未付款订单,请取消后再试');
} }
$plan = Plan::find($request->input('plan_id')); $plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if (!$plan) { if (!$plan) {
abort(500, '该订阅不存在'); abort(500, '该订阅不存在');
} }
if (!($plan->show || $user->plan_id == $plan->id)) { if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
abort(500, '该订阅已售罄'); abort(500, '该订阅已售罄');
} }
if (!$plan->show && !$plan->renew) { if (!$plan->renew && $user->plan_id == $plan->id) {
abort(500, '该订阅无法续费,请更换其他订阅'); abort(500, '该订阅无法续费,请更换其他订阅');
} }
@ -103,7 +156,7 @@ class OrderController extends Controller
abort(500, '优惠券已过期'); abort(500, '优惠券已过期');
} }
} }
DB::beginTransaction(); DB::beginTransaction();
$order = new Order(); $order = new Order();
$order->user_id = $request->session()->get('id'); $order->user_id = $request->session()->get('id');
@ -111,35 +164,16 @@ class OrderController extends Controller
$order->cycle = $request->input('cycle'); $order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid(); $order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')]; $order->total_amount = $plan[$request->input('cycle')];
// renew and change subscribe process // coupon start
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;
}
// invite process
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);
}
}
// coupon process
if (isset($coupon)) { if (isset($coupon)) {
switch ($coupon->type) { switch ($coupon->type) {
case 1: $order->discount_amount = $coupon->value; case 1:
$order->discount_amount = $coupon->value;
break; break;
case 2: $order->discount_amount = $order->total_amount * ($coupon->value / 100); case 2:
$order->discount_amount = $order->total_amount * ($coupon->value / 100);
break; break;
} }
$order->total_amount = $order->total_amount - $order->discount_amount;
if ($coupon->limit_use !== NULL) { if ($coupon->limit_use !== NULL) {
$coupon->limit_use = $coupon->limit_use - 1; $coupon->limit_use = $coupon->limit_use - 1;
if (!$coupon->save()) { if (!$coupon->save()) {
@ -148,17 +182,68 @@ class OrderController extends Controller
} }
} }
} }
// free process // coupon complete
if ($order->total_amount <= 0) { // discount start
$order->total_amount = 0; if ($user->discount) {
$order->status = 1; $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()) { if (!$order->save()) {
DB::rollback(); DB::rollback();
abort(500, '订单创建失败'); abort(500, '订单创建失败');
} }
DB::commit(); DB::commit();
return response([ return response([
@ -166,7 +251,8 @@ class OrderController extends Controller
]); ]);
} }
public function checkout (Request $request) { public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no'); $tradeNo = $request->input('trade_no');
$method = $request->input('method'); $method = $request->input('method');
$order = Order::where('trade_no', $tradeNo) $order = Order::where('trade_no', $tradeNo)
@ -176,6 +262,13 @@ class OrderController extends Controller
if (!$order) { if (!$order) {
abort(500, '订单不存在或已支付'); abort(500, '订单不存在或已支付');
} }
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
$order->save();
exit();
}
switch ($method) { switch ($method) {
// return type => 0: QRCode / 1: URL // return type => 0: QRCode / 1: URL
case 0: case 0:
@ -214,12 +307,21 @@ class OrderController extends Controller
'type' => 1, 'type' => 1,
'data' => $this->bitpayX($order) 'data' => $this->bitpayX($order)
]); ]);
case 5:
if (!(int)config('v2board.paytaro_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->payTaro($order)
]);
default: default:
abort(500, '支付方式不存在'); abort(500, '支付方式不存在');
} }
} }
public function check (Request $request) { public function check(Request $request)
{
$tradeNo = $request->input('trade_no'); $tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo) $order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id')) ->where('user_id', $request->session()->get('id'))
@ -232,7 +334,8 @@ class OrderController extends Controller
]); ]);
} }
public function getPaymentMethod () { public function getPaymentMethod()
{
$data = []; $data = [];
if ((int)config('v2board.alipay_enable')) { if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass(); $alipayF2F = new \StdClass();
@ -260,18 +363,27 @@ class OrderController extends Controller
if ((int)config('v2board.bitpayx_enable')) { if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass(); $bitpayX = new \StdClass();
$bitpayX->name = '虚拟货币'; $bitpayX->name = '聚合支付';
$bitpayX->method = 4; $bitpayX->method = 4;
$bitpayX->icon = 'bitcoin'; $bitpayX->icon = 'wallet';
array_push($data, $bitpayX); 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([ return response([
'data' => $data 'data' => $data
]); ]);
} }
public function cancel (Request $request) { public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) { if (empty($request->input('trade_no'))) {
abort(500, '参数有误'); abort(500, '参数有误');
} }
@ -284,8 +396,8 @@ class OrderController extends Controller
if ($order->status !== 0) { if ($order->status !== 0) {
abort(500, '只可以取消待支付订单'); abort(500, '只可以取消待支付订单');
} }
$order->status = 2; $orderService = new OrderService($order);
if (!$order->save()) { if (!$orderService->cancel()) {
abort(500, '取消失败'); abort(500, '取消失败');
} }
return response([ return response([
@ -293,7 +405,8 @@ class OrderController extends Controller
]); ]);
} }
private function alipayF2F ($tradeNo, $totalAmount) { private function alipayF2F($tradeNo, $totalAmount)
{
$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'));
@ -302,7 +415,7 @@ class OrderController extends Controller
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify')); $gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase(); $request = $gateway->purchase();
$request->setBizContent([ $request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅', 'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo, 'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100 'total_amount' => $totalAmount / 100
]); ]);
@ -310,22 +423,30 @@ class OrderController extends Controller
$response = $request->send(); $response = $request->send();
$result = $response->getAlipayResponse(); $result = $response->getAlipayResponse();
if ($result['code'] !== '10000') { if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']); abort(500, $result['sub_msg']);
} }
// 获取收款二维码内容 // 获取收款二维码内容
return $response->getQrCode(); return $response->getQrCode();
} }
private function stripeAlipay ($order) { private function stripeAlipay($order)
$exchange = Helper::exchange('CNY', 'HKD'); {
$currency = config('stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) { if (!$exchange) {
abort(500, '货币转换超时,请稍后再试'); abort(500, '货币转换超时,请稍后再试');
} }
Stripe::setApiKey(config('v2board.stripe_sk_live')); Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([ $source = Source::create([
'amount' => floor($order->total_amount * $exchange), 'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd', 'currency' => $currency,
'type' => 'alipay', 'type' => 'alipay',
'statement_descriptor' => $order->trade_no,
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [ 'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order' 'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
] ]
@ -333,24 +454,30 @@ class OrderController extends Controller
if (!$source['redirect']['url']) { if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败'); abort(500, '支付网关请求失败');
} }
if (!Redis::set($source['id'], $order->trade_no)) { if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败'); abort(500, '订单创建失败');
} }
Redis::expire($source['id'], 3600);
return $source['redirect']['url']; return $source['redirect']['url'];
} }
private function stripeWepay ($order) { private function stripeWepay($order)
$exchange = Helper::exchange('CNY', 'HKD'); {
$currency = config('stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) { if (!$exchange) {
abort(500, '货币转换超时,请稍后再试'); abort(500, '货币转换超时,请稍后再试');
} }
Stripe::setApiKey(config('v2board.stripe_sk_live')); Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([ $source = Source::create([
'amount' => floor($order->total_amount * $exchange), 'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd', 'currency' => $currency,
'type' => 'wechat', 'type' => 'wechat',
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [ 'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order' 'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
] ]
@ -358,29 +485,42 @@ class OrderController extends Controller
if (!$source['wechat']['qr_code_url']) { if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败'); abort(500, '支付网关请求失败');
} }
if (!Redis::set($source['id'], $order->trade_no)) { if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败'); abort(500, '订单创建失败');
} }
Redis::expire($source['id'], 3600);
return $source['wechat']['qr_code_url']; return $source['wechat']['qr_code_url'];
} }
private function bitpayX ($order) { private function bitpayX($order)
{
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret')); $bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [ $params = [
'merchant_order_id' => 'V2Board_' . $order->trade_no, 'merchant_order_id' => $order->trade_no,
'price_amount' => $order->total_amount / 100, 'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY', 'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no, 'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元', 'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'), 'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order', 'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_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']); $strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign); $params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params); $result = $bitpayX->mprequest($params);
Log::info('bitpayXSubmit: ' . json_encode($result)); // Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false; 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

@ -1,14 +1,15 @@
<?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\Plan; use App\Models\Plan;
class PlanController extends Controller class PlanController extends Controller
{ {
public function fetch (Request $request) { public function fetch(Request $request)
{
if ($request->input('id')) { if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id')) $plan = Plan::where('id', $request->input('id'))
->first(); ->first();

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();
@ -29,9 +33,9 @@ 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);
if ($server[$i]['parent_id']) { if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['parent_id']); $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else { } else {
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']); $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
} }
} }
return response([ return response([
@ -39,23 +43,27 @@ class ServerController extends Controller {
]); ]);
} }
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();
@ -65,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,7 +48,8 @@ class TicketController extends Controller
]); ]);
} }
public function save (TicketSave $request) { public function save(TicketSave $request)
{
DB::beginTransaction(); DB::beginTransaction();
$ticket = Ticket::create(array_merge($request->only([ $ticket = Ticket::create(array_merge($request->only([
'subject', 'subject',
@ -75,7 +77,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 +115,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 +135,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

@ -1,15 +1,16 @@
<?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\Tutorial; use App\Models\Tutorial;
class TutorialController extends Controller class TutorialController extends Controller
{ {
public function getSubscribeUrl (Request $request) { public function getSubscribeUrl(Request $request)
{
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
return response([ return response([
'data' => [ 'data' => [
@ -18,7 +19,8 @@ class TutorialController extends Controller
]); ]);
} }
public function getAppleID (Request $request) { public function getAppleID(Request $request)
{
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) { if ($user->expired_at < time()) {
return response([ return response([
@ -34,7 +36,8 @@ class TutorialController extends Controller
]); ]);
} }
public function fetch (Request $request) { public function fetch(Request $request)
{
if ($request->input('id')) { if ($request->input('id')) {
$tutorial = Tutorial::where('show', 1) $tutorial = Tutorial::where('show', 1)
->where('id', $request->input('id')) ->where('id', $request->input('id'))
@ -46,9 +49,10 @@ class TutorialController extends Controller
'data' => $tutorial 'data' => $tutorial
]); ]);
} }
$tutorial = Tutorial::select(['id', 'title', 'description', 'icon']) $tutorial = Tutorial::select(['id', 'category_id', 'title'])
->where('show', 1) ->where('show', 1)
->get(); ->get()
->groupBy('category_id');
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
$response = [ $response = [
'data' => [ 'data' => [
@ -56,15 +60,15 @@ class TutorialController extends Controller
'safe_area_var' => [ 'safe_area_var' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'], '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'), 'app_name' => config('v2board.app_name', 'V2board'),
'apple_id' => $user->expired_at > time() ? config('v2board.apple_id', '管理员暂无提供AppleID信息') : '账号过期或未订阅', 'apple_id' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id', '本站暂无提供AppleID信息') : '账号过期或未订阅',
'apple_id_password' => $user->expired_at > time() ? config('v2board.apple_id_password', '管理员暂无提供AppleID信息') : '账号过期或未订阅' 'apple_id_password' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id_password', '本站暂无提供AppleID信息') : '账号过期或未订阅'
] ]
] ]
]; ];
// fuck support shadowrocket urlsafeb64 subscribe // fuck support shadowrocket urlsafeb64 subscribe
$response['data']['safe_area_var']['b64_subscribe_url'] = str_replace( $response['data']['safe_area_var']['b64_subscribe_url'] = str_replace(
array('+','/','='), array('+', '/', '='),
array('-','_',''), array('-', '_', ''),
base64_encode($response['data']['safe_area_var']['subscribe_url']) base64_encode($response['data']['safe_area_var']['subscribe_url'])
); );
// end // end

View File

@ -1,10 +1,10 @@
<?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 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,13 +15,16 @@ 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(Request $request)
{
if (empty($request->input('old_password'))) { if (empty($request->input('old_password'))) {
abort(500, '旧密码不能为空'); abort(500, '旧密码不能为空');
} }
@ -29,10 +32,15 @@ class UserController extends Controller
abort(500, '新密码不能为空'); 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,21 +49,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' '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';
@ -64,7 +76,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'))
@ -80,7 +93,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);
@ -93,8 +107,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();
@ -102,21 +117,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

@ -7,22 +7,32 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest class ConfigSave extends FormRequest
{ {
CONST RULES = [ CONST RULES = [
// invite & commission
'safe_mode_enable' => 'in:0,1',
'invite_force' => 'in:0,1', 'invite_force' => 'in:0,1',
'invite_commission' => 'integer', 'invite_commission' => 'integer',
'invite_gen_limit' => 'integer', 'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1', 'invite_never_expire' => 'in:0,1',
'commission_first_time_enable' => 'in:0,1',
// site
'stop_register' => 'in:0,1', 'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1', 'email_verify' => 'in:0,1',
'app_name' => '', 'app_name' => '',
'app_url' => 'url', 'app_description' => '',
'subscribe_url' => 'url', 'app_url' => 'nullable|url',
'plan_update_fee' => 'numeric', 'subscribe_url' => 'nullable|url',
'plan_is_update' => 'in:0,1',
'try_out_enable' => 'in:0,1', 'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer', 'try_out_plan_id' => 'integer',
'try_out_day' => 'integer', 'try_out_hour' => 'numeric',
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => '',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1',
'renew_reset_traffic_enable' => 'in:0,1',
// server // server
'server_token' => 'nullable|min:16', 'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
// alipay // alipay
'alipay_enable' => 'in:0,1', 'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16', 'alipay_appid' => 'nullable|integer|min:16',
@ -34,18 +44,26 @@ class ConfigSave extends FormRequest
'stripe_sk_live' => '', 'stripe_sk_live' => '',
'stripe_pk_live' => '', 'stripe_pk_live' => '',
'stripe_webhook_key' => '', 'stripe_webhook_key' => '',
'stripe_currency' => 'in:hkd,usd,sgd',
// bitpayx // bitpayx
'bitpayx_enable' => 'in:0,1', 'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '', '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 // tutorial
'apple_id' => 'email', 'apple_id' => 'email',
'apple_id_password' => '' 'apple_id_password' => '',
// email
'email_template' => ''
]; ];
public static function filter() {
// abort(500, json_encode(array_keys(self::RULES)));
return array_keys(self::RULES);
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -55,10 +73,13 @@ class ConfigSave extends FormRequest
{ {
return self::RULES; return self::RULES;
} }
public function messages() public function messages()
{ {
// illiteracy prompt
return [ return [
'app_url.url' => '站点URL格式不正确必须携带http(s)://',
'subscribe_url.url' => '订阅URL格式不正确必须携带http(s)://'
]; ];
} }
} }

View File

@ -22,7 +22,7 @@ class CouponSave extends FormRequest
'limit_use' => 'nullable|integer' 'limit_use' => 'nullable|integer'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

View File

@ -14,13 +14,13 @@ class MailSend extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'type' => 'required|in:1,2', 'type' => 'required|in:1,2,3,4',
'subject' => 'required', 'subject' => 'required',
'content' => 'required', 'content' => 'required',
'receiver' => 'array' 'receiver' => 'array'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [

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' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer'
];
} }
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.integer' => '月付金额格式有误', 'month_price.integer' => '月付金额格式有误',
'quarter_price.integer' => '季付金额格式有误', 'quarter_price.integer' => '季付金额格式有误',
'half_year_price.integer' => '半年付金额格式有误', 'half_year_price.integer' => '半年付金额格式有误',
'year_price.integer' => '年付金额格式有误' '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,22 @@ 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' => ''
];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -13,25 +29,14 @@ class ServerSave extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'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格式不正确', 'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空', 'host.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

@ -6,6 +6,12 @@ use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends 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. * Get the validation rules that apply to the request.
* *
@ -13,19 +19,16 @@ class TutorialSave extends FormRequest
*/ */
public function rules() public function rules()
{ {
return [ return self::RULES;
'title' => 'required',
'description' => 'required',
'icon' => 'required'
];
} }
public function messages() public function messages()
{ {
return [ return [
'title.required' => '标题不能为空', 'title.required' => '标题不能为空',
'description.required' => '描述不能为空', 'category_id.required' => '分类不能为空',
'icon.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

@ -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,64 @@
<?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');
// 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

@ -10,10 +10,11 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use App\Models\MailLog; use App\Models\MailLog;
class SendEmail implements ShouldQueue class SendEmailJob implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params; protected $params;
/** /**
* Create a new job instance. * Create a new job instance.
* *
@ -21,6 +22,7 @@ class SendEmail implements ShouldQueue
*/ */
public function __construct($params) public function __construct($params)
{ {
$this->onQueue('send_email');
$this->params = $params; $this->params = $params;
} }
@ -34,12 +36,13 @@ class SendEmail implements ShouldQueue
$params = $this->params; $params = $this->params;
$email = $params['email']; $email = $params['email'];
$subject = $params['subject']; $subject = $params['subject'];
$params['template_name'] = 'mail.' . config('v2board.email_template', 'default') . '.' . $params['template_name'];
try { try {
Mail::send( Mail::send(
$params['template_name'], $params['template_name'],
$params['template_value'], $params['template_value'],
function ($message) use($email, $subject) { function ($message) use ($email, $subject) {
$message->to($email)->subject($subject); $message->to($email)->subject($subject);
} }
); );
} catch (\Exception $e) { } catch (\Exception $e) {

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,108 @@
<?php
namespace App\Services;
use App\Models\User;
use App\Models\Server;
class ServerService
{
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 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;
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;
}
}
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);
}
}
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;
}
return $json;
}
}

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,43 +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 = [
"v" => "2", "v" => "2",
"ps" => $item->name, "ps" => $server->name,
"add" => $item->host, "add" => $server->host,
"port" => $item->port, "port" => $server->port,
"id" => $user->v2ray_uuid, "id" => $user->v2ray_uuid,
"aid" => "2", "aid" => "2",
"net" => $item->network, "net" => $server->network,
"type" => "none", "type" => "none",
"host" => "", "host" => "",
"path" => "", "path" => "",
"tls" => $item->tls?"tls":"" "tls" => $server->tls ? "tls" : ""
]; ];
if ($item->network == 'ws') { if ((string)$server->network === 'ws') {
$wsSettings = json_decode($item->settings); $wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path; if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host; 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"
}, },

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.3'
]; ];

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

@ -7,6 +7,18 @@ SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET NAMES utf8mb4; SET NAMES utf8mb4;
DROP TABLE IF EXISTS `failed_jobs`;
CREATE TABLE `failed_jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `v2_coupon`; DROP TABLE IF EXISTS `v2_coupon`;
CREATE TABLE `v2_coupon` ( CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -29,6 +41,7 @@ CREATE TABLE `v2_invite_code` (
`user_id` int(11) NOT NULL, `user_id` int(11) NOT NULL,
`code` char(32) NOT NULL, `code` char(32) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '0',
`pv` int(11) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@ -72,6 +85,9 @@ CREATE TABLE `v2_order` (
`callback_no` varchar(255) DEFAULT NULL, `callback_no` varchar(255) DEFAULT NULL,
`total_amount` int(11) NOT NULL, `total_amount` int(11) NOT NULL,
`discount_amount` int(11) DEFAULT NULL, `discount_amount` int(11) DEFAULT NULL,
`surplus_amount` int(11) DEFAULT NULL COMMENT '剩余价值',
`refund_amount` int(11) DEFAULT NULL COMMENT '退款金额',
`balance_amount` int(11) DEFAULT NULL COMMENT '使用余额',
`status` tinyint(1) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '0',
`commission_status` tinyint(1) NOT NULL DEFAULT '0', `commission_status` tinyint(1) NOT NULL DEFAULT '0',
`commission_balance` int(11) NOT NULL DEFAULT '0', `commission_balance` int(11) NOT NULL DEFAULT '0',
@ -94,6 +110,7 @@ CREATE TABLE `v2_plan` (
`quarter_price` int(11) DEFAULT '0', `quarter_price` int(11) DEFAULT '0',
`half_year_price` int(11) DEFAULT '0', `half_year_price` int(11) DEFAULT '0',
`year_price` int(11) DEFAULT '0', `year_price` int(11) DEFAULT '0',
`onetime_price` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@ -109,11 +126,15 @@ CREATE TABLE `v2_server` (
`host` varchar(255) NOT NULL, `host` varchar(255) NOT NULL,
`port` int(11) NOT NULL, `port` int(11) NOT NULL,
`server_port` int(11) NOT NULL, `server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL, `tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL, `tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL, `rate` varchar(11) NOT NULL,
`network` varchar(11) NOT NULL, `network` varchar(11) NOT NULL,
`settings` text, `settings` text,
`rules` text,
`networkSettings` text,
`tlsSettings` text,
`ruleSettings` text,
`show` tinyint(1) NOT NULL DEFAULT '0', `show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
@ -137,7 +158,7 @@ CREATE TABLE `v2_server_log` (
`server_id` int(11) NOT NULL, `server_id` int(11) NOT NULL,
`u` varchar(255) NOT NULL, `u` varchar(255) NOT NULL,
`d` varchar(255) NOT NULL, `d` varchar(255) NOT NULL,
`rate` int(11) NOT NULL, `rate` decimal(10,2) NOT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL `updated_at` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -172,9 +193,8 @@ CREATE TABLE `v2_ticket_message` (
DROP TABLE IF EXISTS `v2_tutorial`; DROP TABLE IF EXISTS `v2_tutorial`;
CREATE TABLE `v2_tutorial` ( CREATE TABLE `v2_tutorial` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`title` varchar(255) CHARACTER SET utf8mb4 NOT NULL, `title` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`icon` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`steps` text, `steps` text,
`show` tinyint(1) NOT NULL DEFAULT '0', `show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
@ -182,11 +202,11 @@ CREATE TABLE `v2_tutorial` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES INSERT INTO `v2_tutorial` (`id`, `category_id`, `title`, `steps`, `show`, `created_at`, `updated_at`) VALUES
(1, 'Windows', '兼容 Windows 7 以上的版本', 'fab fa-2x fa-windows', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577984396), (1, 1, 'V2rayN', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577980882),
(2, 'Android', '兼容 Android 6 以上的版本', 'fab fa-2x fa-android', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577984397), (2, 4, 'V2rayNG', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577981610),
(3, 'macOS', '兼容 Yosemite 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577984397), (3, 2, 'ClashX', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577981646),
(4, 'iOS', '兼容 iOS 9 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]', 1, 1577982016, 1577983283); (4, 3, 'Shadowrocket', '[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]', 1, 1577982016, 1577983283);
DROP TABLE IF EXISTS `v2_user`; DROP TABLE IF EXISTS `v2_user`;
CREATE TABLE `v2_user` ( CREATE TABLE `v2_user` (
@ -194,14 +214,15 @@ CREATE TABLE `v2_user` (
`invite_user_id` int(11) DEFAULT NULL, `invite_user_id` int(11) DEFAULT NULL,
`email` varchar(64) NOT NULL, `email` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL, `password` varchar(64) NOT NULL,
`password_algo` char(10) DEFAULT NULL,
`balance` int(11) NOT NULL DEFAULT '0', `balance` int(11) NOT NULL DEFAULT '0',
`discount` int(11) DEFAULT NULL,
`commission_rate` int(11) DEFAULT NULL, `commission_rate` int(11) DEFAULT NULL,
`commission_balance` int(11) NOT NULL DEFAULT '0', `commission_balance` int(11) NOT NULL DEFAULT '0',
`t` int(11) NOT NULL DEFAULT '0', `t` int(11) NOT NULL DEFAULT '0',
`u` bigint(20) NOT NULL DEFAULT '0', `u` bigint(20) NOT NULL DEFAULT '0',
`d` bigint(20) NOT NULL DEFAULT '0', `d` bigint(20) NOT NULL DEFAULT '0',
`transfer_enable` bigint(20) NOT NULL DEFAULT '0', `transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`enable` tinyint(1) NOT NULL DEFAULT '1',
`banned` tinyint(1) NOT NULL DEFAULT '0', `banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0', `is_admin` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL, `last_login_at` int(11) DEFAULT NULL,
@ -214,7 +235,7 @@ CREATE TABLE `v2_user` (
`remind_expire` tinyint(4) DEFAULT '1', `remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1', `remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL, `token` char(32) NOT NULL,
`expired_at` bigint(20) NOT NULL DEFAULT '0', `expired_at` bigint(20) DEFAULT '0',
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@ -222,4 +243,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2020-01-03 14:42:13 -- 2020-03-17 14:16:01

View File

View File

View File

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

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