feat: 初始化proxy代理请求
This commit is contained in:
parent
9725145a43
commit
12e5184126
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ dist
|
||||
coverage
|
||||
|
||||
.DS_Store
|
||||
upload
|
7
app.config.json5
Normal file
7
app.config.json5
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
api: {
|
||||
host: 'codeflow.xiongxiao.me',
|
||||
},
|
||||
domain: 'kevisual.xiongxiao.me',
|
||||
resources: 'minio.xiongxiao.me/resources',
|
||||
}
|
@ -5,14 +5,21 @@
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "nodemon --exec tsx src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.7.4",
|
||||
"nodemon": "^3.1.7",
|
||||
"rollup": "^4.24.0",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@abearxiong/use-config": "^0.0.2",
|
||||
"@abearxiong/use-file-store": "^0.0.1",
|
||||
"ioredis": "^5.4.1",
|
||||
"nanoid": "^5.0.7"
|
||||
}
|
||||
}
|
||||
|
975
pnpm-lock.yaml
generated
975
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
15
src/index.ts
15
src/index.ts
@ -0,0 +1,15 @@
|
||||
import http from 'http';
|
||||
import { handleRequest } from './module/index.ts';
|
||||
import { useConfig } from '@abearxiong/use-config';
|
||||
useConfig();
|
||||
const server = http.createServer((req, res) => {
|
||||
// res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
// const pathname = new URL(req.url, `http://${dns.hostName}`).pathname;
|
||||
handleRequest(req, res);
|
||||
// res.write(`Request from ${dns.hostName} with IP: ${dns.ip}\n`);
|
||||
// res.end('Hello World\n');
|
||||
});
|
||||
|
||||
server.listen(3005, () => {
|
||||
console.log('Server running at http://localhost:3005/');
|
||||
});
|
18
src/module/get-content-type.ts
Normal file
18
src/module/get-content-type.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import path from 'path';
|
||||
// 获取文件的 content-type
|
||||
export const getContentType = (filePath: string) => {
|
||||
const extname = path.extname(filePath);
|
||||
const contentType = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
'.css': 'text/css',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.wav': 'audio/wav',
|
||||
'.mp4': 'video/mp4',
|
||||
};
|
||||
return contentType[extname] || 'application/octet-stream';
|
||||
};
|
283
src/module/get-user-app.ts
Normal file
283
src/module/get-user-app.ts
Normal file
@ -0,0 +1,283 @@
|
||||
import path from 'path';
|
||||
import { redis, subscriber } from './redis/redis.ts';
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import { useConfig } from '@abearxiong/use-config';
|
||||
import fs from 'fs';
|
||||
import crypto from 'crypto';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { pipeline } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
const pipelineAsync = promisify(pipeline);
|
||||
|
||||
const { resources } = useConfig<{ resources: string }>();
|
||||
const fileStore = useFileStore('upload');
|
||||
|
||||
const demoData = {
|
||||
user: 'root',
|
||||
key: 'codeflow',
|
||||
appType: 'web-single', //
|
||||
version: '1.0.0',
|
||||
domain: null,
|
||||
type: 'local',
|
||||
data: {
|
||||
files: [
|
||||
{
|
||||
name: 'index.html',
|
||||
path: 'codeflow/index.html',
|
||||
},
|
||||
{
|
||||
name: 'assets/index-14y4J8dP.js',
|
||||
path: 'codeflow/assets/index-14y4J8dP.js',
|
||||
},
|
||||
{
|
||||
name: 'assets/index-C-libw4a.css',
|
||||
path: 'codeflow/assets/index-C-libw4a.css',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const demoData2 = {
|
||||
user: 'root',
|
||||
key: 'codeflow',
|
||||
appType: 'web-single', //
|
||||
version: '0.0.1',
|
||||
domain: null,
|
||||
type: 'oss', // 是否使用oss
|
||||
data: {
|
||||
files: [
|
||||
{
|
||||
name: 'index.html',
|
||||
path: 'root/codeflow/0.0.1/index.html',
|
||||
},
|
||||
{
|
||||
name: 'assets/index-14y4J8dP.js',
|
||||
path: 'root/codeflow/0.0.1/assets/index-14y4J8dP.js',
|
||||
},
|
||||
{
|
||||
name: 'assets/index-C-libw4a.css',
|
||||
path: 'root/codeflow/0.0.1/assets/index-C-libw4a.css',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
type UserAppOptions = {
|
||||
user: string;
|
||||
app: string;
|
||||
};
|
||||
export class UserApp {
|
||||
user: string;
|
||||
app: string;
|
||||
constructor(options: UserAppOptions) {
|
||||
this.user = options.user;
|
||||
this.app = options.app;
|
||||
}
|
||||
async getExist() {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:exist:' + app + ':' + user;
|
||||
const value = await redis.get(key);
|
||||
return value;
|
||||
}
|
||||
async getCache() {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:' + app + ':' + user;
|
||||
const value = await redis.get(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async getFile(appFileUrl: string) {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:set:' + app + ':' + user;
|
||||
const value = await redis.hget(key, appFileUrl);
|
||||
return value;
|
||||
}
|
||||
async setCacheData() {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:' + app + ':' + user;
|
||||
// 如果demoData 不存在则返回
|
||||
if (!demoData2) {
|
||||
return false;
|
||||
}
|
||||
const value = await downloadUserAppFiles(user, app, demoData2);
|
||||
const valueIndexHtml = value.data.files.find((file) => file.name === 'index.html');
|
||||
await redis.set(key, JSON.stringify(value));
|
||||
await redis.set('user:app:exist:' + app + ':' + user, valueIndexHtml.path, 'EX', 60 * 60 * 24 * 7); // 24小时
|
||||
const files = value.data.files;
|
||||
// await redis.hset(key, 'files', JSON.stringify(files));
|
||||
const data = {};
|
||||
|
||||
// 将文件名和路径添加到 `data` 对象中
|
||||
files.forEach((file) => {
|
||||
data[file.name] = file.path;
|
||||
});
|
||||
await redis.hset('user:app:set:' + app + ':' + user, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
async getAllCacheData() {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:' + app + ':' + user;
|
||||
const value = await redis.get(key);
|
||||
console.log('getAllCacheData', JSON.parse(value));
|
||||
const exist = await redis.get('user:app:exist:' + app + ':' + user);
|
||||
console.log('getAllCacheData:exist', exist);
|
||||
const files = await redis.hgetall('user:app:set:' + app + ':' + user);
|
||||
console.log('getAllCacheData:files', files);
|
||||
}
|
||||
async clearCacheData() {
|
||||
const app = this.app;
|
||||
const user = this.user;
|
||||
const key = 'user:app:' + app + ':' + user;
|
||||
await redis.del(key);
|
||||
await redis.del('user:app:exist:' + app + ':' + user);
|
||||
await redis.del('user:app:set:' + app + ':' + user);
|
||||
console.log('clear user data', key);
|
||||
// 删除所有文件
|
||||
deleteUserAppFiles(user, app);
|
||||
}
|
||||
async getData() {
|
||||
return demoData;
|
||||
}
|
||||
async close() {
|
||||
// 关闭连接
|
||||
await redis.quit();
|
||||
}
|
||||
}
|
||||
export const downloadUserAppFiles = async (user: string, app: string, data: typeof demoData) => {
|
||||
const {
|
||||
data: { files, ...dataRest },
|
||||
...rest
|
||||
} = data;
|
||||
const uploadFiles = path.join(fileStore, user, app);
|
||||
if (!checkFileExistsSync(uploadFiles)) {
|
||||
fs.mkdirSync(uploadFiles, { recursive: true });
|
||||
}
|
||||
const newFiles = [];
|
||||
if (data.type === 'local') {
|
||||
// local copy file
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const copyFile = path.join(fileStore, file.path);
|
||||
const destFile = path.join(uploadFiles, file.name);
|
||||
const destDir = path.dirname(destFile); // 获取目标文件所在的目录路径
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
if (!checkFileExistsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true }); // 递归创建目录
|
||||
}
|
||||
fs.copyFileSync(copyFile, destFile);
|
||||
// const etag = await setEtag(fs.readFileSync(destFile, 'utf-8'));
|
||||
const etag = nanoid();
|
||||
newFiles.push({
|
||||
name: file.name,
|
||||
path: destFile.replace(fileStore, '') + '||' + etag,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (data.type === 'oss') {
|
||||
const serverPath = 'https://' + resources + '/';
|
||||
// server download file
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const destFile = path.join(uploadFiles, file.name);
|
||||
const destDir = path.dirname(destFile); // 获取目标文件所在的目录路径
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
if (!checkFileExistsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true }); // 递归创建目录
|
||||
}
|
||||
// 下载文件到 destFile
|
||||
await downloadFile(serverPath + file.path, destFile);
|
||||
const etag = nanoid();
|
||||
newFiles.push({
|
||||
name: file.name,
|
||||
path: destFile.replace(fileStore, '') + '||' + etag,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
...dataRest,
|
||||
files: newFiles,
|
||||
},
|
||||
};
|
||||
};
|
||||
export const checkFileExistsSync = (filePath: string) => {
|
||||
try {
|
||||
// 使用 F_OK 检查文件或目录是否存在
|
||||
fs.accessSync(filePath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
export const deleteUserAppFiles = async (user: string, app: string) => {
|
||||
const uploadFiles = path.join(fileStore, user, app);
|
||||
try {
|
||||
fs.rmSync(uploadFiles, { recursive: true });
|
||||
} catch (err) {
|
||||
console.error('deleteUserAppFiles', err);
|
||||
}
|
||||
// console.log('deleteUserAppFiles', res);
|
||||
};
|
||||
async function downloadFile(fileUrl: string, destFile: string) {
|
||||
const res = await fetch(fileUrl);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch ${fileUrl}: ${res.statusText}`);
|
||||
}
|
||||
console.log('destFile', destFile);
|
||||
const destStream = fs.createWriteStream(destFile);
|
||||
|
||||
// 使用 `pipeline` 将 `res.body` 中的数据传递给 `destStream`
|
||||
await pipelineAsync(res.body, destStream);
|
||||
|
||||
console.log(`File downloaded to ${destFile}`);
|
||||
}
|
||||
|
||||
export const clearAllUserApp = async () => {
|
||||
// redis 删除 所有的 user:app:*
|
||||
const keys = await redis.keys('user:app:*');
|
||||
console.log('clearAllUserApp', keys);
|
||||
if (keys.length > 0) {
|
||||
const pipeline = redis.pipeline();
|
||||
keys.forEach((key) => pipeline.del(key)); // 将每个键的删除操作添加到 pipeline 中
|
||||
await pipeline.exec(); // 执行 pipeline 中的所有命令
|
||||
console.log('All keys deleted successfully using pipeline');
|
||||
}
|
||||
};
|
||||
export const setEtag = async (fileContent: string) => {
|
||||
const eTag = crypto.createHash('md5').update(fileContent).digest('hex');
|
||||
return eTag;
|
||||
};
|
||||
|
||||
// redis 监听 user:app:exist:*的过期
|
||||
subscriber.on('ready', () => {
|
||||
console.log('Subscriber is ready and connected.');
|
||||
});
|
||||
|
||||
// 订阅 Redis 频道
|
||||
subscriber.subscribe('__keyevent@0__:expired', (err, count) => {
|
||||
if (err) {
|
||||
console.error('Failed to subscribe: ', err);
|
||||
} else {
|
||||
console.log(`Subscribed to ${count} channel(s). Waiting for expired events...`);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听消息事件
|
||||
subscriber.on('message', (channel, message) => {
|
||||
// 检查是否匹配 user:app:exist:* 模式
|
||||
if (message.startsWith('user:app:exist:')) {
|
||||
const [_user, _app, _exist, app, user] = message.split(':');
|
||||
// 在这里执行你的逻辑,例如清理缓存或通知用户
|
||||
console.log('User app exist key expired:', app, user);
|
||||
const userApp = new UserApp({ user, app });
|
||||
userApp.clearCacheData();
|
||||
}
|
||||
});
|
167
src/module/index.ts
Normal file
167
src/module/index.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { getDNS, isLocalhost } from '@/utils/dns.ts';
|
||||
import http from 'http';
|
||||
import { UserApp } from './get-user-app.ts';
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { useConfig } from '@abearxiong/use-config';
|
||||
import { redis } from './redis/redis.ts';
|
||||
import { getContentType } from './get-content-type.ts';
|
||||
const { api, domain } = useConfig<{
|
||||
api: {
|
||||
host: string;
|
||||
};
|
||||
domain: string;
|
||||
}>();
|
||||
|
||||
const fileStore = useFileStore('upload');
|
||||
console.log('filePath', fileStore);
|
||||
const noProxyUrl = ['/', '/favicon.ico'];
|
||||
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
const dns = getDNS(req);
|
||||
|
||||
let user, app;
|
||||
let domainApp = false;
|
||||
if (isLocalhost(dns.hostName)) {
|
||||
// 本地开发环境 测试
|
||||
// user = 'root';
|
||||
// app = 'codeflow';
|
||||
// domainApp = true;
|
||||
} else {
|
||||
// 生产环境
|
||||
// 验证域名
|
||||
if (dns.hostName !== domain) {
|
||||
// redis获取域名对应的用户和应用
|
||||
domainApp = true;
|
||||
const key = 'domain:' + dns.hostName;
|
||||
const value = await redis.get(key);
|
||||
if (!value) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.write('Invalid domain\n');
|
||||
return res.end();
|
||||
}
|
||||
const [_user, _app] = value.split(':');
|
||||
if (!_user || !_app) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.write('Invalid domain, Config error\n');
|
||||
return res.end();
|
||||
}
|
||||
user = _user;
|
||||
app = _app;
|
||||
}
|
||||
}
|
||||
const url = req.url;
|
||||
if (!domainApp && noProxyUrl.includes(req.url)) {
|
||||
res.write('No proxy for this URL\n');
|
||||
return res.end();
|
||||
}
|
||||
if (!domainApp) {
|
||||
// 原始url地址
|
||||
const urls = url.split('/');
|
||||
if (urls.length < 3) {
|
||||
console.log('urls errpr', urls);
|
||||
res.writeHead(404, { 'Content-Type': 'text/html' });
|
||||
res.write('Invalid Proxy URL\n');
|
||||
return res.end();
|
||||
}
|
||||
const [_, _user, _app] = urls;
|
||||
if (!_user || !_app) {
|
||||
res.write('Invalid URL\n');
|
||||
return res.end();
|
||||
}
|
||||
user = _user;
|
||||
app = _app;
|
||||
}
|
||||
const [_, _api] = req.url.split('/');
|
||||
if (_api === 'api') {
|
||||
// 代理到 http://codeflow.xiongxiao.me/api
|
||||
// 设置代理请求的目标 URL 和请求头
|
||||
const options = {
|
||||
host: api.host,
|
||||
path: req.url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': req.headers['content-type'],
|
||||
Authroization: req.headers?.['authorization'] || '',
|
||||
},
|
||||
};
|
||||
// 创建代理请求
|
||||
const proxyReq = http.request(options, (proxyRes) => {
|
||||
// 将代理服务器的响应头和状态码返回给客户端
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
// 将代理响应流写入客户端响应
|
||||
proxyRes.pipe(res, { end: true });
|
||||
});
|
||||
// 处理代理请求的错误事件
|
||||
proxyReq.on('error', (err) => {
|
||||
console.error(`Proxy request error: ${err.message}`);
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
res.write(`Proxy request error: ${err.message}`);
|
||||
});
|
||||
// 处理 POST 请求的请求体(传递数据到目标服务器)
|
||||
req.pipe(proxyReq, { end: true });
|
||||
return;
|
||||
}
|
||||
const userApp = new UserApp({ user, app });
|
||||
let isExist = await userApp.getExist();
|
||||
if (!isExist) {
|
||||
try {
|
||||
const hasApp = await userApp.setCacheData();
|
||||
if (!hasApp) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/html' });
|
||||
res.write('Not Found App\n');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('setCacheData error', error);
|
||||
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||
res.write('Server Error\n');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
isExist = await userApp.getExist();
|
||||
if (!isExist) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/html' });
|
||||
res.write('Not Found App Index Page\n');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const indexFile = isExist;
|
||||
let appFileUrl: string;
|
||||
if (domainApp) {
|
||||
appFileUrl = (url + '').replace(`/`, '');
|
||||
} else {
|
||||
appFileUrl = (url + '').replace(`/${user}/${app}/`, '');
|
||||
}
|
||||
const appFile = await userApp.getFile(appFileUrl);
|
||||
if (!appFile) {
|
||||
const [indexFilePath, etag] = indexFile.split('||');
|
||||
// 不存在的文件,返回indexFile的文件
|
||||
res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' });
|
||||
const filePath = path.join(fileStore, indexFilePath);
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.pipe(res);
|
||||
return;
|
||||
} else {
|
||||
const [appFilePath, eTag] = appFile.split('||');
|
||||
// 检查 If-None-Match 头判断缓存是否有效
|
||||
if (req.headers['if-none-match'] === eTag) {
|
||||
res.statusCode = 304; // 内容未修改
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const filePath = path.join(fileStore, appFilePath);
|
||||
let contentType = getContentType(filePath);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'public, max-age=3600', // 设置缓存时间为 1 小时
|
||||
ETag: eTag,
|
||||
});
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.pipe(res);
|
||||
return;
|
||||
}
|
||||
};
|
28
src/module/redis/access-time.ts
Normal file
28
src/module/redis/access-time.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { redis } from './redis.ts';
|
||||
|
||||
/**
|
||||
* 更新键的访问时间
|
||||
* @param key
|
||||
*/
|
||||
export const accessKeyWithTimestamp = async (key: string) => {
|
||||
const value = await redis.get(key);
|
||||
if (value !== null) {
|
||||
// 记录上一次访问时间(使用当前 Unix 时间戳)
|
||||
await redis.hset('key_last_access_time', key, Math.floor(Date.now() / 1000));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/**
|
||||
* 更新键的访问计数
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
export const accessKeyAndCount = async (key: string) => {
|
||||
const value = await redis.get(key);
|
||||
if (value !== null) {
|
||||
// 增加访问计数
|
||||
await redis.incr(`access_count:${key}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
48
src/module/redis/redis.ts
Normal file
48
src/module/redis/redis.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Redis } from 'ioredis';
|
||||
import { useConfig } from '@abearxiong/use-config';
|
||||
|
||||
const config = useConfig<{
|
||||
redis: ConstructorParameters<typeof Redis>;
|
||||
}>();
|
||||
// 配置 Redis 连接
|
||||
export const redis = new Redis({
|
||||
host: 'localhost', // Redis 服务器的主机名或 IP 地址
|
||||
port: 6379, // Redis 服务器的端口号
|
||||
// password: 'your_password', // Redis 的密码 (如果有)
|
||||
db: 0, // 要使用的 Redis 数据库索引 (0-15)
|
||||
keyPrefix: '', // key 前缀
|
||||
retryStrategy(times) {
|
||||
// 连接重试策略
|
||||
return Math.min(times * 50, 2000); // 每次重试时延迟增加
|
||||
},
|
||||
maxRetriesPerRequest: null, // 允许请求重试的次数 (如果需要无限次重试)
|
||||
...config.redis,
|
||||
});
|
||||
export const subscriber = redis.duplicate(); // 创建一个订阅者连接
|
||||
|
||||
async function ensureKeyspaceNotifications() {
|
||||
try {
|
||||
// 获取当前的 `notify-keyspace-events` 配置
|
||||
const currentConfig = (await redis.config('GET', 'notify-keyspace-events')) as string[];
|
||||
|
||||
// 检查返回的数组长度是否大于1,表示获取成功
|
||||
if (currentConfig && currentConfig.length > 1) {
|
||||
const currentSetting = currentConfig[1]; // 值在数组的第二个元素
|
||||
// 检查当前配置是否包含 "Ex"
|
||||
if (!currentSetting.includes('E') || !currentSetting.includes('x')) {
|
||||
console.log('Keyspace notifications are not fully enabled. Setting correct value...');
|
||||
await redis.config('SET', 'notify-keyspace-events', 'Ex');
|
||||
console.log('Keyspace notifications enabled with setting "Ex".');
|
||||
} else {
|
||||
// console.log('Keyspace notifications are already correctly configured.');
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to get the current notify-keyspace-events setting.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while configuring Redis keyspace notifications:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 确保键空间通知被正确设置
|
||||
ensureKeyspaceNotifications().catch(console.error);
|
41
src/scripts/copy.ts
Normal file
41
src/scripts/copy.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { UserApp, clearAllUserApp } from '../module/get-user-app.ts';
|
||||
import { redis } from '../module/redis/redis.ts';
|
||||
import path from 'path';
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
const filePath = useFileStore('upload');
|
||||
|
||||
const main = async () => {
|
||||
const userApp = new UserApp({ user: 'root', app: 'codeflow' });
|
||||
const res = await userApp.setCacheData();
|
||||
console.log(res);
|
||||
// userApp.close();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// main();
|
||||
|
||||
const getAll = async () => {
|
||||
const userApp = new UserApp({ user: 'root', app: 'codeflow' });
|
||||
const res = await userApp.getAllCacheData();
|
||||
userApp.close();
|
||||
};
|
||||
|
||||
// getAll();
|
||||
|
||||
// console.log('path', path.join(filePath, '/module/get-user-app.ts'));
|
||||
|
||||
const clearData = async () => {
|
||||
const userApp = new UserApp({ user: 'root', app: 'codeflow' });
|
||||
const res = await userApp.clearCacheData();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// clearData();
|
||||
clearAllUserApp();
|
||||
|
||||
const expireData = async () => {
|
||||
await redis.set('user:app:exist:' + 'codeflow:root', 'value', 'EX', 2);
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// expireData();
|
11
src/utils/dns.ts
Normal file
11
src/utils/dns.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import http from 'http';
|
||||
|
||||
export const getDNS = (req: http.IncomingMessage) => {
|
||||
const hostName = req.headers.host;
|
||||
const ip = req.socket.remoteAddress;
|
||||
return { hostName, ip };
|
||||
};
|
||||
|
||||
export const isLocalhost = (hostName: string) => {
|
||||
return hostName.includes('localhost');
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user