抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

自建代理转换节点

Github Peng-YM大佬的Sub-Store提供了几乎全平台的代理转换,Loon、Surge等可以安装其提供的模块直接使用,本教程是将Sub-Store搭建为网站,更方便clash等使用

前期准备

  • 域名
  • 证书
  • 服务器vps

服务器需要安装好nginx,并提前上传证书,开放端口

域名购买后可以添加二级域名解析,如购买的域名为 zqzess.com,添加一个sub.zqzess.com

克隆并运行项目

名称 地址
前端 Sub-Store-Front-End https://github.com/sub-store-org/Sub-Store-Front-End.git
后端 Sub-Store/backend https://github.com/sub-store-org/Sub-Store.git
  • 使用 git clone + 地址的方式克隆

  • 安装 pnpm npm i pnpm -g

  • 在前端 Sub-Store-Front-End文件夹下执行pnpm i 安装依赖

  • 依赖安装完成后,执行pnpm dev既可以使用浏览器打开http://localhost:8888/

  • 浏览器出现下面的页面即代表成功运行

    此时由于没有运行后端,所以会出现数据加载失败

修改Sub-Store前端

修改后端接口地址

可以使用WebStorm或者VScode打开前端项目

打开项目根目录下的.env.production文件,将文件里线上环境接口地址改为自己准备的域名,比如https://sub.zqzess.top

添加登陆页面

由于是要部署到服务器上,即为公开的,所以还需要一个登陆页面,阻止任何人访问

在项目根目录下的src/views下新建Login.vue文件

Login.vue代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<template>
<div class='wrapper'>
<div>欢迎使用</div>
<nut-form :model-value='formData' ref='ruleForm'>
<nut-form-item label='用户名' prop='name' required :rules="[{ required: true, message: '请填写用户名' }]">
<input class='nut-input-text' @blur="customBlurValidate('name')" v-model='formData.name'
placeholder='请输入用户名' type='text' />
</nut-form-item>
<nut-form-item label='密码' prop='pwd' required :rules="[
{ required: true, message: '请填写密码' },
// { validator: customValidator, message: '必须输入数字' },
// { regex: /^(\d{1,2}|1\d{2}|200000000)$/, message: '必须输入0-200000000区间' }
]">
<input class='nut-input-text' v-model='formData.pwd' placeholder='请输入密码' type='text' />
</nut-form-item>
<nut-cell>
<nut-button type='primary' size='small' style='margin-right: 10px' @click='submit'>登录</nut-button>
<nut-button size='small' @click='reset'>重置</nut-button>
</nut-cell>
</nut-form>
</div>
</template>

<script lang='ts'>
import { Notify, Toast } from '@nutui/nutui';
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
export default {
setup() {
const router = useRouter();
const formData = reactive({
name: '',
pwd: '',
});
const validate = (item: any) => {
console.log(item);
};
const ruleForm = ref<any>(null);

const submit = () => {
ruleForm.value.validate().then(({ valid, errors }: any) => {
if (valid) {
console.log('success', formData);
if (formData.name == 'name') {
if (formData.pwd == 'password') {
Notify.success('登录成功,欢迎回来!',{ duration: 1000 });
Toast.loading('', {
cover: false // 透明罩
});
sessionStorage.setItem('token', 'token') // 临时存储,关闭标签后就清除
setTimeout(() => {
router.push({path: '/sub'});
// router.replace('/sub')
Toast.hide();
}, 1200);
} else {
Notify.danger('密码错误!');
}
} else {
Notify.warn('用户不存在!');
}
} else {
console.log('error submit!!', errors);
}
});
};
const reset = () => {
ruleForm.value.reset();
};
// 失去焦点校验
const customBlurValidate = (prop: string) => {
ruleForm.value.validate(prop).then(({ valid, errors }: any) => {
if (valid) {
console.log('success', formData);
} else {
console.log('error submit!!', errors);
}
});
};
// 函数校验
const customValidator = (val: string) => /^\d+$/.test(val);
// Promise 异步校验
const asyncValidator = (val: string) => {
return new Promise((resolve) => {
Toast.loading('模拟异步验证中...');
setTimeout(() => {
Toast.hide();
resolve(/^400(-?)[0-9]{7}$|^1\d{10}$|^0[0-9]{2,3}-[0-9]{7,8}$/.test(val));
}, 1000);
});
};
return { ruleForm, formData, validate, customValidator, asyncValidator, customBlurValidate, submit, reset };
},
};
</script>

<style lang='scss' scoped>
.wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;

h3 {
padding-bottom: 24px;
}
}
</style>

修改代码中的用户名、密码、token

示例:

