添加删除文件

This commit is contained in:
熊潇 2025-03-26 00:05:58 +08:00
parent 64c70ce527
commit 501a92eb88
10 changed files with 236 additions and 9 deletions

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "submodules/oss"]
path = submodules/oss
url = git@git.xiongxiao.me:kevisual/kevisual-oss.git
[submodule "submodules/pay-center-code"]
path = submodules/pay-center-code
url = git@git.xiongxiao.me:kevisual/pay-center-code.git

38
pnpm-lock.yaml generated
View File

@ -301,6 +301,18 @@ importers:
specifier: ^8.4.0
version: 8.4.0(tsx@4.19.3)(typescript@5.8.2)
submodules/pay-center-code:
devDependencies:
'@kevisual/router':
specifier: 0.0.10-beta.1
version: 0.0.10-beta.1
'@kevisual/use-config':
specifier: ^1.0.10
version: 1.0.10(dotenv@16.4.7)
tsup:
specifier: ^8.4.0
version: 8.4.0(tsx@4.19.3)(typescript@5.8.2)
submodules/permission:
devDependencies:
tsup:
@ -510,6 +522,9 @@ packages:
'@kevisual/use-config': ^1.0.5
pm2: ^5.4.3
'@kevisual/router@0.0.10-beta.1':
resolution: {integrity: sha512-jl3f6HMdEd0B/6y14w437NatvpOKQ7Gkkr9vFNXvJ3tnYk7ozwjtavLSP3k4MWr5Er9SCT0KBX7+FjnvslCsSw==}
'@kevisual/router@0.0.9':
resolution: {integrity: sha512-qPyC2GVJ7iOIdJCCKNDsWMAKOQeSJW9HBpL5ZWKHTbi+t4jJBGTzIlXmjKeMHRd0lr/Qq1imQvlkSh4hlrbodA==}
@ -2739,6 +2754,18 @@ packages:
utf-8-validate:
optional: true
ws@8.18.1:
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml2js@0.5.0:
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
engines: {node: '>=4.0.0'}
@ -2909,6 +2936,15 @@ snapshots:
'@kevisual/use-config': 1.0.10(dotenv@16.4.7)
pm2: 6.0.5
'@kevisual/router@0.0.10-beta.1':
dependencies:
path-to-regexp: 8.2.0
selfsigned: 2.4.1
ws: 8.18.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@kevisual/router@0.0.9':
dependencies:
path-to-regexp: 8.2.0
@ -5343,6 +5379,8 @@ snapshots:
ws@8.18.0: {}
ws@8.18.1: {}
xml2js@0.5.0:
dependencies:
sax: 1.4.1

View File

