更改目录结构

This commit is contained in:
bjdgyc
2021-03-01 15:46:08 +08:00
parent 3464d1d10e
commit 0f91c779e3
105 changed files with 29099 additions and 96 deletions

79
server/admin/api_base.go Normal file
View File

@@ -0,0 +1,79 @@
package admin
import (
"fmt"
"net/http"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/gorilla/mux"
)
// Login 登陆接口
func Login(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd))
_ = r.ParseForm()
adminUser := r.PostFormValue("admin_user")
adminPass := r.PostFormValue("admin_pass")
// 认证错误
if !(adminUser == base.Cfg.AdminUser &&
utils.PasswordVerify(adminPass, base.Cfg.AdminPass)) {
RespError(w, RespUserOrPassErr)
return
}
// token有效期
expiresAt := time.Now().Unix() + 3600*3
jwtData := map[string]interface{}{"admin_user": adminUser}
tokenString, err := SetJwtData(jwtData, expiresAt)
if err != nil {
RespError(w, 1, err)
return
}
data := make(map[string]interface{})
data["token"] = tokenString
data["admin_user"] = adminUser
data["expires_at"] = expiresAt
RespSucess(w, data)
}
func authMiddleware(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")
if r.Method == http.MethodOptions {
return
}
route := mux.CurrentRoute(r)
name := route.GetName()
// fmt.Println("bb", r.URL.Path, name)
if utils.InArrStr([]string{"login", "index", "static"}, name) {
// 不进行鉴权
next.ServeHTTP(w, r)
return
}
// 进行登陆鉴权
jwtToken := r.Header.Get("Jwt")
if jwtToken == "" {
jwtToken = r.FormValue("jwt")
}
data, err := GetJwtData(jwtToken)
if err != nil || base.Cfg.AdminUser != fmt.Sprint(data["admin_user"]) {
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

108
server/admin/api_group.go Normal file
View File

@@ -0,0 +1,108 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http"
"strconv"
"github.com/bjdgyc/anylink/dbdata"
)
func GroupList(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS)
if page < 1 {
page = 1
}
var pageSize = dbdata.PageSize
count := dbdata.CountAll(&dbdata.Group{})
var datas []dbdata.Group
err := dbdata.All(&datas, pageSize, page)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
data := map[string]interface{}{
"count": count,
"page_size": pageSize,
"datas": datas,
}
RespSucess(w, data)
}
func GroupNames(w http.ResponseWriter, r *http.Request) {
var names = dbdata.GetGroupNames()
data := map[string]interface{}{
"count": len(names),
"page_size": 0,
"datas": names,
}
RespSucess(w, data)
}
func GroupDetail(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "Id错误")
return
}
var data dbdata.Group
err := dbdata.One("Id", id, &data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, data)
}
func GroupSet(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
defer r.Body.Close()
v := &dbdata.Group{}
err = json.Unmarshal(body, v)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
err = dbdata.SetGroup(v)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, nil)
}
func GroupDel(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "Id错误")
return
}
data := dbdata.Group{Id: id}
err := dbdata.Del(&data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, nil)
}

111
server/admin/api_ip_map.go Normal file
View File

@@ -0,0 +1,111 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http"
"strconv"
"time"
"github.com/bjdgyc/anylink/dbdata"
)
func UserIpMapList(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS)
if page < 1 {
page = 1
}
var pageSize = dbdata.PageSize
count := dbdata.CountAll(&dbdata.IpMap{})
var datas []dbdata.IpMap
err := dbdata.All(&datas, pageSize, page)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
data := map[string]interface{}{
"count": count,
"page_size": pageSize,
"datas": datas,
}
RespSucess(w, data)
}
func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "用户名错误")
return
}
var data dbdata.IpMap
err := dbdata.One("Id", id, &data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, data)
}
func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
defer r.Body.Close()
v := &dbdata.IpMap{}
err = json.Unmarshal(body, v)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
// fmt.Println(v, len(v.Ip), len(v.MacAddr))
if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
RespError(w, RespParamErr, "IP或MAC错误")
return
}
v.UpdatedAt = time.Now()
err = dbdata.Save(v)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, nil)
}
func UserIpMapDel(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "IP映射id错误")
return
}
data := dbdata.IpMap{Id: id}
err := dbdata.Del(&data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, nil)
}

62
server/admin/api_other.go Normal file
View File

