05. 项目结构解析

4 阅读5分钟

05. 项目结构解析

本章将深入分析 sco-commit 的项目结构,帮助你理解前后端代码的组织方式。掌握项目结构是进行开发的前提,让我们从整体到局部逐步了解。

本章目标

  • 理解前后端目录组织方式
  • 掌握分层架构设计思想
  • 了解各目录和文件的职责
  • 能够定位特定功能的代码位置

项目整体结构

sco-commit 采用标准的 Go + React 双仓库结构:

commit-dashboard/
├── server/                 # Go 后端服务
│   ├── main.go           # 程序入口
│   ├── go.mod            # Go 模块定义
│   ├── router/           # 路由配置
│   ├── app/              # 业务逻辑层
│   ├── infrastructure/   # 基础设施层
│   ├── common/           # 通用工具
│   ├── config/          # 配置管理
│   ├── migrations/      # 数据库迁移
│   └── utils/            # 工具函数
│
├── web/                   # React 前端应用
│   ├── src/              # 源代码
│   ├── public/           # 静态资源
│   ├── index.html        # HTML 入口
│   └── package.json      # 依赖管理
│
├── docker-compose.yml     # Docker 编排
├── Dockerfile.server      # 后端镜像
└── Dockerfile.web         # 前端镜像

这种结构将后端和前端完全分离,各自独立运行,通过 HTTP API 通信。

后端结构详解

后端采用分层架构,将不同职责的代码分离到不同目录:

server/
├── main.go                    # 应用入口
├── router/router.go           # 主路由配置
├── config/database.go         # 数据库配置
│
├── app/                       # 业务逻辑层
│   ├── gitea/                # Gitea 相关业务
│   │   ├── handlers/        # 处理器(接收请求)
│   │   ├── models/          # 数据模型
│   │   │   ├── request/     # 请求参数
│   │   │   ├── response/    # 响应结构
│   │   │   └── db/          # 数据库模型
│   │   ├── repository/      # 数据仓库(数据库操作)
│   │   ├── services/        # 业务服务
│   │   ├── helpers/        # 辅助函数
│   │   └── router/         # Gitea 模块路由
│   │
│   └── ai/                  # AI 相关业务
│       ├── handlers/        # AI 处理器
│       └── router/         # AI 模块路由
│
├── infrastructure/           # 基础设施层
│   ├── database/            # 数据库连接
│   ├── gitea/              # Gitea API 客户端
│   ├── llm/                # LLM 客户端(INO)
│   └── agent/              # AI Agent 核心
│
├── common/                   # 通用工具
│   ├── response/           # 统一响应格式
│   ├── pagination/         # 分页工具
│   └── option/             # 选项模式
│
├── migrations/               # 数据库迁移
│   ├── gitea/              # Gitea 模块表
│   └── ai/                 # AI 模块表
│
└── utils/                    # 工具函数
    ├── env.go              # 环境变量
    └── jwt.go              # JWT 认证

入口文件:main.go

每个 Go 程序都有一个入口文件,这是程序开始执行的地方:

// server/main.go
package main

import (
    "log"

    "github.com/commit-dashboard/server/config"
    "github.com/commit-dashboard/server/router"
)

func main() {
    // 初始化数据库连接
    config.InitDB()

    // 设置路由
    app := router.Setup()

    // 启动服务器
    log.Fatal(app.Listen(":3000"))
}

这段代码做了三件事:

  1. 初始化数据库连接
  2. 配置路由
  3. 监听 3000 端口启动服务

业务逻辑层:app/

app/ 目录是核心业务代码所在,按照功能模块划分:

目录职责说明
app/gitea/Gitea 集成仓库、同步、提交等业务逻辑
app/ai/AI 功能AI Agent 对话、周报生成

每个功能模块内部采用类似的结构:

app/gitea/
├── handlers/      # 处理 HTTP 请求
├── models/         # 数据结构定义
├── repository/     # 数据库操作
├── services/       # 业务逻辑
├── helpers/        # 辅助函数
└── router/         # 模块路由

基础设施层:infrastructure/

infrastructure/ 目录存放与外部系统交互的代码:

// infrastructure/gitea/client.go
// Gitea API 客户端封装
type GiteaClient struct {
    BaseURL   string
    Token     string
    HTTPClient *http.Client
}

// infrastructure/llm/eino_client.go
// LLM 客户端封装
type EinoClient struct {
    APIKey  string
    BaseURL string
}

通用工具:common/

common/ 目录存放各模块都可能用到的工具代码:

// common/response/response.go
// 统一响应格式
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

// 成功响应
func Success(data interface{}) Response {
    return Response{Code: 0, Message: "success", Data: data}
}

// 失败响应
func Error(msg string) Response {
    return Response{Code: -1, Message: msg}
}

数据库迁移:migrations/

SQL 脚本定义数据库表结构:

