Files
anylink/server/admin/api_user.go
2025-08-29 15:11:38 +08:00

413 lines
8.6 KiB
Go
Raw 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 admin
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"text/template"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
"github.com/skip2/go-qrcode"
mail "github.com/xhit/go-simple-mail/v2"
)
func UserList(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
prefix := r.FormValue("prefix")
prefix = strings.TrimSpace(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 {
fuzzy := "%" + prefix + "%"
where := "username LIKE ? OR nickname LIKE ? OR email LIKE ? OR type LIKE ?"
count = dbdata.FindWhereCount(&dbdata.User{}, where, fuzzy, fuzzy, fuzzy, fuzzy)
err = dbdata.FindWhere(&datas, pageSize, page, where, fuzzy, fuzzy, fuzzy, fuzzy)
} else {
count = dbdata.CountAll(&dbdata.User{})
err = dbdata.Find(&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 := io.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
}
if len(data.PinCode) < 6 {
data.PinCode = utils.RandomRunes(8)
base.Info("用户", data.Username, "随机密码为:", data.PinCode)
}
plainpwd := data.PinCode
err = dbdata.SetUser(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
data.PinCode = plainpwd
// 发送邮件
if data.SendEmail {
err = userAccountMail(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
}
// 修改用户资料后执行过期用户检测
sessdata.CloseUserLimittimeSession()
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()
b64S := r.FormValue("b64")
idS := r.FormValue("id")
id, _ := strconv.Atoi(idS)
var b64 bool
if b64S == "1" {
b64 = true
}
data, err := userOtpQr(id, b64)
if err != nil {
base.Error(err)
}
io.WriteString(w, data)
}
func userOtpQr(uid int, b64 bool) (string, error) {
var user dbdata.User
err := dbdata.One("Id", uid, &user)
if err != nil {
return "", err
}
issuer := url.QueryEscape(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 {
data, err := qr.PNG(300)
if err != nil {
return "", err
}
s := base64.StdEncoding.EncodeToString(data)
return s, nil
}
buf := bytes.NewBuffer(nil)
err = qr.Write(300, buf)
return buf.String(), err
}
// 在线用户
func UserOnline(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
search_cate := r.FormValue("search_cate")
search_text := r.FormValue("search_text")
show_sleeper := r.FormValue("show_sleeper")
showSleeper, _ := strconv.ParseBool(show_sleeper)
// one_offline := r.FormValue("one_offline")
// datas := sessdata.OnlineSess()
datas := sessdata.GetOnlineSess(search_cate, search_text, showSleeper)
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, dbdata.UserLogoutAdmin)
RespSucess(w, nil)
}
func UserReline(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
token := r.FormValue("token")
sessdata.CloseCSess(token)
RespSucess(w, nil)
}
// 批量发送邮件
func UserBatchSendEmail(w http.ResponseWriter, r *http.Request) {
var req struct {
UserIds []int `json:"user_ids"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespError(w, RespInternalErr, err)
return
}
if len(req.UserIds) == 0 {
RespError(w, RespInternalErr, errors.New("用户ID列表不能为空"))
return
}
successCount := 0
failCount := 0
for _, userId := range req.UserIds {
user := &dbdata.User{}
err := dbdata.One("Id", userId, user)
if err != nil {
failCount++
continue
}
// 发送邮件
err = userAccountMail(user)
if err != nil {
base.Error("批量发送邮件失败:", user.Username, err)
failCount++
} else {
successCount++
}
}
msg := fmt.Sprintf("批量发送邮件完成,成功:%d失败%d", successCount, failCount)
if successCount > 0 {
RespSucess(w, msg)
} else {
RespError(w, RespInternalErr, errors.New(msg))
}
}
// 批量删除用户
func UserBatchDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
UserIds []int `json:"user_ids"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespError(w, RespInternalErr, err)
return
}
if len(req.UserIds) == 0 {
RespError(w, RespInternalErr, errors.New("用户ID列表不能为空"))
return
}
successCount := 0
failCount := 0
for _, userId := range req.UserIds {
user := &dbdata.User{}
err := dbdata.One("Id", userId, user)
if err != nil {
failCount++
continue
}
err = dbdata.Del(user)
if err != nil {
base.Error("批量删除用户失败:", user.Username, err)
failCount++
} else {
successCount++
}
}
msg := fmt.Sprintf("批量删除完成,成功:%d失败%d", successCount, failCount)
if successCount > 0 {
RespSucess(w, msg)
} else {
RespError(w, RespInternalErr, errors.New(msg))
}
}
type userAccountMailData struct {
Issuer string
LinkAddr string
Group string
Username string
Nickname string
PinCode string
LimitTime string
OtpImg string
OtpImgBase64 string
DisableOtp bool
}
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
}
setting := &dbdata.SettingOther{}
err = dbdata.SettingGet(setting)
if err != nil {
base.Error(err)
return err
}
otpData, _ := userOtpQr(user.Id, true)
data := userAccountMailData{
Issuer: base.Cfg.Issuer,
LinkAddr: setting.LinkAddr,
Group: strings.Join(user.Groups, ","),
Username: user.Username,
Nickname: user.Nickname,
PinCode: user.PinCode,
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
OtpImgBase64: "data:image/png;base64," + otpData,
DisableOtp: user.DisableOtp,
}
if user.Type == "ldap" {
data.PinCode = "同ldap密码"
}
if user.LimitTime == nil {
data.LimitTime = "无限制"
} else {
data.LimitTime = user.LimitTime.Local().Format("2006-01-02")
}
w := bytes.NewBufferString("")
t, _ := template.New("auth_complete").Parse(htmlBody)
err = t.Execute(w, data)
if err != nil {
return err
}
// fmt.Println(w.String())
var attach *mail.File
if user.DisableOtp {
attach = nil
} else {
imgData, _ := userOtpQr(user.Id, false)
attach = &mail.File{
MimeType: "image/png",
Name: "userOtpQr.png",
Data: []byte(imgData),
Inline: true,
}
}
return SendMail(base.Cfg.Issuer, user.Email, w.String(), attach)
}