mirror of
https://github.com/certd/certd.git
synced 2026-04-14 12:30:54 +08:00
pref: 安全特性支持,站点隐藏功能
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
import crypto, { BinaryToTextEncoding } from 'crypto';
|
||||
import crypto, { BinaryToTextEncoding } from "crypto";
|
||||
|
||||
function md5(data: string, digest: BinaryToTextEncoding = 'hex') {
|
||||
return crypto.createHash('md5').update(data).digest(digest);
|
||||
function md5(data: string, digest: BinaryToTextEncoding = "hex") {
|
||||
return crypto.createHash("md5").update(data).digest(digest);
|
||||
}
|
||||
function sha256(data: string, digest: BinaryToTextEncoding = 'hex') {
|
||||
return crypto.createHash('sha256').update(data).digest(digest);
|
||||
function sha256(data: string, digest: BinaryToTextEncoding = "hex") {
|
||||
return crypto.createHash("sha256").update(data).digest(digest);
|
||||
}
|
||||
|
||||
function hmacSha256(data: string, digest: BinaryToTextEncoding = 'base64') {
|
||||
return crypto.createHmac('sha256', data).update(Buffer.alloc(0)).digest(digest);
|
||||
function hmacSha256(data: string, digest: BinaryToTextEncoding = "base64") {
|
||||
return crypto.createHmac("sha256", data).update(Buffer.alloc(0)).digest(digest);
|
||||
}
|
||||
|
||||
function base64(data: string) {
|
||||
return Buffer.from(data).toString('base64');
|
||||
return Buffer.from(data).toString("base64");
|
||||
}
|
||||
export const hashUtils = {
|
||||
md5,
|
||||
|
||||
@@ -66,10 +66,15 @@ export const Constants = {
|
||||
code: 404,
|
||||
message: '页面/文件/资源不存在',
|
||||
},
|
||||
|
||||
preview: {
|
||||
code: 10001,
|
||||
message: '对不起,预览环境不允许修改此数据',
|
||||
},
|
||||
siteOff:{
|
||||
code: 10010,
|
||||
message: '站点已关闭',
|
||||
},
|
||||
openKeyError: {
|
||||
code: 20000,
|
||||
message: 'ApiToken错误',
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './vip-exception.js';
|
||||
export * from './common-exception.js';
|
||||
export * from './not-found-exception.js';
|
||||
export * from './param-exception.js';
|
||||
export * from './site-off-exception.js';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
/**
|
||||
*/
|
||||
export class SiteOffException extends BaseException {
|
||||
constructor(message) {
|
||||
super('SiteOffException', Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message);
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export class SysSuiteSetting extends BaseSettings {
|
||||
static __key__ = 'sys.suite';
|
||||
static __access__ = 'private';
|
||||
|
||||
enabled = false;
|
||||
enabled:boolean = false;
|
||||
|
||||
registerGift?: {
|
||||
productId: number;
|
||||
@@ -180,3 +180,25 @@ export class SysSuiteSetting extends BaseSettings {
|
||||
|
||||
intro?: string;
|
||||
}
|
||||
|
||||
|
||||
export type SiteHidden = {
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
//md5 hash 两次后保存
|
||||
openPassword?: string;
|
||||
autoHiddenTimes?: number;
|
||||
hiddenOpenApi?: boolean
|
||||
};
|
||||
export class SysSafeSetting extends BaseSettings {
|
||||
static __title__ = '站点安全设置';
|
||||
static __key__ = 'sys.safe';
|
||||
static __access__ = 'private';
|
||||
|
||||
// 站点隐藏
|
||||
hidden:SiteHidden = {
|
||||
enabled: false,
|
||||
hiddenOpenApi:false,
|
||||
autoHiddenTimes: 5,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-vue": "^1.9.16",
|
||||
"sortablejs": "^1.15.3",
|
||||
"spark-md5": "^3.0.2",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"theme-colors": "^0.1.0",
|
||||
|
||||
@@ -54,23 +54,23 @@ const steps = ref<Step[]>([
|
||||
descriptions: ["本教程演示如何自动申请证书并部署到Nginx上", "仅需3步,全自动申请部署证书"],
|
||||
body: () => {
|
||||
return <SimpleSteps></SimpleSteps>;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/1-add.png",
|
||||
title: "创建证书流水线",
|
||||
descriptions: ["点击添加证书流水线,填写证书申请信息"]
|
||||
descriptions: ["点击添加证书流水线,填写证书申请信息"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/3-add-success.png",
|
||||
title: "流水线创建成功",
|
||||
descriptions: ["点击手动触发即可申请证书"]
|
||||
descriptions: ["点击手动触发即可申请证书"],
|
||||
},
|
||||
{
|
||||
title: "接下来演示如何自动部署证书",
|
||||
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"]
|
||||
}
|
||||
]
|
||||
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "添加部署证书任务",
|
||||
@@ -79,29 +79,29 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/5-1-add-host.png",
|
||||
title: "添加证书部署任务",
|
||||
descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"]
|
||||
descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-2-add-host.png",
|
||||
title: "填写任务参数",
|
||||
descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"]
|
||||
descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-3-add-host.png",
|
||||
title: "让新证书生效",
|
||||
descriptions: ["执行重启脚本", "让证书生效"]
|
||||
descriptions: ["执行重启脚本", "让证书生效"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-4-add-host.png",
|
||||
title: "部署任务添加成功",
|
||||
descriptions: ["现在可以运行"]
|
||||
descriptions: ["现在可以运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-5-plugin-list.png",
|
||||
title: "本系统提供茫茫多的部署插件",
|
||||
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"]
|
||||
}
|
||||
]
|
||||
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "运行与测试",
|
||||
@@ -110,44 +110,44 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/9-start.png",
|
||||
title: "运行测试一下",
|
||||
descriptions: ["点击手动触发按钮,即可测试运行"]
|
||||
descriptions: ["点击手动触发按钮,即可测试运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/10-1-log.png",
|
||||
title: "查看日志",
|
||||
descriptions: ["点击任务可以查看状态和日志"]
|
||||
descriptions: ["点击任务可以查看状态和日志"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-1-error.png",
|
||||
title: "执行失败如何排查",
|
||||
descriptions: ["查看错误日志"]
|
||||
descriptions: ["查看错误日志"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-2-error.png",
|
||||
title: "执行失败如何排查",
|
||||
descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"]
|
||||
descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-1-log-success.png",
|
||||
title: "执行成功",
|
||||
descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"]
|
||||
descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-2-skip-log.png",
|
||||
title: "成功后自动跳过",
|
||||
descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"]
|
||||
descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-1-result.png",
|
||||
title: "查看证书部署成功",
|
||||
descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"]
|
||||
descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-3-download.png",
|
||||
title: "还可以下载证书,手动部署",
|
||||
descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"]
|
||||
}
|
||||
]
|
||||
descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "设置定时执行和邮件通知",
|
||||
@@ -156,22 +156,19 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/14-timer.png",
|
||||
title: "设置定时执行",
|
||||
descriptions: [
|
||||
"流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
|
||||
"推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。"
|
||||
]
|
||||
descriptions: ["流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了", "推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/15-1-email.png",
|
||||
title: "设置邮件通知",
|
||||
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"]
|
||||
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"],
|
||||
},
|
||||
{
|
||||
title: "教程结束",
|
||||
descriptions: ["感谢观看,希望对你有所帮助"]
|
||||
}
|
||||
]
|
||||
}
|
||||
descriptions: ["感谢观看,希望对你有所帮助"],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const current = ref(0);
|
||||
|
||||
@@ -16,6 +16,6 @@ export const util = {
|
||||
router: routerUtils,
|
||||
tree: treeUtils,
|
||||
hash: hashUtils,
|
||||
amount: amountUtils
|
||||
amount: amountUtils,
|
||||
};
|
||||
export const utils = util;
|
||||
|
||||
@@ -5,5 +5,5 @@ export const amountUtils = {
|
||||
|
||||
toYuan(amount: number): number {
|
||||
return parseFloat((amount / 100).toFixed(2));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,4 +47,13 @@ export default {
|
||||
}
|
||||
return desc.replace(/\[(.*)\]\((.*)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
},
|
||||
|
||||
randomString(length: number) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const hashUtils = {
|
||||
md5(data: string) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@ export const site = {
|
||||
title: function (titleText: string, baseTitle?: string) {
|
||||
const processTitle = baseTitle || env.TITLE || "Certd";
|
||||
window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export class WebStorage {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
time: Date.now(),
|
||||
expire: expire != null ? new Date().getTime() + expire * 1000 : null
|
||||
expire: expire != null ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
this.storage.setItem(this.getKey(key), stringData);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export async function GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function AddObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function UpdateObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function DelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,14 @@ export async function GetObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function Unlock(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/unlockBlock",
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as api from "./api";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
@@ -26,16 +27,36 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right"
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
unlock: {
|
||||
title: "解除登录锁定",
|
||||
text: null,
|
||||
type: "link",
|
||||
icon: "ion:lock-open-outline",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: "确定要解除该用户的登录锁定吗?",
|
||||
onOk: async () => {
|
||||
await api.Unlock(row.id);
|
||||
notification.success({
|
||||
message: "解除成功",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: {
|
||||
//使用固定列时需要设置此值,并且大于等于列宽度之和的值
|
||||
x: 1400
|
||||
}
|
||||
x: 1400,
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
@@ -44,8 +65,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
@@ -53,8 +74,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 180,
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
// updateTime: {
|
||||
// title: "修改时间",
|
||||
@@ -72,64 +93,64 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入用户名" },
|
||||
{ max: 50, message: "最大50个字符" }
|
||||
]
|
||||
{ max: 50, message: "最大50个字符" },
|
||||
],
|
||||
},
|
||||
editForm: { component: { disabled: false } },
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 200
|
||||
}
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: "密码",
|
||||
type: "text",
|
||||
key: "password",
|
||||
column: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
component: {
|
||||
showPassword: true
|
||||
showPassword: true,
|
||||
},
|
||||
helper: "填写则修改密码"
|
||||
}
|
||||
helper: "填写则修改密码",
|
||||
},
|
||||
},
|
||||
nickName: {
|
||||
title: "昵称",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: "邮箱",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160
|
||||
}
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: "手机号",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 130
|
||||
}
|
||||
width: 130,
|
||||
},
|
||||
},
|
||||
avatar: {
|
||||
title: "头像",
|
||||
@@ -140,12 +161,12 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
//设置高度,修复操作列错位的问题
|
||||
style: {
|
||||
height: "30px",
|
||||
width: "auto"
|
||||
width: "auto",
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `/api/basic/file/download?&key=` + key;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
@@ -154,7 +175,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
cropper: {
|
||||
aspectRatio: 1,
|
||||
autoCropArea: 1,
|
||||
viewMode: 0
|
||||
viewMode: 0,
|
||||
},
|
||||
onReady: null,
|
||||
uploader: {
|
||||
@@ -162,17 +183,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
action: "/basic/file/upload",
|
||||
name: "file",
|
||||
headers: {
|
||||
Authorization: "Bearer " + userStore.getToken
|
||||
Authorization: "Bearer " + userStore.getToken,
|
||||
},
|
||||
successHandle(res: any) {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `/api/basic/file/download?&key=` + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
@@ -180,24 +201,24 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: 1, color: "green" },
|
||||
{ label: "禁用", value: 0, color: "red" }
|
||||
]
|
||||
{ label: "禁用", value: 0, color: "red" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100
|
||||
}
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
type: "text",
|
||||
column: {
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 100, message: "最大100个字符" }]
|
||||
}
|
||||
rules: [{ max: 100, message: "最大100个字符" }],
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
title: "角色",
|
||||
@@ -205,17 +226,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
dict: dict({
|
||||
url: "/sys/authority/role/list",
|
||||
value: "id",
|
||||
label: "name"
|
||||
label: "name",
|
||||
}), // 数据字典
|
||||
form: {
|
||||
component: { mode: "multiple" }
|
||||
component: { mode: "multiple" },
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sortable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sortable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,3 +103,4 @@ export async function GetSmsTypeDefine(type: string) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<!-- </template>-->
|
||||
<div class="sys-settings-body md:p-5">
|
||||
<a-tabs :active-key="activeKey" type="card" class="sys-settings-tabs" @update:active-key="onChange">
|
||||
<a-tab-pane key="" tab="基本设置">
|
||||
<SettingBase v-if="activeKey === ''" />
|
||||
<a-tab-pane key="base" tab="基本设置">
|
||||
<SettingBase v-if="activeKey === 'base'" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="register" tab="注册设置">
|
||||
<SettingRegister v-if="activeKey === 'register'" />
|
||||
@@ -14,6 +14,9 @@
|
||||
<a-tab-pane v-if="settingsStore.isComm" key="payment" tab="支付设置">
|
||||
<SettingPayment v-if="activeKey === 'payment'" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="save" tab="安全设置">
|
||||
<SettingSafe v-if="activeKey === 'save'" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</fs-page>
|
||||
@@ -23,6 +26,7 @@
|
||||
import SettingBase from "/@/views/sys/settings/tabs/base.vue";
|
||||
import SettingRegister from "/@/views/sys/settings/tabs/register.vue";
|
||||
import SettingPayment from "/@/views/sys/settings/tabs/payment.vue";
|
||||
import SettingSafe from "/@/views/sys/settings/tabs/safe.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
@@ -30,11 +34,11 @@ defineOptions({
|
||||
name: "SysSettings",
|
||||
});
|
||||
const settingsStore = useSettingStore();
|
||||
const activeKey = ref("");
|
||||
const activeKey = ref("base");
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
if (route.query.tab) {
|
||||
activeKey.value = (route.query.tab as string) || "";
|
||||
activeKey.value = (route.query.tab as string) || "base";
|
||||
}
|
||||
|
||||
function onChange(value: string) {
|
||||
@@ -52,7 +56,7 @@ function onChange(value: string) {
|
||||
<style lang="less">
|
||||
.page-sys-settings {
|
||||
.sys-settings-form {
|
||||
width: 500px;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
151
packages/ui/certd-client/src/views/sys/settings/tabs/safe.vue
Normal file
151
packages/ui/certd-client/src/views/sys/settings/tabs/safe.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="sys-settings-form sys-settings-safe">
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<div>站点隐藏</div>
|
||||
<a-form-item label="启用站点隐藏" :name="['hidden', 'enabled']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
<div class="helper">可以在平时关闭站点的可访问性,需要时再打开,增强站点安全性</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="随机地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<a-input-search v-model:value="formState.hidden.openPath" :allow-clear="true" @search="changeOpenPath">
|
||||
<template #enterButton>
|
||||
<fs-icon icon="ion:refresh"></fs-icon>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div class="helper">站点被隐藏后,需要访问此URL解锁,才能正常访问</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="完整解除隐藏地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<div class="flex"><fs-copyable v-model="openUrl" class="flex-inline"></fs-copyable></div>
|
||||
<div class="helper red">请保存好此地址</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="解除密码" :name="['hidden', 'openPassword']" :required="false">
|
||||
<a-input-password v-model:value="formState.hidden.openPassword" :allow-clear="true" />
|
||||
<div class="helper">解除隐藏时需要输入密码,第一次需要设置密码,填写则重置密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="自动隐藏时间" :name="['hidden', 'autoHiddenTimes']" :required="true">
|
||||
<a-input-number v-model:value="formState.hidden.autoHiddenTimes" :allow-clear="true" />
|
||||
<div class="helper">多少分钟内无请求自动隐藏</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="隐藏开放接口" :name="['hidden', 'hiddenOpenApi']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.hiddenOpenApi" />
|
||||
<div class="helper">是否隐藏开放接口,是否放开/api/v1开头的接口</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="立即隐藏站点">
|
||||
<loading-button class="ml-1" type="primary" html-type="button" :click="doHiddenImmediate">立即隐藏</loading-button>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { request } from "/@/api/service";
|
||||
import { util, utils } from "/@/utils";
|
||||
defineOptions({
|
||||
name: "SettingSafe",
|
||||
});
|
||||
|
||||
const api = {
|
||||
async SettingGet() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/get",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async SettingSave(data: any) {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/save",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
},
|
||||
async HiddenImmediate() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/hidden",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
hidden: {
|
||||
enabled: false,
|
||||
autoHiddenTimes: 5,
|
||||
hiddenOpenApi: false,
|
||||
},
|
||||
};
|
||||
const formRef = ref<any>(defaultState);
|
||||
type SiteHidden = {
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
autoHiddenTimes?: number;
|
||||
openPassword?: string;
|
||||
hiddenOpenApi?: boolean;
|
||||
};
|
||||
|
||||
const formState = reactive<
|
||||
Partial<{
|
||||
hidden: SiteHidden;
|
||||
}>
|
||||
>({
|
||||
hidden: { enabled: false },
|
||||
});
|
||||
|
||||
function changeOpenPath() {
|
||||
formState.hidden.openPath = util.randomString(16);
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const data: any = await api.SettingGet();
|
||||
merge(formState, defaultState, formState, data);
|
||||
if (!formState.hidden.openPath) {
|
||||
changeOpenPath();
|
||||
}
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
const openUrl = computed(() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = `/api/unhidden/${formState.hidden?.openPath || ""}`;
|
||||
//@ts-ignore
|
||||
url.query = undefined;
|
||||
url.hash = "";
|
||||
return url.href;
|
||||
});
|
||||
|
||||
const onClick = async () => {
|
||||
const form = await formRef.value.validateFields();
|
||||
//密码md5
|
||||
// if (form.hidden?.openPassword) {
|
||||
// form.hidden.openPassword = util.hash.md5(form.hidden.openPassword);
|
||||
// }
|
||||
await api.SettingSave(form);
|
||||
await loadSettings();
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
};
|
||||
|
||||
async function doHiddenImmediate() {
|
||||
Modal.confirm({
|
||||
title: "确定要立即隐藏站点吗?",
|
||||
content: "隐藏后,将无法访问站点,请谨慎操作",
|
||||
async onOk() {
|
||||
await api.HiddenImmediate();
|
||||
notification.success({
|
||||
message: "站点已隐藏",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.sys-settings-base {
|
||||
}
|
||||
</style>
|
||||
@@ -14,36 +14,19 @@ import DefineOptions from "unplugin-vue-define-options/vite";
|
||||
// 增加环境变量 _
|
||||
process.env.VITE_APP_VERSION = require("./package.json").version;
|
||||
process.env.VITE_APP_BUILD_TIME = require("dayjs")().format("YYYY-M-D HH:mm:ss");
|
||||
|
||||
import { theme } from "ant-design-vue";
|
||||
import * as https from "node:https";
|
||||
const { defaultAlgorithm, defaultSeed } = theme;
|
||||
|
||||
const mapToken = defaultAlgorithm(defaultSeed);
|
||||
|
||||
export default ({ command, mode }) => {
|
||||
console.log("args", command, mode);
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
let devServerFs: any = {};
|
||||
let devAlias: any[] = [];
|
||||
if (mode.startsWith("debug")) {
|
||||
devAlias = [
|
||||
{ find: /@fast-crud\/fast-crud\/dist/, replacement: path.resolve("../../fast-crud/src/") },
|
||||
// { find: /@fast-crud\/fast-crud$/, replacement: path.resolve("../../fast-crud/src/") },
|
||||
{ find: /@fast-crud\/fast-extends\/dist/, replacement: path.resolve("../../fast-extends/src/") },
|
||||
// { find: /@fast-crud\/fast-extends$/, replacement: path.resolve("../../fast-extends/src/") },
|
||||
// { find: /@fast-crud\/ui-antdv$/, replacement: path.resolve("../../ui/ui-antdv/src/") },
|
||||
// { find: /@fast-crud\/ui-interface$/, replacement: path.resolve("../../ui/ui-interface/src/") }
|
||||
{ find: /@fast-crud\/ui-antdv4\/dist/, replacement: path.resolve("../../ui/ui-antdv4/src/") }
|
||||
];
|
||||
devServerFs = {
|
||||
// 这里配置dev启动时读取的项目根目录
|
||||
allow: ["../../../"]
|
||||
};
|
||||
console.log("devAlias", devAlias);
|
||||
}
|
||||
const devServerFs: any = {};
|
||||
const devAlias: any[] = [];
|
||||
const base = "/";
|
||||
// if (mode.startsWith("dev")) {
|
||||
// base = "/dev";
|
||||
// }
|
||||
return {
|
||||
base: "/",
|
||||
base: base,
|
||||
plugins: [
|
||||
DefineOptions(),
|
||||
vueJsx(),
|
||||
@@ -52,12 +35,12 @@ export default ({ command, mode }) => {
|
||||
inject: {
|
||||
data: {
|
||||
title: env.VITE_APP_TITLE,
|
||||
projectPath: env.VITE_APP_PROJECT_PATH
|
||||
}
|
||||
}
|
||||
projectPath: env.VITE_APP_PROJECT_PATH,
|
||||
},
|
||||
},
|
||||
}),
|
||||
// 压缩build后的代码
|
||||
viteCompression()
|
||||
viteCompression(),
|
||||
//主题色替换
|
||||
//...configThemePlugin(true),
|
||||
// viteThemePlugin({
|
||||
@@ -71,29 +54,29 @@ export default ({ command, mode }) => {
|
||||
drop: command === "build" ? ["debugger"] : [],
|
||||
pure: ["console.log", "debugger"],
|
||||
jsxFactory: "h",
|
||||
jsxFragment: "Fragment"
|
||||
jsxFragment: "Fragment",
|
||||
},
|
||||
resolve: {
|
||||
alias: [...devAlias, { find: "/@", replacement: path.resolve("./src") }, { find: "/#", replacement: path.resolve("./types") }],
|
||||
dedupe: ["vue"]
|
||||
dedupe: ["vue"],
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["ant-design-vue"]
|
||||
include: ["ant-design-vue"],
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
plugins: [visualizer()]
|
||||
}
|
||||
plugins: [visualizer()],
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
// 修改默认主题颜色,配置less变量
|
||||
// modifyVars: generateModifyVars(),
|
||||
javascriptEnabled: true
|
||||
javascriptEnabled: true,
|
||||
// modifyVars: mapToken
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
@@ -105,9 +88,9 @@ export default ({ command, mode }) => {
|
||||
//配套后端 https://github.com/fast-crud/fs-server-js
|
||||
target: "https://127.0.0.1:7002",
|
||||
//忽略证书
|
||||
agent: new https.Agent({ rejectUnauthorized: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
agent: new https.Agent({ rejectUnauthorized: false }),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import * as libServer from '@certd/lib-server';
|
||||
import * as commercial from '@certd/commercial-core';
|
||||
import * as upload from '@midwayjs/upload';
|
||||
import { setLogger } from '@certd/acme-client';
|
||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||
process.on('uncaughtException', error => {
|
||||
console.error('未捕获的异常:', error);
|
||||
// 在这里可以添加日志记录、发送错误通知等操作
|
||||
@@ -77,6 +78,8 @@ export class MainConfiguration {
|
||||
this.app.useMiddleware([
|
||||
//统一异常处理
|
||||
GlobalExceptionMiddleware,
|
||||
//站点隐藏
|
||||
HiddenMiddleware,
|
||||
//预览模式限制修改id<1000的数据
|
||||
PreviewMiddleware,
|
||||
//授权处理
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import {Body, Controller, Get, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {Constants, NotFoundException, ParamException, SysInstallInfo, SysSettingsService} from '@certd/lib-server';
|
||||
import {utils} from "@certd/basic";
|
||||
import {hiddenStatus, SafeService} from "../../modules/sys/settings/safe-service.js";
|
||||
import {IMidwayKoaContext} from "@midwayjs/koa";
|
||||
|
||||
const unhiddenHtml = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>certd解除站点隐藏</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin:50px;width:500px">
|
||||
<h3>解除站点隐藏</h3>
|
||||
<form method="post">
|
||||
请输入解除密码: <input type="password" name="password" /> <button type="submit">确定</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/unhidden')
|
||||
export class HnhiddenController {
|
||||
@Inject()
|
||||
ctx: IMidwayKoaContext;
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
|
||||
@Post('/:randomPath', {summary: Constants.per.guest})
|
||||
async randomPath(@Body("password") password: any) {
|
||||
await this.checkUnhiddenPath()
|
||||
const hiddenSetting = await this.safeService.getHiddenSetting()
|
||||
if (utils.hash.md5(password) === hiddenSetting.openPassword) {
|
||||
//解锁
|
||||
hiddenStatus.isHidden = false;
|
||||
const setting = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo)
|
||||
const bindUrl = setting.bindUrl
|
||||
//解锁成功,跳转回首页,redirect
|
||||
this.ctx.response.redirect(bindUrl || "/");
|
||||
return
|
||||
} else {
|
||||
//密码错误
|
||||
throw new ParamException('解锁密码错误');
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:randomPath', {summary: Constants.per.guest})
|
||||
async unhiddenGet() {
|
||||
await this.checkUnhiddenPath()
|
||||
this.ctx.response.body = unhiddenHtml
|
||||
}
|
||||
|
||||
async checkUnhiddenPath() {
|
||||
const hiddenSetting = await this.safeService.getHiddenSetting()
|
||||
if (this.ctx.path != `/api/unhidden/${hiddenSetting.openPath}`) {
|
||||
this.ctx.res.statusCode = 404
|
||||
throw new NotFoundException("Page not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {BaseController, SysSafeSetting} from '@certd/lib-server';
|
||||
import {cloneDeep} from 'lodash-es';
|
||||
import {SafeService} from "../../../modules/sys/settings/safe-service.js";
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/sys/settings/safe')
|
||||
export class SysSettingsController extends BaseController {
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
|
||||
|
||||
|
||||
@Post("/get", { summary: "sys:settings:view" })
|
||||
async safeGet() {
|
||||
const res = await this.safeService.getSafeSetting()
|
||||
const clone:SysSafeSetting = cloneDeep(res);
|
||||
delete clone.hidden?.openPassword;
|
||||
return this.ok(clone);
|
||||
}
|
||||
|
||||
@Post("/save", { summary: "sys:settings:edit" })
|
||||
async safeSave(@Body(ALL) body: any) {
|
||||
await this.safeService.saveSafeSetting(body);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即隐藏
|
||||
*/
|
||||
@Post("/hidden", { summary: "sys:settings:edit" })
|
||||
async hiddenImmediate() {
|
||||
await this.safeService.hiddenImmediately();
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { CrudController, SysPrivateSettings, SysPublicSettings, SysSettingsEntity, SysSettingsService } from '@certd/lib-server';
|
||||
import { merge } from 'lodash-es';
|
||||
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
|
||||
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
|
||||
import { getEmailSettings } from '../../../modules/sys/settings/fix.js';
|
||||
import { http, logger, simpleNanoId } from '@certd/basic';
|
||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||
import { SmsServiceFactory } from '../../../modules/basic/sms/factory.js';
|
||||
import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
|
||||
import {
|
||||
CrudController,
|
||||
SysPrivateSettings,
|
||||
SysPublicSettings,
|
||||
SysSafeSetting,
|
||||
SysSettingsEntity,
|
||||
SysSettingsService
|
||||
} from '@certd/lib-server';
|
||||
import {cloneDeep, merge} from 'lodash-es';
|
||||
import {PipelineService} from '../../../modules/pipeline/service/pipeline-service.js';
|
||||
import {UserSettingsService} from '../../../modules/mine/service/user-settings-service.js';
|
||||
import {getEmailSettings} from '../../../modules/sys/settings/fix.js';
|
||||
import {http, logger, simpleNanoId, utils} from '@certd/basic';
|
||||
import {CodeService} from '../../../modules/basic/service/code-service.js';
|
||||
import {SmsServiceFactory} from '../../../modules/basic/sms/factory.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -159,4 +166,29 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
||||
async getSmsTypeDefine(@Body('type') type: string) {
|
||||
return this.ok(SmsServiceFactory.getDefine(type));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Post("/safe/get", { summary: "sys:settings:view" })
|
||||
async safeGet() {
|
||||
const res = await this.service.getSetting<SysSafeSetting>(SysSafeSetting);
|
||||
const clone:SysSafeSetting = cloneDeep(res);
|
||||
delete clone.hidden?.openPassword;
|
||||
return this.ok(clone);
|
||||
}
|
||||
|
||||
@Post("/safe/save", { summary: "sys:settings:edit" })
|
||||
async safeSave(@Body(ALL) body: any) {
|
||||
if(body.hidden.openPassword){
|
||||
body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword);
|
||||
}
|
||||
const blankSetting = new SysSafeSetting()
|
||||
const setting = await this.service.getSetting<SysSafeSetting>(SysSafeSetting);
|
||||
const newSetting = merge(blankSetting,cloneDeep(setting), body);
|
||||
if(newSetting.hidden?.enabled && !newSetting.hidden?.openPassword){
|
||||
throw new Error("首次设置需要填写解锁密码")
|
||||
}
|
||||
await this.service.saveSetting(blankSetting);
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
|
||||
54
packages/ui/certd-server/src/middleware/hidden.ts
Normal file
54
packages/ui/certd-server/src/middleware/hidden.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {Inject, Provide} from '@midwayjs/core';
|
||||
import {IMidwayKoaContext, IWebMiddleware, NextFunction} from '@midwayjs/koa';
|
||||
import {hiddenStatus, SafeService} from "../modules/sys/settings/safe-service.js";
|
||||
import {SiteOffException} from "@certd/lib-server";
|
||||
|
||||
|
||||
/**
|
||||
* 隐藏环境
|
||||
*/
|
||||
@Provide()
|
||||
export class HiddenMiddleware implements IWebMiddleware {
|
||||
@Inject()
|
||||
hiddenService: SafeService;
|
||||
|
||||
resolve() {
|
||||
return async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||
|
||||
async function pass() {
|
||||
hiddenStatus.updateRequestTime()
|
||||
await next();
|
||||
}
|
||||
|
||||
const hiddenSetting = await this.hiddenService.getHiddenSetting();
|
||||
if (!hiddenSetting || !hiddenSetting?.enabled) {
|
||||
//未开启站点隐藏,直接通过
|
||||
return await pass()
|
||||
}
|
||||
|
||||
const req = ctx.request;
|
||||
if (hiddenSetting.hiddenOpenApi === false && req.url.startsWith(`/api/v1/`) ) {
|
||||
//不隐藏开放接口
|
||||
await next();
|
||||
return
|
||||
}
|
||||
|
||||
//判断当前是否是隐藏状态
|
||||
if (!hiddenStatus.isHidden) {
|
||||
return await pass()
|
||||
}
|
||||
|
||||
//判断是否有解锁文件,如果有就返回true并删除文件
|
||||
if (hiddenStatus.hasUnHiddenFile()) {
|
||||
//临时修改为未隐藏
|
||||
hiddenStatus.isHidden = false;
|
||||
return await pass()
|
||||
}
|
||||
|
||||
if (req.url === `/api/unhidden/${hiddenSetting.openPath}`) {
|
||||
return await pass();
|
||||
}
|
||||
throw new SiteOffException('此站点已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||
import { nanoid } from 'nanoid';
|
||||
import crypto from 'crypto';
|
||||
import {SafeService} from "../sys/settings/safe-service.js";
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -18,6 +19,8 @@ export class AutoAInitSite {
|
||||
sysSettingsService: SysSettingsService;
|
||||
@Inject()
|
||||
plusService: PlusService;
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
@@ -57,6 +60,8 @@ export class AutoAInitSite {
|
||||
logger.error('授权许可验证失败', e);
|
||||
}
|
||||
|
||||
//加载站点隐藏配置
|
||||
await this.safeService.reloadHiddenStatus(true)
|
||||
logger.info('初始化站点完成');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {SiteHidden, SysSafeSetting, SysSettingsService} from "@certd/lib-server";
|
||||
import fs from "fs";
|
||||
import {logger, utils} from "@certd/basic";
|
||||
import {cloneDeep, merge} from "lodash-es";
|
||||
|
||||
|
||||
export class HiddenStatus {
|
||||
|
||||
|
||||
isHidden = false;
|
||||
lastRequestTime = 0;
|
||||
intervalId: any = null;
|
||||
|
||||
hasUnHiddenFile() {
|
||||
if (fs.existsSync(`./data/.unhidden`)) {
|
||||
fs.unlinkSync(`./data/.unhidden`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
updateRequestTime() {
|
||||
this.lastRequestTime = Date.now();
|
||||
}
|
||||
|
||||
startCheck(autoHiddenTimes = 5) {
|
||||
this.stopCheck()
|
||||
this.intervalId = setInterval(() => {
|
||||
//默认5分钟后自动隐藏
|
||||
if (!this.isHidden && Date.now() - this.lastRequestTime > 1000 * 60 * autoHiddenTimes) {
|
||||
this.isHidden = true;
|
||||
}
|
||||
}, 1000 * 60)
|
||||
}
|
||||
|
||||
stopCheck() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const hiddenStatus = new HiddenStatus();
|
||||
|
||||
|
||||
@Provide('safeService')
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
export class SafeService {
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
|
||||
async reloadHiddenStatus(immediate = false) {
|
||||
const hidden = await this.getHiddenSetting()
|
||||
if (hidden.enabled) {
|
||||
logger.error("启动站点隐藏");
|
||||
hiddenStatus.isHidden = false
|
||||
if (immediate) {
|
||||
hiddenStatus.isHidden = true;
|
||||
}
|
||||
const autoHiddenTimes = hidden.autoHiddenTimes || 5;
|
||||
hiddenStatus.startCheck(autoHiddenTimes);
|
||||
} else {
|
||||
logger.error("关闭站点隐藏");
|
||||
hiddenStatus.isHidden = false;
|
||||
hiddenStatus.stopCheck()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getHiddenSetting(): Promise<SiteHidden> {
|
||||
const safeSetting = await this.getSafeSetting()
|
||||
return safeSetting.hidden || {enabled: false}
|
||||
}
|
||||
|
||||
async getSafeSetting() {
|
||||
return await this.sysSettingsService.getSetting<SysSafeSetting>(SysSafeSetting)
|
||||
}
|
||||
|
||||
async hiddenImmediately() {
|
||||
return hiddenStatus.isHidden = true
|
||||
}
|
||||
|
||||
async saveSafeSetting(body: SysSafeSetting) {
|
||||
|
||||
// 更新hidden配置
|
||||
if (body.hidden.openPassword) {
|
||||
body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword);
|
||||
}
|
||||
const blankSetting = new SysSafeSetting()
|
||||
const setting = await this.getSafeSetting()
|
||||
const newSetting = merge(blankSetting, cloneDeep(setting), body);
|
||||
if (newSetting.hidden?.enabled && !newSetting.hidden?.openPassword) {
|
||||
throw new Error("首次设置需要填写解锁密码")
|
||||
}
|
||||
|
||||
if(isNaN(newSetting.hidden.autoHiddenTimes) || newSetting.hidden.autoHiddenTimes < 1){
|
||||
newSetting.hidden.autoHiddenTimes = 1
|
||||
}
|
||||
|
||||
await this.sysSettingsService.saveSetting(newSetting);
|
||||
|
||||
await this.reloadHiddenStatus(false)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -128,11 +128,11 @@ export class DeployCertToTencentAll extends AbstractTaskPlugin {
|
||||
});
|
||||
|
||||
let certId:string = null
|
||||
if (typeof certId === 'string') {
|
||||
certId = this.tencentCertId as string;
|
||||
} else {
|
||||
if (typeof certId === 'object') {
|
||||
//上传
|
||||
certId = await this.uploadToTencent(access,this.tencentCertId as CertInfo);
|
||||
} else {
|
||||
certId = this.tencentCertId as string;
|
||||
}
|
||||
|
||||
const params = {
|
||||
|
||||
Reference in New Issue
Block a user