@@ -0,0 +1,62 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/bjdgyc/anylink/dbdata"
)
func setOtherGet(data interface{}, w http.ResponseWriter) {
err := dbdata.SettingGet(data)
if err != nil && !dbdata.CheckErrNotFound(err) {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, data)
}
func setOtherEdit(data interface{}, w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
defer r.Body.Close()
err = json.Unmarshal(body, data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
// fmt.Println(data)
err = dbdata.SettingSet(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, data)
}
func SetOtherSmtp(w http.ResponseWriter, r *http.Request) {
data := &dbdata.SettingSmtp{}
setOtherGet(data, w)
}
func SetOtherSmtpEdit(w http.ResponseWriter, r *http.Request) {
data := &dbdata.SettingSmtp{}
setOtherEdit(data, w, r)
}
func SetOther(w http.ResponseWriter, r *http.Request) {
data := &dbdata.SettingOther{}
setOtherGet(data, w)
}
func SetOtherEdit(w http.ResponseWriter, r *http.Request) {
data := &dbdata.SettingOther{}
setOtherEdit(data, w, r)
}

93
server/admin/api_set.go Normal file
View File

@@ -0,0 +1,93 @@
package admin
import (
"fmt"
"net/http"
"runtime"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
)
func SetHome(w http.ResponseWriter, r *http.Request) {
data := make(map[string]interface{})
sess := sessdata.OnlineSess()
data["counts"] = map[string]int{
"online": len(sess),
"user": dbdata.CountAll(&dbdata.User{}),
"group": dbdata.CountAll(&dbdata.Group{}),
"ip_map": dbdata.CountAll(&dbdata.IpMap{}),
}
RespSucess(w, data)
}
func SetSystem(w http.ResponseWriter, r *http.Request) {
data := make(map[string]interface{})
m, _ := mem.VirtualMemory()
data["mem"] = map[string]interface{}{
"total": utils.HumanByte(m.Total),
"free": utils.HumanByte(m.Free),
"percent": decimal(m.UsedPercent),
}
d, _ := disk.Usage("/")
data["disk"] = map[string]interface{}{
"total": utils.HumanByte(d.Total),
"free": utils.HumanByte(d.Free),
"percent": decimal(d.UsedPercent),
}
cc, _ := cpu.Counts(true)
c, _ := cpu.Info()
ci := c[0]
cpuUsedPercent, _ := cpu.Percent(0, false)
cup := cpuUsedPercent[0]
if cup == 0 {
cup = 1
}
data["cpu"] = map[string]interface{}{
"core": cc,
"modelName": ci.ModelName,
"ghz": fmt.Sprintf("%.2f GHz", ci.Mhz/1000),
"percent": decimal(cup),
}
hi, _ := host.Info()
l, _ := load.Avg()
data["sys"] = map[string]interface{}{
"goOs": runtime.GOOS,
"goArch": runtime.GOARCH,
"goVersion": runtime.Version(),
"goroutine": runtime.NumGoroutine(),
"hostname": hi.Hostname,
"platform": fmt.Sprintf("%v %v %v", hi.Platform, hi.PlatformFamily, hi.PlatformVersion),
"kernel": hi.KernelVersion,
"load": fmt.Sprint(l.Load1, l.Load5, l.Load15),
}
RespSucess(w, data)
}
func SetSoft(w http.ResponseWriter, r *http.Request) {
data := base.ServerCfg2Slice()
RespSucess(w, data)
}
func decimal(f float64) float64 {
i := int(f * 100)
return float64(i) / 100
}

245
server/admin/api_user.go Normal file
View File

@@ -0,0 +1,245 @@
package admin
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/skip2/go-qrcode"
)
func UserList(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
prefix := r.FormValue("prefix")
pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS)
if page < 1 {
page = 1
}
var (
pageSize = dbdata.PageSize
count int
datas []dbdata.User
err error
)
// 查询前缀匹配
if len(prefix) > 0 {
count = pageSize
err = dbdata.Prefix("Username", prefix, &datas, pageSize, 1)
} else {
count = dbdata.CountAll(&dbdata.User{})
err = dbdata.All(&datas, pageSize, page)
}
if err != nil && !dbdata.CheckErrNotFound(err) {
RespError(w, RespInternalErr, err)
return
}
data := map[string]interface{}{
"count": count,
"page_size": pageSize,
"datas": datas,
}
RespSucess(w, data)
}
func UserDetail(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "用户名错误")
return
}
var user dbdata.User
err := dbdata.One("Id", id, &user)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, user)
}
func UserSet(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
defer r.Body.Close()
data := &dbdata.User{}
err = json.Unmarshal(body, data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
err = dbdata.SetUser(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
// 发送邮件
if data.SendEmail {
err = userAccountMail(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
}
RespSucess(w, nil)
}
func UserDel(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
if id < 1 {
RespError(w, RespParamErr, "用户id错误")
return
}
user := dbdata.User{Id: id}
err := dbdata.Del(&user)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
RespSucess(w, nil)
}
func UserOtpQr(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
b64 := r.FormValue("b64")
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
var user dbdata.User
err := dbdata.One("Id", id, &user)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
issuer := base.Cfg.Issuer
qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
qr, _ := qrcode.New(qrstr, qrcode.High)
if b64 == "1" {
data, _ := qr.PNG(300)
s := base64.StdEncoding.EncodeToString(data)
_, err = fmt.Fprint(w, s)
if err != nil {
base.Error(err)
}
return
}
err = qr.Write(300, w)
if err != nil {
base.Error(err)
}
}
// 在线用户
func UserOnline(w http.ResponseWriter, r *http.Request) {
datas := sessdata.OnlineSess()
data := map[string]interface{}{
"count": len(datas),
"page_size": dbdata.PageSize,
"datas": datas,
}
RespSucess(w, data)
}
func UserOffline(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
token := r.FormValue("token")
sessdata.CloseSess(token)
RespSucess(w, nil)
}
func UserReline(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
token := r.FormValue("token")
sessdata.CloseCSess(token)
RespSucess(w, nil)
}
type userAccountMailData struct {
Issuer string
LinkAddr string
Group string
Username string
PinCode string
OtpImg string
}
func userAccountMail(user *dbdata.User) error {
// 平台通知
htmlBody := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Hello AnyLink!</title>
</head>
<body>
%s
</body>
</html>
`
dataOther := &dbdata.SettingOther{}
err := dbdata.SettingGet(dataOther)
if err != nil {
base.Error(err)
return err
}
htmlBody = fmt.Sprintf(htmlBody, dataOther.AccountMail)
// fmt.Println(htmlBody)
// token有效期3天
expiresAt := time.Now().Unix() + 3600*24*3
jwtData := map[string]interface{}{"id": user.Id}
tokenString, err := SetJwtData(jwtData, expiresAt)
if err != nil {
return err
}
data := userAccountMailData{
LinkAddr: base.Cfg.LinkAddr,
Group: strings.Join(user.Groups, ","),
Username: user.Username,
PinCode: user.PinCode,
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", base.Cfg.LinkAddr, user.Id, tokenString),
}
w := bytes.NewBufferString("")
t, _ := template.New("auth_complete").Parse(htmlBody)
err = t.Execute(w, data)
if err != nil {
return err
}
// fmt.Println(w.String())
return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String())
}

108
server/admin/common.go Normal file
View File

@@ -0,0 +1,108 @@
package admin
import (
"crypto/tls"
"errors"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/dgrijalva/jwt-go"
mail "github.com/xhit/go-simple-mail/v2"
// "github.com/mojocn/base64Captcha"
)
func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) {
jwtData := jwt.MapClaims{"exp": expiresAt}
for k, v := range data {
jwtData[k] = v
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtData)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString([]byte(base.Cfg.JwtSecret))
return tokenString, err
}
func GetJwtData(jwtToken string) (map[string]interface{}, error) {
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return []byte(base.Cfg.JwtSecret), nil
})
if err != nil || !token.Valid {
return nil, err
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("data is parse err")
}
return claims, nil
}
func SendMail(subject, to, htmlBody string) error {
dataSmtp := &dbdata.SettingSmtp{}
err := dbdata.SettingGet(dataSmtp)
if err != nil {
base.Error(err)
return err
}
server := mail.NewSMTPClient()
// SMTP Server
server.Host = dataSmtp.Host
server.Port = dataSmtp.Port
server.Username = dataSmtp.Username
server.Password = dataSmtp.Password
if dataSmtp.UseSSl {
server.Encryption = mail.EncryptionSSL
}
// Since v2.3.0 you can specified authentication type:
// - PLAIN (default)
// - LOGIN
// - CRAM-MD5
server.Authentication = mail.AuthPlain
// Variable to keep alive connection
server.KeepAlive = false
// Timeout for connect to SMTP Server
server.ConnectTimeout = 10 * time.Second
// Timeout for send the data and wait respond
server.SendTimeout = 10 * time.Second
// Set TLSConfig to provide custom TLS configuration. For example,
// to skip TLS verification (useful for testing):
server.TLSConfig = &tls.Config{InsecureSkipVerify: true}
// SMTP client
smtpClient, err := server.Connect()
if err != nil {
base.Error(err)
return err
}
// New email simple html with inline and CC
email := mail.NewMSG()
email.SetFrom(dataSmtp.From).
AddTo(to).
SetSubject(subject)
email.SetBody(mail.TextHTML, htmlBody)
// Call Send and pass the client
err = email.Send(smtpClient)
if err != nil {
base.Error(err)
}
return err
}

View File

@@ -0,0 +1,23 @@
package admin
import (
"testing"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/stretchr/testify/assert"
)
func TestJwtData(t *testing.T) {
assert := assert.New(t)
base.Cfg.JwtSecret = "dsfasfdfsadfasdfasd3sdaf"
data := map[string]interface{}{
"key": "value",
}
expiresAt := time.Now().Add(time.Minute).Unix()
token, err := SetJwtData(data, expiresAt)
assert.Nil(err)
dataN, err := GetJwtData(token)
assert.Nil(err)
assert.Equal(dataN["key"], "value")
}

15
server/admin/error.go Normal file
View File

@@ -0,0 +1,15 @@
package admin
// 返回码
const (
RespSuccess = 0
RespInternalErr = 1
RespTokenErr = 2
RespUserOrPassErr = 3
RespParamErr = 4
)
var RespMap = map[int]string{
RespTokenErr: "客户端TOKEN错误",
RespUserOrPassErr: "用户名或密码错误",
}

64
server/admin/resp.go Normal file
View File

@@ -0,0 +1,64 @@
package admin
import (
"encoding/json"
"fmt"
"net/http"
"runtime"
"github.com/bjdgyc/anylink/base"
)
type Resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Location string `json:"location"`
Data interface{} `json:"data"`
}
func respHttp(w http.ResponseWriter, respCode int, data interface{}, errS ...interface{}) {
resp := Resp{
Code: respCode,
Msg: "success",
Data: data,
}
_, file, line, _ := runtime.Caller(2)
resp.Location = fmt.Sprintf("%v:%v", file, line)
if respCode != 0 {
resp.Msg = ""
if v, ok := RespMap[respCode]; ok {
resp.Msg += v
}
if len(errS) > 0 {
resp.Msg += fmt.Sprint(errS...)
}
}
b, err := json.Marshal(resp)
if err != nil {
base.Error(err, resp)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err = w.Write(b)
if err != nil {
base.Error(err)
}
// 记录返回数据
// logger.Category("response").Debug(string(b))
}
func RespSucess(w http.ResponseWriter, data interface{}) {
respHttp(w, 0, data, "")
}
func RespError(w http.ResponseWriter, respCode int, errS ...interface{}) {
respHttp(w, respCode, nil, errS...)
}
func RespData(w http.ResponseWriter, data interface{}, err error) {
respHttp(w, http.StatusOK, data, "")
}

39
server/admin/resp_test.go Normal file
View File

@@ -0,0 +1,39 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRespSucess(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespSucess(w, "data")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 0)
assert.Equal(res.Data, "data")
}
func TestRespError(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespError(w, 10, "err-msg")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 10)
assert.Equal(res.Msg, "err-msg")
}

71
server/admin/server.go Normal file
View File

@@ -0,0 +1,71 @@
// admin:后台管理接口
package admin
import (
"net/http"
"net/http/pprof"
"github.com/bjdgyc/anylink/base"
"github.com/gorilla/mux"
)
// 开启服务
func StartAdmin() {
r := mux.NewRouter()
r.Use(authMiddleware)
r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)).Name("index")
r.PathPrefix("/ui/").Handler(
http.StripPrefix("/ui/", http.FileServer(http.Dir(base.Cfg.UiPath))),
).Name("static")
r.HandleFunc("/base/login", Login).Name("login")
r.HandleFunc("/set/home", SetHome)
r.HandleFunc("/set/system", SetSystem)
r.HandleFunc("/set/soft", SetSoft)
r.HandleFunc("/set/other", SetOther)
r.HandleFunc("/set/other/edit", SetOtherEdit)
r.HandleFunc("/set/other/smtp", SetOtherSmtp)
r.HandleFunc("/set/other/smtp/edit", SetOtherSmtpEdit)
r.HandleFunc("/user/list", UserList)
r.HandleFunc("/user/detail", UserDetail)
r.HandleFunc("/user/set", UserSet)
r.HandleFunc("/user/del", UserDel)
r.HandleFunc("/user/online", UserOnline)
r.HandleFunc("/user/offline", UserOffline)
r.HandleFunc("/user/reline", UserReline)
r.HandleFunc("/user/otp_qr", UserOtpQr)
r.HandleFunc("/user/ip_map/list", UserIpMapList)
r.HandleFunc("/user/ip_map/detail", UserIpMapDetail)
r.HandleFunc("/user/ip_map/set", UserIpMapSet)
r.HandleFunc("/user/ip_map/del", UserIpMapDel)
r.HandleFunc("/group/list", GroupList)
r.HandleFunc("/group/names", GroupNames)
r.HandleFunc("/group/detail", GroupDetail)
r.HandleFunc("/group/set", GroupSet)
r.HandleFunc("/group/del", GroupDel)
// pprof
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
base.Info("Listen admin", base.Cfg.AdminAddr)
err := http.ListenAndServe(base.Cfg.AdminAddr, r)
if err != nil {
base.Fatal(err)
}
}
func location(url string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", url)
w.WriteHeader(http.StatusFound)
}
}