使用pg数据库重构

This commit is contained in:
2025-02-16 23:08:25 +08:00
parent 3d729996bb
commit 9af60b0dbe
40 changed files with 314 additions and 695 deletions

View File

@@ -1,15 +1,15 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import MountUserAgent from "../Plugs/Middleware/MountUserAgent"; import MountUserAgent from "../Plugs/Middleware/MountUserAgent";
import axios from "axios"; import axios from "axios";
import MountIP from "../Plugs/Middleware/MountIP"; import MountIP from "../Plugs/Middleware/MountIP";
import CheckCaptchaPassed from "../Plugs/Middleware/CheckCaptchaPassed"; import { BlogComment as BlogCommentType } from "@/Types/Schema"
// 提交博客评论 // 提交博客评论
class BlogComment extends API { class BlogComment extends API {
constructor() { constructor() {
super('POST', '/blogComment', CheckCaptchaPassed, MountUserAgent, MountIP); super('POST', '/blogComment', MountUserAgent, MountIP);
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
@@ -26,7 +26,7 @@ class BlogComment extends API {
// 获取IPAddress // 获取IPAddress
let ip_address = '未知' let ip_address = '未知'
try { try {
let ipAddressRes = await axios.get(`https://mesh.if.iqiyi.com/aid/ip/info?version=1.1.1&ip=`+_ip); let ipAddressRes = await axios.get(`https://mesh.if.iqiyi.com/aid/ip/info?version=1.1.1&ip=` + _ip);
if (ipAddressRes.data && ipAddressRes.data.msg == 'success') { if (ipAddressRes.data && ipAddressRes.data.msg == 'success') {
ip_address = ipAddressRes.data.data.countryCN == '中国' ? ipAddressRes.data.data.provinceCN : ipAddressRes.data.data.countryCN; ip_address = ipAddressRes.data.data.countryCN == '中国' ? ipAddressRes.data.data.provinceCN : ipAddressRes.data.data.countryCN;
@@ -35,8 +35,8 @@ class BlogComment extends API {
this.logger.warn('获取IP属地失败', error); this.logger.warn('获取IP属地失败', error);
} }
let blogLikeRes = await MySQLConnection.execute('INSERT INTO blog_comment (uuid, content, name, ip, ip_address, user_agent, time) VALUES (?,?,?,?,?,?,?)', [bloguuid, content.trim(), name.trim(), _ip, ip_address, _userAgent, Date.now()]); let blogLikeRes = await Database.query<BlogCommentType>('INSERT INTO blog_comment (uuid, content, name, ip, ip_address, user_agent, display, created_at) VALUES ($1,$2,$3,$4,$5,$6,true,$7)', [bloguuid, content.trim(), name.trim(), _ip, ip_address, _userAgent, new Date()]);
if (!blogLikeRes || blogLikeRes.affectedRows != 1) { if (!blogLikeRes) {
this.logger.error('发布博客评论时,数据库发生错误'); this.logger.error('发布博客评论时,数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }

View File

@@ -1,8 +1,9 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import axios from "axios"; import axios from "axios";
import { Blog } from "@/Types/Schema";
// 点赞 // 点赞
@@ -18,15 +19,11 @@ class BlogLike extends API {
return res.json(ServerStdResponse.INVALID_PARAMS); return res.json(ServerStdResponse.INVALID_PARAMS);
} }
let blogLikeRes = await MySQLConnection.execute('UPDATE blog SET like_count = like_count + 1 WHERE access_level > ? AND uuid = ? ', [this.defaultAccessLevel, bloguuid]); let blogLikeRes = await Database.query<Blog>('UPDATE blog SET like_count = like_count + 1 WHERE access_level > $1 AND uuid = $2 ', [this.defaultAccessLevel, bloguuid]);
if (!blogLikeRes) { if (!blogLikeRes) {
this.logger.error('点赞博客时,数据库发生错误'); this.logger.error('点赞博客时,数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
if (blogLikeRes.affectedRows != 1) {
this.logger.warn('查询的博客不存在或不可见', bloguuid);
return res.json(ServerStdResponse.BLOG.NOTFOUND);
}
return res.json(ServerStdResponse.OK); return res.json(ServerStdResponse.OK);
} }
} }

View File

@@ -1,41 +0,0 @@
import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse";
import captchaSession from "../Plugs/Service/captchaSession";
// 检查人机验证
class CheckCaptcha extends API {
constructor() {
super('POST', '/checkCaptcha');
}
public async onRequset(data: any, res: any) {
let { session, rotateDeg } = data;
if (!session || !rotateDeg) {
return res.json(ServerStdResponse.PARAMS_MISSING);
}
switch (await captchaSession.check(session, rotateDeg)) {
case 0:
// 验证码已过期或服务器错误
res.json(ServerStdResponse.CAPTCHA.NOTFOUND);
break;
case 1:
// 验证通过
res.json(ServerStdResponse.OK);
break;
case -1:
// 超过最大尝试次数
res.json(ServerStdResponse.CAPTCHA.MAX_TRY_COUNT);
break;
case -2:
// 角度不正确
res.json(ServerStdResponse.CAPTCHA.NOTRIGHT);
break;
default:
// 未知错误
res.json(ServerStdResponse.SERVER_ERROR);
break;
}
}
}
export default CheckCaptcha;

View File

@@ -1,6 +1,6 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
// 删除博客 // 删除博客
@@ -10,13 +10,13 @@ class DelBlog extends API {
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
let { id } = data; let { uuid } = data;
if (!id) { if (!uuid) {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
let execRes = await MySQLConnection.execute('DELETE FROM blog WHERE `id` = ?', [id]); let execRes = await Database.query('DELETE FROM blog WHERE uuid = $1', [uuid]);
if (!execRes || execRes.affectedRows != 1) { if (!execRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
return res.json({ ...ServerStdResponse.OK }); return res.json({ ...ServerStdResponse.OK });

View File

@@ -1,6 +1,6 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
// 删除资源 // 删除资源
@@ -10,13 +10,13 @@ class DelResource extends API {
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
let { id } = data; let { uuid } = data;
if (!id) { if (!uuid) {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
let execRes = await MySQLConnection.execute('DELETE FROM resource WHERE `id` = ?', [id]); let execRes = await Database.query('DELETE FROM resource WHERE uuid = $1', [uuid]);
if (!execRes || execRes.affectedRows != 1) { if (!execRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
return res.json({ ...ServerStdResponse.OK }); return res.json({ ...ServerStdResponse.OK });

View File

@@ -1,7 +1,8 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import { Blog } from "@/Types/Schema";
// 获取博客列表 // 获取博客列表
class GetBlogs extends API { class GetBlogs extends API {
@@ -10,8 +11,7 @@ class GetBlogs extends API {
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
// const { uuid } = data._jwt; let resourcesRes = await Database.query<Blog>("SELECT * FROM blog ORDER BY created_at DESC");
let resourcesRes = await MySQLConnection.execute("SELECT * FROM blog ORDER BY id DESC");
if (!resourcesRes) { if (!resourcesRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }

View File

@@ -1,6 +1,6 @@
import { API, RequestData } from "../../Plugs/API/API"; import { API, RequestData } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import config from "../../config"; import config from "../../config";

View File

@@ -1,7 +1,8 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import { Resource } from "@/Types/Schema";
// 获取资源列表 // 获取资源列表
class GetResources extends API { class GetResources extends API {
@@ -11,7 +12,7 @@ class GetResources extends API {
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
// const { uuid } = data._jwt; // const { uuid } = data._jwt;
let resourcesRes = await MySQLConnection.execute("SELECT * FROM resource"); let resourcesRes = await Database.query<Resource>("SELECT * FROM resource ORDER BY type, recommand, created_at DESC");
if (!resourcesRes) { if (!resourcesRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }

View File

@@ -1,17 +1,17 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import MountUserAgent from "../../Plugs/Middleware/MountUserAgent"; import MountUserAgent from "../../Plugs/Middleware/MountUserAgent";
import MountIP from "../../Plugs/Middleware/MountIP"; import MountIP from "../../Plugs/Middleware/MountIP";
import CheckCaptchaPassed from "../../Plugs/Middleware/CheckCaptchaPassed";
import config from "../../config"; import config from "../../config";
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import crypto from 'crypto' import crypto from 'crypto'
import { User } from "@/Types/Schema";
// 登录 // 登录
class Login extends API { class Login extends API {
constructor() { constructor() {
super('POST', '/console/login', CheckCaptchaPassed, MountUserAgent, MountIP); super('POST', '/console/login', MountUserAgent, MountIP);
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
@@ -21,28 +21,28 @@ class Login extends API {
} }
// 检查用户是否存在 // 检查用户是否存在
let userInfoRes = await MySQLConnection.execute('SELECT * FROM user WHERE username = ?', [username]); let userInfoRes = await Database.query<User>('SELECT * FROM user WHERE username = $1', [username]);
if(!userInfoRes){ if (!userInfoRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
if (userInfoRes.length != 1) { if (userInfoRes.length != 1) {
return res.json(ServerStdResponse.USER.NOTFOUND); return res.json(ServerStdResponse.USER.NOTFOUND);
} }
userInfoRes = userInfoRes[0]; const UserInfo = userInfoRes[0];
// 检查密码是否正确 // 检查密码是否正确
if(crypto.createHash('sha256').update(`${userInfoRes.salt}${password}`).digest('hex') != userInfoRes.password){ if (crypto.createHash('sha256').update(`${UserInfo.salt}${password}`).digest('hex') != UserInfo.password) {
return res.json(ServerStdResponse.USER.PASSWORD_ERROR); return res.json(ServerStdResponse.USER.PASSWORD_ERROR);
} }
// 准备jwtToken // 准备jwtToken
const jwtPayload = { const jwtPayload = {
uuid: userInfoRes.uuid, uuid: UserInfo.uuid,
loginTime: Date.now() loginTime: Date.now()
} }
let jwtToken = jwt.sign(jwtPayload, config.jwt.secret, { expiresIn: config.jwt.expiresIn }); let jwtToken = jwt.sign(jwtPayload, config.jwt.secret, { expiresIn: config.jwt.expiresIn });
// 写入登录日志 // 写入登录日志
MySQLConnection.execute('INSERT INTO user_login_log (user_uuid, ip, user_agent, time) VALUES (?,?,?,?)', [userInfoRes.uuid, _ip, _userAgent, Date.now()]); Database.query('INSERT INTO user_login_log (user_uuid, ip, user_agent, time) VALUES ($1,$2,$3,$4)', [UserInfo.uuid, _ip, _userAgent, Date.now()]);
return res.json({ ...ServerStdResponse.OK, data: { token: jwtToken } }); return res.json({ ...ServerStdResponse.OK, data: { token: jwtToken } });
} }
} }

View File

@@ -1,8 +1,9 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import crypto from 'crypto' import crypto from 'crypto'
import { Blog } from "@/Types/Schema";
// 保存博客 // 保存博客
class SaveBlog extends API { class SaveBlog extends API {
@@ -11,21 +12,21 @@ class SaveBlog extends API {
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
let { id, uuid, title, description, publish_time, src, access_level } = data; let { uuid, title, description, created_at, src, access_level } = data;
if (!title || !description || !publish_time || !src || !access_level) { if (!title || !description || !created_at || !src || !access_level) {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
let execRes: any; let execRes: any;
if (id) { if (uuid) {
// 保存 // 保存
execRes = await MySQLConnection.execute('UPDATE blog SET title = ?, description = ?, publish_time = ?, src = ?, access_level = ? WHERE `id` = ?', [title, description, publish_time, src, access_level, id]); execRes = await Database.query<Blog>('UPDATE blog SET title = $1, description = $2, created_at = $3, src = $4, access_level = $5 WHERE uuid = $6', [title, description, created_at, src, access_level, uuid]);
} else { } else {
// 新建 // 新建
const uuid = crypto.createHash('md5').update(`${Math.random()}${Date.now()}`).digest('hex'); const uuid = crypto.createHash('md5').update(`${Math.random()}${Date.now()}`).digest('hex');
execRes = await MySQLConnection.execute('INSERT INTO blog (uuid, title, description, src, publish_time, access_level, visit_count, like_count) VALUES (?,?,?,?,?,?,?,?)', [uuid, title, description, src, publish_time, access_level, 0, 0]); execRes = await Database.query<Blog>('INSERT INTO blog (uuid, title, description, src, created_at, access_level, visit_count, like_count) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)', [uuid, title, description, src, created_at, access_level, 0, 0]);
} }
if (!execRes || execRes.affectedRows != 1) { if (!execRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
return res.json({ ...ServerStdResponse.OK }); return res.json({ ...ServerStdResponse.OK });

View File

@@ -1,7 +1,9 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import { Resource } from "@/Types/Schema";
import Crypto from 'crypto'
// 保存资源 // 保存资源
class SaveResource extends API { class SaveResource extends API {
@@ -10,20 +12,21 @@ class SaveResource extends API {
} }
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
let { id, type, recommand, title, describe, icon_src, addition, src } = data; let { uuid, type, recommand, title, describe, icon_src, addition, src } = data;
if (!type || !recommand || !title || !describe || !icon_src || !addition || !src) { if (!type || !recommand || !title || !describe || !icon_src || !addition || !src) {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
let execRes: any; let execRes: any;
if (id) { if (uuid) {
// 保存 // 保存
execRes = await MySQLConnection.execute('UPDATE resource SET `type` = ?, `recommand` = ?, `title` = ?, `describe` = ?, `addition` = ?, `icon_src` = ?, `src` = ? WHERE `id` = ?', [type, recommand, title, describe, addition, icon_src, src, id]); execRes = await Database.query<Resource>('UPDATE resource SET "type" = $1, "recommand" = $2, "title" = $3, "describe" = $4, "addition" = $5, "icon_src" = $6, "src" = $7 WHERE "uuid" = $8', [type, recommand, title, describe, addition, icon_src, src, uuid]);
} else { } else {
// 新建 // 新建
execRes = await MySQLConnection.execute('INSERT INTO resource (`type`, `recommand`, `title`, `describe`, `addition`, `icon_src`, `src`) VALUES (?,?,?,?,?,?,?)', [type, recommand, title, describe, addition, icon_src, src]); uuid = Crypto.createHash('md5').update(`${Math.random()}${Date.now()}`).digest('hex');
execRes = await Database.query<Resource>('INSERT INTO resource ("uuid","type", "recommand", "title", "describe", "addition", "icon_src", "src", "created_at") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)', [uuid, type, recommand, title, describe, addition, icon_src, src, new Date()]);
} }
if (!execRes || execRes.affectedRows != 1) { if (!execRes) {
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
return res.json({ ...ServerStdResponse.OK }); return res.json({ ...ServerStdResponse.OK });

View File

@@ -1,8 +1,9 @@
import { API } from "../../Plugs/API/API"; import { API } from "../../Plugs/API/API";
import ServerStdResponse from "../../ServerStdResponse"; import ServerStdResponse from "../../ServerStdResponse";
import MySQLConnection from '../../Plugs/MySQLConnection' import Database from '../../Plugs/Database'
import Auth from "../../Plugs/Middleware/Auth"; import Auth from "../../Plugs/Middleware/Auth";
import crypto from 'crypto' import crypto from 'crypto'
import { Blog } from "@/Types/Schema";
// 设置博客密码 // 设置博客密码
class SetBlogPasswd extends API { class SetBlogPasswd extends API {
@@ -16,7 +17,7 @@ class SetBlogPasswd extends API {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
const encrypt_p = crypto.createHash('sha256').update(passwd).digest('hex'); const encrypt_p = crypto.createHash('sha256').update(passwd).digest('hex');
MySQLConnection.execute('UPDATE blog SET encrypt_p = ? WHERE uuid = ?', [encrypt_p, uuid]); Database.query<Blog>('UPDATE blog SET encrypt_p = $1 WHERE uuid = $2', [encrypt_p, uuid]);
return res.json({ ...ServerStdResponse.OK }); return res.json({ ...ServerStdResponse.OK });
} }
} }

View File

@@ -1,6 +1,7 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import { BlogComment } from "@/Types/Schema";
// 获取博客评论 // 获取博客评论
class GetBlogComment extends API { class GetBlogComment extends API {
@@ -16,7 +17,7 @@ class GetBlogComment extends API {
return res.json(ServerStdResponse.INVALID_PARAMS); return res.json(ServerStdResponse.INVALID_PARAMS);
} }
let blogCommentRes = await MySQLConnection.execute('SELECT content, name, ip_address, time FROM blog_comment WHERE uuid = ? AND display = 1 ORDER BY time DESC LIMIT ? OFFSET ?;', [bloguuid, this.pageSize, (page - 1) * this.pageSize]); let blogCommentRes = await Database.query<BlogComment>('SELECT content, name, ip_address, created_at FROM blog_comment WHERE uuid = $1 AND display = true ORDER BY created_at DESC LIMIT $2 OFFSET $3;', [bloguuid, this.pageSize, (page - 1) * this.pageSize]);
if (!blogCommentRes) { if (!blogCommentRes) {
this.logger.error('获取博客评论时,数据库发生错误'); this.logger.error('获取博客评论时,数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);

View File

@@ -1,10 +1,11 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import axios from "axios"; import axios from "axios";
import crypto from 'crypto' import crypto from 'crypto'
import MountIP from "../Plugs/Middleware/MountIP"; import MountIP from "../Plugs/Middleware/MountIP";
import { Blog } from "@/Types/Schema";
// 获取博客内容 // 获取博客内容
class GetBlogContent extends API { class GetBlogContent extends API {
@@ -22,14 +23,14 @@ class GetBlogContent extends API {
return res.json(ServerStdResponse.INVALID_PARAMS); return res.json(ServerStdResponse.INVALID_PARAMS);
} }
let blogContentRes = await MySQLConnection.execute(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.allow.join(',')}) AND uuid = ? `, [bloguuid]); let blogContentRes = await Database.query<Blog>(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.allow.join(',')}) AND uuid = $1 `, [bloguuid]);
if (!blogContentRes) { if (!blogContentRes) {
this.logger.error('查询时数据库发生错误'); this.logger.error('查询时数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
if (blogContentRes.length == 0) { if (blogContentRes.length == 0) {
// 公开范围不可见,查询允许无连接加密查看的数据 // 公开范围不可见,查询允许无连接加密查看的数据
blogContentRes = await MySQLConnection.execute(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.encrypt_allow.join(',')}) AND uuid = ? `, [bloguuid]); blogContentRes = await Database.query<Blog>(`SELECT * from blog WHERE access_level in (${this.AccessLevelRule.encrypt_allow.join(',')}) AND uuid = $1 `, [bloguuid]);
if (!blogContentRes) { if (!blogContentRes) {
this.logger.error('查询时数据库发生错误'); this.logger.error('查询时数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
@@ -57,14 +58,14 @@ class GetBlogContent extends API {
const base64Content = Buffer.from(response.data, 'utf-8').toString('base64'); const base64Content = Buffer.from(response.data, 'utf-8').toString('base64');
// 访问次数+1 // 访问次数+1
MySQLConnection.execute('UPDATE blog SET visit_count = visit_count + 1 WHERE uuid = ?', [bloguuid]); Database.query('UPDATE blog SET visit_count = visit_count + 1 WHERE uuid = $1', [bloguuid]);
return res.json({ return res.json({
...ServerStdResponse.OK, data: { ...ServerStdResponse.OK, data: {
data: base64Content, data: base64Content,
info: { info: {
title: blogContentRes[0].title, title: blogContentRes[0].title,
description: blogContentRes[0].description, description: blogContentRes[0].description,
publish_time: blogContentRes[0].publish_time, publish_time: blogContentRes[0].created_at,
visit_count: blogContentRes[0].visit_count, visit_count: blogContentRes[0].visit_count,
like_count: blogContentRes[0].like_count like_count: blogContentRes[0].like_count
} }

View File

@@ -1,6 +1,7 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import { Blog } from "@/Types/Schema";
// 获取博客列表 // 获取博客列表
class GetBlogList extends API { class GetBlogList extends API {
@@ -10,12 +11,12 @@ class GetBlogList extends API {
private defaultAccessLevel = 9; 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, access_level, visit_count, like_count from blog WHERE access_level >= ? ORDER BY publish_time DESC',[this.defaultAccessLevel]); let blogListRes = await Database.query<Blog>('SELECT uuid, title, description, created_at, access_level, visit_count, like_count from blog WHERE access_level >= $1 ORDER BY created_at 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);
} }
return res.json({...ServerStdResponse.OK, data: blogListRes}); return res.json({ ...ServerStdResponse.OK, data: blogListRes });
} }
} }

View File

@@ -1,36 +0,0 @@
import fs from "fs";
import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse";
import captchaSession from "../Plugs/Service/captchaSession";
import path from "path";
import sharp from "sharp";
import crypto from 'crypto'
// 获取人机验证图片及标识符
class GetCaptcha extends API {
constructor() {
super('GET', '/captcha');
}
public async onRequset(data: any, res: any) {
const imgsPath = path.join(__dirname, '../assets/captchaImgs');
const fileList = fs.readdirSync(imgsPath)
const imgPath = path.join(imgsPath, fileList[Math.floor(Math.random() * fileList.length)]);
const rotateDeg = Math.floor(Math.random() * 240) + 60;
const img = Buffer.from(await sharp(imgPath).rotate(-rotateDeg).toBuffer()).toString('base64')
const session = crypto.createHash('md5').update(`${Math.random()} ${Date.now()}`).digest('hex');
if (await captchaSession.add(session, rotateDeg)) {
return res.json({
...ServerStdResponse.OK, data: {
img: img,
session: session,
imgPreStr: 'data:image/jpeg;base64,'
}
});
} else {
return res.json(ServerStdResponse.SERVER_ERROR)
}
}
}
export default GetCaptcha;

View File

@@ -1,6 +1,7 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection' import Database from '../Plugs/Database'
import { Resource } from "@/Types/Schema";
// 获取资源列表 // 获取资源列表
class GetResourceList extends API { class GetResourceList extends API {
@@ -11,18 +12,18 @@ class GetResourceList extends API {
public async onRequset(data: any, res: any) { public async onRequset(data: any, res: any) {
let { type } = data; let { type } = data;
if(!type){ if (!type) {
return res.json(ServerStdResponse.PARAMS_MISSING); return res.json(ServerStdResponse.PARAMS_MISSING);
} }
if(!this.typeList.includes(type)){ if (!this.typeList.includes(type)) {
return res.json(ServerStdResponse.INVALID_PARAMS); return res.json(ServerStdResponse.INVALID_PARAMS);
} }
let resourceListRes = await MySQLConnection.execute('SELECT * from resource WHERE type = ? ORDER BY recommand ASC',[type]); let resourceListRes = await Database.query<Resource>('SELECT * from resource WHERE type = $1 ORDER BY recommand ASC', [type]);
if(!resourceListRes){ if (!resourceListRes) {
this.logger.error('查询时数据库发生错误'); this.logger.error('查询时数据库发生错误');
return res.json(ServerStdResponse.SERVER_ERROR); return res.json(ServerStdResponse.SERVER_ERROR);
} }
return res.json({...ServerStdResponse.OK, data: resourceListRes}); return res.json({ ...ServerStdResponse.OK, data: resourceListRes });
} }
} }

View File

@@ -1,6 +1,5 @@
import { API } from "../Plugs/API/API"; import { API } from "../Plugs/API/API";
import ServerStdResponse from "../ServerStdResponse"; import ServerStdResponse from "../ServerStdResponse";
import MySQLConnection from '../Plugs/MySQLConnection'
// 测试接口 // 测试接口
class GetTest extends API { class GetTest extends API {

View File

@@ -0,0 +1,78 @@
/**
* @file Database.ts
* @version 1.0.0
* @description PostgreSQL数据库连接池
*/
import pg from 'pg';
import Logger from "./Logger";
import config from "../config";
class DatabasePool {
private pool: pg.Pool;
private logger = new Logger('Database');
constructor() {
this.pool = this.createConnectPool();
this.logger.info("数据库连接池已创建")
this.pool.on('error', (err) => {
this.logger.error(`数据库连接池发生错误:${err}`);
});
this.testConnection();
}
private createConnectPool() {
return new pg.Pool({
host: config.pg.host,
database: config.pg.database,
user: config.pg.user,
password: config.pg.password,
connectionTimeoutMillis: 5000, // 连接超时时间
max: 20, // 最大连接数
idleTimeoutMillis: 30000, // 空闲连接超时时间
allowExitOnIdle: true // 允许空闲时退出
})
}
private async testConnection() {
let res = await this.query<{ result: number }>("SELECT 1 + 1 As result");
if (!res) {
this.logger.error("数据库连接测试失败");
return;
}
if (res[0].result == 2)
return;
this.logger.error("数据库连接测试失败?");
}
/**
* 执行SQL查询
* @param sql SQL语句
* @param values 可选的查询参数列表
* @param database 可选的数据库
* @returns Promise<any | undefined> 查询结果
*/
public async query<T>(sql: string, values?: any[]): Promise<T[] | undefined> {
let connection;
try {
connection = await this.pool.connect();
const res = await connection.query(sql, values);
return res.rows;
} catch (error) {
if (!connection) {
this.logger.error(`数据库连接失败:${error}`);
} else {
this.logger.error(`数据库查询发生错误:${error}`);
}
return undefined;
} finally {
if (connection) {
connection.release();
}
}
}
}
const Database = new DatabasePool();
export default Database;

View File

@@ -1,17 +0,0 @@
import { Request, Response, NextFunction } from "express";
import ServerStdResponse from "../../ServerStdResponse";
import Logger from "../Logger";
import captchaSession from "../Service/captchaSession";
const logger = new Logger("CheckCaptcha");
const CheckCaptchaPassed = async (req: Request, res: Response, next: NextFunction) => {
let session = req.query.session || req.body.session || '';
if (session) {
if (await captchaSession.isPassed(session))
return next();
}
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
logger.info(`API[${req.method}][${req.url.split('?')[0]}] 请求人机验证未通过[${ip}]`);
res.json(ServerStdResponse.AUTH_ERROR);
}
export default CheckCaptchaPassed;

View File

@@ -1,84 +0,0 @@
/**
* @file MySQLConnection.ts
* @version 1.0.0
* @description MySQL数据库连接池
*/
import mysql from "mysql2/promise";
import Logger from "./Logger";
import config from "../config";
class MySQLConnectPool {
private pool: any;
private logger = new Logger('MySQLConnection');
constructor() {
this.pool = this.createConnectPool();
this.logger.info("数据库连接池已创建")
setTimeout(async () => {
let res = await this.testConnection();
if (res)
this.logger.info("数据库测试成功")
else
this.logger.error("数据库测试失败")
}, 10);
}
private createConnectPool() {
return mysql.createPool({
host: config.mysql.host,
database: config.mysql.database,
user: config.mysql.user,
password: config.mysql.password,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
}
private async testConnection() {
try {
let res = await this.execute("SELECT 1 + 1 As result");
if (res[0].result == 2)
return 1;
else
return 0;
} catch (error) {
this.logger.error(`数据库测试发生了错误:` + error);
return 0;
}
}
/**
* 执行SQL查询
* @param sql SQL语句
* @param values 可选的查询参数列表
* @param database 可选的数据库
* @returns Promise<any | undefined> 查询结果
*/
public async execute(sql: string, values?: any[], database?: string): Promise<any | undefined> {
let connection: any;
try {
connection = await this.pool.getConnection();
// 如果指定了数据库,则更改当前连接的数据库
if (database) {
await connection.changeUser({ database });
}
let [rows, fields] = await connection.execute(sql, values);
return rows;
} catch (error) {
this.logger.error("数据库发生错误:" + error, '\n##', sql, '\n##', JSON.stringify(values));
return undefined;
} finally {
if (database)
await connection.changeUser({ database: config.mysql.database });// 恢复默认数据库
if (connection)
connection.release();
}
}
}
const MySQLConnection = new MySQLConnectPool();
export default MySQLConnection;

View File

@@ -1,43 +0,0 @@
import Redis from 'ioredis';
import config from '../config';
import Logger from './Logger';
class RedisConnection {
private pool?: Redis
private logger = new Logger('Redis')
constructor() {
try {
this.pool = new Redis({
port: config.redis.port,
host: config.redis.host,
password: config.redis.password,
maxRetriesPerRequest: 10,
});
this.logger.info('数据库连接池已创建')
} catch (error) {
this.logger.error('数据库连接池创建失败:' + error)
}
setTimeout(async () => {
if(this.pool == undefined)
return;
try {
let res = await this.pool.set('redis_test', '1');
if (res)
this.logger.info('数据库测试成功')
else
throw new Error('返回值错误')
} catch (error) {
this.logger.error('数据库测试失败:' + error)
}
}, 10);
}
public getPool(): Redis {
return <Redis>this.pool;
}
}
const redisConnection = new RedisConnection();
export default redisConnection.getPool();

View File

@@ -1,124 +0,0 @@
/**
* @file captchaSession.ts
* @version 1.0.0
* @description 旋转图像验证服务
*/
import Logger from '../Logger';
import RedisConnection from '../RedisConnection';
type CaptchaSessionDataJSON = {
rotateDeg: number,
tryCount: number,
isPassed: boolean
}
class _captchaSession {
private readonly logger = new Logger('Service][captchaSession');
private readonly AllowMaxTryCount: number = 5;
private readonly AllowMaxAngleDiff: number = 8;
private readonly ExpriedTimeSec: number = 60;
private readonly RedisCommonKey: string = 'Service:captchaSession:';
constructor() {
this.logger.info('旋转图像验证服务已启动');
}
private async get(session: string): Promise<CaptchaSessionDataJSON | undefined> {
try {
const result = await RedisConnection.get(this.RedisCommonKey + session);
if (result === null)
return;
return JSON.parse(result);
} catch (error) {
this.logger.error(`获取session[${session}]时发生错误:${error}`);
return;
}
}
private async remove(session: string): Promise<void> {
try {
await RedisConnection.del(this.RedisCommonKey + session);
} catch (error) {
this.logger.error(`删除session[${session}]失败`);
}
}
/**
*
* @param session 验证会话标识符
* @param rotateDeg 图片旋转角度
* @returns true存储成功 false存储失败
*/
public async add(session: string, rotateDeg: number): Promise<boolean> {
const result: CaptchaSessionDataJSON = {
rotateDeg: rotateDeg,
tryCount: 0,
isPassed: false
}
try {
const res = await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
if (res && res === 'OK') {
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
this.logger.info(`session[${session}]及角度[${rotateDeg}]已存储`);
return true;
}
this.logger.error(`session[${session}]及角度[${rotateDeg}]存储失败`);
return false;
} catch (error) {
this.logger.error(`session[${session}]及角度[${rotateDeg}]存储失败:${error}`);
return false;
}
}
/**
*
* @param session 验证会话标识符
* @returns true已验证过期 false未通过
*/
public async isPassed(session: string): Promise<boolean> {
const result = await this.get(session);
if (!result)
return false;
if (result.isPassed)
this.remove(session);
return result.isPassed;
}
/**
*
* @param session 验证会话标识符
* @param rotateDeg 图片旋转角度
* @returns 0验证已过期或服务器错误 1通过验证 -1超过最大允许尝试次数 -2角度差异过大
*/
public async check(session: string, rotateDeg: number): Promise<number> {
try {
let result = await this.get(session);
if (!result) {
return 0;
}
if (result.isPassed) {
this.logger.info(`session[${session}]已通过验证,无需重复验证`);
return 1;
}
if (Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff) {
result.isPassed = true;
await RedisConnection.del(this.RedisCommonKey + session);
await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
return 1;
}
result.tryCount++;
if (result.tryCount >= this.AllowMaxTryCount) {
this.remove(session);
return -1;
}
RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
return -2;
} catch (error) {
this.logger.error(`检查session[${session}]时发生错误:${error}`);
return 0;
}
}
}
const captchaSession = new _captchaSession();
export default captchaSession;

View File

@@ -2,9 +2,6 @@ import Logger from "../Plugs/Logger";
import { APILoader } from "../Plugs/API/APILoader"; import { APILoader } from "../Plugs/API/APILoader";
import config from "../config"; import config from "../config";
// 加载Plugs
import '../Plugs/Service/captchaSession'
// 加载API // 加载API
import GetTest from "../APIs/GetTest"; import GetTest from "../APIs/GetTest";
import GetResourceList from "../APIs/GetResourceList"; import GetResourceList from "../APIs/GetResourceList";
@@ -13,8 +10,6 @@ import GetBlogContent from "../APIs/GetBlogContent";
import BlogLike from "../APIs/BlogLike"; import BlogLike from "../APIs/BlogLike";
import BlogComment from "../APIs/BlogComment"; import BlogComment from "../APIs/BlogComment";
import GetBlogComment from "../APIs/GetBlogComment"; import GetBlogComment from "../APIs/GetBlogComment";
import GetCaptcha from "../APIs/GetCaptcha";
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";
@@ -45,8 +40,6 @@ class Server {
this.apiLoader.add(BlogLike); this.apiLoader.add(BlogLike);
this.apiLoader.add(BlogComment); this.apiLoader.add(BlogComment);
this.apiLoader.add(GetBlogComment); this.apiLoader.add(GetBlogComment);
this.apiLoader.add(GetCaptcha);
this.apiLoader.add(CheckCaptcha);
this.apiLoader.add(Login); this.apiLoader.add(Login);
this.apiLoader.add(GetResources); this.apiLoader.add(GetResources);

101
Server/src/Types/Schema.ts Normal file
View File

@@ -0,0 +1,101 @@
/** 博客文章 */
export type Blog = {
/** 博客uuid */
uuid: string;
/** 标题 */
title: string;
/** 描述 */
description: string;
/** 文章md链接 */
src: string;
/** 访问级别 */
access_level: number;
/** 访问数 */
visit_count: number;
/** 点赞数 */
like_count: number;
/** 文章加密密码 */
encrypt_p?: string;
/** 发布时间 */
created_at: Date;
};
/** 博客评论 */
export type BlogComment = {
/** 评论id */
id: number;
/** 文章uuid */
uuid: string;
/** 评论内容 */
content: string;
/** 昵称 */
name: string;
/** ip地址 */
ip: string;
/** ip属地 */
ip_address: string;
/** 用户代理 */
user_agent: string;
/** 是否显示 */
display: boolean;
/** 评论时间 */
time: Date;
};
/** 资源信息 */
export type Resource = {
/** 资源ID */
uuid: string;
/** 资源类型 */
type: 'download' | 'resource';
/** 推荐程度,越小越推荐 */
recommand?: number;
/** 标题 */
title: string;
/** 描述 */
describe: string;
/** 附加信息 */
addition: any;
/** 图片url */
icon_src: string;
/** 资源src */
src: string;
/** 创建时间 */
created_at: Date;
};
/** 用户信息 */
export type User = {
/** 用户uuid */
uuid: string;
/** 用户名 */
username: string;
/** 密码salt */
salt: string;
/** 密码hashed */
password: string;
/** 手机号 */
phone: string;
/** 权限列表 */
permission: any;
/** 邮箱 */
email?: string;
/** 创建时间 */
created_at: Date;
/** 更新信息时间 */
updated_at?: Date;
};
/** 用户登录日志 */
export type UserLoginLog = {
/** 登陆记录id */
id: number;
/** 用户uuid */
user_uuid: string;
/** 登陆ip */
ip: string;
/** 用户代理 */
user_agent: string;
/** 登陆时间 */
time: Date;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,30 +1,24 @@
const config = { const config = {
mysql: { pg: {
host: 'localhost', host: 'localhost',
// host:'server.tonesc.cn', // host:'server.tonesc.cn',
database: 'tonecn', database: 'tonecn',
user: 'root', user: 'tone',
password: 'Shi15023847146'// localhost password: 'Shi15023847146'// localhost
// password: '245565' // server // password: '245565' // server
}, },
redis: {
port: 6379,
host: 'localhost',
password: ''// localhost
// password: '2Pj4Ss9al3mS1'// server
},
jwt: { jwt: {
secret: '17e50223f4a545ec9e36ebf08e2f71bb', secret: '17e50223f4a545ec9e36ebf08e2f71bb',
expiresIn: '1d', expiresIn: '1d',
}, },
oss: { oss: {
accessKeyId: '', accessKeyId: 'LTAI5t6ceNJqmMzPkjETgTzg',
accessKeySecret: '', accessKeySecret: 'Eak6jFN0koDQ0yOg5KdxubjMbQ00Tk',
roleArn: '', roleArn: 'acs:ram::1283668906130262:role/ctbu-co-oss-role',
bucket: '', bucket: 'tone-personal',
region: '', region: 'oss-cn-chengdu',
callbackUrl: '', callbackUrl: '',
dir: '', dir: 'personal-web',
stsExpirationSec: 3600 stsExpirationSec: 3600
}, },
apiPort: 23500, apiPort: 23500,

View File

@@ -1,6 +1,6 @@
<script setup lang='ts'> <script setup lang='ts'>
import { request, type BaseResponseData } from '@/lib/request'; import { request, type BaseResponseData } from '@/lib/request';
import { computed, onMounted, reactive, ref, watch } from 'vue'; import { computed, onMounted, reactive, ref, watch, watchEffect } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { timestampToString } from '@/lib/timestampToString'; import { timestampToString } from '@/lib/timestampToString';
const model = defineModel(); const model = defineModel();
@@ -39,8 +39,9 @@ const loadComment = async () => {
} }
} }
watch(model, (newValue, oldValue) => { watchEffect(() => {
if (newValue) { if (model.value) {
model.value = false;
loadComment(); loadComment();
} }
}) })
@@ -54,8 +55,10 @@ watch(model, (newValue, oldValue) => {
<div class="my-[10px]" v-for="blogcomment of blogCommentList"> <div class="my-[10px]" v-for="blogcomment of blogCommentList">
<div class=" text-[#555] dark:text-[#fff]">{{ blogcomment.name }}</div> <div class=" text-[#555] dark:text-[#fff]">{{ blogcomment.name }}</div>
<div class="text-[12px] text-[#888] dark:text-[#aaa]">IP属地{{ blogcomment.ip_address }}</div> <div class="text-[12px] text-[#888] dark:text-[#aaa]">IP属地{{ blogcomment.ip_address }}</div>
<div class="text-[12px] text-[#888] dark:text-[#aaa]">{{ timestampToString(blogcomment.time) }}</div> <div class="text-[12px] text-[#888] dark:text-[#aaa]">{{ new Date(blogcomment.created_at).toLocaleString()
<div class="py-[10px] border-b border-b-[#ddd] text-[#333] dark:text-[#fff]">{{ blogcomment.content }}</div> }}</div>
<div class="py-[10px] border-b border-b-[#ddd] text-[#333] dark:text-[#fff] whitespace-pre-wrap">{{
blogcomment.content }}</div>
</div> </div>
<div class="text-[14px] text-[#666] dark:text-[#fff] my-[15px]"> {{ getStatusText }} </div> <div class="text-[14px] text-[#666] dark:text-[#fff] my-[15px]"> {{ getStatusText }} </div>
</div> </div>

View File

@@ -4,7 +4,6 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { request, type BaseResponseData } from '@/lib/request'; import { request, type BaseResponseData } from '@/lib/request';
import RotationVerification from '../Common/RotationVerification.vue';
const route = useRoute() const route = useRoute()
const bloguuid = route.params.uuid; const bloguuid = route.params.uuid;
const emit = defineEmits(['comment-success']) const emit = defineEmits(['comment-success'])
@@ -56,7 +55,7 @@ const commentHandle = () => {
cancelButtonText: '取消', cancelButtonText: '取消',
}).then(({ value }) => { }).then(({ value }) => {
inputCommentName = value ? value : ''; inputCommentName = value ? value : '';
isCaptchaViewShow.value = true; submitComment();
}).catch(() => { }).catch(() => {
ElMessage.info('已取消') ElMessage.info('已取消')
}) })
@@ -73,6 +72,7 @@ const submitComment = async () => {
}) })
if (commentRes.code == 0) { if (commentRes.code == 0) {
emit('comment-success'); emit('comment-success');
inputComment.value = '';
return ElMessage.success('评论成功~'); return ElMessage.success('评论成功~');
} else { } else {
throw new Error(commentRes.message); throw new Error(commentRes.message);
@@ -96,6 +96,4 @@ const submitComment = async () => {
</div> </div>
</div> </div>
</transition> </transition>
<RotationVerification v-if="isCaptchaViewShow"
@fail="() => { isCaptchaViewShow = false; ElMessage.warning('验证失败') }" @success="submitComment" />
</template> </template>

View File

@@ -17,7 +17,7 @@ let copyTextwithMsg = (text : string) => {
<a href="https://beian.miit.gov.cn/"> <a href="https://beian.miit.gov.cn/">
<div class="mt-[5px] cursor-pointer dark:text-[#ccc]">备案号渝ICP备2023009516号-1</div> <div class="mt-[5px] cursor-pointer dark:text-[#ccc]">备案号渝ICP备2023009516号-1</div>
</a> </a>
<div class="mt-[6px] sm:mt-0 dark:text-[#ccc]">Copyright ©2020-2024 TONE All Rights Reserved.</div> <div class="mt-[6px] sm:mt-0 dark:text-[#ccc]">Copyright ©2020-{{ new Date().getFullYear() }} TONE All Rights Reserved.</div>
</div> </div>
<div class="mr-0 sm:mr-[25px] flex sm:pb-0 pb-[20px]"> <div class="mr-0 sm:mr-[25px] flex sm:pb-0 pb-[20px]">
<el-popover trigger="click" placement="top" :width="160"> <el-popover trigger="click" placement="top" :width="160">

View File

@@ -1,199 +0,0 @@
<script setup lang='ts'>
/**
* 旋转图像验证码组件
* @event success 验证成功
* @event fail 验证失败
* @requires ServerSDK
* @since 1.0.2
*/
import { request, type BaseResponseData } from '@/lib/request';
import { onBeforeMount, onMounted, ref } from 'vue';
let imageBase64 = ref('');
const emit = defineEmits(['fail', 'success'])
let onVerifying = ref(false);
let onVerifyFail = ref(false);
onBeforeMount(async () => {
try {
let res: BaseResponseData = await request.get('captcha')
localStorage.setItem('captcha-session', res.data.session)
imageBase64.value = res.data.imgPreStr + res.data.img;
} catch (error) {
console.log("获取图像验证码失败:" + error);
emit('fail');
}
})
onMounted(() => {
let slider = document.getElementById('RV-slider');
let imageEle = document.getElementById('RV-image');
let isDragging = false;// 正在移动标志位
let silderMoveable = true;// 是否可以开始移动标识位
let startX: number;
let deltaX: number;// 拖动距离
if(!slider || !imageEle){
throw new Error('element is null')
}
slider.addEventListener('mousedown', function (e) {
if (silderMoveable) {
isDragging = true;
startX = e.clientX;
onVerifyFail.value = false;
}
})
slider.addEventListener('touchstart', function (e) {
if (silderMoveable) {
e.preventDefault();
isDragging = true;
startX = e.touches[0].clientX;
onVerifyFail.value = false;
}
}, { passive: false });
document.addEventListener('mousemove', function (e) {
if (isDragging) {
silderMoveable = false;// 产生了位移,则需等待验证后才可再次移动
deltaX = e.clientX - startX;
// 对位移距离限幅
if (deltaX < 0)
deltaX = 0;
if (deltaX > 200)
deltaX = 200;
// 调整滑块条位置样式
slider.style.transform = ` translateX(${deltaX}px)`;
// 调整图片旋转样式 200 -> 360映射 y = 9/5x
imageEle.style.transform = `rotate(${deltaX * 9 / 5}deg)`
}
});
document.addEventListener('touchmove', function (e) {
if (isDragging) {
e.preventDefault();
silderMoveable = false;
deltaX = e.touches[0].clientX - startX;
if (deltaX < 0) deltaX = 0;
if (deltaX > 200) deltaX = 200;
slider.style.transform = `translateX(${deltaX}px)`;
imageEle.style.transform = `rotate(${deltaX * 9 / 5}deg)`;
}
}, { passive: false });
document.addEventListener('mouseup', async () => {
if (isDragging) {
isDragging = false;
if (!deltaX) {
silderMoveable = true;// 位移距离为0无需验证可以继续移动
} else {
try {
onVerifying.value = true;
let res: BaseResponseData = await request.post('checkCaptcha', {
session: localStorage.getItem('captcha-session'),
rotateDeg: deltaX * 9 / 5
})
switch (res.code) {
case 0:
// 验证成功
emit('success');
break;
case -5002:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
default:
console.log('验证session过期、不存在、服务器错误')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:" + error);
emit('fail');
} finally {
onVerifying.value = false;
}
}
}
});
document.addEventListener('touchend', async () => {
if (isDragging) {
isDragging = false;
if (!deltaX) {
silderMoveable = true;// 位移距离为0无需验证可以继续移动
} else {
try {
onVerifying.value = true;
let res: BaseResponseData = await request.post('checkCaptcha', {
session: localStorage.getItem('captcha-session'),
rotateDeg: deltaX * 9 / 5
})
switch (res.code) {
case 0:
// 验证成功
emit('success');
break;
case -5002:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
default:
console.log('验证session过期、不存在、服务器错误')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:" + error);
emit('fail');
} finally {
onVerifying.value = false;
}
}
}
});
})
</script>
<template>
<transition name="el-fade-in-linear">
<div class="fixed w-full h-full inset-0 bg-[#00000022] z-[2000] flex justify-center items-center" @click="$emit('fail')">
<div class="w-[300px] h-[400px] bg-white dark:bg-[#222] dark:border-[1px] flex flex-col items-center rounded-[15px]" onclick="event.stopPropagation()">
<div class="mt-[25px] text-[#888] dark:text-white">安全验证</div>
<div class="mt-[8px] dark:text-[#ccc]">{{ onVerifying ? "正在验证,请稍后..." : (onVerifyFail ? "验证失败,请再试一次" : "拖动滑块,使图片角度为水平")
}}
</div>
<div class="mt-[28px] w-[160px] h-[160px] rounded-full flex justify-center items-center overflow-hidden">
<img class="w-[226px] h-[226px] bg-gray-400 object-cover" :src="imageBase64" alt="" id="RV-image">
</div>
<div class="w-[240px] h-[40px] rounded-[20px] bg-slate-200 dark:bg-[#444] mt-[25px] flex items-center">
<div class="w-[45px] h-[45px] leading-[45px] bg-white dark:bg-[#999] rounded-full shadow-md text-center cursor-pointer relative translate-x-0" id="RV-slider">
<svg t="1706696449802" class="absolute left-[14px] top-[14px] dark:fill-white" viewBox="0 0 1024 1024" fill="#666" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1924" width="18" height="18">
<path
d="M567.32505 547.18536c20.970614-21.479197 20.970614-56.307424 0-77.790714L185.251168 77.115332c-20.971637-21.47715-54.975079-21.47715-75.948763 0-20.973684 21.484314-20.973684 56.30947 0 77.793784l344.188016 353.383446-344.188016 353.384469c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.971637 21.482267 54.975079 21.482267 75.948763 0l382.072858-392.280337 0.001024-0.004094zM440.60802 154.908092l344.18597 353.383446-344.18597 353.385493c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.972661 21.482267 54.975079 21.482267 75.949786 0l382.074905-392.281361c20.966521-21.478174 20.966521-56.307424 0-77.790714L516.555759 77.115332c-20.972661-21.47715-54.975079-21.47715-75.949786 0-20.971637 21.48329-20.971637 56.30947 0.002047 77.79276z"
p-id="1925"></path>
</svg>
</div>
</div>
</div>
</div>
</transition>
</template>

View File

@@ -6,11 +6,10 @@ import { timestampToString } from '@/lib/timestampToString';
const tableData: Ref<any[]> = ref([]) const tableData: Ref<any[]> = ref([])
const dialogEditFormVisible = ref(false); const dialogEditFormVisible = ref(false);
type BlogContentData = { type BlogContentData = {
id: string,
uuid: string, uuid: string,
title: string, title: string,
description: string, description: string,
publish_time: Date, created_at: Date,
src: string, src: string,
access_level: number, access_level: number,
visit_count: number, visit_count: number,
@@ -34,11 +33,10 @@ const loadTableData = async () => {
} }
} }
const editForm: BlogContentData = reactive({ const editForm: BlogContentData = reactive({
id: '',
uuid: '', uuid: '',
title: '', title: '',
description: '', description: '',
publish_time: new Date(), created_at: new Date(),
src: '', src: '',
encrypt_p: '', encrypt_p: '',
access_level: 0, access_level: 0,
@@ -46,22 +44,20 @@ const editForm: BlogContentData = reactive({
like_count: 0 like_count: 0
}) })
const editHandle = (data: any) => { const editHandle = (data: any) => {
editForm.id = data.id;
editForm.uuid = data.uuid; editForm.uuid = data.uuid;
editForm.title = data.title; editForm.title = data.title;
editForm.description = data.description; editForm.description = data.description;
editForm.publish_time = new Date(+data.publish_time); editForm.created_at = new Date(data.created_at);
editForm.src = data.src; editForm.src = data.src;
editForm.access_level = data.access_level; editForm.access_level = data.access_level;
editForm.visit_count = data.visit_count; editForm.visit_count = data.visit_count;
dialogEditFormVisible.value = true; dialogEditFormVisible.value = true;
} }
const addHandle = () => { const addHandle = () => {
editForm.id = '';
editForm.uuid = ''; editForm.uuid = '';
editForm.title = ''; editForm.title = '';
editForm.description = ''; editForm.description = '';
editForm.publish_time = new Date(); editForm.created_at = new Date();
editForm.src = ''; editForm.src = '';
editForm.access_level = 10; editForm.access_level = 10;
editForm.visit_count = 0; editForm.visit_count = 0;
@@ -70,16 +66,15 @@ const addHandle = () => {
} }
const saveHandle = async () => { const saveHandle = async () => {
// 表单验证 // 表单验证
if (!editForm.title || !editForm.description || !editForm.publish_time || !editForm.src || !editForm.access_level) { if (!editForm.title || !editForm.description || !editForm.created_at || !editForm.src || !editForm.access_level) {
return ElMessage.warning('请先完成表单') return ElMessage.warning('请先完成表单')
} }
try { try {
let res: BaseResponseData = await request.post('/console/saveBlog', { let res: BaseResponseData = await request.post('/console/saveBlog', {
id: editForm.id,
uuid: editForm.uuid, uuid: editForm.uuid,
title: editForm.title, title: editForm.title,
description: editForm.description, description: editForm.description,
publish_time: editForm.publish_time.getTime(), created_at: editForm.created_at,
src: editForm.src, src: editForm.src,
access_level: editForm.access_level, access_level: editForm.access_level,
}) })
@@ -134,10 +129,10 @@ const saveHandle = async () => {
return ElMessage.error(`保存失败 ${error}`); return ElMessage.error(`保存失败 ${error}`);
} }
} }
const delHandle = async (data: { id: string, [key: string]: any }) => { const delHandle = async (data: { uuid: string, [key: string]: any }) => {
let { id } = data; let { uuid } = data;
try { try {
let res: BaseResponseData = await request.delete('/console/blog?id=' + id); let res: BaseResponseData = await request.delete('/console/blog?uuid=' + uuid);
if (res.code == 0) { if (res.code == 0) {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
loadTableData(); loadTableData();
@@ -149,7 +144,7 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
} }
} }
const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => { const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
return timestampToString(row.publish_time); return new Date(row.created_at).toLocaleString();
} }
</script> </script>
<template> <template>
@@ -159,11 +154,10 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
</div> </div>
<!-- 数据列表 --> <!-- 数据列表 -->
<el-table :data="tableData" border class="w-full"> <el-table :data="tableData" border class="w-full">
<el-table-column prop="id" label="id" width="50" />
<el-table-column prop="uuid" label="uuid" width="120" show-overflow-tooltip /> <el-table-column prop="uuid" label="uuid" width="120" show-overflow-tooltip />
<el-table-column prop="title" label="标题" width="240" /> <el-table-column prop="title" label="标题" width="250" />
<el-table-column prop="description" label="描述" width="200" show-overflow-tooltip /> <el-table-column prop="description" label="描述" width="300" show-overflow-tooltip />
<el-table-column prop="publish_time" label="发布时间" width="160" :formatter="formatTime" /> <el-table-column prop="created_at" label="发布时间" width="160" :formatter="formatTime" />
<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" />
@@ -171,7 +165,7 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<template #default="scope"> <template #default="scope">
<el-text>{{ scope.encrypt_p ? "是" : "否" }}</el-text> <el-text>{{ scope.encrypt_p ? "是" : "否" }}</el-text>
</template> </template>
</el-table-column> --> </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>
@@ -182,9 +176,6 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<!-- 编辑添加博客对话框 --> <!-- 编辑添加博客对话框 -->
<el-dialog v-model="dialogEditFormVisible" title="编辑" width="800"> <el-dialog v-model="dialogEditFormVisible" title="编辑" width="800">
<el-form :model="editForm" label-width="auto" style="margin: 0 30px;"> <el-form :model="editForm" label-width="auto" style="margin: 0 30px;">
<el-form-item label="id">
<el-input v-model="editForm.id" disabled />
</el-form-item>
<el-form-item label="uuid"> <el-form-item label="uuid">
<el-input v-model="editForm.uuid" disabled /> <el-input v-model="editForm.uuid" disabled />
</el-form-item> </el-form-item>
@@ -195,7 +186,7 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<el-input v-model="editForm.description" type="textarea" autosize /> <el-input v-model="editForm.description" type="textarea" autosize />
</el-form-item> </el-form-item>
<el-form-item label="发布时间"> <el-form-item label="发布时间">
<el-date-picker v-model="editForm.publish_time" type="datetime" placeholder="选择发布时间" /> <el-date-picker v-model="editForm.created_at" type="datetime" placeholder="选择发布时间" />
</el-form-item> </el-form-item>
<el-form-item label="文章链接"> <el-form-item label="文章链接">
<el-input v-model="editForm.src" type="textarea" autosize /> <el-input v-model="editForm.src" type="textarea" autosize />

View File

@@ -3,7 +3,7 @@ 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 } from 'element-plus';
type ResourceData = { type ResourceData = {
id: string, uuid: string,
type: string, type: string,
recommand: number, recommand: number,
title: string, title: string,
@@ -31,7 +31,7 @@ const loadTableData = async () => {
} }
} }
const editForm: ResourceData = reactive({ const editForm: ResourceData = reactive({
id: '', uuid: '',
type: '', type: '',
recommand: 1, recommand: 1,
title: '', title: '',
@@ -42,7 +42,7 @@ const editForm: ResourceData = reactive({
}) })
const openEditFormSrc = () => { window.open(editForm.src); } const openEditFormSrc = () => { window.open(editForm.src); }
const editHandle = (data: ResourceData) => { const editHandle = (data: ResourceData) => {
editForm.id = data.id; editForm.uuid = data.uuid;
editForm.type = data.type; editForm.type = data.type;
editForm.recommand = +data.recommand; editForm.recommand = +data.recommand;
editForm.title = data.title; editForm.title = data.title;
@@ -53,7 +53,7 @@ const editHandle = (data: ResourceData) => {
dialogEditFormVisible.value = true; dialogEditFormVisible.value = true;
} }
const addHandle = () => { const addHandle = () => {
editForm.id = ''; editForm.uuid = '';
editForm.type = ''; editForm.type = '';
editForm.recommand = 1; editForm.recommand = 1;
editForm.title = ''; editForm.title = '';
@@ -70,7 +70,7 @@ const saveHandle = async () => {
} }
try { try {
let res: BaseResponseData = await request.post('/console/saveResource', { let res: BaseResponseData = await request.post('/console/saveResource', {
id: editForm.id, uuid: editForm.uuid,
type: editForm.type, type: editForm.type,
recommand: editForm.recommand, recommand: editForm.recommand,
title: editForm.title, title: editForm.title,
@@ -91,9 +91,9 @@ const saveHandle = async () => {
} }
} }
const delHandle = async (data: { id: string, [key: string]: any }) => { const delHandle = async (data: { id: string, [key: string]: any }) => {
let { id } = data; let { uuid } = data;
try { try {
let res: BaseResponseData = await request.delete('/console/resource?id=' + id); let res: BaseResponseData = await request.delete('/console/resource?uuid=' + uuid);
if (res.code == 0) { if (res.code == 0) {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
loadTableData(); loadTableData();
@@ -104,6 +104,9 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
return ElMessage.error(`删除失败 ${error}`); return ElMessage.error(`删除失败 ${error}`);
} }
} }
const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
return new Date(row.created_at).toLocaleString();
}
</script> </script>
<template> <template>
<div class="py-[15px] px-[20px]"> <div class="py-[15px] px-[20px]">
@@ -112,7 +115,7 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
</div> </div>
<!-- 数据列表 --> <!-- 数据列表 -->
<el-table :data="tableData" border class="w-full"> <el-table :data="tableData" border class="w-full">
<el-table-column prop="id" label="id" width="50" /> <el-table-column prop="uuid" label="uuid" width="60" show-overflow-tooltip />
<el-table-column prop="type" label="类型" width="80" sortable> <el-table-column prop="type" label="类型" width="80" sortable>
<template #default="scope"> <template #default="scope">
{{ scope.row.type == 'resource' ? '资源' : scope.row.type == 'download' ? '下载' : '未知' }} {{ scope.row.type == 'resource' ? '资源' : scope.row.type == 'download' ? '下载' : '未知' }}
@@ -140,12 +143,13 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
<el-button link type="primary" size="small" @click="delHandle(scope.row)">删除</el-button> <el-button link type="primary" size="small" @click="delHandle(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180" :formatter="formatTime" />
</el-table> </el-table>
<!-- 编辑添加资源对话框 --> <!-- 编辑添加资源对话框 -->
<el-dialog v-model="dialogEditFormVisible" title="编辑" width="800"> <el-dialog v-model="dialogEditFormVisible" title="编辑" width="800">
<el-form :model="editForm" label-width="auto" class="mx-[30px]"> <el-form :model="editForm" label-width="auto" class="mx-[30px]">
<el-form-item label="id"> <el-form-item label="uuid">
<el-input v-model="editForm.id" disabled /> <el-input v-model="editForm.uuid" disabled />
</el-form-item> </el-form-item>
<el-form-item label="类型"> <el-form-item label="类型">
<el-select v-model="editForm.type" placeholder="请选择类型"> <el-select v-model="editForm.type" placeholder="请选择类型">

View File

@@ -4,8 +4,8 @@ type BaseResponseData = {
message: string, message: string,
data: any data: any
}; };
// axios.defaults.baseURL = "http://localhost:23500"; axios.defaults.baseURL = "http://localhost:23500";
axios.defaults.baseURL = "https://tonesc.cn/apis"; // axios.defaults.baseURL = "https://tonesc.cn/apis";
axios.interceptors.response.use((response) => { axios.interceptors.response.use((response) => {
if (response.data && response.data.code == -5) { if (response.data && response.data.code == -5) {

View File

@@ -1,7 +1,6 @@
<script setup lang='ts'> <script setup lang='ts'>
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import RotationVerification from '@/components/Common/RotationVerification.vue';
import { request, type BaseResponseData } from '@/lib/request'; import { request, type BaseResponseData } from '@/lib/request';
const containerHeight = ref('800px'); const containerHeight = ref('800px');
@@ -11,21 +10,17 @@ const formData = reactive({
username: '', username: '',
password: '' password: ''
}) })
const loginHandle = () => { const login = async () => {
if (!formData.username || !formData.password) { if (!formData.username || !formData.password) {
return ElMessage.warning('请填写账户名和密码') return ElMessage.warning('请填写账户名和密码')
} }
isCaptchaShow.value = true;
}
const login = async () => {
loginStatus.value = true;
try { try {
loginStatus.value = true;
let loginRes: BaseResponseData = await request.post('/console/login', { let loginRes: BaseResponseData = await request.post('/console/login', {
username: formData.username, username: formData.username,
password: formData.password, password: formData.password,
session: localStorage.getItem('captcha-session') session: localStorage.getItem('captcha-session')
}) })
loginStatus.value = false;
switch (loginRes.code) { switch (loginRes.code) {
case 0: case 0:
// 成功 // 成功
@@ -50,8 +45,9 @@ const login = async () => {
return ElMessage.error(`未知错误 ${loginRes.message}`) return ElMessage.error(`未知错误 ${loginRes.message}`)
} }
} catch (error) { } catch (error) {
loginStatus.value = false;
return ElMessage.error(`未知错误 ${error}`) return ElMessage.error(`未知错误 ${error}`)
} finally {
loginStatus.value = false;
} }
} }
onMounted(async () => { onMounted(async () => {
@@ -75,12 +71,11 @@ onMounted(async () => {
clearable> clearable>
</el-input> </el-input>
<el-input v-model="formData.password" show-password class="w-full h-[35px] text-[16px] mt-[10px]" <el-input v-model="formData.password" show-password class="w-full h-[35px] text-[16px] mt-[10px]"
placeholder="密码" @keyup.enter="loginHandle"> placeholder="密码" @keyup.enter="login">
</el-input> </el-input>
<el-button class="mt-[12px] mb-[120px] w-full h-[35px] font-bold login-button hover:!bg-white hover:!border-gray-300 hover:!text-gray-800" @click="loginHandle" <el-button
:loading="loginStatus">登录</el-button> class="mt-[12px] mb-[120px] w-full h-[35px] font-bold login-button hover:!bg-white hover:!border-gray-300 hover:!text-gray-800"
@click="login" :loading="loginStatus">登录</el-button>
</div> </div>
</div> </div>
<RotationVerification v-if="isCaptchaShow" @fail="() => { isCaptchaShow = false; ElMessage.warning('验证失败') }"
@success="() => { isCaptchaShow = false; login() }" />
</template> </template>