feat: 完善项目架构和功能
- 修复路由配置,实现根路径自动重定向到/project - 新增Gitea OAuth认证系统和相关组件 - 完善日志系统实现,包含pino日志工具和中间件 - 重构页面结构,分离项目管理和环境管理页面 - 新增CORS、Session等关键中间件 - 优化前端请求封装和类型定义 - 修复TypeScript类型错误和参数传递问题
This commit is contained in:
94
apps/server/libs/gitea.ts
Normal file
94
apps/server/libs/gitea.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
interface TokenResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
interface GiteaUser {
|
||||
id: number;
|
||||
login: string;
|
||||
login_name: string;
|
||||
source_id: number;
|
||||
full_name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
language: string;
|
||||
is_admin: boolean;
|
||||
last_login: string;
|
||||
created: string;
|
||||
restricted: boolean;
|
||||
active: boolean;
|
||||
prohibit_login: boolean;
|
||||
location: string;
|
||||
website: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
followers_count: number;
|
||||
following_count: number;
|
||||
starred_repos_count: number;
|
||||
username: string;
|
||||
}
|
||||
|
||||
class Gitea {
|
||||
private get config() {
|
||||
return {
|
||||
giteaUrl: process.env.GITEA_URL!,
|
||||
clientId: process.env.GITEA_CLIENT_ID!,
|
||||
clientSecret: process.env.GITEA_CLIENT_SECRET!,
|
||||
redirectUri: process.env.GITEA_REDIRECT_URI!,
|
||||
}
|
||||
}
|
||||
|
||||
async getToken(code: string) {
|
||||
const { giteaUrl, clientId, clientSecret, redirectUri } = this.config;
|
||||
console.log('this.config', this.config);
|
||||
const response = await fetch(
|
||||
`${giteaUrl}/login/oauth/access_token`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
console.log(await response.json());
|
||||
throw new Error(`Fetch failed: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as TokenResponse;
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param accessToken 访问令牌
|
||||
*/
|
||||
async getUserInfo(accessToken: string) {
|
||||
const response = await fetch(`${this.config.giteaUrl}/api/v1/user`, {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(accessToken),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fetch failed: ${response.status}`);
|
||||
}
|
||||
const result = (await response.json()) as GiteaUser;
|
||||
return result;
|
||||
}
|
||||
|
||||
private getHeaders(accessToken?: string) {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (accessToken) {
|
||||
headers['Authorization'] = `token ${accessToken}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
export const gitea = new Gitea();
|
||||
38
apps/server/libs/logger.ts
Normal file
38
apps/server/libs/logger.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import pino from 'pino';
|
||||
|
||||
class Logger {
|
||||
private readonly logger: pino.Logger;
|
||||
|
||||
constructor() {
|
||||
this.logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
singleLine: true,
|
||||
colorize: true,
|
||||
translateTime: 'SYS:standard',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
level: 'debug',
|
||||
});
|
||||
}
|
||||
|
||||
debug(tag: string, message: string, ...args: unknown[]) {
|
||||
if (args.length > 0) {
|
||||
this.logger.debug({ TAG: tag }, message, ...(args as []));
|
||||
} else {
|
||||
this.logger.debug({ TAG: tag }, message);
|
||||
}
|
||||
}
|
||||
|
||||
info(tag: string, message: string, ...args: unknown[]) {
|
||||
this.logger.info({ TAG: tag }, message, ...(args as []));
|
||||
}
|
||||
|
||||
error(tag: string, message: string, ...args: unknown[]) {
|
||||
this.logger.error({ TAG: tag }, message, ...(args as []));
|
||||
}
|
||||
}
|
||||
|
||||
export const log = new Logger();
|
||||
@@ -74,8 +74,6 @@ export class RouteScanner {
|
||||
default:
|
||||
console.warn(`未支持的HTTP方法: ${route.method}`);
|
||||
}
|
||||
|
||||
console.log(`注册路由: ${route.method} ${fullPath} -> ${ControllerClass.name}.${route.propertyKey}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,10 +113,7 @@ export class RouteScanner {
|
||||
const result = await method.call(instance, ctx, next);
|
||||
|
||||
// 如果控制器返回了数据,则包装成统一响应格式
|
||||
if (result !== undefined) {
|
||||
ctx.body = createAutoSuccessResponse(result);
|
||||
}
|
||||
// 如果控制器没有返回数据,说明已经自己处理了响应
|
||||
ctx.body = createAutoSuccessResponse(result);
|
||||
} catch (error) {
|
||||
// 错误由全局异常处理中间件处理
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user