初步实现图床和api功能

This commit is contained in:
Jerry 2025-04-04 22:23:44 +08:00
parent 06f82c886a
commit ad5c9140ec
26 changed files with 1223 additions and 133 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
PORT=3000
DEBUG=true# 调试日志

14
.gitignore vendored
View File

@ -1 +1,13 @@
/node_modules/ /tmp
/out-tsc
/node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.pnp
.pnp.js
.vscode/*
/dist/
/logs/

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/crystelf-core.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/crystelf-core.iml" filepath="$PROJECT_DIR$/.idea/crystelf-core.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -1,2 +0,0 @@
# crystelf-core
晶灵核心服务

1
app.ts
View File

@ -1 +0,0 @@
import express from 'express';

View File

@ -1,5 +1,22 @@
{ {
"name": "crystelf-core",
"version": "1.0.0",
"scripts": {
"dev": "ts-node-dev src/main.ts",
"start": "node dist/main.js",
"build": "tsc"
},
"dependencies": { "dependencies": {
"express": "^5.1.0" "chalk": "4",
"dotenv": "^16.0.0",
"express": "^4.18.0"
},
"devDependencies": {
"@types/express": "^4.17.0",
"@types/mkdirp": "^2.0.0",
"@types/node": "^18.0.0",
"prettier": "^3.5.3",
"ts-node-dev": "^2.0.0",
"typescript": "^5.0.0"
} }
} }

923
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

25
src/app.ts Normal file
View File

@ -0,0 +1,25 @@
import express from 'express';
import logger from './utils/logger';
import fc from './utils/file';
import paths from './utils/path';
import sampleController from './modules/sample/sample.controller';
import imageController from './modules/image/image.controller';
const apps = {
createApp() {
const app = express();
app.use(express.json());
app.use('/public', express.static(paths.get('public')));
logger.debug(`路由/public挂载成功..`);
app.use('/api/sample', sampleController.getRouter());
logger.debug(`路由/api/sample挂载成功..`);
app.use('/images', imageController.getRouter());
logger.debug(`路由/images挂载成功..`);
fc.createDir(paths.get('log'));
logger.info('晶灵核心初始化成功!');
return app;
},
};
export default apps;

10
src/main.ts Normal file
View File

@ -0,0 +1,10 @@
import apps from './app';
import logger from './utils/logger';
const PORT = process.env.PORT || 3000;
const app = apps.createApp();
app.listen(PORT, () => {
logger.info(`Crystelf-core listening on ${PORT}`);
});

View File

@ -0,0 +1,51 @@
import express from 'express';
import ImageService from './image.service';
import logger from '../../utils/logger';
class ImageController {
private readonly router: express.Router;
private readonly imageService: ImageService;
constructor() {
this.router = express.Router();
this.imageService = new ImageService();
this.initializeRoutes();
}
public getRouter(): express.Router {
return this.router;
}
private initializeRoutes(): void {
this.router.get('/:filename', this.handleGetImage);
}
private handleGetImage = (req: express.Request, res: express.Response): void => {
try {
const filename = req.params.filename;
logger.debug(`有个小可爱正在请求${filename}噢..`);
const filePath = this.imageService.getImage(filename);
if (!filePath) {
this.sendError(res, 404, '文件不存在啦!');
return;
}
res.sendFile(filePath);
logger.info(`成功投递文件: ${filePath}`);
} catch (error) {
this.sendError(res, 500, '晶灵服务处理图像请求时出错..');
logger.error('晶灵图像请求处理失败:', error);
}
};
private sendError(res: express.Response, statusCode: number, message: string): void {
res.status(statusCode).json({
success: false,
error: message,
timestamp: new Date().toISOString(),
});
}
}
export default new ImageController();

View File

@ -0,0 +1,30 @@
import path from 'path';
import fs from 'fs';
import paths from '../../utils/path';
import logger from '../../utils/logger';
class ImageService {
private readonly imageDir: string;
constructor() {
this.imageDir = paths.get('images');
logger.info(`晶灵云图数据中心初始化..数据存储在: ${this.imageDir}`);
}
public getImage(filename: string): string | null {
const filePath = path.join(this.imageDir, filename);
logger.debug(`尝试访问图像路径: ${filePath}`);
if (!this.isValidFilename(filename)) {
throw new Error('无效的文件名格式..');
}
return fs.existsSync(filePath) ? filePath : null;
}
private isValidFilename(filename: string): boolean {
return /^[a-zA-Z0-9_\-\.]+$/.test(filename);
}
}
export default ImageService;

View File

@ -0,0 +1,57 @@
import express from 'express';
import sampleService from './sample.service';
class SampleController {
private readonly router: express.Router;
constructor() {
this.router = express.Router();
this.initializeRoutes();
}
public getRouter(): express.Router {
return this.router;
}
private initializeRoutes(): void {
this.router.get('/hello', this.getHello);
this.router.post('/greet', this.postGreet);
}
private getHello = (req: express.Request, res: express.Response): void => {
try {
const result = sampleService.getHello();
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, error);
}
};
private postGreet = (req: express.Request, res: express.Response): void => {
try {
const { name } = req.body;
const result = sampleService.generateGreeting(name);
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, error);
}
};
private sendSuccess(res: express.Response, data: any, statusCode = 200): void {
res.status(statusCode).json({
success: true,
data,
timestamp: new Date().toISOString(),
});
}
private sendError(res: express.Response, error: any, statusCode = 500): void {
res.status(statusCode).json({
success: false,
message: error.message,
timestamp: new Date().toISOString(),
});
}
}
export default new SampleController();

View File

@ -0,0 +1,19 @@
import logger from '../../utils/logger';
class SampleService {
getHello() {
logger.debug(`有个小可爱正在请求GetHello方法..`);
return { message: 'Hello World!' };
}
generateGreeting(name: string) {
logger.debug(`有个小可爱正在请求generateGreeting方法..`);
if (!name) {
logger.warn('Name is required');
throw new Error('Name is required');
}
return { message: `Hello, ${name}!` };
}
}
export default new SampleService();

23
src/utils/config.ts Normal file
View File

@ -0,0 +1,23 @@
import dotenv from 'dotenv';
dotenv.config();
let config = {
get: (key: string, defaultValue: any) => {
const value = process.env[key];
if (value === undefined) {
if (defaultValue !== undefined) return defaultValue;
throw new Error(`环境变量${key}未定义..`);
}
if (typeof defaultValue === 'number') return Number(value);
if (typeof defaultValue === 'boolean') return value === 'true';
return value;
},
set: (key: string, value: any) => {
process.env[key] = value;
},
};
export default config;

11
src/utils/date.ts Normal file
View File

@ -0,0 +1,11 @@
let date = {
getCurrentDate: () => {
const now = new Date();
return `${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}`;
},
getCurrentTime: () => {
return new Date().toLocaleTimeString('en-US', { hour12: false });
},
};
export default date;

46
src/utils/file.ts Normal file
View File

@ -0,0 +1,46 @@
import path from 'path';
import paths from './path';
import date from './date';
import fs from 'fs';
import chalk from 'chalk';
import logger from './logger';
let fc = {
createDir: (targetPath: string = '', includeFile: boolean = false) => {
const root = paths.get('root');
if (path.isAbsolute(targetPath)) {
if (includeFile) {
const parentDir = path.dirname(targetPath);
if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true });
logger.debug(`成功创建绝对目录: ${parentDir}`);
}
return;
}
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true });
logger.debug(`成功创建绝对目录: ${targetPath}`);
}
return;
}
const fullPath = includeFile
? path.join(root, path.dirname(targetPath))
: path.join(root, targetPath);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
logger.debug(`成功创建相对目录: ${fullPath}`);
}
},
logToFile: (level: string, message: string) => {
const logFile = path.join(paths.get('log'), `${date.getCurrentDate()}.log`);
const logMessage = `[${date.getCurrentTime()}] [${level}] ${message}\n`;
fs.appendFile(logFile, logMessage, (err) => {
if (err) console.error(chalk.red('[LOGGER] 写入日志失败:'), err);
});
},
};
export default fc;

30
src/utils/logger.ts Normal file
View File

@ -0,0 +1,30 @@
import chalk from 'chalk';
import config from './config';
import fc from './file';
const logger = {
debug: (...args: any[]) => {
if (config.get('DEBUG', false)) {
//const message = args.join(' ');
console.log(chalk.cyan('[DEBUG]'), ...args);
//fc.logToFile('DEBUG', message);
}
},
info: (...args: any[]) => {
const message = args.join(' ');
console.log(chalk.green('[INFO]'), ...args);
fc.logToFile('INFO', message);
},
warn: (...args: any[]) => {
const message = args.join(' ');
console.log(chalk.yellow('[WARN]'), ...args);
fc.logToFile('WARN', message);
},
error: (...args: any[]) => {
const message = args.join(' ');
console.log(chalk.red('[ERROR]'), ...args);
fc.logToFile('ERROR', message);
},
};
export default logger;

24
src/utils/path.ts Normal file
View File

@ -0,0 +1,24 @@
import path from 'path';
const paths = {
get: (value?: string) => {
switch (value) {
case 'root':
return path.join(__dirname, '..');
case 'public':
return path.join(__dirname, '../public');
case 'images':
return path.join(__dirname, '../../public/images/');
case 'log':
return path.join(__dirname, '../../logs');
default:
return path.join(__dirname, '..');
}
},
resolve: (segments: string) => {
return path.join(__dirname, segments);
},
};
export default paths;

0
src/utils/tool.ts Normal file
View File

12
tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}