#1 合并dev

Merged
Pchen0 merged 60 commits from gitnexus_team/dev into gitnexus_team/master 1 month ago

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 node_modules/
 *.log
+config.json
 uploads/*
 !uploads/.gitkeep

+ 74 - 0
apis/AI/AIChat.js

@@ -0,0 +1,74 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const Redis = require('../../plugin/DataBase/Redis')
+const { v4: uuidv4 } = require('uuid')
+const axios = require("axios")
+const { core_url } = require("../../config.json")
+
+class AIChat extends API {
+    constructor() {
+        super()
+        this.setMethod("POST")
+        this.setPath("/AI/Chat")
+    }
+
+    async onRequest(req, res) {
+        const { uuid, session, message } = req.body
+
+        // 参数校验
+        if (!uuid || !session || !message)
+            return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
+
+
+        // session 校验
+        const sessionValid = await AccessControl.checkSession(uuid, session)
+        if (!sessionValid) {
+            return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED })
+        }
+
+        const userTime = new Date().getTime() // 用户消息发送时间
+        const msgid = uuidv4()
+
+        res.json({
+            ...BaseStdResponse.OK,
+            id: msgid
+        })
+
+        try {
+            const endpoint = `${core_url}/ai/chat`
+            const aiResponse = await axios.post(endpoint, { uuid, message })
+
+            const aiData = aiResponse?.data?.msg
+            if (!aiData)
+                throw new Error("AI响应格式错误")
+
+            const aiTime = new Date().getTime()
+
+            await Redis.set(`message:${msgid}`, aiData, {
+                EX: 600
+            })
+
+            // 异步写入数据库
+            const insertUserMessage = db.query(
+                'INSERT INTO messages (uuid, content, time, type) VALUES (?, ?, ?, "user")',
+                [uuid, message, userTime]
+            )
+
+            const insertAIMessage = db.query(
+                'INSERT INTO messages (uuid, content, time, type) VALUES (?, ?, ?, "ai")',
+                [uuid, aiData, aiTime]
+            )
+
+            await Promise.all([insertUserMessage, insertAIMessage])
+        } catch (error) {
+            this.logger.error("消息发送失败:" + error.stack)
+            await Redis.set(`message:${msgid}`, '消息发送失败,请稍后再试', {
+                EX: 600
+            })
+        }
+    }
+}
+
+module.exports.AIChat = AIChat

+ 37 - 0
apis/AI/DeleteAIChatMessages.js

@@ -0,0 +1,37 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class DeleteAIChatMessages extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/DeleteAIChatMessages')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session } = req.query
+        if ([uuid, session].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 = 'DELETE FROM messages WHERE uuid = ?'
+        await db.query(sql, [uuid])
+
+        res.json({
+            ...BaseStdResponse.OK
+        })
+
+    }
+}
+
+module.exports.DeleteAIChatMessages = DeleteAIChatMessages

+ 38 - 0
apis/AI/GetAIChatMessage.js

@@ -0,0 +1,38 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const Redis = require('../../plugin/DataBase/Redis')
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class GetAIChatMessage extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetAIChatMessage')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id } = req.query
+        if ([uuid, session].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
+            })
+
+        const data = await Redis.get(`message:${id}`)
+       
+        res.json({
+            ...BaseStdResponse.OK,
+            data
+        })
+
+    }
+}
+
+module.exports.GetAIChatMessage = GetAIChatMessage

+ 44 - 0
apis/AI/GetAIChatMessages.js

@@ -0,0 +1,44 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class GetAIChatMessages extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetAIChatMessages')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session } = req.query
+        if ([uuid, session].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 content, time, type, \`read\` FROM messages WHERE uuid = ?'
+        let rows = await db.query(sql, [uuid])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+
+    }
+}
+
+module.exports.GetAIChatMessages = GetAIChatMessages

+ 44 - 0
apis/AI/GetCommitSummary.js

@@ -0,0 +1,44 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class GetCommitSummary extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetCommitSummary')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let {uuid, session, id, hash } = req.query
+        if ([uuid, session, id, hash].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 create_time, start_time, end_time, result, repo_hash FROM commit_summary_tasks WHERE create_user = ? AND repo_id = ? AND repo_hash = ?'
+        let rows = await db.query(sql, [uuid, id, hash])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+
+    }
+}
+
+module.exports.GetCommitSummary = GetCommitSummary

+ 61 - 0
apis/AI/GetFileSummary.js

@@ -0,0 +1,61 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const path = require('path')
+
+class GetFileSummary extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetFileSummary')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, hash, filePath } = req.query
+        if ([uuid, session, id, hash, filePath].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 rows = await db.query(sql, [uuid, id])
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '未找到仓库'
+            })
+
+        if (rows[0].state !== 1 || !rows[0].path)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库未成功克隆!'
+            })
+
+        filePath = path.join(rows[0].path, filePath)
+
+        sql = 'SELECT create_time, start_time, end_time, result, repo_hash FROM file_summary_tasks WHERE create_user = ? AND repo_id = ? AND repo_hash = ? AND filepath = ?'
+        rows = await db.query(sql, [uuid, id, hash, filePath])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+
+    }
+}
+
+module.exports.GetFileSummary = GetFileSummary

+ 44 - 0
apis/AI/GetScanTaskDetail.js

@@ -0,0 +1,44 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class GetScanTaskDetail extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetScanTaskDetail')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, task_id } = req.query
+        if ([uuid, session, id, task_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 id, create_time, scan_start_time, scan_end_time, result, repo_hash, state, repo_id, type, source FROM scan_tasks WHERE create_user = ? AND repo_id = ? AND id  = ? ORDER BY id DESC'
+        let rows = await db.query(sql, [uuid, id, task_id])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+
+    }
+}
+
+module.exports.GetScanTaskDetail = GetScanTaskDetail

+ 44 - 0
apis/AI/GetScanTaskList.js

@@ -0,0 +1,44 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+
+class GetScanTaskList extends API {
+    constructor() {
+        super()
+
+        this.setPath('/AI/GetScanTaskList')
+        this.setMethod('GET')
+    }
+
+    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 id, create_time, scan_start_time, scan_end_time, result, repo_hash, state, repo_id, type, source FROM scan_tasks WHERE create_user = ? AND repo_id = ? ORDER BY id DESC'
+        let rows = await db.query(sql, [uuid, id])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+
+    }
+}
+
+module.exports.GetScanTaskList = GetScanTaskList

+ 72 - 0
apis/AI/ScanRepo.js

@@ -0,0 +1,72 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const axios = require('axios')
+const simpleGit = require('simple-git')
+const { core_url } = require('../../config.json')
+
+class ScanRepo extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/AI/ScanRepo")
+    }
+
+    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, url FROM repos WHERE create_user = ? AND id = ?'
+        let rows = await db.query(sql, [uuid, id])
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '未找到仓库'
+            })
+
+        if (rows[0].state !== 1 || !rows[0].path)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库未成功克隆!'
+            })
+
+        try {
+            const git = simpleGit()
+            await git.cwd(rows[0].path)
+            const hash = await git.revparse(['HEAD'])
+            const time = new Date().getTime()
+
+            sql = 'INSERT INTO scan_tasks (repo_id, create_time, create_user, repo_hash, source) VALUES (?, ?, ?, ?, ?)'
+            let r = await db.query(sql, [id, time, uuid, hash, 'hand'])
+            if(!r || r.affectedRows !== 1)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '扫描任务添加失败!'
+                })
+
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+            let endpoint = core_url + '/ai/scan'
+            await axios.post(endpoint, { uuid, repo_url: rows[0].url, task_id: String(r.insertId), repo_id: id })
+        } catch (error) {
+            this.logger.error('获取仓库历史失败!' + error.stack)
+        }
+
+    }
+}
+
+module.exports.ScanRepo = ScanRepo

+ 67 - 0
apis/AI/SummaryCommit.js

@@ -0,0 +1,67 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const axios = require('axios')
+const { core_url } = require('../../config.json')
+
+class SummaryCommit extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/AI/SummaryCommit")
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, hash } = req.query
+
+        if ([uuid, session, id, hash].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, url FROM repos WHERE create_user = ? AND id = ?'
+        let rows = await db.query(sql, [uuid, id])
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '未找到仓库'
+            })
+
+        if (rows[0].state !== 1 || !rows[0].path)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库未成功克隆!'
+            })
+
+        try {
+            const time = new Date().getTime()
+            sql = 'INSERT INTO commit_summary_tasks (repo_id, create_time, create_user, repo_hash) VALUES (?, ?, ?, ?)'
+            let r = await db.query(sql, [id, time, uuid, hash])
+
+            if (!r || r.affectedRows !== 1)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '扫描任务添加失败!'
+                })
+
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+            let endpoint = core_url + '/ai/summaryCommit'
+            await axios.post(endpoint, { uuid, repo_url: rows[0].url, task_id: String(r.insertId), repo_id: id })
+        } catch (error) {
+            this.logger.error('添加AI分析任务失败!' + error.stack)
+        }
+    }
+}
+
+module.exports.SummaryCommit = SummaryCommit

+ 202 - 0
apis/AI/SummaryFile.js

@@ -0,0 +1,202 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const axios = require('axios')
+const { core_url } = require('../../config.json')
+const path = require('path')
+const { isBinaryFileSync } = require('isbinaryfile')
+
+class SummaryFile extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/AI/SummaryFile")
+    }
+
+    // 定义支持的语言后缀
+    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"],
+        "Assembly": [".asm", ".s"],
+        "Objective-C": [".m", ".mm"],
+        "F#": [".fs", ".fsi", ".fsx", ".fsscript"],
+        "Haskell": [".hs", ".lhs"],
+        "OCaml": [".ml", ".mli"],
+        "Nim": [".nim", ".nims"],
+        "Julia": [".jl"],
+        "Fortran": [".f", ".for", ".f90", ".f95"],
+        "COBOL": [".cbl", ".cob"],
+        "VHDL": [".vhd", ".vhdl"],
+        "Verilog": [".v", ".vh"],
+        "Ada": [".adb", ".ads"],
+        "Matlab": [".m"],  // 注意 Objective-C 也使用 .m,可根据上下文判断
+        "Scala": [".scala"],
+        "VB.NET": [".vb"],
+        "Groovy": [".groovy", ".gvy", ".gy", ".gsh"],
+        "Makefile": ["Makefile", "makefile", ".mk"],
+        "Clojure": [".clj", ".cljs", ".cljc", ".edn"],
+        "Common Lisp": [".lisp", ".lsp"],
+        "Scheme": [".scm", ".ss"],
+        "Prolog": [".pl", ".pro", ".P"],
+        "Smalltalk": [".st", ".squeak"],
+        "Tcl": [".tcl"],
+        "Crystal": [".cr"],
+        "Solidity": [".sol"],
+        "Vim script": [".vim"],
+        "ReScript": [".res", ".resi"],
+        "ReasonML": [".re", ".rei"],
+        "Pug": [".pug", ".jade"],
+        "Handlebars": [".hbs", ".handlebars"],
+        "XML": [".xml"],
+        "INI": [".ini", ".cfg", ".conf"],
+        "Log": [".log"],
+        "ENV": [".env"],
+        "ReStructuredText": [".rst"],
+        "AsciiDoc": [".adoc", ".asciidoc"],
+        "Racket": [".rkt"],
+        "Zig": [".zig"],
+        "Haxe": [".hx"],
+        "Dotenv": [".env"],
+        "Config": [".config"],
+        "PlantUML": [".puml", ".plantuml"],
+        "Visual Basic": [".bas", ".vbs"],
+        "Batch": [".bat", ".cmd"],
+        "BibTeX": [".bib"],
+        "TeX/LaTeX": [".tex", ".ltx", ".sty", ".cls"],
+        "Apache": [".htaccess", "httpd.conf"],
+        "NGINX": ["nginx.conf"],
+        "Terraform": [".tf", ".tfvars"],
+        "HCL": [".hcl"],
+        "QML": [".qml"],
+        "Cue": [".cue"],
+        "GDScript": [".gd"], // 用于 Godot 引擎
+        "ANTLR": [".g4"],
+        "Pascal": [".pas"],
+        "Logtalk": [".lgt"],
+        "Awk": [".awk"],
+        "Sed": [".sed"],
+        "ConfigScript": [".conf", ".cfg"],
+        "YANG": [".yang"],
+        "NetLogo": [".nlogo"]
+    }
+
+    isBinaryFile(filePath) {
+        try {
+            return isBinaryFileSync(filePath)
+        } catch (err) {
+            return false
+        }
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, hash, filePath } = req.query
+
+        if ([uuid, session, id, hash, filePath].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
+            })
+
+        const ext = path.extname(filePath).toLowerCase()
+        const language = Object.keys(this.languageExtensions).find(lang =>
+            this.languageExtensions[lang].includes(ext)
+        )
+        if (!language)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '该文件类型不支持AI分析!'
+            })
+
+        let sql = 'SELECT state, path, url FROM repos WHERE create_user = ? AND id = ?'
+        let rows = await db.query(sql, [uuid, id])
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '未找到仓库'
+            })
+
+        if (rows[0].state !== 1 || !rows[0].path)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库未成功克隆!'
+            })
+
+        try {
+            filePath = path.join(rows[0].path, filePath)
+
+            if (this.isBinaryFile(filePath))
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '该文件类型不支持AI总结!'
+                })
+
+            const time = new Date().getTime()
+            sql = 'INSERT INTO file_summary_tasks (repo_id, create_time, create_user, repo_hash, filepath) VALUES (?, ?, ?, ?, ?)'
+            let r = await db.query(sql, [id, time, uuid, hash, filePath])
+
+            if (!r || r.affectedRows !== 1)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '扫描任务添加失败!'
+                })
+
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+            let endpoint = core_url + '/ai/summaryFile'
+            await axios.post(endpoint, { uuid, repo_url: rows[0].url, task_id: String(r.insertId), file_path: filePath, repo_id: id })
+        } catch (error) {
+            this.logger.error('添加AI分析任务失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '添加AI分析任务失败!'
+            })
+        }
+    }
+}
+
+module.exports.SummaryFile = SummaryFile

+ 1 - 1
apis/Captcha/ImageCaptcha.js

@@ -18,7 +18,7 @@ class ImageCaptcha extends API {
             size: 4, // 4个字母
             noise: 2, // 干扰线2条
             color: true, 
-            background: "#666",
+            background: "#444",
         }
         const captcha = svgCaptcha.create(options) //字母和数字随机验证码
         let { text, data } = captcha

+ 22 - 39
apis/Captcha/SendEmail.js

@@ -1,70 +1,50 @@
-const API = require("../../lib/API");
-const { BaseStdResponse } = require("../../BaseStdResponse");
-const Redis = require('../../plugin/DataBase/Redis');
-const sendEmail = require('../../plugin/Email/Email');
+const API = require("../../lib/API")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const Redis = require('../../plugin/DataBase/Redis')
+const EmailTemplate = require('../../plugin/Email/emailTemplate')
 
 // 发送邮箱验证码
 class SendEmail extends API {
     constructor() {
-        super();
+        super()
 
-        this.setMethod("POST");
-        this.setPath("/Captcha/SendEmail");
+        this.setMethod("POST")
+        this.setPath("/Captcha/SendEmail")
     }
 
     async onRequest(req, res) {
-        const { email, text, id, type } = req.body;
+        const { email, text, id, type } = req.body
 
-        if ([email, text, id, type].some(value => value === '' || value === null || value === undefined)) {
-            res.json({
+        if ([email, text, id, type].some(value => value === '' || value === null || value === undefined))
+            return res.json({
                 ...BaseStdResponse.MISSING_PARAMETER,
                 endpoint: 1513126
-            });
-            return;
-        }
-
+            })
+            
         try {
-            const code = await Redis.get(`captcha:${id}`);
+            const code = await Redis.get(`captcha:${id}`)
             if (!code || code != text.toLowerCase())
                 return res.json({
                     ...BaseStdResponse.SMS_CHECK_FAIL,
                     msg: '验证码输入错误或已过期'
                 })
 
-            await Redis.del(`captcha:${id}`);
+            await Redis.del(`captcha:${id}`)
         } catch (err) {
-            this.logger.error(`验证图片验证码失败!${err.stack}`);
+            this.logger.error(`验证图片验证码失败!${err.stack}`)
             return res.json({
                 ...BaseStdResponse.DATABASE_ERR,
                 msg: '验证失败!'
             })
         }
 
-        let content;
-        switch (type) {
-            case 'register':
-                content = '您正在注册CTBU_CLUB账号,';
-                break;
-            case 'forget':
-                content = '您正在找回CTBU_CLUB账号,';
-                break;
-            case 'bind':
-                content = '您正在进行换绑邮箱操作,';
-                break;
-            default:
-                return res.json({
-                    ...BaseStdResponse.METHOD_NOT_EXIST
-                })
-        }
-
-        const code = Math.random().toFixed(6).slice(-6);
+        const code = Math.random().toFixed(6).slice(-6)
         try {
             await Redis.set(`email:${email}`, code, {
                 EX: 600
-            });
-            await sendEmail(email, '验证码', `${content}您的验证码为:${code}。此验证码10分钟内有效,请妥善保管您的验证码,非本人操作请忽略。`);
+            })
         } catch (err) {
-            this.logger.error(`发送邮箱验证码失败!${err.stack}`);
+            this.logger.error(`发送邮箱验证码失败!${err.stack}`)
             return res.json({
                 ...BaseStdResponse.SMS_SEND_FAIL,
                 msg: '请检查邮箱格式后再试!'
@@ -74,7 +54,10 @@ class SendEmail extends API {
         res.json({
             ...BaseStdResponse.OK
         })
+
+        // 先返回后发送
+        EmailTemplate.checkEmail(email, code)
     }
 }
 
-module.exports.SendEmail = SendEmail;
+module.exports.SendEmail = SendEmail

+ 76 - 0
apis/GitActions/ChangeBranch.js

@@ -0,0 +1,76 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const simpleGit = require('simple-git')
+
+class ChangeBranch extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/GitActions/ChangeBranch")
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, branch } = req.query
+
+        if ([uuid, session, id, branch ].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, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            const redisKey = [`gitLogs:${r[0].path}`,`contributors:${r[0].path}`,`codeStats:${r[0].path}`]
+            await redis.del(redisKey)
+
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+
+            const branches = await git.branch()
+            const remoteBranch = 'remotes/origin/' + branch
+            if(!branches.all.includes(branch) && !branches.all.includes(remoteBranch))
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '分支不存在!'
+                })
+
+            await git.checkout(branch)
+            
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+        } catch (error) {
+            this.logger.error('切换仓库分支失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '切换仓库分支失败!'
+            })
+        }
+
+    }
+}
+
+module.exports.ChangeBranch = ChangeBranch

+ 70 - 0
apis/GitActions/GitPull.js

@@ -0,0 +1,70 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const simpleGit = require('simple-git')
+
+class GitPull extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/GitActions/GitPull")
+    }
+
+    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, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            const redisKey = [`gitLogs:${r[0].path}`,`contributors:${r[0].path}`,`codeStats:${r[0].path}`]
+            await redis.del(redisKey)
+
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+            await git.pull()
+
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+            const time = new Date().getTime()
+            sql = 'UPDATE repos SET update_time = ? WHERE create_user = ? AND id = ?'
+            await db.query(sql, [time, uuid, id])
+        } catch (error) {
+            this.logger.error('拉取更新失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '拉取更新失败!'
+            })
+        }
+
+    }
+}
+
+module.exports.GitPull = GitPull

+ 74 - 0
apis/Repos/AddRepo.js

@@ -0,0 +1,74 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const axios = require('axios')
+const { core_url } = require('../../config.json')
+
+// 添加仓库
+class AddRepo extends API {
+    constructor() {
+        super()
+
+        this.setMethod("POST")
+        this.setPath("/Repos")
+    }
+
+    checkName(name) {
+        const regex = /^(?!\.)(?!.*\/{2,})(?!.*\.git$)[\u4E00-\u9FA5A-Za-z0-9._-]+(?<!\/)$/
+        return regex.test(name)
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, name, url } = req.body
+
+        if ([uuid, session, name, url].some(value => value === '' || value === null || value === undefined))
+            return res.json({
+                ...BaseStdResponse.MISSING_PARAMETER
+            })
+
+        if (!this.checkName(name))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库名称不合法!'
+            })
+
+        // 检查 session
+        if (!await AccessControl.checkSession(uuid, session))
+            return res.status(401).json({
+                ...BaseStdResponse.ACCESS_DENIED
+            })
+
+        // 检查仓库是否重复
+        let sql = 'SELECT id FROM repos WHERE create_user = ? AND url = ?'
+        let r = await db.query(sql, [uuid, url])
+        if (r && r.length > 0)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '用户已添加该仓库!'
+            })
+
+        const time = new Date().getTime()
+        sql = 'INSERT INTO repos (name, url, create_user, create_time) VALUES (?, ?, ?, ?)'
+
+        let result = await db.query(sql, [name, url, uuid, time])
+
+        if (result && result.affectedRows > 0) {
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+            // 发起克隆
+            try {
+                const clone_url = core_url + '/git/clone'
+                await axios.post(clone_url, { uuid, repo_url: url, repo_id: result.insertId })
+            } catch (error) {
+                this.logger.error('克隆仓库失败!' + error.stack)
+            }
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '添加仓库失败!' })
+        }
+    }
+}
+
+module.exports.AddRepo = AddRepo

+ 58 - 0
apis/Repos/DeleteRepo.js

@@ -0,0 +1,58 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const fs = require('fs')
+
+// 删除仓库
+class DeleteRepo extends API {
+    constructor() {
+        super()
+
+        this.setMethod("DELETE")
+        this.setPath("/Repos")
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id } = req.body
+
+        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 path FROM repos WHRER \`create_user\` = ? AND id = ?'
+        let r = await db.query(sql, [uuid, id])
+
+        if (r && r[0].path) {
+            const redisKey = [`gitLogs:${r[0].path}`,`contributors:${r[0].path}`,`codeStats:${r[0].path}`]
+            await redis.del(redisKey)
+
+            fs.rm(r[0].path, { recursive: true, force: true }, (err) => { 
+                this.logger.error('删除仓库失败!' + err)
+                res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '删除仓库失败!' })
+            })
+        }
+            
+        sql = 'DELETE FROM repos WHERE create_user = ? AND id = ?'
+
+        let result = await db.query(sql, [uuid, id])
+
+        if (result && result.affectedRows > 0) {
+            res.json({
+                ...BaseStdResponse.OK
+            })
+
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '删除仓库失败!' })
+        }
+    }
+}
+
+module.exports.DeleteRepo = DeleteRepo

+ 118 - 0
apis/Repos/GetCommitDetail.js

@@ -0,0 +1,118 @@
+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')
+
+class GetCommitDetail extends API {
+    constructor() {
+        super()
+        this.setMethod("GET")
+        this.setPath("/Repos/GetCommitDetail")
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, hash } = req.query
+
+        if ([uuid, session, id, hash].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: '仓库未成功克隆!'
+            })
+
+        try {
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+
+            const showCommitDetails = (hash) => {
+                return new Promise((resolve, reject) => {
+                    git.show([hash], (err, result) => {
+                        if (err) reject(err)
+                        resolve(result)
+                    })
+                })
+            }
+
+            const result = await showCommitDetails(hash)
+
+            // 更新正则表达式,捕获多行 commit message(包括 body)
+            const commitRegex = /^commit (\w+)\nAuthor: (.+) <(.+)>\nDate:\s+(.+)\n\n([\s\S]+?)(?=\ndiff --git|$)/
+            const match = result.match(commitRegex)
+            if (!match)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '解析提交详情失败!'
+                })
+
+            // 拆分 message 和 body
+            const fullMessage = match[5].trim().split("\n")
+            const message = fullMessage[0] // 第一行短描述
+            const body = fullMessage.slice(1).join("\n").trim() // 多行 body
+
+            const commitInfo = {
+                hash: match[1],
+                author: match[2],
+                email: match[3],
+                date: match[4],
+                message,
+                body,
+                files: [],
+                diffs: []
+            }
+
+            // 检查是否为合并提交
+            const isMergeCommit = message.startsWith('Merge')
+
+            if (isMergeCommit) {
+                // 合并提交处理:如果是合并提交,直接返回合并信息
+                commitInfo.mergeInfo = {
+                    message: message,  // 这里返回合并提交的信息
+                    body: body         // 可能包含合并的详情
+                }
+            } else {
+                // 解析 diff 信息
+                const diffRegex = /diff --git a\/(.+?) b\/.+?\nindex .+\n--- a\/.+?\n\+\+\+ b\/.+?\n([\s\S]+?)(?=(diff --git a\/|$))/g
+                let diffMatch
+                while ((diffMatch = diffRegex.exec(result)) !== null) {
+                    commitInfo.files.push(diffMatch[1])
+                    commitInfo.diffs.push({
+                        file: diffMatch[1],
+                        changes: diffMatch[2].trim().split('\n')
+                    })
+                }
+            }
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: commitInfo
+            })
+        } catch (error) {
+            this.logger.error('获取提交详情失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取提交详情失败!'
+            })
+        }
+    }
+}
+
+module.exports.GetCommitDetail = GetCommitDetail

+ 94 - 0
apis/Repos/GetFileDetail.js

@@ -0,0 +1,94 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const path = require('path')
+const { isBinaryFileSync } = require('isbinaryfile')
+const simpleGit = require('simple-git')
+
+class GetFileDetail extends API {
+    constructor() {
+        super()
+        this.setMethod("GET")
+        this.setPath("/Repos/GetFileDetail")
+    }
+
+    isBinaryFile(filePath) {
+        try {
+            return isBinaryFileSync(filePath)
+        } catch (err) {
+            return false
+        }
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, id, filePath, hash } = req.query
+
+        if ([uuid, session, id, filePath].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 name, logo, create_time, update_time, state, path, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+            const targetHash = hash || 'HEAD'
+            const log = await git.log({
+                file: filePath,
+                n: 1,
+                format: {
+                    hash: '%H',
+                    abbrevHash: '%h',
+                    title: '%s',
+                    message: '%B',
+                    author_name: '%aN',
+                    email: '%aE',
+                    date: '%ad'
+                }
+            })
+
+            if (this.isBinaryFile(path.join(r[0].path, filePath)))
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    type: 'binary',
+                    commit: log
+                })
+
+            const content = await git.show([`${targetHash}:${filePath}`])
+            res.json({
+                ...BaseStdResponse.OK,
+                type: 'text',
+                commit: log,
+                content: btoa(encodeURI(content))
+            })
+        } catch (error) {
+            this.logger.error('获取文件详情失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取文件详情失败!'
+            })
+        }
+    }
+}
+
+module.exports.GetFileDetail = GetFileDetail

+ 109 - 0
apis/Repos/GetFiles.js

@@ -0,0 +1,109 @@
+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')
+
+class GetFiles extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/Repos/GetFiles")
+    }
+
+    buildTreeStructure(paths) {
+        const root = []
+
+        paths.forEach(filePath => {
+            const parts = filePath.split('/')
+            let currentLevel = root
+
+            parts.forEach((part, index) => {
+                const existingNode = currentLevel.find(node => node.title === part)
+
+                // 如果存在节点,则继续深入
+                if (existingNode) {
+                    currentLevel = existingNode.children
+                } else {
+                    // 判断是否为文件
+                    const isFile = index === parts.length - 1
+
+                    // 新增节点
+                    const newNode = {
+                        title: part,
+                        key: filePath,
+                        type: isFile ? 'file' : 'folder',
+                        children: []
+                    }
+
+                    // 判断是否为叶子节点
+                    if (index === parts.length - 1) {
+                        newNode.icon = () => h(IconDriveFile)
+                        newNode.children = undefined
+                    }
+
+                    currentLevel.push(newNode)
+                    currentLevel = newNode.children
+                }
+            })
+        })
+
+        return root
+    }
+
+    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 name, logo, create_time, update_time, state, path, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+            const treeResult = await git.raw(['ls-tree', '-r', '--name-only', 'HEAD'])
+
+            const filePaths = treeResult.split('\n').filter(path => path) // 去掉空行
+
+            // 构建树结构
+            const treeStructure = this.buildTreeStructure(filePaths)
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: treeStructure
+            })
+
+        } catch (error) {
+            this.logger.error('获取文件列表失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取文件列表失败!'
+            })
+        }
+
+    }
+}
+
+module.exports.GetFiles = GetFiles

+ 77 - 0
apis/Repos/GetRepoList.js

@@ -0,0 +1,77 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const axios = require("axios")
+const { core_url } = require('../../config.json')
+
+class GetRepoList extends API {
+    constructor() {
+        super()
+
+        this.setPath('/Repos/List')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let {
+            uuid,
+            session,
+            keyword
+        } = req.query
+
+        if ([uuid, session].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 id, name, url, create_time, state, logo, update_time FROM repos WHERE create_user = ?'
+        let params = [uuid]
+
+        if ((keyword ?? '').trim() !== '') {
+            sql += ' AND name LIKE ?'
+            params.push(`%${keyword}%`)
+        }
+
+        sql += ' ORDER BY update_time DESC'
+
+        let rows = await db.query(sql, params)
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: []
+            })
+
+        const status_url = core_url + '/git/status'
+
+        try {
+            // 并行执行所有请求
+            const statusPromises = rows.map(item =>
+                axios.post(status_url, { uuid, repo_url: item.url, repo_id: item.id })
+                    .then(res => ({ ...item, status: res.data }))
+                    .catch(() => ({ ...item, status: null }))
+            )
+
+            const items = await Promise.all(statusPromises)
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: items
+            })
+        } catch (error) {
+            res.json({
+                ...BaseStdResponse.ERR,
+                message: '获取仓库状态失败'
+            })
+        }
+    }
+}
+
+module.exports.GetRepoList = GetRepoList

+ 103 - 0
apis/Repos/GetRepoLog.js

@@ -0,0 +1,103 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const simpleGit = require('simple-git')
+
+class GetRepoLog extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/Repos/Log")
+    }
+
+    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, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            let rawLogs
+            const redisKey = `gitLogs:${r[0].path}`
+            const cachedLogs = await redis.get(redisKey)
+
+            if (cachedLogs)
+                rawLogs = JSON.parse(cachedLogs)
+            else {
+                const git = simpleGit()
+                await git.cwd(r[0].path)
+
+                rawLogs = await git.raw([
+                    "log",
+                    "--no-merges",
+                    "--pretty=format:%H|%an|%ae|%ad|%s",
+                    "--shortstat"
+                ])
+
+                redis.set(redisKey, JSON.stringify(rawLogs), {
+                    EX: 172800
+                })
+            }
+
+            const lines = rawLogs.split("\n")
+            const commits = []
+            let current = null
+
+            for (let i = 0; i < lines.length; i++) {
+                const line = lines[i].trim()
+
+                // 提交信息行
+                if (line.includes("|") && !line.includes("file changed")) {
+                    const [hash, name, email, date, ...messageParts] = line.split("|")
+
+                    current = {
+                        hash,
+                        name,
+                        email,
+                        date,
+                        message: messageParts.join("|").trim()
+                    }
+
+                    commits.push(current)
+                }
+            }
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: commits
+            })
+        } catch (err) {
+            res.json({
+                ...BaseStdResponse.ERR,
+                message: "Git 日志分析失败"
+            });
+        }
+    }
+}
+
+module.exports.GetRepoLog = GetRepoLog

+ 86 - 0
apis/Repos/GetRepoStatus.js

@@ -0,0 +1,86 @@
+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')
+
+class GetRepoStatus extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/Repos/Status")
+    }
+
+    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 name, logo, create_time, update_time, state, path, url 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: '仓库未成功克隆!'
+            })
+
+        try {
+            const git = simpleGit()
+            await git.cwd(r[0].path)
+            let status = await git.status()
+            const branches = await git.branch()
+            const remoteBranches = Object.keys(branches.branches)
+                .filter(branch => branch.startsWith('remotes/origin/'))
+                .reduce((acc, branch) => {
+                    const newBranchName = branch.replace('remotes/origin/', '')
+                    acc[newBranchName] = {
+                        ...branches.branches[branch], 
+                        name: newBranchName
+                    };
+                    return acc
+                }, {})
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: {
+                    status,
+                    branches: remoteBranches,
+                    info: {
+                        name: r[0].name,
+                        url: r[0].url,
+                        logo: r[0].logo,
+                        create_time: r[0].create_time,
+                        update_time: r[0].update_time,
+                        state: r[0].state
+                    },
+                }
+            })
+
+        } catch (error) {
+            this.logger.error('获取仓库状态失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取仓库状态失败!'
+            })
+        }
+
+    }
+}
+
+module.exports.GetRepoStatus = GetRepoStatus

+ 229 - 0
apis/Repos/GitCodeStats.js

@@ -0,0 +1,229 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const simpleGit = require('simple-git')
+const fs = require("fs")
+const path = require("path")
+const { isBinaryFileSync } = require('isbinaryfile')
+
+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"],  // 文本文件
+        "Assembly": [".asm", ".s"],
+        "Objective-C": [".m", ".mm"],
+        "F#": [".fs", ".fsi", ".fsx", ".fsscript"],
+        "Haskell": [".hs", ".lhs"],
+        "OCaml": [".ml", ".mli"],
+        "Nim": [".nim", ".nims"],
+        "Julia": [".jl"],
+        "Fortran": [".f", ".for", ".f90", ".f95"],
+        "COBOL": [".cbl", ".cob"],
+        "VHDL": [".vhd", ".vhdl"],
+        "Verilog": [".v", ".vh"],
+        "Ada": [".adb", ".ads"],
+        "Matlab": [".m"],  // 注意 Objective-C 也使用 .m,可根据上下文判断
+        "Scala": [".scala"],
+        "VB.NET": [".vb"],
+        "Groovy": [".groovy", ".gvy", ".gy", ".gsh"],
+        "Makefile": ["Makefile", "makefile", ".mk"],
+        "Clojure": [".clj", ".cljs", ".cljc", ".edn"],
+        "Common Lisp": [".lisp", ".lsp"],
+        "Scheme": [".scm", ".ss"],
+        "Prolog": [".pl", ".pro", ".P"],
+        "Smalltalk": [".st", ".squeak"],
+        "Tcl": [".tcl"],
+        "Crystal": [".cr"],
+        "Solidity": [".sol"],
+        "Vim script": [".vim"],
+        "ReScript": [".res", ".resi"],
+        "ReasonML": [".re", ".rei"],
+        "Pug": [".pug", ".jade"],
+        "Handlebars": [".hbs", ".handlebars"],
+        "XML": [".xml"],
+        "INI": [".ini", ".cfg", ".conf"],
+        "Log": [".log"],
+        "ENV": [".env"],
+        "ReStructuredText": [".rst"],
+        "AsciiDoc": [".adoc", ".asciidoc"],
+        "Racket": [".rkt"],
+        "Zig": [".zig"],
+        "Haxe": [".hx"],
+        "Dotenv": [".env"],
+        "Config": [".config"],
+        "PlantUML": [".puml", ".plantuml"],
+        "Visual Basic": [".bas", ".vbs"],
+        "Batch": [".bat", ".cmd"],
+        "BibTeX": [".bib"],
+        "TeX/LaTeX": [".tex", ".ltx", ".sty", ".cls"],
+        "Apache": [".htaccess", "httpd.conf"],
+        "NGINX": ["nginx.conf"],
+        "Terraform": [".tf", ".tfvars"],
+        "HCL": [".hcl"],
+        "QML": [".qml"],
+        "Cue": [".cue"],
+        "GDScript": [".gd"], // 用于 Godot 引擎
+        "ANTLR": [".g4"],
+        "Pascal": [".pas"],
+        "Logtalk": [".lgt"],
+        "Awk": [".awk"],
+        "Sed": [".sed"],
+        "ConfigScript": [".conf", ".cfg"],
+        "YANG": [".yang"],
+        "NetLogo": [".nlogo"],
+        "Other": [".bin", ".dat", ".exe", ".dll", ".obj", ".so", ".class"] // 二进制及无法识别的文件
+    }
+
+    // 判断文件是否为二进制文件
+    isBinaryFile(filePath) {
+        try {
+            return isBinaryFileSync(filePath)
+        } catch (err) {
+            return false
+        }
+    }
+
+    // 统计单个文件的行数
+    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: '仓库未成功克隆!'
+            })
+
+        const cachedCodeStats = await redis.get(`codeStats:${r[0].path}`)
+        if(cachedCodeStats)
+            return res.json({
+                ...BaseStdResponse.OK,
+                data: JSON.parse(cachedCodeStats)
+            })
+
+        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 this.countLines(fullPath)
+                    totalLines += lines
+                    languageStats[language] = (languageStats[language] || 0) + lines
+                } else {
+                    // 如果文件不属于任何已知语言,归类为其他
+                    const lines = await this.countLines(fullPath)
+                    totalLines += lines
+                    languageStats["Other"] = (languageStats["Other"] || 0) + lines
+                }
+            }
+        } catch (error) {
+            this.logger.error('获取仓库代码信息失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取仓库代码信息失败!'
+            })
+        }
+
+        const data = { totalLines, languageStats }
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data
+        })
+        redis.set(`codeStats:${r[0].path}`, JSON.stringify(data), {
+            EX: 172800
+        })
+    }
+}
+
+module.exports.gitCodeStats = gitCodeStats

+ 153 - 0
apis/Repos/GitContributors.js

@@ -0,0 +1,153 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+const redis = require('../../plugin/DataBase/Redis')
+const simpleGit = require('simple-git')
+
+class GitContributors extends API {
+    constructor() {
+        super()
+
+        this.setMethod("GET")
+        this.setPath("/Repos/Contributors")
+    }
+
+    async analyzeGitContributors(repoPath) {
+        try {
+            // 检查 Redis 中是否已存在缓存日志
+            const redisKey = `gitLogs:${repoPath}`
+            const cachedLogs = await redis.get(redisKey)
+
+            let logs
+            if (cachedLogs)
+                logs = JSON.parse(cachedLogs)
+            else {
+                const git = simpleGit()
+                await git.cwd(repoPath)
+
+                // 获取详细日志,带上插入删除统计并排除 merge 提交
+                logs = await git.raw([
+                    "log",
+                    "--no-merges",
+                    "--pretty=format:%H|%an|%ae|%ad|%s",
+                    "--shortstat"
+                ])
+
+                redis.set(redisKey, JSON.stringify(logs), {
+                    EX: 172800
+                })
+            }
+
+            const lines = logs.split("\n")
+            const contributors = {}
+            let currentAuthor = null
+
+            for (let i = 0; i < lines.length; i++) {
+                const line = lines[i].trim()
+
+                if (line.includes("|")) {
+                    const [commitHash, name, email, date, ...messageParts] = line.split("|")
+                    currentAuthor = email // 用邮箱作为唯一 key 更稳妥
+                    if (!contributors[currentAuthor]) {
+                        contributors[currentAuthor] = {
+                            name,
+                            email,
+                            commits: 0,
+                            linesAdded: 0,
+                            linesDeleted: 0,
+                            filesChanged: 0,
+                            linesChanged: 0 // total changes
+                        }
+                    }
+                    contributors[currentAuthor].commits += 1
+
+                    // 接下来的 shortstat 统计信息在下一行
+                    const statLine = lines[i + 1]?.trim() || ""
+                    const statMatch = {
+                        filesChanged: statLine.match(/(\d+) file[s]? changed/),
+                        insertions: statLine.match(/(\d+) insertion[s]?\(\+\)/),
+                        deletions: statLine.match(/(\d+) deletion[s]?\(-\)/),
+                    }
+
+                    const files = statMatch.filesChanged ? parseInt(statMatch.filesChanged[1], 10) : 0
+                    const insertions = statMatch.insertions ? parseInt(statMatch.insertions[1], 10) : 0
+                    const deletions = statMatch.deletions ? parseInt(statMatch.deletions[1], 10) : 0
+
+                    contributors[currentAuthor].filesChanged += files
+                    contributors[currentAuthor].linesAdded += insertions
+                    contributors[currentAuthor].linesDeleted += deletions
+                    contributors[currentAuthor].linesChanged += insertions + deletions
+
+                    if (statLine) i++ // 跳过 shortstat 行
+                }
+            }
+
+            const sorted = Object.values(contributors).sort(
+                (a, b) => b.linesChanged - a.linesChanged || b.commits - a.commits
+            )
+
+            return sorted
+        } catch (err) {
+            console.error("分析 Git 贡献者失败:", err)
+            throw new Error("分析 Git 贡献者失败")
+        }
+    }
+
+
+    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 contributors
+        try {
+            const cachedContributors = await redis.get(`contributors:${r[0].path}`)
+            if (cachedContributors)
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    data: JSON.parse(cachedContributors)
+                })
+            contributors = await this.analyzeGitContributors(r[0].path)
+        } catch (error) {
+            this.logger.error('获取仓库开发者失败!' + error.stack)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '获取仓库开发者失败!'
+            })
+        }
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: contributors
+        })
+        redis.set(`contributors:${r[0].path}`, JSON.stringify(contributors), {
+            EX: 172800
+        })
+    }
+}
+
+module.exports.GitContributors = GitContributors

+ 54 - 0
apis/Repos/UpdateRepoInfo.js

@@ -0,0 +1,54 @@
+const API = require("../../lib/API")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+const db = require("../../plugin/DataBase/db")
+
+// 更新仓库信息
+class UpdateRepoInfo extends API {
+    constructor() {
+        super()
+
+        this.setMethod("POST")
+        this.setPath("/Repos/UpdateInfo")
+    }
+
+    checkName(name) {
+        const regex = /^(?!\.)(?!.*\/{2,})(?!.*\.git$)[\u4E00-\u9FA5A-Za-z0-9._-]+(?<!\/)$/
+        return regex.test(name)
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, name, id } = req.body
+
+        if ([uuid, session, name, id].some(value => value === '' || value === null || value === undefined))
+            return res.json({
+                ...BaseStdResponse.MISSING_PARAMETER
+            })
+
+        if (!this.checkName(name))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '仓库名称不合法!'
+            })
+
+        // 检查 session
+        if (!await AccessControl.checkSession(uuid, session))
+            return res.status(401).json({
+                ...BaseStdResponse.ACCESS_DENIED
+            })
+
+        let sql = 'UPDATE repos SET \`name\` = ? WHERE create_user = ? AND id = ?'
+
+        let result = await db.query(sql, [name, uuid, id])
+
+        if (result && result.affectedRows > 0) {
+            res.json({
+                ...BaseStdResponse.OK
+            })
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '更新仓库信息失败!' })
+        }
+    }
+}
+
+module.exports.UpdateRepoInfo = UpdateRepoInfo

+ 106 - 0
apis/Upload/UploadAvatar.js

@@ -0,0 +1,106 @@
+const API = require("../../lib/API");
+const { v4: uuidv4 } = require('uuid');
+const { BaseStdResponse } = require("../../BaseStdResponse");
+const db = require("../../plugin/DataBase/db");
+const AccessControl = require("../../lib/AccessControl");
+const multer = require('multer');
+const path = require('path');
+
+const { url } = require("../../config.json")
+
+// 配置Multer的存储选项
+const storage = multer.diskStorage({
+    destination: (req, file, cb) => {
+        cb(null, 'uploads/');
+    },
+    filename: (req, file, cb) => {
+        const fileName = uuidv4();
+        const fileExtension = path.extname(file.originalname);
+        cb(null, `${fileName}${fileExtension}`);
+    }
+});
+
+// 限制文件类型
+const fileFilter = (req, file, cb) => {
+    // 只接受以下扩展名的图片文件
+    const allowedTypes = /jpeg|jpg|png|gif/;
+    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
+    const mimetype = allowedTypes.test(file.mimetype);
+
+    if (extname && mimetype) {
+        return cb(null, true);
+    } else {
+        cb(new Error('只允许上传图片文件 (jpeg, jpg, png, gif)'));
+    }
+};
+
+// 初始化Multer中间件
+const upload = multer({
+    storage: storage,
+    fileFilter: fileFilter,
+    limits: { fileSize: 3 * 1024 * 1024 } // 限制文件大小为3MB
+}).single('upload');
+
+class UploadAvatar extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/UploadAvatar");
+    }
+
+    async onRequest(req, res) {
+        // 使用Multer中间件处理文件上传
+        upload(req, res, async (err) => {
+            if (err) {
+                this.logger.error(`图片上传失败!${err.stack || ''}`)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '图片上传失败!'
+                });
+            }
+
+            let { uuid, session } = req.body
+
+            if ([uuid, session].some(value => value === '' || value === null || value === undefined)) {
+                return res.json({
+                    ...BaseStdResponse.MISSING_PARAMETER,
+                    endpoint: 1513126
+                });
+            }
+
+            if (!await AccessControl.checkSession(uuid, session)) {
+                return res.status(401).json({
+                    ...BaseStdResponse.ACCESS_DENIED,
+                    endpoint: 481545
+                });
+            }
+
+            if (!req.file) {
+                return res.json({
+                    ...BaseStdResponse.MISSING_PARAMETER,
+                    msg: '请上传图片文件'
+                });
+            }
+
+            const picturePath = url + req.file.filename; // 获取文件路径
+
+            let sql = 'UPDATE users SET avatar = ? WHERE uuid = ?'
+            let r = await db.query(sql, [picturePath, uuid])
+            if (r && r.affectedRows > 0)
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    data: {
+                        picturePath
+                    }
+                });
+
+            res.json({
+                ...BaseStdResponse.ERR,
+                msg: '上传头像失败!'
+            })
+        });
+    }
+}
+
+module.exports.UploadAvatar = UploadAvatar;

+ 65 - 0
apis/User/BindEmail.js

@@ -0,0 +1,65 @@
+const API = require("../../lib/API");
+const db = require("../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../BaseStdResponse");
+const Redis = require('../../plugin/DataBase/Redis');
+const sendEmail = require('../../plugin/Email/Email');
+const AccessControl = require("../../lib/AccessControl");
+
+class BindEmail extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/BindEmail");
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, email, code } = req.body;
+
+        if ([uuid, session, email, code].some(value => value === '' || value === null || value === undefined)) {
+            return res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+        }
+
+        // 检查 session
+        if (!await AccessControl.checkSession(uuid, session))
+            return res.status(401).json({
+                ...BaseStdResponse.ACCESS_DENIED
+            })
+
+
+        const VerifyCode = await Redis.get(`email:${email}`);
+        if (!VerifyCode || VerifyCode != code)
+            return res.json({
+                ...BaseStdResponse.SMS_CHECK_FAIL,
+                msg: '邮箱验证码输入错误或已过期'
+            })
+
+        let sql = 'SELECT email FROM users WHERE email = ?';
+        let EmailRows = await db.query(sql, [email]);
+        if (EmailRows.length > 0)
+            return res.json({
+                ...BaseStdResponse.USER_ALREADY_EXISTS,
+                msg: '该邮箱已被注册!'
+            })
+
+        sql = 'UPDATE users SET email = ? WHERE uuid = ?';
+        let result = await db.query(sql, [email, uuid]);
+
+        if (result && result.affectedRows > 0) {
+            // 注册成功后删除邮箱对应的验证码 避免注册失败后重复获取
+            await Redis.del(`email:${email}`);
+
+            res.json({
+                ...BaseStdResponse.OK
+            });
+            await sendEmail(email, '换绑邮箱成功', `您的GitNexus账号换绑邮箱成功,操作时间:${new Date().toLocaleString()}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '操作失败!' });
+        }
+    }
+}
+
+module.exports.BindEmail = BindEmail;

+ 46 - 0
apis/User/GetRepos.js

@@ -0,0 +1,46 @@
+const API = require("../../lib/API")
+const db = require("../../plugin/DataBase/db")
+const AccessControl = require("../../lib/AccessControl")
+const { BaseStdResponse } = require("../../BaseStdResponse")
+class GetRepos extends API {
+    constructor() {
+        super()
+
+        this.setPath('/User/GetRepos')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let {
+            uuid,
+            session
+        } = req.query
+
+        if ([uuid, session].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 id, name, url, logo, create_time, update_time FROM repos WHERE create_user = ? AND state = 1 ORDER BY update_time DESC LIMIT 6'
+        let rows = await db.query(sql, [uuid])
+
+        if (!rows)
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '数据库错误!'
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows
+        })
+    }
+}
+
+module.exports.GetRepos = GetRepos

+ 47 - 0
apis/User/GetUserInfo.js

@@ -0,0 +1,47 @@
+const API = require("../../lib/API");
+const db = require("../../plugin/DataBase/db");
+const AccessControl = require("../../lib/AccessControl");
+const { BaseStdResponse } = require("../../BaseStdResponse");
+
+class GetRepoList extends API {
+    constructor() {
+        super();
+
+        this.setPath('/User/Info')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        let {
+            uuid,
+            session
+        } = req.query
+
+        if ([uuid, session].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 uuid, session, username, permission AS roles, avatar, email, registTime FROM users WHERE uuid = ?'
+        let rows = await db.query(sql, [uuid])
+
+        if (!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.MISSING_FILE,
+                msg: '获取用户信息失败!'
+            })
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: rows[0]
+        })
+    }
+}
+
+module.exports.GetRepoList = GetRepoList;

+ 4 - 6
apis/User/Login.js

@@ -43,7 +43,7 @@ class Login extends API {
             })
         }
 
-        let sql = 'SELECT * FROM users WHERE username = ?';
+        let sql = 'SELECT id, uuid, password, username, permission, avatar, email  FROM users WHERE username = ?';
         let rows = await db.query(sql, [username]);
 
         if (!rows || rows.length !== 1 || !bcryptjs.compareSync(password, rows[0].password))
@@ -64,11 +64,9 @@ class Login extends API {
                     uuid: rows[0].uuid,
                     username: rows[0].username,
                     session,
-                    roles: rows[0].permission || [],
-                    vip: rows[0].vip,
-                    ic_count: rows[0].ic_count,
-                    crouse_count: rows[0].crouse_count,
-                    realname: rows[0].realname
+                    avatar: rows[0].avatar,
+                    email: rows[0].email,
+                    roles: rows[0].permission || []
                 }
             });
         } else {

+ 4 - 4
apis/User/Register.js

@@ -4,7 +4,7 @@ const bcryptjs = require('bcryptjs');
 const { BaseStdResponse } = require("../../BaseStdResponse");
 const db = require("../../plugin/DataBase/db");
 const Redis = require('../../plugin/DataBase/Redis');
-const sendEmail = require('../../plugin/Email/Email');
+const EmailTemplate = require('../../plugin/Email/emailTemplate')
 
 // 用户注册
 class Register extends API {
@@ -45,7 +45,7 @@ class Register extends API {
         if(!this.checkUsername(username))
             return res.json({
                 ...BaseStdResponse.ERR,
-                msg: '用户名需在4到8位之间,且只能含有英文字母和汉字'
+                msg: '用户名需在4到12位之间,且不能含有特殊字符'
             })
 
         password = atob(password);
@@ -102,8 +102,8 @@ class Register extends API {
 
             res.json({
                 ...BaseStdResponse.OK
-            });
-            await sendEmail(email, '账号注册成功', `您已成功注册CTBU_CLUB账号,用户名${username},注册时间:${new Date().toLocaleString()}`);
+            })
+            await EmailTemplate.registerSuccess(email, username)
         } else {
             res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '注册失败!'});
         }

+ 6 - 5
config.json → config-example.json

@@ -1,11 +1,11 @@
 {
     "port": 30000,
     "database": {
-        "host": "8.137.37.202",
+        "host": "localhost",
         "database": "gitnexus",
         "port": 3306,
         "user": "gitnexus",
-        "password": "Yx3ud937"
+        "password": ""
     },
     "redis": {
         "host": "localhost",
@@ -16,8 +16,9 @@
         "host": "smtp.exmail.qq.com",
         "port": 465,
         "secure": true,
-        "user": "mail@ctbu.top",
-        "password": "FPhxUVrt5J9WxgiP"
+        "user": "",
+        "password": ""
     },
-    "url": "https://hk1-api.ctbu.top/"
+    "url": "https://cd1-api.gitnexus.cn/",
+    "core_url": "http://cd2-api.gitnexus.cn"
 }

+ 15 - 0
lib/MessageContral.js

@@ -0,0 +1,15 @@
+const Logger = require('./Logger')
+const path = require('path')
+const db = require("../../plugin/DataBase/db")
+
+class MessageContral {
+    constructor() {
+        this.logger = new Logger(path.join(__dirname, '../logs/Message.log'), 'INFO');
+    }
+
+    sendMessage(uuid, title, content, type) {
+        
+    }
+}
+
+module.exports = MessageContral

+ 3 - 2
package.json

@@ -14,12 +14,13 @@
     "chalk": "^4.1.2",
     "cors": "^2.8.5",
     "express": "^4.21.1",
+    "isbinaryfile": "^5.0.4",
     "multer": "^1.4.5-lts.1",
     "mysql2": "^3.11.0",
     "nodemailer": "^6.9.14",
-    "puppeteer": "^23.9.0",
     "redis": "^4.7.0",
+    "simple-git": "^3.27.0",
     "svg-captcha": "^1.4.0",
     "uuid": "^11.0.3"
   }
-}
+}

+ 2 - 2
plugin/Email/Email.js

@@ -18,10 +18,10 @@ const transporter = nodemailer.createTransport({
 async function sendEmail(email, subject, content) {
     return new Promise((resolve, reject) => {
         const mail = {
-            from: `CTBU_CLUB <${transporter.options.auth.user}>`,
+            from: `GitNexus <${transporter.options.auth.user}>`,
             to: email,
             subject: subject,
-            text: content
+            html: content
         }
         try {
             transporter.sendMail(mail, (error) => {

+ 189 - 0
plugin/Email/emailTemplate.js

@@ -0,0 +1,189 @@
+const sendEmail = require('./Email')
+
+class emailTemplate {
+    stramptoTime(time) {
+        if (time < 10)
+            return '';
+        return new Date(+time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
+    }
+
+    async checkEmail(email, code) {
+        const time = new Date().getTime()
+        await sendEmail(email, 'GitNexus - 邮箱验证码',
+            `<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>GitNexus - 邮箱验证码</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            background-color: #f4f4f4;
+            margin: 0;
+            padding: 0;
+        }
+
+        .container {
+            width: 80%;
+            margin: 20px auto;
+            background-color: #fff;
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+        }
+
+        .head {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            gap:10px;
+            color: #2c3e50;
+        }
+
+        p {
+            font-size: 16px;
+            color: #34495e;
+            line-height: 1.6;
+            text-indent: 2em;
+        }
+
+        .code {
+            margin: 20px 0;
+            font-size: 1.5em;
+            text-align: center;
+            font-weight: bold;
+        }
+
+        .important {
+            color: #e74c3c;
+            font-weight: bold;
+        }
+
+        .footer {
+            font-size: 14px;
+            text-align: center;
+            color: #7f8c8d;
+            margin-top: 50px;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container">
+        <div class="head">
+            <svg t="1743769783333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2616" width="32" height="32"><path d="M512 0C229.23264 0 0 229.23264 0 512c0 282.76736 229.23264 512 512 512 51.623253 0 101.451093-7.673173 148.43904-21.886293 127.91808-38.68672 234.666667-126.07488 298.707627-240.59904 25.78432-46.107307 44.632747-96.610987 55.166293-150.09792 0.054613-0.273067 0.12288-0.53248 0.177493-0.805547C1020.7232 578.70336 1024 545.737387 1024 512 1024 229.23264 794.76736 0 512 0zM823.958187 542.180693l-281.750187 281.750187c-16.329387 16.308907-42.769067 16.308907-59.06432 0L215.69536 556.516693l-12.192427-12.192427-3.46112-3.46112c-16.308907-16.308907-16.308907-42.728107 0-59.057493l132.87424-132.860587 0.34816-0.34816 54.12864-54.114987 49.493333 49.493333c-3.80928 7.92576-5.9392 16.820907-5.9392 26.200747 0 26.48064 16.91648 48.96768 40.523093 57.316693l0 169.04192c-23.606613 8.349013-40.523093 30.856533-40.523093 57.316693 0 33.573547 27.211093 60.78464 60.78464 60.78464s60.78464-27.211093 60.78464-60.78464c0-26.48064-16.923307-48.96768-40.523093-57.316693L511.993173 427.47904c2.02752-0.709973 4.007253-1.51552 5.9392-2.450773l81.025707 81.025707c-3.80928 7.92576-5.9392 16.820907-5.9392 26.200747 0 33.573547 27.211093 60.78464 60.78464 60.78464 33.573547 0 60.78464-27.211093 60.78464-60.78464 0-33.573547-27.211093-60.78464-60.78464-60.78464-9.37984 0-18.254507 2.1504-26.200747 5.9392L546.583893 396.383573c3.80928-7.92576 5.9392-16.820907 5.9392-26.200747 0-33.573547-27.211093-60.78464-60.78464-60.78464-9.37984 0-18.254507 2.1504-26.200747 5.9392l-49.493333-49.493333 27.613867-27.613867 0.211627-0.211627 10.67008-10.67008 27.27936-27.27936c16.329387-16.308907 42.769067-16.308907 59.057493 0l76.32896 76.322133 19.203413 19.196587 187.55584 187.542187 0-0.02048C840.267093 499.411627 840.267093 525.871787 823.958187 542.180693z" p-id="2617" fill="#1296db"></path></svg>
+            <h2>GitNexus - 邮箱验证码</h2>
+        </div>
+        
+        <p>尊敬的用户:</p>
+        <p>您正在本站进行邮箱验证操作,如非您本人操作,请忽略此邮件。</p>
+        <p>您的验证码为:</p>
+        <div class="code">
+            ${code}
+        </div>
+        <p class="important">验证码5分钟内有效,超时请重新获取</p>
+        <p class="footer">Copyright © 2025 Gitnexus.cn</p>
+    </div>
+</body>
+
+</html>`
+        )
+    }
+
+    async registerSuccess(email, username) {
+        const time = new Date().getTime()
+        await sendEmail(email, '您已成功注册GitNexus账号',
+            `<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>GitNexus - 邮箱验证码</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            background-color: #f4f4f4;
+            margin: 0;
+            padding: 0;
+        }
+
+        .container {
+            width: 80%;
+            margin: 20px auto;
+            background-color: #fff;
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+        }
+
+        .head {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            gap: 10px;
+            color: #2c3e50;
+        }
+
+        p {
+            font-size: 16px;
+            color: #34495e;
+            line-height: 1.6;
+            text-indent: 2em;
+        }
+
+        .info {
+            background-color: #ecf0f1;
+            padding: 15px;
+            border-radius: 5px;
+            margin: 20px 0;
+        }
+
+        .info p {
+            margin: 5px 0;
+        }
+
+        .important {
+            color: #e74c3c;
+            font-weight: bold;
+        }
+
+        .footer {
+            font-size: 14px;
+            text-align: center;
+            color: #7f8c8d;
+            margin-top: 50px;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container">
+        <div class="head">
+            <svg t="1743769783333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+                p-id="2616" width="32" height="32">
+                <path
+                    d="M512 0C229.23264 0 0 229.23264 0 512c0 282.76736 229.23264 512 512 512 51.623253 0 101.451093-7.673173 148.43904-21.886293 127.91808-38.68672 234.666667-126.07488 298.707627-240.59904 25.78432-46.107307 44.632747-96.610987 55.166293-150.09792 0.054613-0.273067 0.12288-0.53248 0.177493-0.805547C1020.7232 578.70336 1024 545.737387 1024 512 1024 229.23264 794.76736 0 512 0zM823.958187 542.180693l-281.750187 281.750187c-16.329387 16.308907-42.769067 16.308907-59.06432 0L215.69536 556.516693l-12.192427-12.192427-3.46112-3.46112c-16.308907-16.308907-16.308907-42.728107 0-59.057493l132.87424-132.860587 0.34816-0.34816 54.12864-54.114987 49.493333 49.493333c-3.80928 7.92576-5.9392 16.820907-5.9392 26.200747 0 26.48064 16.91648 48.96768 40.523093 57.316693l0 169.04192c-23.606613 8.349013-40.523093 30.856533-40.523093 57.316693 0 33.573547 27.211093 60.78464 60.78464 60.78464s60.78464-27.211093 60.78464-60.78464c0-26.48064-16.923307-48.96768-40.523093-57.316693L511.993173 427.47904c2.02752-0.709973 4.007253-1.51552 5.9392-2.450773l81.025707 81.025707c-3.80928 7.92576-5.9392 16.820907-5.9392 26.200747 0 33.573547 27.211093 60.78464 60.78464 60.78464 33.573547 0 60.78464-27.211093 60.78464-60.78464 0-33.573547-27.211093-60.78464-60.78464-60.78464-9.37984 0-18.254507 2.1504-26.200747 5.9392L546.583893 396.383573c3.80928-7.92576 5.9392-16.820907 5.9392-26.200747 0-33.573547-27.211093-60.78464-60.78464-60.78464-9.37984 0-18.254507 2.1504-26.200747 5.9392l-49.493333-49.493333 27.613867-27.613867 0.211627-0.211627 10.67008-10.67008 27.27936-27.27936c16.329387-16.308907 42.769067-16.308907 59.057493 0l76.32896 76.322133 19.203413 19.196587 187.55584 187.542187 0-0.02048C840.267093 499.411627 840.267093 525.871787 823.958187 542.180693z"
+                    p-id="2617" fill="#1296db"></path>
+            </svg>
+            <h2>GitNexus - 邮箱验证码</h2>
+        </div>
+
+        <p>尊敬的 ${username}:</p>
+        <p>您已成功注册GitNexus账号:</p>
+        <div class="info">
+            <p><strong>用户名:</strong> ${username}</p>
+            <p><strong>注册时间:</strong> ${this.stramptoTime(time)}</p>
+        </div>
+
+        <p class="footer">Copyright © 2025 Gitnexus.cn</p>
+    </div>
+</body>
+
+</html>`
+        )
+    }
+}
+
+const EmailTemplate = new emailTemplate()
+module.exports = EmailTemplate