@ -1,6 +1,6 @@
import { app } from '@/app.ts';
import { AppModel } from './module/app.ts';
import { AppDomainModel } from './module/app-domain.ts';
import { AppModel } from '../module/app.ts';
import { AppDomainModel } from '../module/app-domain.ts';
app
.route({
@ -12,7 +12,7 @@ app
// const query = {
// }
const domainInfo = await AppDomainModel.findOne({ where: { domain } });
if (!domainInfo) {
if (!domainInfo || !domainInfo.appId) {
ctx.throw(404, 'app not found');
}
const app = await AppModel.findByPk(domainInfo.appId);

View File

@ -0,0 +1,2 @@
import './domain-self.ts';
import './manager.ts';

View File

@ -0,0 +1,125 @@
import { app } from '@/app.ts';
import { AppDomainModel } from '../module/app-domain.ts';
import { AppModel } from '../module/app.ts';
import { CustomError } from '@kevisual/router';
app
.route({
path: 'app.domain.manager',
key: 'list',
middleware: ['auth-admin'],
})
.define(async (ctx) => {
const { page = 1, pageSize = 999 } = ctx.query.data || {};
const { count, rows } = await AppDomainModel.findAndCountAll({
offset: (page - 1) * pageSize,
limit: pageSize,
});
ctx.body = { count, list: rows, pagination: { page, pageSize } };
return ctx;
})
.addTo(app);
app
.route({
path: 'app.domain.manager',
key: 'update',
middleware: ['auth-admin'],
})
.define(async (ctx) => {
const { domain, data, id, ...rest } = ctx.query.data || {};
if (!domain) {
ctx.throw(400, 'domain is required');
}
let domainInfo: AppDomainModel;
if (id) {
domainInfo = await AppDomainModel.findByPk(id);
} else {
domainInfo = await AppDomainModel.create({ domain });
}
const checkAppId = async () => {
const isUUID = (id: string) => {
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id);
};
if (rest.appId) {
if (!isUUID(rest.appId)) {
ctx.throw(400, 'appId is not valid');
}
const appInfo = await AppModel.findByPk(rest.appId);
if (!appInfo) {
ctx.throw(400, 'appId is not exist');
}
}
};
try {
if (!domainInfo) {
domainInfo = await AppDomainModel.create({ domain, data: {}, ...rest });
await checkAppId();
} else {
if (rest.status && domainInfo.status !== rest.status) {
await domainInfo.clearCache();
}
await checkAppId();
await domainInfo.update({
domain,
data: {
...domainInfo.data,
...data,
},
...rest,
});
}
ctx.body = domainInfo;
} catch (error) {
if (error.code) {
ctx.throw(error.code, error.message);
}
console.error(error);
ctx.throw(500, 'update domain failed, please check the data');
}
return ctx;
})
.addTo(app);
app
.route({
path: 'app.domain.manager',
key: 'delete',
middleware: ['auth-admin'],
})
.define(async (ctx) => {
const { id, domain } = ctx.query.data || {};
if (!id && !domain) {
ctx.throw(400, 'id or domain is required');
}
if (id) {
await AppDomainModel.destroy({ where: { id }, force: true });
} else {
await AppDomainModel.destroy({ where: { domain }, force: true });
}
ctx.body = { message: 'delete domain success' };
return ctx;
})
.addTo(app);
app
.route({
path: 'app.domain.manager',
key: 'get',
middleware: ['auth-admin'],
})
.define(async (ctx) => {
const { id, domain } = ctx.query.data || {};
if (!id && !domain) {
ctx.throw(400, 'id or domain is required');
}
const domainInfo = await AppDomainModel.findOne({ where: { id } });
if (!domainInfo) {
ctx.throw(404, 'domain not found');
}
ctx.body = domainInfo;
return ctx;
})
.addTo(app);

View File

@ -2,6 +2,6 @@ import './list.ts';
import './user-app.ts';
import './public/index.ts';
import './domain.ts';
import './domain/index.ts';
export * from './module/index.ts';

View File

@ -1,6 +1,7 @@
import { sequelize } from '../../../modules/sequelize.ts';
import { DataTypes, Model } from 'sequelize';
export type DomainList = Partial<InstanceType<typeof AppDomainModel>>;
import { redis } from '../../../modules/redis.ts';
// 审核,通过,驳回
const appDomainStatus = ['audit', 'auditReject', 'auditPending', 'running', 'stop'] as const;
@ -16,6 +17,7 @@ export class AppDomainModel extends Model {
// 状态,
declare status: AppDomainStatus;
declare uid: string;
declare data: Record<string, any>;
declare createdAt: Date;
declare updatedAt: Date;
@ -28,6 +30,18 @@ export class AppDomainModel extends Model {
// 原本是审核状态,不能修改。
return false;
}
async clearCache() {
// 清除缓存
const cacheKey = `domain:${this.domain}`;
const checkHas = async () => {
const has = await redis.get(cacheKey);
return has;
};
const has = await checkHas();
if (has) {
await redis.set(cacheKey, '', 'EX', 1);
}
}
}
AppDomainModel.init(
@ -43,13 +57,22 @@ AppDomainModel.init(
allowNull: false,
unique: true,
},
appId: {
data: {
type: DataTypes.JSONB,
allowNull: true,
},
status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'running',
},
appId: {
type: DataTypes.STRING,
allowNull: true,
},
uid: {
type: DataTypes.STRING,
allowNull: false,
allowNull: true,
},
},
{

View File

@ -1,5 +1,5 @@
import { app } from '@/app.ts';
import { getFileStat, getMinioList, deleteFile, updateFileStat } from './module/get-minio-list.ts';
import { getFileStat, getMinioList, deleteFile, updateFileStat, deleteFiles } from './module/get-minio-list.ts';
import path from 'path';
import { CustomError } from '@kevisual/router';
import { get } from 'http';
@ -147,3 +147,37 @@ app
return ctx;
})
.addTo(app);
app
.route({
path: 'file',
key: 'delete-all',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
let directory = ctx.query.data?.directory as string;
if (!directory) {
ctx.throw(400, 'directory is required');
}
if (directory.startsWith('/')) {
ctx.throw(400, 'directory is invalid, cannot start with /');
}
if (directory.endsWith('/')) {
ctx.throw(400, 'directory is invalid, cannot end with /');
}
const prefix = tokenUser.username + '/' + directory + '/';
const list = await getMinioList<true>({ prefix, recursive: true });
if (list.length === 0) {
ctx.throw(400, 'directory is empty');
}
const res = await deleteFiles(list.map((item) => item.name));
if (!res) {
ctx.throw(500, 'delete all failed');
}
ctx.body = {
deleted: list.length,
message: 'delete all success',
};
})
.addTo(app);

View File

@ -16,10 +16,10 @@ export type MinioDirectory = {
size: number;
};
export type MinioList = (MinioFile | MinioDirectory)[];
export const getMinioList = async (opts: MinioListOpt): Promise<MinioList> => {
export const getMinioList = async <IS_FILE extends boolean>(opts: MinioListOpt): Promise<IS_FILE extends true ? MinioFile[] : MinioDirectory[]> => {
const prefix = opts.prefix;
const recursive = opts.recursive ?? false;
return await new Promise((resolve, reject) => {
const res = await new Promise((resolve, reject) => {
let res: any[] = [];
let hasError = false;
minioClient
@ -40,6 +40,7 @@ export const getMinioList = async (opts: MinioListOpt): Promise<MinioList> => {
}
});
});
return res as IS_FILE extends true ? MinioFile[] : MinioDirectory[];
};
export const getFileStat = async (prefix: string, isFile?: boolean): Promise<any> => {
try {

@ -0,0 +1 @@
Subproject commit 951280f0975f9c46a465d45919250fd2d49503fd