-- migrations/gitea/001_init.sql
CREATE TABLE repositories (
    id BIGSERIAL PRIMARY KEY,
    owner VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    stars INTEGER DEFAULT 0,
    language VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

前端结构详解

前端采用 React + TypeScript + Vite:

web/src/
├── main.tsx                 # React 入口
├── index.css               # 全局样式
├── api/                    # API 客户端
│   ├── index.ts           # axios 实例配置
│   ├── gitea/             # Gitea API 调用
│   └── ai/                # AI API 调用
│
├── components/             # 可复用组件
│   ├── sag-ui/           # 基础 UI 组件
│   │   ├── page-container/
│   │   ├── pagination/
│   │   └── custom-error/
│   └── ai-elements/      # AI 相关组件
│       ├── agent.tsx
│       └── sources.tsx
│
├── pages/                  # 页面组件
│   ├── dashboard/         # 总览页
│   ├── repos/             # 仓库列表页
│   ├── commits/           # 提交分析页
│   ├── members/           # 成员贡献页
│   ├── ai/                # AI 助手页
│   ├── sync/              # 同步管理页
│   └── settings/          # 设置页
│
├── hooks/                  # 自定义 Hooks
│   └── use-repos.ts
│
├── stores/                 # 状态管理(Zustand)
│   ├── app.ts
│   ├── gitea.ts
│   ├── sync-page.ts
│   ├── recent-members.ts
│   └── recent-repos.ts
│
├── routes/                  # 路由配置
│   ├── __root.tsx         # 根路由
│   ├── _layout.tsx        # 布局路由
│   └── _layout/          # 各页面路由
│
├── types/                   # TypeScript 类型
│   ├── api.ts
│   └── gitea.ts
│
├── utils/                   # 工具函数
│   ├── helpers.ts
│   └── request/           # 请求封装
│
└── layout/                  # 布局组件
    ├── index.tsx
    └── sidebar/

API 客户端:api/

封装与后端通信的逻辑:

// web/src/api/index.ts
import axios from 'axios';

// 创建 axios 实例
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000',
  timeout: 30000,
});

// 请求拦截器
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default request;
// web/src/api/gitea/repos.ts
import request from '../index';

// 获取仓库列表
export const getRepos = (params?: { page?: number; limit?: number }) => {
  return request.get('/api/gitea/repos', { params });
};

// 获取单个仓库详情
export const getRepo = (owner: string, repo: string) => {
  return request.get(`/api/gitea/repos/${owner}/${repo}`);
};

页面组件:pages/

每个功能模块对应一个页面目录:

pages/
├── dashboard/           # 总览仪表盘
│   └── index.tsx
├── repos/               # 仓库管理
│   ├── index.tsx       # 列表页
│   └── components/     # 页面内组件
├── commits/             # 提交分析
│   ├── index.tsx
│   └── components/     # 图表组件
├── members/             # 成员贡献
│   └── components/
├── ai/                  # AI 助手
│   └── index.tsx
├── sync/                # 数据同步
│   └── components/     # 同步相关组件
└── settings/            # 系统设置

路由配置:routes/

使用 TanStack Router 的文件路由:

// web/src/routes/__root.tsx
import { createRootRoute, createRouter } from '@tanstack/react-router';
import { rootRoute } from './__root';
import { layoutRoute } from './_layout';
import { indexRoute } from './_layout/index';

// 构建路由树
const routeTree = rootRoute.addChildren([
  layoutRoute.addChildren([
    indexRoute,
    // 更多子路由...
  ]),
]);

export const router = createRouter({ routeTree });

状态管理:stores/

使用 Zustand 进行状态管理:

// web/src/stores/gitea.ts
import { create } from 'zustand';

interface GiteaState {
  // 状态
  repos: Repo[];
  selectedRepo: Repo | null;
  
  // 方法
  setRepos: (repos: Repo[]) => void;
  setSelectedRepo: (repo: Repo | null) => void;
}

export const useGiteaStore = create<GiteaState>((set) => ({
  repos: [],
  selectedRepo: null,
  
  setRepos: (repos) => set({ repos }),
  setSelectedRepo: (repo) => set({ selectedRepo: repo }),
}));

分层架构解析

整个项目采用清晰的分层架构:

graph TB
    subgraph "前端 Web"
        A[Pages 页面] --> B[Components 组件]
        B --> C[API 客户端]
        C --> D[Stores 状态]
    end

    subgraph "后端 Server"
        E[Router 路由] --> F[Handlers 处理器]
        F --> G[Services 服务]
        G --> H[Repository 仓库]
        H --> I[Database 数据库]
    end

    C -.->|HTTP| E
    I --> J[(PostgreSQL)]
    
    subgraph "外部服务"
        K[Gitea]
        L[LLM 服务]
    end

    G --> K
    G --> L

各层职责

层级前端后端
表现层Pages / ComponentsHandlers
业务层Hooks / StoresServices
数据层API 客户端Repository
基础设施-Database / Gitea Client / LLM Client

数据流向

  1. 前端请求流程

    • 用户在页面点击 → 调用 API 函数 → Zustand 存储状态 → 页面更新
  2. 后端处理流程

    • 收到 HTTP 请求 → 路由匹配 → 处理器处理 → 服务层业务逻辑 → 仓库层数据库操作 → 返回响应

代码定位示例

假设我们需要查找「获取仓库列表」功能的代码:

flowchart LR
    A[前端] --> B[web/src/api/gitea/repos.ts]
    A --> C[web/src/pages/repos/index.tsx]
    
    D[后端] --> E[server/app/gitea/handlers/repo.go]
    D --> F[server/app/gitea/repository/repo_repo.go]
    D --> G[server/app/gitea/router/gitea.go]
功能前端文件后端文件
API 调用api/gitea/repos.ts-
页面展示pages/repos/index.tsx-
请求处理-handlers/repo.go
数据库操作-repository/repo_repo.go
路由配置routes/router/gitea.go