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
DB_CONNECTION=mysql
DB_HOST=db
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=123456
BROADCAST_DRIVER=log
CACHE_DRIVER=file
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=file
SESSION_DRIVER=redis
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
@ -31,6 +31,8 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=null
MAILGUN_DOMAIN=
MAILGUN_SECRET=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
/node_modules
/config/v2panel.php
/config/v2board.php
/public/hot
/public/storage
/public/env.example.js

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;
use App\Services\OrderService;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\User;
@ -42,14 +43,14 @@ class CheckOrder extends Command
*/
public function handle()
{
$order = Order::get();
foreach ($order as $item) {
$orders = Order::get();
foreach ($orders as $item) {
switch ($item->status) {
// cancel
case 0:
if (strtotime($item->created_at) <= (time() - 1800)) {
$item->status = 2;
$item->save();
$orderService = new OrderService($item);
$orderService->cancel();
}
break;
case 1:
@ -60,15 +61,30 @@ class CheckOrder extends Command
}
}
private function orderHandle ($order) {
private function orderHandle(Order $order)
{
$user = User::find($order->user_id);
return $this->buy($order, $user);
$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 buy ($order, $user) {
$plan = Plan::find($order->plan_id);
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->enable = 1;
if ((int)config('v2board.renew_reset_traffic_enable', 1)) {
$user->u = 0;
$user->d = 0;
}
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
@ -78,15 +94,37 @@ class CheckOrder extends Command
}
}
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()) {
$timestamp = time();
}
switch ($str) {
case 'month_price': return strtotime('+1 month', $timestamp);
case 'quarter_price': return strtotime('+3 month', $timestamp);
case 'half_year_price': return strtotime('+6 month', $timestamp);
case 'year_price': return strtotime('+12 month', $timestamp);
case 'month_price':
return strtotime('+1 month', $timestamp);
case 'quarter_price':
return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
}
}
}

View File

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

View File

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

View File

@ -8,7 +8,7 @@ use App\Models\Order;
use App\Models\Server;
use App\Models\ServerLog;
use App\Utils\Helper;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class V2boardCache extends Command
{
@ -43,26 +43,5 @@ class V2boardCache extends Command
*/
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;
use Illuminate\Console\Command;
use Illuminate\Encryption\Encrypter;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Support\Facades\DB;
@ -40,14 +41,35 @@ class V2boardInstall extends Command
*/
public function handle()
{
try {
$this->info("__ ______ ____ _ ");
$this->info("\ \ / /___ \| __ ) ___ __ _ _ __ __| | ");
$this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | ");
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.lock')) {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件');
}
\Artisan::call('key:generate');
sleep(2);
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();
$file = \File::get(base_path() . '/install.sql');
} catch (\Exception $e) {
abort(500, '数据库连接失败');
}
$file = \File::get(base_path() . '/database/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
@ -57,11 +79,13 @@ class V2boardInstall extends Command
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
$this->info('数据库导入完成');
$email = '';
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
@ -75,16 +99,56 @@ class V2boardInstall extends Command
}
$this->info('一切就绪');
$this->info('访问 http(s)://你的站点/admin 进入管理面板');
\File::put(base_path() . '/.lock', time());
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
private function registerAdmin ($email, $password) {
private function registerAdmin($email, $password)
{
$user = new User();
$user->email = $email;
if (strlen($password) < 8) {
abort(500, '管理员密码长度最小为8位字符');
}
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
$user->is_admin = 1;
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

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

View File

@ -28,10 +28,9 @@ class Kernel extends ConsoleKernel
$schedule->command('v2board:cache')->hourly();
// check
$schedule->command('check:order')->everyMinute();
$schedule->command('check:expire')->everyMinute();
$schedule->command('check:commission')->everyMinute();
// reset
$schedule->command('reset:traffic')->monthly();
$schedule->command('reset:traffic')->daily();
$schedule->command('reset:serverLog')->monthly();
// send
$schedule->command('send:remindMail')->dailyAt('11:30');
@ -44,7 +43,7 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}

View File

@ -46,6 +46,9 @@ class Handler extends ExceptionHandler
*/
public function render($request, Exception $exception)
{
if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
abort(429, '请求频繁,请稍后再试');
}
return parent::render($request, $exception);
}

