添加 加密博客文章功能
This commit is contained in:
24
Server/src/APIs/Console/SetBlogPasswd.ts
Normal file
24
Server/src/APIs/Console/SetBlogPasswd.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { API } from "../../Plugs/API/API";
|
||||||
|
import ServerStdResponse from "../../ServerStdResponse";
|
||||||
|
import MySQLConnection from '../../Plugs/MySQLConnection'
|
||||||
|
import Auth from "../../Plugs/Middleware/Auth";
|
||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
// 设置博客密码
|
||||||
|
class SetBlogPasswd extends API {
|
||||||
|
constructor() {
|
||||||
|
super('POST', '/console/setBlogPasswd', Auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onRequset(data: any, res: any) {
|
||||||
|
let { uuid, passwd } = data;
|
||||||
|
if (!uuid || !passwd) {
|
||||||
|
return res.json(ServerStdResponse.PARAMS_MISSING);
|
||||||
|
}
|
||||||
|
const encrypt_p = crypto.createHash('sha256').update(passwd).digest('hex');
|
||||||
|
MySQLConnection.execute('UPDATE blog SET encrypt_p = ? WHERE uuid = ?', [encrypt_p, uuid]);
|
||||||
|
return res.json({ ...ServerStdResponse.OK });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SetBlogPasswd;
|
||||||
@@ -3,22 +3,33 @@ import ServerStdResponse from "../ServerStdResponse";
|
|||||||
import MySQLConnection from '../Plugs/MySQLConnection'
|
import MySQLConnection from '../Plugs/MySQLConnection'
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import MountIP from "../Plugs/Middleware/MountIP";
|
||||||
|
|
||||||
// 获取博客内容
|
// 获取博客内容
|
||||||
class GetBlogContent extends API {
|
class GetBlogContent extends API {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('GET', '/blogContent');
|
super('GET', '/blogContent', MountIP);
|
||||||
}
|
}
|
||||||
private defaultAccessLevel = 6;
|
private AccessLevelRule = {
|
||||||
|
allow: [8, 10],
|
||||||
|
encrypt_allow: [7, 9]
|
||||||
|
};
|
||||||
|
|
||||||
public async onRequset(data: any, res: any) {
|
public async onRequset(data: any, res: any) {
|
||||||
let { bloguuid } = data;
|
let { bloguuid, passwd } = data;
|
||||||
if (!bloguuid || bloguuid.length != 32) {
|
if (!bloguuid || bloguuid.length != 32) {
|
||||||
return res.json(ServerStdResponse.INVALID_PARAMS);
|
return res.json(ServerStdResponse.INVALID_PARAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
let blogContentRes = await MySQLConnection.execute('SELECT * from blog WHERE access_level > ? AND uuid = ? ', [this.defaultAccessLevel, bloguuid]);
|
let blogContentRes = await MySQLConnection.execute(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.allow.join(',')}) AND uuid = ? `, [bloguuid]);
|
||||||
|
if (!blogContentRes) {
|
||||||
|
this.logger.error('查询时数据库发生错误');
|
||||||
|
return res.json(ServerStdResponse.SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (blogContentRes.length == 0) {
|
||||||
|
// 公开范围不可见,查询允许无连接加密查看的数据
|
||||||
|
blogContentRes = await MySQLConnection.execute(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.encrypt_allow.join(',')}) AND uuid = ? `, [bloguuid]);
|
||||||
if (!blogContentRes) {
|
if (!blogContentRes) {
|
||||||
this.logger.error('查询时数据库发生错误');
|
this.logger.error('查询时数据库发生错误');
|
||||||
return res.json(ServerStdResponse.SERVER_ERROR);
|
return res.json(ServerStdResponse.SERVER_ERROR);
|
||||||
@@ -27,13 +38,26 @@ class GetBlogContent extends API {
|
|||||||
this.logger.warn('查询的博客不存在或不可见', bloguuid);
|
this.logger.warn('查询的博客不存在或不可见', bloguuid);
|
||||||
return res.json(ServerStdResponse.BLOG.NOTFOUND);
|
return res.json(ServerStdResponse.BLOG.NOTFOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证密码是否存在和正确
|
||||||
|
if (!passwd) {
|
||||||
|
this.logger.warn(`客户端[${data._ip}]尝试访问受限制的博客,但并未提供密码`)
|
||||||
|
return res.json(ServerStdResponse.BLOG.PROTECT_FLAG)
|
||||||
|
}
|
||||||
|
if (crypto.createHash('sha256').update(passwd).digest('hex') != blogContentRes[0].encrypt_p){
|
||||||
|
this.logger.warn(`客户端[${data._ip}]尝试访问受限制的博客,并提供了错误的密码:${passwd}`)
|
||||||
|
return res.json(ServerStdResponse.BLOG.PASSWD_ERROR)
|
||||||
|
}
|
||||||
|
this.logger.info(`客户端[${data._ip}]访问了受限制的博客`)
|
||||||
|
}
|
||||||
// 返回处理后的数据
|
// 返回处理后的数据
|
||||||
try {
|
try {
|
||||||
const markdownUrl = blogContentRes[0].src;
|
const markdownUrl = blogContentRes[0].src;
|
||||||
const response = await axios.get(markdownUrl);
|
const response = await axios.get(markdownUrl);
|
||||||
const base64Content = Buffer.from(response.data, 'utf-8').toString('base64');
|
const base64Content = Buffer.from(response.data, 'utf-8').toString('base64');
|
||||||
|
|
||||||
MySQLConnection.execute('UPDATE blog SET visit_count = visit_count + 1 WHERE uuid = ?', [bloguuid]);
|
// 访问次数+1
|
||||||
|
// MySQLConnection.execute('UPDATE blog SET visit_count = visit_count + 1 WHERE uuid = ?', [bloguuid]);
|
||||||
return res.json({
|
return res.json({
|
||||||
...ServerStdResponse.OK, data: {
|
...ServerStdResponse.OK, data: {
|
||||||
data: base64Content,
|
data: base64Content,
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ class GetBlogList extends API {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super('GET', '/blogList');
|
super('GET', '/blogList');
|
||||||
}
|
}
|
||||||
private defaultAccessLevel = 6;
|
private defaultAccessLevel = 9;
|
||||||
|
|
||||||
public async onRequset(data: any, res: any) {
|
public async onRequset(data: any, res: any) {
|
||||||
let blogListRes = await MySQLConnection.execute('SELECT uuid, title, description, publish_time, visit_count, like_count from blog WHERE access_level > ? ORDER BY publish_time DESC',[this.defaultAccessLevel]);
|
let blogListRes = await MySQLConnection.execute('SELECT uuid, title, description, publish_time, visit_count, like_count from blog WHERE access_level >= ? ORDER BY publish_time DESC',[this.defaultAccessLevel]);
|
||||||
if(!blogListRes){
|
if(!blogListRes){
|
||||||
this.logger.error('查询时数据库发生错误');
|
this.logger.error('查询时数据库发生错误');
|
||||||
return res.json(ServerStdResponse.SERVER_ERROR);
|
return res.json(ServerStdResponse.SERVER_ERROR);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const logger = new Logger('MountIP')
|
|||||||
|
|
||||||
let MountIP = (req: Request, res: Response, next: NextFunction) => {
|
let MountIP = (req: Request, res: Response, next: NextFunction) => {
|
||||||
req.body._ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
|
req.body._ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
|
||||||
logger.info(`[${req.method}][${req.url.split('?')[0]}] IP解析成功:${req.body._ip}`);
|
// logger.info(`[${req.method}][${req.url.split('?')[0]}] IP解析成功:${req.body._ip}`);
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import CheckCaptcha from "../APIs/CheckCaptcha";
|
|||||||
import Login from "../APIs/Console/Login";
|
import Login from "../APIs/Console/Login";
|
||||||
import GetResources from "../APIs/Console/GetResources";
|
import GetResources from "../APIs/Console/GetResources";
|
||||||
import GetBlogs from '../APIs/Console/GetBlogs'
|
import GetBlogs from '../APIs/Console/GetBlogs'
|
||||||
|
import SetBlogPasswd from "../APIs/Console/SetBlogPasswd";
|
||||||
import SaveResource from '../APIs/Console/SaveResource'
|
import SaveResource from '../APIs/Console/SaveResource'
|
||||||
import DelResource from '../APIs/Console/DelResource'
|
import DelResource from '../APIs/Console/DelResource'
|
||||||
import SaveBlog from '../APIs/Console/SaveBlog'
|
import SaveBlog from '../APIs/Console/SaveBlog'
|
||||||
@@ -52,6 +53,7 @@ class Server {
|
|||||||
this.apiLoader.add(DelResource);
|
this.apiLoader.add(DelResource);
|
||||||
this.apiLoader.add(GetBlogs)
|
this.apiLoader.add(GetBlogs)
|
||||||
this.apiLoader.add(SaveBlog);
|
this.apiLoader.add(SaveBlog);
|
||||||
|
this.apiLoader.add(SetBlogPasswd);
|
||||||
this.apiLoader.add(DelBlog);
|
this.apiLoader.add(DelBlog);
|
||||||
this.apiLoader.add(GetOSSToken);
|
this.apiLoader.add(GetOSSToken);
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const ServerStdResponse = {
|
|||||||
NOTFOUND: {
|
NOTFOUND: {
|
||||||
code: -4001,
|
code: -4001,
|
||||||
message: 'Blog not found'
|
message: 'Blog not found'
|
||||||
|
},
|
||||||
|
PROTECT_FLAG: {
|
||||||
|
code: -4002,
|
||||||
|
message: 'Blog is protected, need password'
|
||||||
|
},
|
||||||
|
PASSWD_ERROR: {
|
||||||
|
code: -4003,
|
||||||
|
message: 'Blog is protected, and password is not right'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CAPTCHA: {
|
CAPTCHA: {
|
||||||
|
|||||||
1
tonecn/components.d.ts
vendored
1
tonecn/components.d.ts
vendored
@@ -36,6 +36,7 @@ declare module 'vue' {
|
|||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
ElText: typeof import('element-plus/es')['ElText']
|
ElText: typeof import('element-plus/es')['ElText']
|
||||||
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
FileOnline: typeof import('./src/components/Console/FileOnline.vue')['default']
|
FileOnline: typeof import('./src/components/Console/FileOnline.vue')['default']
|
||||||
Footer: typeof import('./src/components/Common/Footer.vue')['default']
|
Footer: typeof import('./src/components/Common/Footer.vue')['default']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { onMounted, reactive, ref, type Ref } from 'vue';
|
import { onMounted, reactive, ref, type Ref } from 'vue';
|
||||||
import { request, type BaseResponseData } from '../../lib/request'
|
import { request, type BaseResponseData } from '../../lib/request'
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { timestampToString } from '@/lib/timestampToString';
|
import { timestampToString } from '@/lib/timestampToString';
|
||||||
const tableData: Ref<any[]> = ref([])
|
const tableData: Ref<any[]> = ref([])
|
||||||
const dialogEditFormVisible = ref(false);
|
const dialogEditFormVisible = ref(false);
|
||||||
@@ -14,7 +14,8 @@ type BlogContentData = {
|
|||||||
src: string,
|
src: string,
|
||||||
access_level: number,
|
access_level: number,
|
||||||
visit_count: number,
|
visit_count: number,
|
||||||
like_count: number
|
like_count: number,
|
||||||
|
encrypt_p: string,
|
||||||
}
|
}
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadTableData();
|
await loadTableData();
|
||||||
@@ -39,6 +40,7 @@ const editForm: BlogContentData = reactive({
|
|||||||
description: '',
|
description: '',
|
||||||
publish_time: new Date(),
|
publish_time: new Date(),
|
||||||
src: '',
|
src: '',
|
||||||
|
encrypt_p: '',
|
||||||
access_level: 0,
|
access_level: 0,
|
||||||
visit_count: 0,
|
visit_count: 0,
|
||||||
like_count: 0
|
like_count: 0
|
||||||
@@ -84,7 +86,47 @@ const saveHandle = async () => {
|
|||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
dialogEditFormVisible.value = false;
|
dialogEditFormVisible.value = false;
|
||||||
loadTableData();
|
loadTableData();
|
||||||
|
if ([7, 9].includes(editForm.access_level)) {
|
||||||
|
ElMessageBox.prompt('保存成功,当前文章可访问级别为:受保护。是否立即添加密码?', '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputPattern: /\S+/,
|
||||||
|
inputErrorMessage: '输入不能为空或仅包含空白字符'
|
||||||
|
})
|
||||||
|
.then(async ({ value }) => {
|
||||||
|
await request.post('/console/setBlogPasswd', {
|
||||||
|
uuid: editForm.uuid,
|
||||||
|
passwd: value,
|
||||||
|
}).then((res: any) => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: `密码设置成功`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: `密码设置失败`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: `密码设置发生错误`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage({
|
||||||
|
type: 'info',
|
||||||
|
message: '已取消',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
return ElMessage.success('保存成功');
|
return ElMessage.success('保存成功');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.message);
|
throw new Error(res.message);
|
||||||
}
|
}
|
||||||
@@ -125,6 +167,11 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
|
|||||||
<el-table-column prop="access_level" label="可访问级别" width="100" />
|
<el-table-column prop="access_level" label="可访问级别" width="100" />
|
||||||
<el-table-column prop="visit_count" label="访问量" width="80" />
|
<el-table-column prop="visit_count" label="访问量" width="80" />
|
||||||
<el-table-column prop="like_count" label="点赞量" width="80" />
|
<el-table-column prop="like_count" label="点赞量" width="80" />
|
||||||
|
<!-- <el-table-column label="加密" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-text>{{ scope.encrypt_p ? "是" : "否" }}</el-text>
|
||||||
|
</template>
|
||||||
|
</el-table-column> -->
|
||||||
<el-table-column fixed="right" label="操作" min-width="110">
|
<el-table-column fixed="right" label="操作" min-width="110">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button link type="primary" size="small" @click="editHandle(scope.row)">编辑</el-button>
|
<el-button link type="primary" size="small" @click="editHandle(scope.row)">编辑</el-button>
|
||||||
@@ -155,7 +202,16 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="可访问级别">
|
<el-form-item label="可访问级别">
|
||||||
<el-input-number v-model="editForm.access_level" :min="0" />
|
<el-input-number v-model="editForm.access_level" :min="0" />
|
||||||
<el-text style="margin-left: 20px;" size="small">默认可访问大于6的项</el-text>
|
<el-tooltip placement="right">
|
||||||
|
<template #content>
|
||||||
|
1 ~ 6 - 保留<br />
|
||||||
|
7 - 列表不可见,文章内容无身份验证访问<br />
|
||||||
|
8 - 列表不可见,文章内容公开<br />
|
||||||
|
9 - 列表可见,文章内容可无状态验证访问<br />
|
||||||
|
10 - 列表可见,文章内容公开<br />
|
||||||
|
</template>
|
||||||
|
<el-text style="margin-left: 20px;" size="small">查看规则</el-text>
|
||||||
|
</el-tooltip>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="访问量">
|
<el-form-item label="访问量">
|
||||||
<el-input-number v-model="editForm.visit_count" :min="0" disabled />
|
<el-input-number v-model="editForm.visit_count" :min="0" disabled />
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ type BlogInfo = {
|
|||||||
like_count: number,
|
like_count: number,
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
const loadStatus = ref(0);// 0加载中 -1加载失败 -2文章不存在或不可见 1加载成功
|
const loadStatus = ref(0);// 0加载中 -1加载失败 1加载成功
|
||||||
|
const loadStatusDescription = ref('出错啦,返回到上一个界面重试吧');
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const blogContent = ref('');
|
const blogContent = ref('');
|
||||||
const blogInfo: Ref<BlogInfo> = ref({
|
const blogInfo: Ref<BlogInfo> = ref({
|
||||||
@@ -44,7 +45,8 @@ onMounted(async () => {
|
|||||||
return loadStatus.value = -1;
|
return loadStatus.value = -1;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let blogContentRes: BaseResponseData = await request.get(`/blogContent?bloguuid=${bloguuid}`);
|
let blogContentRes: BaseResponseData = await request.get(`/blogContent?bloguuid=${bloguuid}` + (window.location.href.indexOf('?') != -1 ? "&" + window.location.href.split('?')[1] : ""));
|
||||||
|
console.log(blogContent.value)
|
||||||
if (blogContentRes.code == 0) {
|
if (blogContentRes.code == 0) {
|
||||||
try {
|
try {
|
||||||
blogContent.value = await marked.parse(decodeURIComponent(escape(atob(blogContentRes.data.data))))
|
blogContent.value = await marked.parse(decodeURIComponent(escape(atob(blogContentRes.data.data))))
|
||||||
@@ -58,12 +60,21 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
} else if (blogContentRes.code == -4001) {
|
} else if (blogContentRes.code == -4001) {
|
||||||
// 不可见或不存在
|
// 不可见或不存在
|
||||||
|
loadStatus.value = -1;
|
||||||
|
loadStatusDescription.value = '文章不可见或不存在'
|
||||||
|
} else if (blogContentRes.code == -4002) {
|
||||||
|
// 文章加密
|
||||||
|
loadStatus.value = -1;
|
||||||
|
loadStatusDescription.value = '文章被加密啦,请联系发布者'
|
||||||
|
} else if (blogContentRes.code == -4003) {
|
||||||
loadStatus.value = -2;
|
loadStatus.value = -2;
|
||||||
|
loadStatusDescription.value = '密钥好像有点问题,请联系发布者'
|
||||||
} else {
|
} else {
|
||||||
throw new Error(blogContentRes.message);
|
throw new Error(blogContentRes.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求博客内容发生错误 ', error);
|
console.error('请求博客内容发生错误 ', error);
|
||||||
|
loadStatusDescription.value = '加载失败,刷新后重试';
|
||||||
loadStatus.value = -1;
|
loadStatus.value = -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -75,7 +86,7 @@ onUnmounted(() => {
|
|||||||
<div class="bg-default-bg dark:bg-[#222] fixed inset-0 w-full h-full -z-10"></div>
|
<div class="bg-default-bg dark:bg-[#222] fixed inset-0 w-full h-full -z-10"></div>
|
||||||
<div class="flex flex-col max-w-[600px] my-[50px] mx-auto px-[20px]">
|
<div class="flex flex-col max-w-[600px] my-[50px] mx-auto px-[20px]">
|
||||||
<div class="mx-auto dark:text-white" v-if="loadStatus == 0">加载中,请稍后...</div>
|
<div class="mx-auto dark:text-white" v-if="loadStatus == 0">加载中,请稍后...</div>
|
||||||
<el-empty v-if="loadStatus < 0" :description="loadStatus == -1 ? '加载失败,刷新后重试' : '文章不存在或不可见'" />
|
<el-empty v-if="loadStatus < 0" :description="loadStatusDescription" />
|
||||||
<div v-if="loadStatus == 1">
|
<div v-if="loadStatus == 1">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-center text-[28px] font-semibold dark:text-white text-[#222]">{{ blogInfo.title }}</h1>
|
<h1 class="text-center text-[28px] font-semibold dark:text-white text-[#222]">{{ blogInfo.title }}</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user