377 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"fmt"
"net/http"
"regexp"
"time"
"x-ui/web/global"
"x-ui/web/service"
"github.com/gin-gonic/gin"
)
var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
type ServerController struct {
BaseController
serverService service.ServerService
settingService service.SettingService
lastStatus *service.Status
lastGetStatusTime time.Time
lastVersions []string
lastGetVersionsTime time.Time
}
// 〔中文注释〕: 1. 在函数参数中,增加 serverService service.ServerService让它可以接收一个服务实例。
func NewServerController(g *gin.RouterGroup, serverService service.ServerService) *ServerController {
a := &ServerController{
lastGetStatusTime: time.Now(),
// 〔中文注释〕: 2. 将传入的 serverService 赋值给 a.serverService。
// 这样一来,这个 Controller 内部使用的就是我们在 main.go 中创建的那个功能完整的服务了。
serverService: serverService,
}
a.initRouter(g)
a.startTask()
return a
}
func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.GET("/status", a.status)
g.GET("/getXrayVersion", a.getXrayVersion)
g.GET("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.GET("/getNewUUID", a.getNewUUID)
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
g.GET("/getNewmldsa65", a.getNewmldsa65)
g.GET("/getNewmlkem768", a.getNewmlkem768)
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray)
g.POST("/updateGeofile", a.updateGeofile)
g.POST("/updateGeofile/:fileName", a.updateGeofile)
g.POST("/logs/:count", a.getLogs)
g.POST("/xraylogs/:count", a.getXrayLogs)
g.POST("/importDB", a.importDB)
g.POST("/getNewEchCert", a.getNewEchCert)
g.POST("/history/save", a.saveHistory)
g.GET("/history/load", a.loadHistory)
g.POST("/install/subconverter", a.installSubconverter)
g.POST("/openPort", a.openPort)
}
func (a *ServerController) refreshStatus() {
a.lastStatus = a.serverService.GetStatus(a.lastStatus)
}
func (a *ServerController) startTask() {
webServer := global.GetWebServer()
c := webServer.GetCron()
c.AddFunc("@every 2s", func() {
now := time.Now()
if now.Sub(a.lastGetStatusTime) > time.Minute*3 {
return
}
a.refreshStatus()
})
}
func (a *ServerController) status(c *gin.Context) {
a.lastGetStatusTime = time.Now()
jsonObj(c, a.lastStatus, nil)
}
func (a *ServerController) getXrayVersion(c *gin.Context) {
now := time.Now()
if now.Sub(a.lastGetVersionsTime) <= time.Minute {
jsonObj(c, a.lastVersions, nil)
return
}
versions, err := a.serverService.GetXrayVersions()
if err != nil {
jsonMsg(c, I18nWeb(c, "getVersion"), err)
return
}
a.lastVersions = versions
a.lastGetVersionsTime = time.Now()
jsonObj(c, versions, nil)
}
func (a *ServerController) installXray(c *gin.Context) {
version := c.Param("version")
err := a.serverService.UpdateXray(version)
jsonMsg(c, I18nWeb(c, "pages.index.xraySwitchVersionPopover"), err)
}
func (a *ServerController) updateGeofile(c *gin.Context) {
fileName := c.Param("fileName")
err := a.serverService.UpdateGeofile(fileName)
jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), err)
}
func (a *ServerController) stopXrayService(c *gin.Context) {
a.lastGetStatusTime = time.Now()
err := a.serverService.StopXrayService()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.xray.stopError"), err)
return
}
jsonMsg(c, I18nWeb(c, "pages.xray.stopSuccess"), err)
}
func (a *ServerController) restartXrayService(c *gin.Context) {
err := a.serverService.RestartXrayService()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.xray.restartError"), err)
return
}
jsonMsg(c, I18nWeb(c, "pages.xray.restartSuccess"), err)
}
func (a *ServerController) getLogs(c *gin.Context) {
count := c.Param("count")
level := c.PostForm("level")
syslog := c.PostForm("syslog")
logs := a.serverService.GetLogs(count, level, syslog)
jsonObj(c, logs, nil)
}
func (a *ServerController) getXrayLogs(c *gin.Context) {
count := c.Param("count")
filter := c.PostForm("filter")
showDirect := c.PostForm("showDirect")
showBlocked := c.PostForm("showBlocked")
showProxy := c.PostForm("showProxy")
var freedoms []string
var blackholes []string
//getting tags for freedom and blackhole outbounds
config, err := a.settingService.GetDefaultXrayConfig()
if err == nil && config != nil {
if cfgMap, ok := config.(map[string]interface{}); ok {
if outbounds, ok := cfgMap["outbounds"].([]interface{}); ok {
for _, outbound := range outbounds {
if obMap, ok := outbound.(map[string]interface{}); ok {
switch obMap["protocol"] {
case "freedom":
if tag, ok := obMap["tag"].(string); ok {
freedoms = append(freedoms, tag)
}
case "blackhole":
if tag, ok := obMap["tag"].(string); ok {
blackholes = append(blackholes, tag)
}
}
}
}
}
}
}
if len(freedoms) == 0 {
freedoms = []string{"direct"}
}
if len(blackholes) == 0 {
blackholes = []string{"blocked"}
}
logs := a.serverService.GetXrayLogs(count, filter, showDirect, showBlocked, showProxy, freedoms, blackholes)
jsonObj(c, logs, nil)
}
func (a *ServerController) getConfigJson(c *gin.Context) {
configJson, err := a.serverService.GetConfigJson()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.index.getConfigError"), err)
return
}
jsonObj(c, configJson, nil)
}
func (a *ServerController) getDb(c *gin.Context) {
db, err := a.serverService.GetDb()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.index.getDatabaseError"), err)
return
}
filename := "x-ui.db"
if !isValidFilename(filename) {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
return
}
// Set the headers for the response
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+filename)
// Write the file contents to the response
c.Writer.Write(db)
}
func isValidFilename(filename string) bool {
// Validate that the filename only contains allowed characters
return filenameRegex.MatchString(filename)
}
func (a *ServerController) importDB(c *gin.Context) {
// Get the file from the request body
file, _, err := c.Request.FormFile("db")
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.index.readDatabaseError"), err)
return
}
defer file.Close()
// Always restart Xray before return
defer a.serverService.RestartXrayService()
defer func() {
a.lastGetStatusTime = time.Now()
}()
// Import it
err = a.serverService.ImportDB(file)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.index.importDatabaseError"), err)
return
}
jsonObj(c, I18nWeb(c, "pages.index.importDatabaseSuccess"), nil)
}
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
cert, err := a.serverService.GetNewX25519Cert()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewX25519CertError"), err)
return
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewmldsa65(c *gin.Context) {
cert, err := a.serverService.GetNewmldsa65()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewmldsa65Error"), err)
return
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewEchCert(c *gin.Context) {
sni := c.PostForm("sni")
cert, err := a.serverService.GetNewEchCert(sni)
if err != nil {
jsonMsg(c, "get ech certificate", err)
return
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewVlessEnc(c *gin.Context) {
out, err := a.serverService.GetNewVlessEnc()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewVlessEncError"), err)
return
}
jsonObj(c, out, nil)
}
func (a *ServerController) getNewUUID(c *gin.Context) {
uuidResp, err := a.serverService.GetNewUUID()
if err != nil {
jsonMsg(c, "Failed to generate UUID", err)
return
}
jsonObj(c, uuidResp, nil)
}
func (a *ServerController) getNewmlkem768(c *gin.Context) {
out, err := a.serverService.GetNewmlkem768()
if err != nil {
jsonMsg(c, "Failed to generate mlkem768 keys", err)
return
}
jsonObj(c, out, nil)
}
func (a *ServerController) saveHistory(c *gin.Context) {
/* 【中文注释】: 旧的错误代码,因为它期望一个 JSON 请求体,但前端发送的是表单数据
var req struct {
Type string `json:"type"`
Link string `json:"link"`
}
if err := c.ShouldBindJSON(&req); err != nil {
jsonMsg(c, "Invalid request", err)
return
}
err := a.serverService.SaveLinkHistory(req.Type, req.Link)
*/
// 【中文注释】: 修改后的新代码,直接从 POST 表单中获取 'type' 和 'link' 参数
// 【中文注释】: 这与其他 POST 方法(如 getLogs, getXrayLogs的处理方式保持一致解决了数据格式不匹配的问题。
historyType := c.PostForm("type")
link := c.PostForm("link")
// 【中文注释】: 调用服务层方法来保存历史记录
err := a.serverService.SaveLinkHistory(historyType, link)
if err != nil {
jsonMsg(c, "Failed to save history", err)
return
}
jsonMsg(c, "History saved successfully", nil)
}
func (a *ServerController) loadHistory(c *gin.Context) {
history, err := a.serverService.LoadLinkHistory()
if err != nil {
jsonMsg(c, "Failed to load history", err)
return
}
jsonObj(c, history, nil)
}
// 〔新增接口〕: 安装 Subconverter
// 〔中文注释〕: 这个函数是暴露给前端的 API 接口,用于处理安装 Subconverter 的请求。
func (a *ServerController) installSubconverter(c *gin.Context) {
// 〔中文注释〕: 调用服务层中我们刚刚创建的 InstallSubconverter 方法。
err := a.serverService.InstallSubconverter()
if err != nil {
// 〔中文注释〕: 如果 service 层返回了错误,则向前台返回失败的 JSON 消息。
jsonMsg(c, "Subconverter 安装指令执行失败", err)
return
}
// 〔中文注释〕: 如果没有错误,则向前台返回成功的 JSON 消息。
jsonMsg(c, "Subconverter 安装指令已成功发送", nil)
}
// 【新增接口实现】: 前端放行端口
func (a *ServerController) openPort(c *gin.Context) {
// 直接使用 c.PostForm("port") 获取表单数据
port := c.PostForm("port")
// 1. 手动进行参数校验
if port == "" {
jsonMsg(c, "请求端口参数失败", fmt.Errorf("无效的请求参数,请确保端口号存在"))
return
}
// 【中文注释】: 2. 调用服务层方法,该方法会立即返回,并在后台启动一个协程执行任务。
a.serverService.OpenPort(port)
// 【中文注释】: 3. 因为服务层方法是异步的,不再检查它的 error 返回值。
// 直接向前端返回一个成功的消息,告知用户指令已发送。
jsonMsg(c, "端口放行指令已成功发送,正在后台执行...", nil)
}