const API = require("../../lib/API") const AccessControl = require("../../lib/AccessControl") const { BaseStdResponse } = require("../../BaseStdResponse") const db = require("../../plugin/DataBase/db") const simpleGit = require('simple-git') const fs = require("fs") const path = require("path") class gitCodeStats extends API { constructor() { super() this.setMethod("GET") this.setPath("/Repos/CodeStats") } // 定义支持的语言后缀 languageExtensions = { "JavaScript": [".js", ".jsx", ".mjs", ".cjs"], "TypeScript": [".ts", ".tsx"], "Python": [".py"], "Java": [".java"], "C": [".c"], "C++": [".cpp", ".cc", ".cxx", ".h", ".hpp"], "C#": [".cs"], "Go": [".go"], "PHP": [".php"], "HTML": [".html", ".htm"], "CSS": [".css"], "SCSS": [".scss", ".sass"], "JSON": [".json"], "YAML": [".yml", ".yaml"], "Markdown": [".md"], "Shell": [".sh"], "Bash": [".bash"], "PowerShell": [".ps1"], "Ruby": [".rb"], "Vue": [".vue"], // Vue文件 "LESS": [".less"], // LESS样式文件 "Sass": [".sass"], // Sass文件 "Haml": [".haml"], // Haml文件 "Elixir": [".ex", ".exs"], // Elixir语言文件 "Erlang": [".erl", ".hrl"], // Erlang语言文件 "Swift": [".swift"], // Swift语言文件 "Kotlin": [".kt", ".kts"], // Kotlin语言文件 "Rust": [".rs"], // Rust语言文件 "Dart": [".dart"], // Dart语言文件 "Lua": [".lua"], // Lua语言文件 "R": [".r"], // R语言文件 "Perl": [".pl"], // Perl语言文件 "SQL": [".sql"], // SQL文件 "Dockerfile": [".dockerfile", "Dockerfile"], // Dockerfile "GraphQL": [".graphql", ".gql"], // GraphQL文件 "JSON5": [".json5"], // JSON5文件 "TOML": [".toml"], // TOML文件 "Text": [".txt", ".text"], // 文本文件 "Other": [".bin", ".dat", ".exe", ".dll", ".obj", ".so", ".class"] // 二进制及无法识别的文件 } // 判断文件是否为二进制文件 isBinaryFile(filePath) { const binaryExtensions = ['.exe', '.dll', '.obj', '.so', '.bin', '.dat'] const ext = path.extname(filePath).toLowerCase() return binaryExtensions.includes(ext) } // 统计单个文件的行数 countLines(filePath) { return new Promise((resolve) => { if (this.isBinaryFile(filePath)) { return resolve(0) // 二进制文件直接跳过 } let lineCount = 0; const stream = fs.createReadStream(filePath, 'utf8') stream.on("data", (chunk) => { lineCount += chunk.toString().split("\n").length }); stream.on("end", () => resolve(lineCount)) stream.on("error", () => resolve(0)) // 读取错误默认返回 0 }) } async onRequest(req, res) { let { uuid, session, id } = req.query if ([uuid, session, id].some(value => value === '' || value === null || value === undefined)) return res.json({ ...BaseStdResponse.MISSING_PARAMETER }) // 检查 session if (!await AccessControl.checkSession(uuid, session)) return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED }) let sql = 'SELECT state, path FROM repos WHERE create_user = ? AND id = ?' let r = await db.query(sql, [uuid, id]) if (!r || r.length === 0) return res.json({ ...BaseStdResponse.ERR, msg: '未找到仓库' }) if (r[0].state !== 1 || !r[0].path) return res.json({ ...BaseStdResponse.ERR, msg: '仓库未成功克隆!' }) let totalLines = 0 let languageStats = {} try { const git = simpleGit(r[0].path) const files = await git.raw(["ls-files"]) // 获取所有 Git 跟踪的文件 const fileList = files.split("\n").map(f => f.trim()).filter(f => f) for (const file of fileList) { const fullPath = path.join(r[0].path, file) const ext = path.extname(file) const language = Object.keys(this.languageExtensions).find(lang => this.languageExtensions[lang].includes(ext) ) if (language) { const lines = await countLines(fullPath) totalLines += lines languageStats[language] = (languageStats[language] || 0) + lines } else { // 如果文件不属于任何已知语言,归类为其他 const lines = await countLines(fullPath) totalLines += lines languageStats["Other"] = (languageStats["Other"] || 0) + lines } } } catch (error) { return res.json({ ...BaseStdResponse.ERR, msg: '获取仓库信息失败!' }) } res.json({ ...BaseStdResponse.OK, totalLines, languageStats }) } } module.exports.gitCodeStats = gitCodeStats