mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-07-04 06:39:18 +00:00
初步实现图床和api功能
This commit is contained in:
parent
06f82c886a
commit
ad5c9140ec
14
.gitignore
vendored
14
.gitignore
vendored
@ -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
8
.idea/.gitignore
generated
vendored
Normal 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
12
.idea/crystelf-core.iml
generated
Normal 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>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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
6
.idea/jsLibraryMappings.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
23
package.json
23
package.json
@ -1,5 +1,22 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"name": "crystelf-core",
|
||||||
"express": "^5.1.0"
|
"version": "1.0.0",
|
||||||
}
|
"scripts": {
|
||||||
|
"dev": "ts-node-dev src/main.ts",
|
||||||
|
"start": "node dist/main.js",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"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
923
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
25
src/app.ts
Normal file
25
src/app.ts
Normal 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
10
src/main.ts
Normal 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}`);
|
||||||
|
});
|
51
src/modules/image/image.controller.ts
Normal file
51
src/modules/image/image.controller.ts
Normal 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();
|
30
src/modules/image/image.service.ts
Normal file
30
src/modules/image/image.service.ts
Normal 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;
|
57
src/modules/sample/sample.controller.ts
Normal file
57
src/modules/sample/sample.controller.ts
Normal 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();
|
19
src/modules/sample/sample.service.ts
Normal file
19
src/modules/sample/sample.service.ts
Normal 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
23
src/utils/config.ts
Normal 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
11
src/utils/date.ts
Normal 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
46
src/utils/file.ts
Normal 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
30
src/utils/logger.ts
Normal 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
24
src/utils/path.ts
Normal 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
0
src/utils/tool.ts
Normal file
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user