1
2
3
4
5
6
7
if (formData.name == 'zqzess') {
if (formData.pwd == 'zqzess') {
Notify.success('登录成功,欢迎回来!',{ duration: 1000 });
Toast.loading('', {
cover: false // 透明罩
});
sessionStorage.setItem('token', 'code') // 临时存储,关闭标签后就清除

此处sessionStorage.setItem('token', 'code')定义的token为code,一定要记住,后面路由会用到

此处formData.name == 'zqzess'定义的zqzess即为用户名

此处formData.pwd == 'zqzess'定义的zqzess即为密码

1
这种明文写在代码里面账号密码其实也不安全,仅作为一个简单的认证,以后有机会再改写需要后端认证的登陆

修改路由

接下来需要把登陆页面添加至路由里面,并加入拦截,没有token的需要跳转登陆页

项目根目录/src/router/index.ts

添加import Login from '@/views/Login.vue';

接着修改根路由,将如下代码修改为图片上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
routes: [
{
path: '/',
component: AppLayout,
redirect: '/subs',
children: [
{
path: '/subs',
component: Sub,
meta: {
title: 'sub',
needTabBar: true,
needNavBack: false,
},
},


即将第一个path: '/'改为path: '/sub'

接着添加登陆路由
path: '/:pathMatch(.*)'代码后面添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
path: '/:pathMatch(.*)',
component: NotFound,
meta: {
title: 'notFound',
needTabBar: false,
needNavBack: true,
},
},

{
path: '/',
component: Login,
meta: {
title: 'login',
needTabBar: false,
needNavBack: false,
},
}

在本页最后一行export default router;上面添加路由拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
router.beforeEach((to, from, next) => {
// let token = window.localStorage.getItem('token')
// let type = window.localStorage.getItem('type')
if (to.path === '/' || to.path === '/login' || to.path === '/error') {
// console.log("允许直接访问")
next();
} else {
// let token = window.localStorage.getItem('token') // 长期存储
let token = window.sessionStorage.getItem('token') // 临时存储,关闭标签后就清除
// console.log("需要token")
if (token === null || token === '' || token !== 'code') {
// console.log("无token,跳转登录")
next('/');
} else {
// console.log("有token")
next();
}
}
});


export default router;

此处if (token === null || token === '' || token !== 'code') {的code即为上面登陆页填写的token

接着修改根目录/src/views/SubEditor.vue
由于代码较多,可以直接搜索router.replace,会跳转到相应代码
router.replace('/')改为router.replace('/sub')

登陆页的命名与翻译

根目录/src/locales/en.ts

可以使用搜索notFound: '404 Not Found',,再此行下面添加login: 'Login',

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pagesTitle: {
sub: 'Subscription Management',
sync: 'Sync Subscription',
my: 'My Profile',
subEditor: 'Subscription Editor',
themeSetting: 'Theme Setting',
notFound: '404 Not Found',
login: 'Login',
askWhat: {
sync: {
title: 'What is Sync Subscription?',
content:
'Upload your subscriptions to a private Gist, which can be accessed at any time on devices that do not run the Sub Store (e.g. routers, etc.).',
},
},
},

同样

根目录/src/locales/zh.ts
搜索notFound: '地址未找到',,再此行下面添加login: '登录',

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pagesTitle: {
sub: '订阅管理',
sync: '同步订阅',
my: '我的',
subEditor: '订阅编辑',
themeSetting: '主题设置',
notFound: '地址未找到',
login: '登录',
askWhat: {
sync: {
title: '什么是同步订阅?',
content:
'将您的订阅信息上传到私有 Gist,在无法运行 Sub Store 的设备(例如路由器等)上也可以随时访问。',
},
},
},

至此已经全部结束,接下来就是打包发布并部署服务器

前端打包

在根目录下进入终端命令,执行``pnpm build`,然后等待其打包好

打包好后,根目录会多出dist文件夹,此文件夹就是打包好的网站文件,也是我们需要发布服务器的

上传服务器并配置启动后端

在服务器上找一个文件夹存放前后端文件

这是我的前端存放目录

再找一个文件夹把后端项目里的backend文件夹上传

这是我的后端存放目录

命令行进入后端存放目录,执行pnpm i

完成后再执行pm2 start /home/Sub-Store-new/backend/sub-store.min.js,完成后继续执行pm2 save,如果成功,至此后端已经成功启动

pm2需要先安装好

配置Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

stream {
# 这里就是 SNI 识别,将域名映射成一个配置名,请修改自己的一级域名
map $ssl_preread_server_name $backend_name {
sub.whitemoon.top sub;
# 域名都不匹配情况下的默认值
default web;
}
upstream sub {
server 127.0.0.1:10244;
}
# 监听 443 并开启 ssl_preread
server {
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}

}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;

server {
# 开启 HTTP2 支持
listen 10244 ssl http2;
server_name sub.zqzess.com;
# root /home/Sub-Store-new/web/dist;
# index index.html;

gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /usr/share/nginx/key/1_sub.zqzess.com_bundle.crt;
ssl_certificate_key /usr/share/nginx/key/2_sub.zqzess.com.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# # WS 协议转发
# location /fTY9Bx7c {
# proxy_redirect off;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_set_header Host $http_host;
# proxy_pass http://127.0.0.1:16881;
# }

# location / {
# proxy_pass http://127.0.0.1:3000;
# }

location / {
root /home/Sub-Store-new/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location /api {
proxy_pass http://127.0.0.1:3000;
}

location /download {
proxy_pass http://127.0.0.1:3000;
}
}
}

完结

最后访问自己的域名查看效果

参考

Sub-Store

评论

留下的你见解