View File

@ -4,37 +4,51 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ConfigSave;
use Illuminate\Http\Request;
use App\Utils\Dict;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
class ConfigController extends Controller
{
public function init () {
public function 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([
'data' => [
'invite' => [
'invite_force' => (int)config('v2board.invite_force', 0),
'invite_commission' => config('v2board.invite_commission', 10),
'invite_gen_limit' => config('v2board.invite_gen_limit', 5),
'invite_never_expire' => config('v2board.invite_never_expire', 0)
'invite_never_expire' => config('v2board.invite_never_expire', 0),
'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1)
],
'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
'stop_register' => (int)config('v2board.stop_register', 0),
'email_verify' => (int)config('v2board.email_verify', 0),
'app_name' => config('v2board.app_name', 'V2Board'),
'app_description' => config('v2board.app_description', 'V2Board is best!'),
'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'),
'plan_update_fee' => config('v2board.plan_update_fee', 0.5),
'plan_is_update' => (int)config('v2board.plan_is_update', 1),
'try_out_enable' => (int)config('v2board.try_out_enable', 0),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id'),
'try_out_day' => (int)config('v2board.try_out_day', 1)
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
'try_out_hour' => (int)config('v2board.try_out_hour', 1),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1)
],
'pay' => [
// alipay
@ -43,36 +57,52 @@ class ConfigController extends Controller
'alipay_pubkey' => config('v2board.alipay_pubkey'),
'alipay_privkey' => config('v2board.alipay_privkey'),
// stripe
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
'stripe_sk_live' => config('v2board.stripe_sk_live'),
'stripe_pk_live' => config('v2board.stripe_pk_live'),
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable'),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable'),
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
// bitpayx
'bitpayx_enable' => config('v2board.bitpayx_enable'),
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret')
'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0),
'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_token' => config('v2board.server_token')
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license')
],
'tutorial' => [
'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();
$array = \Config::get('v2board');
foreach ($data as $k => $v) {
if (!in_array($k, ConfigSave::filter())) {
if (!in_array($k, array_keys(ConfigSave::RULES))) {
abort(500, '参数' . $k . '不在规则内,禁止修改');
}
$array[$k] = $v;
}
$data = var_export($array, 1);
if(!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
abort(500, '修改失败');
}
\Artisan::call('config:cache');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,11 +10,12 @@ use App\Models\ServerGroup;
use App\Models\Server;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class ServerController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$server = Server::get();
for ($i = 0; $i < count($server); $i++) {
if (!empty($server[$i]['tags'])) {
@ -22,9 +23,9 @@ class ServerController extends Controller
}
$server[$i]['group_id'] = json_decode($server[$i]['group_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 {
$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([
@ -32,38 +33,40 @@ class ServerController extends Controller
]);
}
public function save (ServerSave $request) {
$params = $request->only([
'show',
'group_id',
'parent_id',
'name',
'host',
'port',
'server_port',
'tls',
'tags',
'rate',
'network',
'settings'
]);
public function save(ServerSave $request)
{
$params = $request->only(array_keys(ServerSave::RULES));
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['settings'])) {
if (!is_object(json_decode($params['settings']))) {
if (isset($params['ruleSettings'])) {
if (!is_object(json_decode($params['ruleSettings']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['networkSettings'])) {
if (!is_object(json_decode($params['networkSettings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if (isset($params['tlsSettings'])) {
if (!is_object(json_decode($params['tlsSettings']))) {
abort(500, 'TLS配置格式不正确');
}
}
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
if (!$server->update($params)) {
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
@ -80,7 +83,8 @@ class ServerController extends Controller
]);
}
public function groupFetch (Request $request) {
public function groupFetch(Request $request)
{
if ($request->input('group_id')) {
return response([
'data' => [ServerGroup::find($request->input('group_id'))]
@ -91,7 +95,8 @@ class ServerController extends Controller
]);
}
public function groupSave (Request $request) {
public function groupSave(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
@ -108,7 +113,8 @@ class ServerController extends Controller
]);
}
public function groupDrop (Request $request) {
public function groupDrop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
@ -135,7 +141,8 @@ class ServerController extends Controller
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
@ -147,7 +154,8 @@ class ServerController extends Controller
]);
}
public function update (ServerUpdate $request) {
public function update(ServerUpdate $request)
{
$params = $request->only([
'show',
]);
@ -157,7 +165,9 @@ class ServerController extends Controller
if (!$server) {
abort(500, '该服务器不存在');
}
if (!$server->update($params)) {
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
@ -165,4 +175,19 @@ class ServerController extends Controller
'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\Ticket;
use App\Models\Order;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class StatController extends Controller
{
public function getOverride (Request $request) {
public function getOverride(Request $request)
{
return response([
'data' => [
'month_income' => Redis::get('month_income'),
'month_register_total' => Redis::get('month_register_total'),
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->where('status', '3')
->sum('total_amount'),
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count(),
'ticket_pendding_total' => Ticket::where('status', 0)
->count(),
'commission_pendding_total' => Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->where('status', 3)
->count(),
]
]);
}

View File

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

View File

@ -9,26 +9,25 @@ use App\Models\Tutorial;
class TutorialController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Tutorial::all()
'data' => Tutorial::get()
]);
}
public function save (TutorialSave $request) {
$params = $request->only([
'title',
'description',
'steps',
'icon'
]);
public function save(TutorialSave $request)
{
$params = $request->only(array_keys(TutorialSave::RULES));
if (!$request->input('id')) {
if (!Tutorial::create($params)) {
abort(500, '创建失败');
}
} else {
if (!Tutorial::find($request->input('id'))->update($params)) {
try {
Tutorial::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
@ -38,7 +37,8 @@ class TutorialController extends Controller
]);
}
public function show (Request $request) {
public function show(Request $request)
{
if (empty($request->input('id'))) {
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'))) {
abort(500, '参数有误');
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Utils\Dict;
use App\Utils\CacheKey;
class CommController extends Controller
{
public function config () {
public function config()
{
return response([
'data' => [
'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([
'data' => (int)config('v2board.email_verify', 0) ? 1 : 0
]);
}
public function sendEmailVerify (CommSendEmailVerify $request) {
public function sendEmailVerify(CommSendEmailVerify $request)
{
$email = $request->input('email');
$redisKey = 'sendEmailVerify:' . $email;
if (Redis::get($redisKey)) {
abort(500, '验证码已发送,请过一会在请求');
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
abort(500, '验证码已发送,请过一会再请求');
}
$code = rand(100000, 999999);
$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);
Redis::expire($redisKey, 600);
SendEmailJob::dispatch([
'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([
'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;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Server\Controller;
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\Redis;
use Illuminate\Support\Facades\Cache;
class DeepbworkController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
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');
$server = Server::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Redis::set('server_last_check_at_' . $server->id, time());
$users = User::whereIn('group_id', json_decode($server->group_id))
->select([
'id',
'email',
't',
'u',
'd',
'transfer_enable',
'enable',
'v2ray_uuid',
'v2ray_alter_id',
'v2ray_level'
])
->get();
Cache::put('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@v2panel.user", $user->v2ray_uuid),
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
"alter_id" => $user->v2ray_alter_id,
"level" => $user->v2ray_level,
];
@ -56,8 +57,9 @@ class DeepbworkController extends Controller
}
// 后端提交数据
public function submit (Request $request) {
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
return response([
@ -92,40 +94,18 @@ class DeepbworkController extends Controller
}
// 后端获取配置
public function config (Request $request) {
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
abort(1000, '参数错误');
abort(500, '参数错误');
}
$server = Server::find($nodeId);
if (!$server) {
abort(1001, '节点不存在');
}
$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;
$serverService = new ServerService();
try {
$json = $serverService->getConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
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
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Coupon;
class CouponController extends Controller
{
public function check (Request $request) {
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, '优惠券码不能为空');
}

View File

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Order;
use App\Models\InviteCode;
@ -11,7 +11,8 @@ use App\Utils\Helper;
class InviteController extends Controller
{
public function save (Request $request) {
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
abort(500, '已达到创建数量上限');
}
@ -23,7 +24,8 @@ class InviteController extends Controller
]);
}
public function details (Request $request) {
public function details(Request $request)
{
return response([
'data' => Order::where('invite_user_id', $request->session()->get('id'))
->where('status', 3)
@ -38,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'))
->where('status', 0)
->get();
$commission_rate = config('v2board.invite_commission');
$commission_rate = config('v2board.invite_commission', 10);
$user = User::find($request->session()->get('id'));
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
@ -52,7 +55,7 @@ class InviteController extends Controller
(int)User::where('invite_user_id', $request->session()->get('id'))->count(),
//有效的佣金
(int)Order::where('status', 3)
->where('commission_status', 1)
->where('commission_status', 2)
->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'),
//确认中的佣金
@ -61,7 +64,9 @@ class InviteController extends Controller
->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'),
//佣金比例
(int)$commission_rate
(int)$commission_rate,
//可用佣金
(int)$user->commission_balance
];
return response([
'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
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use App\Http\Requests\OrderSave;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use App\Services\OrderService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use App\Models\Order;
@ -17,16 +19,21 @@ use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function fetch (Request $request) {
$order = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC')
->get();
public function fetch(Request $request)
{
$model = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
if ($request->input('status') !== null) {
$model->where('status', $request->input('status'));
}
$order = $model->get();
$plan = Plan::get();
for($i = 0; $i < count($order); $i++) {
for($x = 0; $x < count($plan); $x++) {
for ($i = 0; $i < count($order); $i++) {
for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
@ -37,7 +44,8 @@ class OrderController extends Controller
]);
}
public function details (Request $request) {
public function details(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
@ -45,7 +53,7 @@ class OrderController extends Controller
abort(500, '订单不存在');
}
$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']) {
abort(500, '订阅不存在');
}
@ -54,8 +62,9 @@ class OrderController extends Controller
]);
}
private function isExistNotPayOrderByUserId ($userId) {
$order = Order::where('status', 0)
private function isNotCompleteOrderByUserId($userId)
{
$order = Order::whereIn('status', [0, 1])
->where('user_id', $userId)
->first();
if (!$order) {
@ -64,8 +73,52 @@ class OrderController extends Controller
return true;
}
public function save (OrderSave $request) {
if ($this->isExistNotPayOrderByUserId($request->session()->get('id'))) {
// surplus value
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, '存在未付款订单,请取消后再试');
}
@ -76,11 +129,11 @@ class OrderController extends Controller
abort(500, '该订阅不存在');
}
if (!($plan->show || $user->plan_id == $plan->id)) {
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
abort(500, '该订阅已售罄');
}
if (!$plan->show && !$plan->renew) {
if (!$plan->renew && $user->plan_id == $plan->id) {
abort(500, '该订阅无法续费,请更换其他订阅');
}
@ -111,35 +164,16 @@ class OrderController extends Controller
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
// renew and change subscribe process
if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
$order->type = 3;
if (!(int)config('v2board.plan_is_update', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
$order->total_amount = $order->total_amount + (ceil(($user->expired_at - time()) / 86400) * config('v2board.plan_update_fee', 0.5) * 100);
} 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
// coupon start
if (isset($coupon)) {
switch ($coupon->type) {
case 1: $order->discount_amount = $coupon->value;
case 1:
$order->discount_amount = $coupon->value;
break;
case 2: $order->discount_amount = $order->total_amount * ($coupon->value / 100);
case 2:
$order->discount_amount = $order->total_amount * ($coupon->value / 100);
break;
}
$order->total_amount = $order->total_amount - $order->discount_amount;
if ($coupon->limit_use !== NULL) {
$coupon->limit_use = $coupon->limit_use - 1;
if (!$coupon->save()) {
@ -148,10 +182,61 @@ class OrderController extends Controller
}
}
}
// free process
if ($order->total_amount <= 0) {
// coupon complete
// discount start
if ($user->discount) {
$order->discount_amount = $order->discount_amount + ($order->total_amount * ($user->discount / 100));
}
// discount end
$order->total_amount = $order->total_amount - $order->discount_amount;
// renew and change subscribe process
if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单');
$order->type = 3;
$order->surplus_amount = $this->getSurplusValue($user);
if ($order->surplus_amount >= $order->total_amount) {
$order->refund_amount = $order->surplus_amount - $order->total_amount;
$order->total_amount = 0;
$order->status = 1;
} 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()) {
@ -166,7 +251,8 @@ class OrderController extends Controller
]);
}
public function checkout (Request $request) {
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
@ -176,6 +262,13 @@ class OrderController extends Controller
if (!$order) {
abort(500, '订单不存在或已支付');
}
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
$order->save();
exit();
}
switch ($method) {
// return type => 0: QRCode / 1: URL
case 0:
@ -214,12 +307,21 @@ class OrderController extends Controller
'type' => 1,
'data' => $this->bitpayX($order)
]);
case 5:
if (!(int)config('v2board.paytaro_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->payTaro($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check (Request $request) {
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
@ -232,7 +334,8 @@ class OrderController extends Controller
]);
}
public function getPaymentMethod () {
public function getPaymentMethod()
{
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
@ -260,18 +363,27 @@ class OrderController extends Controller
if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass();
$bitpayX->name = '虚拟货币';
$bitpayX->name = '聚合支付';
$bitpayX->method = 4;
$bitpayX->icon = 'bitcoin';
$bitpayX->icon = 'wallet';
array_push($data, $bitpayX);
}
if ((int)config('v2board.paytaro_enable')) {
$obj = new \StdClass();
$obj->name = '聚合支付';
$obj->method = 5;
$obj->icon = 'wallet';
array_push($data, $obj);
}
return response([
'data' => $data
]);
}
public function cancel (Request $request) {
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数有误');
}
@ -284,8 +396,8 @@ class OrderController extends Controller
if ($order->status !== 0) {
abort(500, '只可以取消待支付订单');
}
$order->status = 2;
if (!$order->save()) {
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '取消失败');
}
return response([
@ -293,7 +405,8 @@ class OrderController extends Controller
]);
}
private function alipayF2F ($tradeNo, $totalAmount) {
private function alipayF2F($tradeNo, $totalAmount)
{
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
@ -316,16 +429,24 @@ class OrderController extends Controller
return $response->getQrCode();
}
private function stripeAlipay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
private function stripeAlipay($order)
{
$currency = config('stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'currency' => $currency,
'type' => 'alipay',
'statement_descriptor' => $order->trade_no,
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
@ -334,23 +455,29 @@ class OrderController extends Controller
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['redirect']['url'];
}
private function stripeWepay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
private function stripeWepay($order)
{
$currency = config('stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'currency' => $currency,
'type' => 'wechat',
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
@ -358,17 +485,17 @@ class OrderController extends Controller
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['wechat']['qr_code_url'];
}
private function bitpayX ($order) {
private function bitpayX($order)
{
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'merchant_order_id' => 'V2Board_' . $order->trade_no,
'merchant_order_id' => $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
@ -380,7 +507,20 @@ class OrderController extends Controller
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params);
Log::info('bitpayXSubmit: ' . json_encode($result));
// Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false;
}
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
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))
->first();

View File

@ -1,21 +1,25 @@
<?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\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Server;
use App\Models\ServerLog;
use App\Models\User;
use App\Utils\Helper;
class ServerController extends Controller {
public function fetch (Request $request) {
class ServerController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->session()->get('id'));
$server = [];
if ($user->expired_at > time()) {
$userService = new UserService();
if ($userService->isAvailable($user)) {
$servers = Server::where('show', 1)
->orderBy('name')
->get();
@ -29,9 +33,9 @@ class ServerController extends Controller {
for ($i = 0; $i < count($server); $i++) {
$server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = 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 {
$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([
@ -39,18 +43,22 @@ class ServerController extends Controller {
]);
}
public function logFetch (Request $request) {
public function logFetch(Request $request)
{
$type = $request->input('type') ? $request->input('type') : 0;
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$serverLogModel = ServerLog::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
switch ($type) {
case 0: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
case 0:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
break;
case 1: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
case 1:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
break;
case 2: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
case 2:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
}
$sum = [
'u' => $serverLogModel->sum('u'),

View File

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

View File

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

View File

@ -1,10 +1,10 @@
<?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\Requests\User\UserUpdate;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
@ -15,13 +15,16 @@ use App\Models\ServerLog;
class UserController extends Controller
{
public function logout (Request $request) {
public function logout(Request $request)
{
$request->session()->flush();
return response([
'data' => $request->session()->flush()
'data' => true
]);
}
public function changePassword (Request $request) {
public function changePassword(Request $request)
{
if (empty($request->input('old_password'))) {
abort(500, '旧密码不能为空');
}
@ -29,10 +32,15 @@ class UserController extends Controller
abort(500, '新密码不能为空');
}
$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, '旧密码有误');
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '保存失败');
}
@ -42,20 +50,24 @@ class UserController extends Controller
]);
}
public function info (Request $request) {
public function info(Request $request)
{
$user = User::where('id', $request->session()->get('id'))
->select([
'email',
'transfer_enable',
'last_login_at',
'created_at',
'enable',
'banned',
'is_admin',
'remind_expire',
'remind_traffic',
'expired_at',
'balance',
'commission_balance',
'plan_id'
'plan_id',
'discount',
'commission_rate'
])
->first();
$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 = [
Order::where('status', 0)
->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'));
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
@ -94,7 +108,8 @@ class UserController extends Controller
]);
}
public function resetSecurity (Request $request) {
public function resetSecurity(Request $request)
{
$user = User::find($request->session()->get('id'));
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
@ -102,11 +117,12 @@ class UserController extends Controller
abort(500, '重置失败');
}
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([
'remind_expire',
'remind_traffic'
@ -116,7 +132,9 @@ class UserController extends Controller
if (!$user) {
abort(500, '该用户不存在');
}
if (!$user->update($updateData)) {
try {
$user->update($updateData);
} catch (\Exception $e) {
abort(500, '保存失败');
}

View File

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

View File

@ -14,7 +14,7 @@ class Authenticate extends Middleware
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
if (!$request->expectsJson()) {
return route('login');
}
}

View File

@ -9,9 +9,9 @@ class CORS
public function handle($request, Closure $next)
{
$origin = $request->header('origin');
if(empty($origin)){
if (empty($origin)) {
$referer = $request->header('referer');
if(!empty($referer)&&preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)){
if (!empty($referer) && preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)) {
$origin = $matches[0];
}
}

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

@ -15,6 +15,13 @@ class User
*/
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')) {
abort(403, '未登录或登陆已过期');
}

View File

@ -7,22 +7,32 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest
{
CONST RULES = [
// invite & commission
'safe_mode_enable' => 'in:0,1',
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'commission_first_time_enable' => 'in:0,1',
// site
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_update_fee' => 'numeric',
'plan_is_update' => 'in:0,1',
'app_description' => '',
'app_url' => 'nullable|url',
'subscribe_url' => 'nullable|url',
'try_out_enable' => 'in:0,1',
'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_token' => 'nullable|min:16',
'server_license' => 'nullable',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16',
@ -34,18 +44,26 @@ class ConfigSave extends FormRequest
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
'stripe_currency' => 'in:hkd,usd,sgd',
// bitpayx
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// paytaro
'paytaro_enable' => 'in:0,1',
'paytaro_app_id' => '',
'paytaro_app_secret' => '',
// frontend
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black',
'frontend_background_url' => 'nullable|url',
// tutorial
'apple_id' => 'email',
'apple_id_password' => ''
'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.
*
@ -58,7 +76,10 @@ class ConfigSave extends FormRequest
public function messages()
{
// illiteracy prompt
return [
'app_url.url' => '站点URL格式不正确必须携带http(s)://',
'subscribe_url.url' => '订阅URL格式不正确必须携带http(s)://'
];
}
}

View File

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

View File

@ -16,7 +16,7 @@ class NoticeSave extends FormRequest
return [
'title' => 'required',
'content' => 'required',
'img_url' => 'url'
'img_url' => 'nullable|url'
];
}

View File

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

View File

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

View File

@ -6,14 +6,8 @@ use Illuminate\Foundation\Http\FormRequest;
class ServerSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
CONST RULES = [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
@ -21,10 +15,21 @@ class ServerSave extends FormRequest
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'array',
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic'
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
'networkSettings' => '',
'ruleSettings' => '',
'tlsSettings' => ''
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return self::RULES;
}
public function messages()

View File

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

View File

@ -6,6 +6,21 @@ use Illuminate\Foundation\Http\FormRequest;
class UserUpdate extends FormRequest
{
CONST RULES = [
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'is_admin' => 'required|in:0,1',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_balance' => 'integer'
];
/**
* Get the validation rules that apply to the request.
*
@ -13,15 +28,7 @@ class UserUpdate extends FormRequest
*/
public function rules()
{
return [
'email' => 'required|email',
'transfer_enable' => 'numeric',
'expired_at' => 'integer',
'banned' => 'required|in:0,1',
'is_admin' => 'required|in:0,1',
'plan_id' => 'integer',
'commission_rate' => 'nullable|integer|min:0|max:100'
];
return self::RULES;
}
public function messages()
@ -39,7 +46,15 @@ class UserUpdate extends FormRequest
'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确',
'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;
class ForgetIndex extends FormRequest
class AuthForget extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest;
class LoginIndex extends FormRequest
class AuthLogin extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest;
class RegisterIndex extends FormRequest
class AuthRegister extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

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

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;

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

View File

@ -36,7 +36,6 @@ class RouteServiceProvider extends ServiceProvider
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
@ -65,9 +64,14 @@ class RouteServiceProvider extends ServiceProvider
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::group([
'prefix' => '/api/v1',
'middleware' => 'api',
'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;
use App\Models\Server;
use App\Models\User;
class Helper
{
public static function guid ($format = false) {
public static function guid($format = false)
{
if (function_exists('com_create_guid') === true) {
return md5(trim(com_create_guid(), '{}'));
}
@ -14,16 +18,18 @@ class Helper
if ($format) {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)).'-'.time());
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . '-' . time());
}
public static function exchange ($from, $to) {
public static function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangeratesapi.io/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
public static function randomChar($len, $special=false){
public static function randomChar($len, $special = false)
{
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
@ -33,7 +39,7 @@ class Helper
"3", "4", "5", "6", "7", "8", "9"
);
if($special){
if ($special) {
$chars = array_merge($chars, array(
"!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
"%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
@ -44,31 +50,52 @@ class Helper
$charsLen = count($chars) - 1;
shuffle($chars);
$str = '';
for($i=0; $i<$len; $i++){
for ($i = 0; $i < $len; $i++) {
$str .= $chars[mt_rand(0, $charsLen)];
}
return $str;
}
public static function buildVmessLink($item, $user) {
public static function buildVmessLink(Server $server, User $user)
{
$config = [
"v" => "2",
"ps" => $item->name,
"add" => $item->host,
"port" => $item->port,
"ps" => $server->name,
"add" => $server->host,
"port" => $server->port,
"id" => $user->v2ray_uuid,
"aid" => "2",
"net" => $item->network,
"net" => $server->network,
"type" => "none",
"host" => "",
"path" => "",
"tls" => $item->tls?"tls":""
"tls" => $server->tls ? "tls" : ""
];
if ($item->network == 'ws') {
$wsSettings = json_decode($item->settings);
if ((string)$server->network === 'ws') {
$wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
}
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",
"laravel/framework": "^6.0",
"laravel/tinker": "^1.0",
"lokielse/omnipay-alipay": "^3.0",
"lokielse/omnipay-alipay": "3.0.6",
"php-curl-class/php-curl-class": "^8.6",
"stripe/stripe-php": "^7.5",
"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' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View File

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

View File

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

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

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

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

View File

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

View File

@ -123,5 +123,80 @@ SET NAMES utf8mb4;
INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
(1, 'Windows', '兼容 Windows 7 以上的版本', 'fab fa-2x fa-windows', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577980882),
(2, 'Android', '兼容 Android 6 以上的版本', 'fab fa-2x fa-android', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577981610),
(3, 'macOS', '兼容 Yosemite 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577981646),
(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(\'{{$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);
ALTER TABLE `v2_server_log`
CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
ALTER TABLE `v2_order`
DROP `method`;
ALTER TABLE `v2_invite_code`
ADD `pv` int(11) NOT NULL DEFAULT '0' AFTER `status`;
ALTER TABLE `v2_user`
ADD `password_algo` char(10) COLLATE 'utf8_general_ci' NULL AFTER `password`;
ALTER TABLE `v2_server`
CHANGE `tls` `tls` tinyint(4) NOT NULL DEFAULT '0' AFTER `server_port`;
ALTER TABLE `v2_server`
ADD `rules` text COLLATE 'utf8_general_ci' NULL AFTER `settings`;
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;
ALTER TABLE `v2_user`
ADD `discount` int(11) NULL AFTER `balance`;
ALTER TABLE `v2_order`
ADD `surplus_amount` int(11) NULL COMMENT '剩余价值' AFTER `discount_amount`;
ALTER TABLE `v2_order`
ADD `refund_amount` int(11) NULL COMMENT '退款金额' AFTER `surplus_amount`;
ALTER TABLE `v2_tutorial`
ADD `category_id` int(11) NOT NULL AFTER `id`;
ALTER TABLE `v2_tutorial`
DROP `description`;
ALTER TABLE `v2_plan`
CHANGE `month_price` `month_price` int(11) NULL AFTER `content`,
CHANGE `quarter_price` `quarter_price` int(11) NULL AFTER `month_price`,
CHANGE `half_year_price` `half_year_price` int(11) NULL AFTER `quarter_price`,
CHANGE `year_price` `year_price` int(11) NULL AFTER `half_year_price`,
ADD `onetime_price` int(11) NULL AFTER `year_price`;
ALTER TABLE `v2_user`
DROP `enable`,
ADD `banned` tinyint(1) NOT NULL DEFAULT '0' AFTER `transfer_enable`;
ALTER TABLE `v2_user`
CHANGE `expired_at` `expired_at` bigint(20) NULL DEFAULT '0' AFTER `token`;
ALTER TABLE `v2_tutorial`
DROP `icon`;
ALTER TABLE `v2_server`
CHANGE `settings` `networkSettings` text COLLATE 'utf8_general_ci' NULL AFTER `network`,
CHANGE `rules` `ruleSettings` text COLLATE 'utf8_general_ci' NULL AFTER `networkSettings`;
ALTER TABLE `v2_server`
CHANGE `tags` `tags` varchar(255) COLLATE 'utf8_general_ci' NULL AFTER `server_port`,
CHANGE `rate` `rate` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `tags`,
CHANGE `network` `network` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `rate`,
CHANGE `networkSettings` `networkSettings` text COLLATE 'utf8_general_ci' NULL AFTER `network`,
CHANGE `tls` `tls` tinyint(4) NOT NULL DEFAULT '0' AFTER `networkSettings`,
ADD `tlsSettings` text COLLATE 'utf8_general_ci' NULL AFTER `tls`;
ALTER TABLE `v2_order`
ADD `balance_amount` int(11) NULL COMMENT '使用余额' AFTER `refund_amount`;

View File

@ -1,2 +0,0 @@
php artisan key:generate
php artisan config:cache

View File

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

49
library/PayTaro.php Normal file
View File

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

View File

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

5
pm2.yaml Normal file
View File

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

File diff suppressed because one or more lines are too long

1
public/assets/admin/antd.async.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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