init
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/appleboy/gin-jwt/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
"trojan/core"
|
||||
"trojan/util"
|
||||
"trojan/web/controller"
|
||||
)
|
||||
|
||||
var (
|
||||
identityKey = "id"
|
||||
authMiddleware *jwt.GinJWTMiddleware
|
||||
err error
|
||||
)
|
||||
|
||||
// Login auth用户验证结构体
|
||||
type Login struct {
|
||||
Username string `form:"username" json:"username" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func getSecretKey() string {
|
||||
sk, _ := core.GetValue("secretKey")
|
||||
if sk == "" {
|
||||
sk = util.RandString(15, util.ALL)
|
||||
core.SetValue("secretKey", sk)
|
||||
}
|
||||
return sk
|
||||
}
|
||||
|
||||
func jwtInit(timeout int) {
|
||||
authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
|
||||
Realm: "trojan-manager",
|
||||
Key: []byte(getSecretKey()),
|
||||
Timeout: time.Minute * time.Duration(timeout),
|
||||
MaxRefresh: time.Minute * time.Duration(timeout),
|
||||
IdentityKey: identityKey,
|
||||
SendCookie: true,
|
||||
PayloadFunc: func(data interface{}) jwt.MapClaims {
|
||||
if v, ok := data.(*Login); ok {
|
||||
return jwt.MapClaims{
|
||||
identityKey: v.Username,
|
||||
}
|
||||
}
|
||||
return jwt.MapClaims{}
|
||||
},
|
||||
IdentityHandler: func(c *gin.Context) interface{} {
|
||||
claims := jwt.ExtractClaims(c)
|
||||
return &Login{
|
||||
Username: claims[identityKey].(string),
|
||||
}
|
||||
},
|
||||
Authenticator: func(c *gin.Context) (interface{}, error) {
|
||||
var (
|
||||
password string
|
||||
loginVals Login
|
||||
)
|
||||
if err := c.ShouldBind(&loginVals); err != nil {
|
||||
return "", jwt.ErrMissingLoginValues
|
||||
}
|
||||
userID := loginVals.Username
|
||||
pass := loginVals.Password
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID != "admin" {
|
||||
mysql := core.GetMysql()
|
||||
user := mysql.GetUserByName(userID)
|
||||
if user == nil {
|
||||
return nil, jwt.ErrFailedAuthentication
|
||||
}
|
||||
password = user.EncryptPass
|
||||
} else {
|
||||
if password, err = core.GetValue(userID + "_pass"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if password == pass {
|
||||
return &loginVals, nil
|
||||
}
|
||||
return nil, jwt.ErrFailedAuthentication
|
||||
},
|
||||
Authorizator: func(data interface{}, c *gin.Context) bool {
|
||||
if _, ok := data.(*Login); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
Unauthorized: func(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, gin.H{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
},
|
||||
TokenLookup: "header: Authorization, query: token, cookie: jwt",
|
||||
TokenHeadName: "Bearer",
|
||||
TimeFunc: time.Now,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("JWT Error:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func updateUser(c *gin.Context) {
|
||||
responseBody := controller.ResponseBody{Msg: "success"}
|
||||
defer controller.TimeCost(time.Now(), &responseBody)
|
||||
username := c.DefaultPostForm("username", "admin")
|
||||
pass := c.PostForm("password")
|
||||
err := core.SetValue(fmt.Sprintf("%s_pass", username), pass)
|
||||
if err != nil {
|
||||
responseBody.Msg = err.Error()
|
||||
}
|
||||
c.JSON(200, responseBody)
|
||||
}
|
||||
|
||||
// RequestUsername 获取请求接口的用户名
|
||||
func RequestUsername(c *gin.Context) string {
|
||||
claims := jwt.ExtractClaims(c)
|
||||
return claims[identityKey].(string)
|
||||
}
|
||||
|
||||
// Auth 权限router
|
||||
func Auth(r *gin.Engine, timeout int) *jwt.GinJWTMiddleware {
|
||||
jwtInit(timeout)
|
||||
|
||||
newInstall := gin.H{"code": 201, "message": "No administrator account found inside the database", "data": nil}
|
||||
r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
|
||||
claims := jwt.ExtractClaims(c)
|
||||
fmt.Printf("NoRoute claims: %#v\n", claims)
|
||||
c.JSON(404, gin.H{"code": 404, "message": "Page not found"})
|
||||
})
|
||||
r.GET("/auth/check", func(c *gin.Context) {
|
||||
result, _ := core.GetValue("admin_pass")
|
||||
if result == "" {
|
||||
c.JSON(201, newInstall)
|
||||
} else {
|
||||
title, err := core.GetValue("login_title")
|
||||
if err != nil {
|
||||
title = "trojan 管理平台"
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": map[string]string{
|
||||
"title": title,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
r.POST("/auth/login", authMiddleware.LoginHandler)
|
||||
r.POST("/auth/register", updateUser)
|
||||
authO := r.Group("/auth")
|
||||
authO.Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
authO.GET("/loginUser", func(c *gin.Context) {
|
||||
result, _ := core.GetValue("admin_pass")
|
||||
if result == "" {
|
||||
c.JSON(201, newInstall)
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": map[string]string{
|
||||
"username": RequestUsername(c),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
authO.POST("/reset_pass", updateUser)
|
||||
authO.POST("/logout", authMiddleware.LogoutHandler)
|
||||
authO.POST("/refresh_token", authMiddleware.RefreshHandler)
|
||||
}
|
||||
return authMiddleware
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"trojan/core"
|
||||
"trojan/util"
|
||||
"trojan/web/controller"
|
||||
)
|
||||
|
||||
//go:embed templates/*
|
||||
var f embed.FS
|
||||
|
||||
func userRouter(router *gin.Engine) {
|
||||
user := router.Group("/trojan/user")
|
||||
{
|
||||
user.GET("", func(c *gin.Context) {
|
||||
requestUser := RequestUsername(c)
|
||||
c.JSON(200, controller.UserList(requestUser))
|
||||
})
|
||||
user.GET("/page", func(c *gin.Context) {
|
||||
curPageStr := c.DefaultQuery("curPage", "1")
|
||||
pageSizeStr := c.DefaultQuery("pageSize", "10")
|
||||
curPage, _ := strconv.Atoi(curPageStr)
|
||||
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||
c.JSON(200, controller.PageUserList(curPage, pageSize))
|
||||
})
|
||||
user.POST("", func(c *gin.Context) {
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
c.JSON(200, controller.CreateUser(username, password))
|
||||
})
|
||||
user.POST("/update", func(c *gin.Context) {
|
||||
sid := c.PostForm("id")
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
id, _ := strconv.Atoi(sid)
|
||||
c.JSON(200, controller.UpdateUser(uint(id), username, password))
|
||||
})
|
||||
user.POST("/expire", func(c *gin.Context) {
|
||||
sid := c.PostForm("id")
|
||||
sDays := c.PostForm("useDays")
|
||||
id, _ := strconv.Atoi(sid)
|
||||
useDays, _ := strconv.Atoi(sDays)
|
||||
c.JSON(200, controller.SetExpire(uint(id), uint(useDays)))
|
||||
})
|
||||
user.DELETE("/expire", func(c *gin.Context) {
|
||||
sid := c.Query("id")
|
||||
id, _ := strconv.Atoi(sid)
|
||||
c.JSON(200, controller.CancelExpire(uint(id)))
|
||||
})
|
||||
user.DELETE("", func(c *gin.Context) {
|
||||
stringId := c.Query("id")
|
||||
id, _ := strconv.Atoi(stringId)
|
||||
c.JSON(200, controller.DelUser(uint(id)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func trojanRouter(router *gin.Engine) {
|
||||
router.POST("/trojan/start", func(c *gin.Context) {
|
||||
c.JSON(200, controller.Start())
|
||||
})
|
||||
router.POST("/trojan/stop", func(c *gin.Context) {
|
||||
c.JSON(200, controller.Stop())
|
||||
})
|
||||
router.POST("/trojan/restart", func(c *gin.Context) {
|
||||
c.JSON(200, controller.Restart())
|
||||
})
|
||||
router.GET("/trojan/loglevel", func(c *gin.Context) {
|
||||
c.JSON(200, controller.GetLogLevel())
|
||||
})
|
||||
router.GET("/trojan/export", func(c *gin.Context) {
|
||||
result := controller.ExportCsv(c)
|
||||
if result != nil {
|
||||
c.JSON(200, result)
|
||||
}
|
||||
})
|
||||
router.POST("/trojan/import", func(c *gin.Context) {
|
||||
c.JSON(200, controller.ImportCsv(c))
|
||||
})
|
||||
router.POST("/trojan/update", func(c *gin.Context) {
|
||||
c.JSON(200, controller.Update())
|
||||
})
|
||||
router.POST("/trojan/switch", func(c *gin.Context) {
|
||||
tType := c.DefaultPostForm("type", "trojan")
|
||||
c.JSON(200, controller.SetTrojanType(tType))
|
||||
})
|
||||
router.POST("/trojan/loglevel", func(c *gin.Context) {
|
||||
slevel := c.DefaultPostForm("level", "1")
|
||||
level, _ := strconv.Atoi(slevel)
|
||||
c.JSON(200, controller.SetLogLevel(level))
|
||||
})
|
||||
router.POST("/trojan/domain", func(c *gin.Context) {
|
||||
c.JSON(200, controller.SetDomain(c.PostForm("domain")))
|
||||
})
|
||||
router.GET("/trojan/log", func(c *gin.Context) {
|
||||
controller.Log(c)
|
||||
})
|
||||
}
|
||||
|
||||
func dataRouter(router *gin.Engine) {
|
||||
data := router.Group("/trojan/data")
|
||||
{
|
||||
data.POST("", func(c *gin.Context) {
|
||||
sID := c.PostForm("id")
|
||||
sQuota := c.PostForm("quota")
|
||||
id, _ := strconv.Atoi(sID)
|
||||
quota, _ := strconv.Atoi(sQuota)
|
||||
c.JSON(200, controller.SetData(uint(id), quota))
|
||||
})
|
||||
data.DELETE("", func(c *gin.Context) {
|
||||
sID := c.Query("id")
|
||||
id, _ := strconv.Atoi(sID)
|
||||
c.JSON(200, controller.CleanData(uint(id)))
|
||||
})
|
||||
data.POST("/resetDay", func(c *gin.Context) {
|
||||
dayStr := c.DefaultPostForm("day", "1")
|
||||
day, _ := strconv.Atoi(dayStr)
|
||||
c.JSON(200, controller.UpdateResetDay(uint(day)))
|
||||
})
|
||||
data.GET("/resetDay", func(c *gin.Context) {
|
||||
c.JSON(200, controller.GetResetDay())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func commonRouter(router *gin.Engine) {
|
||||
common := router.Group("/common")
|
||||
{
|
||||
common.GET("/version", func(c *gin.Context) {
|
||||
c.JSON(200, controller.Version())
|
||||
})
|
||||
common.GET("/serverInfo", func(c *gin.Context) {
|
||||
c.JSON(200, controller.ServerInfo())
|
||||
})
|
||||
common.GET("/clashRules", func(c *gin.Context) {
|
||||
c.JSON(200, controller.GetClashRules())
|
||||
})
|
||||
common.POST("/clashRules", func(c *gin.Context) {
|
||||
rules := c.PostForm("rules")
|
||||
c.JSON(200, controller.SetClashRules(rules))
|
||||
})
|
||||
common.DELETE("/clashRules", func(c *gin.Context) {
|
||||
c.JSON(200, controller.ResetClashRules())
|
||||
})
|
||||
common.POST("/loginInfo", func(c *gin.Context) {
|
||||
c.JSON(200, controller.SetLoginInfo(c.PostForm("title")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func staticRouter(router *gin.Engine) {
|
||||
staticFs, _ := fs.Sub(f, "templates/static")
|
||||
router.StaticFS("/static", http.FS(staticFs))
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
indexHTML, _ := f.ReadFile("templates/" + "index.html")
|
||||
c.Writer.Write(indexHTML)
|
||||
})
|
||||
}
|
||||
|
||||
func noTokenRouter(router *gin.Engine) {
|
||||
router.GET("/trojan/user/subscribe", func(c *gin.Context) {
|
||||
controller.ClashSubInfo(c)
|
||||
})
|
||||
}
|
||||
|
||||
// Start web启动入口
|
||||
func Start(host string, port, timeout int, isSSL bool) {
|
||||
router := gin.Default()
|
||||
router.SetTrustedProxies(nil)
|
||||
router.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
staticRouter(router)
|
||||
noTokenRouter(router)
|
||||
router.Use(Auth(router, timeout).MiddlewareFunc())
|
||||
trojanRouter(router)
|
||||
userRouter(router)
|
||||
dataRouter(router)
|
||||
commonRouter(router)
|
||||
controller.ScheduleTask()
|
||||
controller.CollectTask()
|
||||
util.OpenPort(port)
|
||||
if isSSL {
|
||||
config := core.GetConfig()
|
||||
ssl := &config.SSl
|
||||
router.RunTLS(fmt.Sprintf("%s:%d", host, port), ssl.Cert, ssl.Key)
|
||||
} else {
|
||||
router.Run(fmt.Sprintf("%s:%d", host, port))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user