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

一、前期准备

  • 域名,托管到 cloudflare(其它地方也行,但是 CF 相对比较方便)
  • 证书,免费证书三个月,自动续订即可
  • 服务器vps,配置高点,不要太低

服务器需要安装好nginx,建议小白安装好宝塔面板,部署好基本环境

二、部署过程

1、域名设置

在 cloudflare 的域名 DNS 记录里面添加两个 A 记录,一个是 substore.domain.com, 一个是 subapi.domain.com (实际 A 记录的名字随意,只要你能区分就行)见下图例子:

用 VPS架设自己的 Sub-store

备注:后面的代理黄云勾不勾随意,如果你用其它第三方 ssl 证书可以不勾,如果用 CF 的证书就勾上。

2、克隆项目

名称地址
前端 Sub-Store-Front-Endhttps://github.com/sub-store-org/Sub-Store-Front-End.git
后端 Sub-Store/backendhttps://github.com/sub-store-org/Sub-Store.git

使用git clone + 地址的方式克隆

3、安装 node和pnpm环境,pm2管理器

①下载和安装 fnm(Node.js 版本管理器)

1
curl -fsSL https://fnm.vercel.app/install | bash

用 VPS 架设自己的Sub-store

看上一步的提示复制粘贴 不要抄下面的

②根据上一步提示执行命令:看上一步的提示复制粘贴 不要抄下面的

③安装 node.js

1
fnm install v16.13.2

④安装 PNPM 软件包管理器

1
curl -fsSL https://get.pnpm.io/install.sh | sh -

用 VPS 架设自己的 Sub-store

看上一步的提示复制粘贴 不要抄下面的

⑤根据上一步提示执行命令:看上一步的提示复制粘贴 不要抄下面的

⑥安装 pnpm

1
npm i pnpm -g

⑦安装pm2项目管理器

进入宝塔面板软件商店安装pm2项目管理器

image-20231122174553288

image-20231122174654449

4、前端修改编译

①修改后端接口地址

宝塔自带文件管理器打开项目根目录 Sub-Store-Front-End文件夹下的.env.production文件,将文件里线上环境接口地址改为自己准备的域名,比如https://subapi.domain.com/token,token尽量复杂,下面Nginx配置需要用到,本文这里填https://subapi.domain.com/UV3yVmNYreAhkVgP

image-20231122182718090

②添加登录页面

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

在项目根目录下的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
<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

image-20231122183245746

示例:

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即为密码

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

③修改路由

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

项目根目录,**/src/router/index.ts**,添加:

1
import Login from '@/views/Login.vue';

image-20231122183720361

接着修改根路由,将如下代码修改为图片上的,就是将第一个path: '/'改为path: '/sub'

image-20231122184529276

接着添加登陆路由,在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,
},
}

image-20231122184807929

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

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

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;

image-20231122185021731

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

image-20231122185218430

④登陆页的命名与翻译

根目录/src/locales/en.ts

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

image-20231122185424740

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

image-20231122185522788

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

⑤前端打包

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

1
pnpm install

在前端 Sub-Store-Front-End文件夹下执行,执行打包命令

1
pnpm build

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

202209182143647

5、部署后端

进入后端项目里的backend文件夹,进入开源后端项目最新Releases下载执行文件

image-20231122190240938

1
2
cd /root/Sub-Store/backend
wget https://github.com/sub-store-org/Sub-Store/releases/download/2.14.99/sub-store.min.js

之后,执行

1
pnpm i

完成后再执行:

1
pm2 start sub-store.min.js

完成后继续执行:

1
pm2 save

202209211913984

如果成功,至此后端已经成功启动

pm2常用命令

1
2
3
4
pm2 list
pm2 log <name>
pm2 stop id
pm2 delete id

6、配置网站

宝塔面板添加站点,并配置好证书

image-20231122190954530

将之前打包好的dist文件夹里面的所有文件剪切到substore.domain.com站点根目录,不要复制整体dist文件夹,而是里面的单个文件

subapi.domain.com域名配置反代,进入站点设置

1
2
3
location /UV3yVmNYreAhkVgP/{      //API-token 也要根据需要修改,尽量复杂
proxy_pass http://127.0.0.1:3000/;
}

image-20231122192313286

三、使用教程

进入网站https://substore.domain.com,输入之前设置的用户名和密码

image-20231122192447260

进入我的——后端设置

image-20231122192610493

如果后端是之前设置的线上环境地址,就不用管了,可以直接转换节点了

image-20231122182718090

如果不是,需要手动添加新后端并保存好,名称随意,链接填刚刚反代的地址,https://subapi.domain.com/UV3yVmNYreAhkVgP

image-20231122193022455


参考文章:

https://www.whitemoon.top/posts/e36e6bfe.html

https://www.evan888.top/1974/