This commit is contained in:
chermack
2026-07-26 00:09:57 +08:00
commit 059be96536
134 changed files with 19678 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
package controller
import (
"github.com/robfig/cron/v3"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"time"
"trojan/asset"
"trojan/core"
"trojan/trojan"
)
// ResponseBody 结构体
type ResponseBody struct {
Duration string
Data interface{}
Msg string
}
type speedInfo struct {
Up uint64
Down uint64
}
var si *speedInfo
// TimeCost web函数执行用时统计方法
func TimeCost(start time.Time, body *ResponseBody) {
body.Duration = time.Since(start).String()
}
func clashRules() string {
rules, _ := core.GetValue("clash-rules")
if rules == "" {
rules = string(asset.GetAsset("clash-rules.yaml"))
}
return rules
}
// Version 获取版本信息
func Version() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
responseBody.Data = map[string]string{
"version": trojan.MVersion,
"buildDate": trojan.BuildDate,
"goVersion": trojan.GoVersion,
"gitVersion": trojan.GitVersion,
"trojanVersion": trojan.Version(),
"trojanUptime": trojan.UpTime(),
"trojanType": trojan.Type(),
}
return &responseBody
}
// SetLoginInfo 设置登录页信息
func SetLoginInfo(title string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
err := core.SetValue("login_title", title)
if err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// SetDomain 设置域名
func SetDomain(domain string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
trojan.SetDomain(domain)
return &responseBody
}
// SetClashRules 设置clash规则
func SetClashRules(rules string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
core.SetValue("clash-rules", rules)
return &responseBody
}
// ResetClashRules 重置clash规则
func ResetClashRules() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
core.DelValue("clash-rules")
responseBody.Data = clashRules()
return &responseBody
}
// GetClashRules 获取clash规则
func GetClashRules() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
responseBody.Data = clashRules()
return &responseBody
}
// SetTrojanType 设置trojan类型
func SetTrojanType(tType string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
err := trojan.SwitchType(tType)
if err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// CollectTask 启动收集主机信息任务
func CollectTask() {
var recvCount, sentCount uint64
c := cron.New()
lastIO, _ := net.IOCounters(true)
var lastRecvCount, lastSentCount uint64
for _, k := range lastIO {
lastRecvCount = lastRecvCount + k.BytesRecv
lastSentCount = lastSentCount + k.BytesSent
}
si = &speedInfo{}
c.AddFunc("@every 2s", func() {
result, _ := net.IOCounters(true)
recvCount, sentCount = 0, 0
for _, k := range result {
recvCount = recvCount + k.BytesRecv
sentCount = sentCount + k.BytesSent
}
si.Up = (sentCount - lastSentCount) / 2
si.Down = (recvCount - lastRecvCount) / 2
lastSentCount = sentCount
lastRecvCount = recvCount
lastIO = result
})
c.Start()
}
// ServerInfo 获取服务器信息
func ServerInfo() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
cpuPercent, _ := cpu.Percent(0, false)
vmInfo, _ := mem.VirtualMemory()
smInfo, _ := mem.SwapMemory()
diskInfo, _ := disk.Usage("/")
loadInfo, _ := load.Avg()
tcpCon, _ := net.Connections("tcp")
udpCon, _ := net.Connections("udp")
netCount := map[string]int{
"tcp": len(tcpCon),
"udp": len(udpCon),
}
responseBody.Data = map[string]interface{}{
"cpu": cpuPercent,
"memory": vmInfo,
"swap": smInfo,
"disk": diskInfo,
"load": loadInfo,
"speed": si,
"netCount": netCount,
}
return &responseBody
}
+105
View File
@@ -0,0 +1,105 @@
package controller
import (
"fmt"
"github.com/robfig/cron/v3"
"strconv"
"time"
"trojan/core"
"trojan/trojan"
)
var c *cron.Cron
// SetData 设置流量限制
func SetData(id uint, quota int) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
if err := mysql.SetQuota(id, quota); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// CleanData 清空流量
func CleanData(id uint) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
if err := mysql.CleanData(id); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
func monthlyResetJob() {
mysql := core.GetMysql()
if err := mysql.MonthlyResetData(); err != nil {
fmt.Println("MonthlyResetError: " + err.Error())
}
}
// GetResetDay 获取重置日
func GetResetDay() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
dayStr, _ := core.GetValue("reset_day")
day, _ := strconv.Atoi(dayStr)
responseBody.Data = map[string]interface{}{
"resetDay": day,
}
return &responseBody
}
// UpdateResetDay 更新重置流量日
func UpdateResetDay(day uint) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
if day > 31 || day < 0 {
responseBody.Msg = fmt.Sprintf("%d为非正常日期", day)
return &responseBody
}
dayStr, _ := core.GetValue("reset_day")
oldDay, _ := strconv.Atoi(dayStr)
if day == uint(oldDay) {
return &responseBody
}
if len(c.Entries()) > 1 {
c.Remove(c.Entries()[len(c.Entries())-1].ID)
}
if day != 0 {
c.AddFunc(fmt.Sprintf("0 0 %d * *", day), func() {
monthlyResetJob()
})
}
core.SetValue("reset_day", strconv.Itoa(int(day)))
return &responseBody
}
// ScheduleTask 定时任务
func ScheduleTask() {
loc, _ := time.LoadLocation("Asia/Shanghai")
c = cron.New(cron.WithLocation(loc))
c.AddFunc("@daily", func() {
mysql := core.GetMysql()
if needRestart, err := mysql.DailyCheckExpire(); err != nil {
fmt.Println("DailyCheckError: " + err.Error())
} else if needRestart {
trojan.Restart()
}
})
dayStr, _ := core.GetValue("reset_day")
if dayStr == "" {
dayStr = "1"
core.SetValue("reset_day", dayStr)
}
day, _ := strconv.Atoi(dayStr)
if day != 0 {
c.AddFunc(fmt.Sprintf("0 0 %d * *", day), func() {
monthlyResetJob()
})
}
c.Start()
}
+200
View File
@@ -0,0 +1,200 @@
package controller
import (
"bufio"
"bytes"
"encoding/csv"
"fmt"
"github.com/gin-gonic/gin"
ws "github.com/gorilla/websocket"
"io"
"strconv"
"strings"
"time"
"trojan/core"
"trojan/trojan"
"trojan/util"
)
// Start 启动trojan
func Start() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
trojan.Start()
return &responseBody
}
// Stop 停止trojan
func Stop() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
trojan.Stop()
return &responseBody
}
// Restart 重启trojan
func Restart() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
trojan.Restart()
return &responseBody
}
// Update trojan更新
func Update() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
trojan.InstallTrojan("")
return &responseBody
}
// SetLogLevel 修改trojan日志等级
func SetLogLevel(level int) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
core.WriteLogLevel(level)
trojan.Restart()
return &responseBody
}
// GetLogLevel 获取trojan日志等级
func GetLogLevel() *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
config := core.GetConfig()
responseBody.Data = map[string]interface{}{
"loglevel": &config.LogLevel,
}
return &responseBody
}
// Log 通过ws查看trojan实时日志
func Log(c *gin.Context) {
var (
wsConn *util.WsConnection
err error
)
if wsConn, err = util.InitWebsocket(c.Writer, c.Request); err != nil {
fmt.Println(err)
return
}
defer wsConn.WsClose()
param := c.DefaultQuery("line", "300")
if !util.IsInteger(param) {
fmt.Println("invalid param: " + param)
return
}
if param == "-1" {
param = "--no-tail"
} else {
param = "-n " + param
}
result, err := util.LogChan("trojan", param, wsConn.CloseChan)
if err != nil {
fmt.Println(err)
return
}
for line := range result {
if err := wsConn.WsWrite(ws.TextMessage, []byte(line+"\n")); err != nil {
fmt.Println("can't send: ", line)
break
}
}
}
// ImportCsv 导入csv文件到trojan数据库
func ImportCsv(c *gin.Context) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
file, header, err := c.Request.FormFile("file")
if err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
defer file.Close()
filename := header.Filename
if !strings.Contains(filename, ".csv") {
responseBody.Msg = "仅支持导入csv格式的文件"
return &responseBody
}
reader := csv.NewReader(bufio.NewReader(file))
var userList []*core.User
for {
line, readErr := reader.Read()
if readErr == io.EOF {
break
} else if readErr != nil {
responseBody.Msg = readErr.Error()
return &responseBody
}
quota, _ := strconv.Atoi(line[4])
download, _ := strconv.Atoi(line[5])
upload, _ := strconv.Atoi(line[6])
useDays, _ := strconv.Atoi(line[7])
userList = append(userList, &core.User{
Username: line[1],
Password: line[2],
EncryptPass: line[3],
Quota: int64(quota),
Download: uint64(download),
Upload: uint64(upload),
UseDays: uint(useDays),
ExpiryDate: line[8],
})
}
mysql := core.GetMysql()
db := mysql.GetDB()
if _, err = db.Exec("DROP TABLE IF EXISTS users;"); err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
if _, err = db.Exec(core.CreateTableSql); err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
for _, user := range userList {
if _, err = db.Exec(fmt.Sprintf(`
INSERT INTO users(username, password, passwordShow, quota, download, upload, useDays, expiryDate) VALUES ('%s','%s','%s', %d, %d, %d, %d, '%s');`,
user.Username, user.EncryptPass, user.Password, user.Quota, user.Download, user.Upload, user.UseDays, user.ExpiryDate)); err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
}
return &responseBody
}
// ExportCsv 导出trojan表数据到csv文件
func ExportCsv(c *gin.Context) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
var dataBytes = new(bytes.Buffer)
//设置UTF-8 BOM, 防止中文乱码
dataBytes.WriteString("\xEF\xBB\xBF")
mysql := core.GetMysql()
userList, err := mysql.GetData()
if err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
wr := csv.NewWriter(dataBytes)
for _, user := range userList {
singleUser := []string{
strconv.Itoa(int(user.ID)),
user.Username,
user.Password,
user.EncryptPass,
strconv.Itoa(int(user.Quota)),
strconv.Itoa(int(user.Download)),
strconv.Itoa(int(user.Upload)),
strconv.Itoa(int(user.UseDays)),
user.ExpiryDate,
}
wr.Write(singleUser)
}
wr.Flush()
c.Writer.Header().Set("Content-type", "application/octet-stream")
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s", fmt.Sprintf("%s.csv", mysql.Database)))
c.String(200, dataBytes.String())
return nil
}
+233
View File
@@ -0,0 +1,233 @@
package controller
import (
"encoding/base64"
"fmt"
"github.com/gin-gonic/gin"
"github.com/tidwall/gjson"
"strconv"
"time"
"trojan/core"
"trojan/trojan"
)
// UserList 获取用户列表
func UserList(requestUser string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
userList, err := mysql.GetData()
if err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
if requestUser != "admin" {
findUser := false
for _, user := range userList {
if user.Username == requestUser {
userList = []*core.User{user}
findUser = true
break
}
}
if !findUser {
userList = []*core.User{}
}
}
domain, port := trojan.GetDomainAndPort()
responseBody.Data = map[string]interface{}{
"domain": domain,
"port": port,
"userList": userList,
}
return &responseBody
}
// PageUserList 分页查询获取用户列表
func PageUserList(curPage int, pageSize int) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
pageData, err := mysql.PageList(curPage, pageSize)
if err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
domain, port := trojan.GetDomainAndPort()
responseBody.Data = map[string]interface{}{
"domain": domain,
"port": port,
"pageData": pageData,
}
return &responseBody
}
// CreateUser 创建用户
func CreateUser(username string, password string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
if username == "admin" {
responseBody.Msg = "不能创建用户名为admin的用户!"
return &responseBody
}
mysql := core.GetMysql()
if user := mysql.GetUserByName(username); user != nil {
responseBody.Msg = "已存在用户名为: " + username + " 的用户!"
return &responseBody
}
pass, err := base64.StdEncoding.DecodeString(password)
if err != nil {
responseBody.Msg = "Base64解码失败: " + err.Error()
return &responseBody
}
if user := mysql.GetUserByPass(password); user != nil {
responseBody.Msg = "已存在密码为: " + string(pass) + " 的用户!"
return &responseBody
}
if err := mysql.CreateUser(username, password, string(pass)); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// UpdateUser 更新用户
func UpdateUser(id uint, username string, password string) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
if username == "admin" {
responseBody.Msg = "不能更改用户名为admin的用户!"
return &responseBody
}
mysql := core.GetMysql()
userList, err := mysql.GetData(strconv.Itoa(int(id)))
if err != nil {
responseBody.Msg = err.Error()
return &responseBody
}
if userList[0].Username != username {
if user := mysql.GetUserByName(username); user != nil {
responseBody.Msg = "已存在用户名为: " + username + " 的用户!"
return &responseBody
}
}
pass, err := base64.StdEncoding.DecodeString(password)
if err != nil {
responseBody.Msg = "Base64解码失败: " + err.Error()
return &responseBody
}
if userList[0].Password != password {
if user := mysql.GetUserByPass(password); user != nil {
responseBody.Msg = "已存在密码为: " + string(pass) + " 的用户!"
return &responseBody
}
}
if err := mysql.UpdateUser(id, username, password, string(pass)); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// DelUser 删除用户
func DelUser(id uint) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
if err := mysql.DeleteUser(id); err != nil {
responseBody.Msg = err.Error()
} else {
trojan.Restart()
}
return &responseBody
}
// SetExpire 设置用户过期
func SetExpire(id uint, useDays uint) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
if err := mysql.SetExpire(id, useDays); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// CancelExpire 取消设置用户过期
func CancelExpire(id uint) *ResponseBody {
responseBody := ResponseBody{Msg: "success"}
defer TimeCost(time.Now(), &responseBody)
mysql := core.GetMysql()
if err := mysql.CancelExpire(id); err != nil {
responseBody.Msg = err.Error()
}
return &responseBody
}
// ClashSubInfo 获取clash订阅信息
func ClashSubInfo(c *gin.Context) {
token := c.Query("token")
if token == "" {
c.String(200, "token is null")
return
}
decodeByte, err := base64.StdEncoding.DecodeString(token)
if err != nil {
c.String(200, "token is error")
return
}
if !gjson.GetBytes(decodeByte, "user").Exists() || !gjson.GetBytes(decodeByte, "pass").Exists() {
c.String(200, "token is error")
return
}
username := gjson.GetBytes(decodeByte, "user").String()
password := gjson.GetBytes(decodeByte, "pass").String()
mysql := core.GetMysql()
user := mysql.GetUserByName(username)
if user != nil {
pass, _ := base64.StdEncoding.DecodeString(user.Password)
if password == string(pass) {
var wsData, wsHost string
userInfo := fmt.Sprintf("upload=%d, download=%d", user.Upload, user.Download)
if user.Quota != -1 {
userInfo = fmt.Sprintf("%s, total=%d", userInfo, user.Quota)
}
if user.ExpiryDate != "" {
utc, _ := time.LoadLocation("Asia/Shanghai")
t, _ := time.ParseInLocation("2006-01-02", user.ExpiryDate, utc)
userInfo = fmt.Sprintf("%s, expire=%d", userInfo, t.Unix())
}
c.Header("content-disposition", fmt.Sprintf("attachment; filename=%s", user.Username))
c.Header("subscription-userinfo", userInfo)
domain, port := trojan.GetDomainAndPort()
name := fmt.Sprintf("%s:%d", domain, port)
configData := string(core.Load(""))
if gjson.Get(configData, "websocket").Exists() && gjson.Get(configData, "websocket.enabled").Bool() {
if gjson.Get(configData, "websocket.host").Exists() {
hostTemp := gjson.Get(configData, "websocket.host").String()
if hostTemp != "" {
wsHost = fmt.Sprintf(", headers: {Host: %s}", hostTemp)
}
}
wsOpt := fmt.Sprintf("{path: %s%s}", gjson.Get(configData, "websocket.path").String(), wsHost)
wsData = fmt.Sprintf(", network: ws, udp: true, ws-opts: %s", wsOpt)
}
proxyData := fmt.Sprintf(" - {name: %s, server: %s, port: %d, type: trojan, password: %s, sni: %s%s}",
name, domain, port, password, domain, wsData)
result := fmt.Sprintf(`proxies:
%s
proxy-groups:
- name: PROXY
type: select
proxies:
- %s
%s
`, proxyData, name, clashRules())
c.String(200, result)
return
}
}
c.String(200, "token is error")
}