release v1.2.0

This commit is contained in:
dushixiang
2021-10-31 17:15:35 +08:00
parent 4665ab6f78
commit 6132a05786
173 changed files with 37928 additions and 9349 deletions

View File

@ -0,0 +1,65 @@
package service
import (
"next-terminal/server/global/gateway"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/term"
)
type AccessGatewayService struct {
accessGatewayRepository *repository.AccessGatewayRepository
}
func NewAccessGatewayService(accessGatewayRepository *repository.AccessGatewayRepository) *AccessGatewayService {
accessGatewayService = &AccessGatewayService{accessGatewayRepository: accessGatewayRepository}
return accessGatewayService
}
func (r AccessGatewayService) GetGatewayAndReconnectById(accessGatewayId string) (g *gateway.Gateway, err error) {
g = gateway.GlobalGatewayManager.GetById(accessGatewayId)
if g == nil || !g.Connected {
accessGateway, err := r.accessGatewayRepository.FindById(accessGatewayId)
if err != nil {
return nil, err
}
g = r.ReConnect(&accessGateway)
}
return g, nil
}
func (r AccessGatewayService) GetGatewayById(accessGatewayId string) (g *gateway.Gateway, err error) {
g = gateway.GlobalGatewayManager.GetById(accessGatewayId)
return g, nil
}
func (r AccessGatewayService) ReConnectAll() error {
gateways, err := r.accessGatewayRepository.FindAll()
if err != nil {
return err
}
for i := range gateways {
r.ReConnect(&gateways[i])
}
return nil
}
func (r AccessGatewayService) ReConnect(m *model.AccessGateway) *gateway.Gateway {
log.Debugf("重建接入网关「%v」中...", m.Name)
r.DisconnectById(m.ID)
sshClient, err := term.NewSshClient(m.IP, m.Port, m.Username, m.Password, m.PrivateKey, m.Passphrase)
var g *gateway.Gateway
if err != nil {
g = gateway.NewGateway(m.ID, m.Localhost, false, err.Error(), nil)
} else {
g = gateway.NewGateway(m.ID, m.Localhost, true, "", sshClient)
}
gateway.GlobalGatewayManager.Add <- g
log.Debugf("重建接入网关「%v」完成", m.Name)
return g
}
func (r AccessGatewayService) DisconnectById(accessGatewayId string) {
gateway.GlobalGatewayManager.Del <- accessGatewayId
}

60
server/service/asset.go Normal file
View File

@ -0,0 +1,60 @@
package service
import (
"next-terminal/server/config"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type AssetService struct {
assetRepository *repository.AssetRepository
}
func NewAssetService(assetRepository *repository.AssetRepository) *AssetService {
return &AssetService{assetRepository: assetRepository}
}
func (r AssetService) Encrypt() error {
items, err := r.assetRepository.FindAll()
if err != nil {
return err
}
for i := range items {
item := items[i]
if item.Encrypted {
continue
}
if err := r.assetRepository.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := r.assetRepository.UpdateById(&item, item.ID); err != nil {
return err
}
}
return nil
}
func (r AssetService) CheckStatus(accessGatewayId string, ip string, port int) (active bool, err error) {
if accessGatewayId != "" && accessGatewayId != "-" {
g, e1 := accessGatewayService.GetGatewayAndReconnectById(accessGatewayId)
if err != nil {
return false, e1
}
uuid := utils.UUID()
exposedIP, exposedPort, e2 := g.OpenSshTunnel(uuid, ip, port)
if e2 != nil {
return false, e2
}
defer g.CloseSshTunnel(uuid)
if g.Connected {
active, err = utils.Tcping(exposedIP, exposedPort)
} else {
active = false
}
} else {
active, err = utils.Tcping(ip, port)
}
return active, err
}

View File

@ -0,0 +1,34 @@
package service
import (
"next-terminal/server/config"
"next-terminal/server/repository"
)
type CredentialService struct {
credentialRepository *repository.CredentialRepository
}
func NewCredentialService(credentialRepository *repository.CredentialRepository) *CredentialService {
return &CredentialService{credentialRepository: credentialRepository}
}
func (r CredentialService) Encrypt() error {
items, err := r.credentialRepository.FindAll()
if err != nil {
return err
}
for i := range items {
item := items[i]
if item.Encrypted {
continue
}
if err := r.credentialRepository.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := r.credentialRepository.UpdateById(&item, item.ID); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,5 @@
package service
var (
accessGatewayService *AccessGatewayService
)

374
server/service/job.go Normal file
View File

@ -0,0 +1,374 @@
package service
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"next-terminal/server/constant"
"next-terminal/server/global/cron"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/term"
"next-terminal/server/utils"
"gorm.io/gorm"
)
type JobService struct {
jobRepository *repository.JobRepository
jobLogRepository *repository.JobLogRepository
assetRepository *repository.AssetRepository
credentialRepository *repository.CredentialRepository
assetService *AssetService
}
func NewJobService(jobRepository *repository.JobRepository, jobLogRepository *repository.JobLogRepository, assetRepository *repository.AssetRepository, credentialRepository *repository.CredentialRepository, assetService *AssetService) *JobService {
return &JobService{jobRepository: jobRepository, jobLogRepository: jobLogRepository, assetRepository: assetRepository, credentialRepository: credentialRepository, assetService: assetService}
}
func (r JobService) ChangeStatusById(id, status string) error {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
if status == constant.JobStatusRunning {
j, err := getJob(&job, &r)
if err != nil {
return err
}
entryID, err := cron.GlobalCron.AddJob(job.Cron, j)
if err != nil {
return err
}
log.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(cron.GlobalCron.Entries()))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusRunning, CronJobId: int(entryID)}
return r.jobRepository.UpdateById(&jobForUpdate)
} else {
cron.GlobalCron.Remove(cron.JobId(job.CronJobId))
log.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(cron.GlobalCron.Entries()))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusNotRunning}
return r.jobRepository.UpdateById(&jobForUpdate)
}
}
func getJob(j *model.Job, jobService *JobService) (job cron.Job, err error) {
switch j.Func {
case constant.FuncCheckAssetStatusJob:
job = CheckAssetStatusJob{
ID: j.ID,
Mode: j.Mode,
ResourceIds: j.ResourceIds,
Metadata: j.Metadata,
jobService: jobService,
assetService: jobService.assetService,
}
case constant.FuncShellJob:
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata, jobService: jobService}
default:
return nil, errors.New("未识别的任务")
}
return job, err
}
type CheckAssetStatusJob struct {
ID string
Mode string
ResourceIds string
Metadata string
jobService *JobService
assetService *AssetService
}
func (r CheckAssetStatusJob) Run() {
if r.ID == "" {
return
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
assets, _ = r.jobService.assetRepository.FindAll()
} else {
assets, _ = r.jobService.assetRepository.FindByIds(strings.Split(r.ResourceIds, ","))
}
if len(assets) == 0 {
return
}
msgChan := make(chan string)
for i := range assets {
asset := assets[i]
go func() {
t1 := time.Now()
var (
msg string
ip = asset.IP
port = asset.Port
)
active, err := r.assetService.CheckStatus(asset.AccessGatewayId, ip, port)
elapsed := time.Since(t1)
if err == nil {
msg = fmt.Sprintf("资产「%v」存活状态检测完成存活「%v」耗时「%v」", asset.Name, active, elapsed)
} else {
msg = fmt.Sprintf("资产「%v」存活状态检测完成存活「%v」耗时「%v」原因 %v", asset.Name, active, elapsed, err.Error())
}
_ = r.jobService.assetRepository.UpdateActiveById(active, asset.ID)
log.Infof(msg)
msgChan <- msg
}()
}
var message = ""
for i := 0; i < len(assets); i++ {
message += <-msgChan + "\n"
}
_ = r.jobService.jobRepository.UpdateLastUpdatedById(r.ID)
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Message: message,
}
_ = r.jobService.jobLogRepository.Create(&jobLog)
}
type ShellJob struct {
ID string
Mode string
ResourceIds string
Metadata string
jobService *JobService
}
type MetadataShell struct {
Shell string
}
func (r ShellJob) Run() {
if r.ID == "" {
return
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
assets, _ = r.jobService.assetRepository.FindByProtocol("ssh")
} else {
assets, _ = r.jobService.assetRepository.FindByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ","))
}
if len(assets) == 0 {
return
}
var metadataShell MetadataShell
err := json.Unmarshal([]byte(r.Metadata), &metadataShell)
if err != nil {
log.Errorf("JSON数据解析失败 %v", err)
return
}
msgChan := make(chan string)
for i := range assets {
asset, err := r.jobService.assetRepository.FindByIdAndDecrypt(assets[i].ID)
if err != nil {
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败查询数据异常「%v」", assets[i].Name, err.Error())
return
}
var (
username = asset.Username
password = asset.Password
privateKey = asset.PrivateKey
passphrase = asset.Passphrase
ip = asset.IP
port = asset.Port
)
if asset.AccountType == "credential" {
credential, err := r.jobService.credentialRepository.FindByIdAndDecrypt(asset.CredentialId)
if err != nil {
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败查询授权凭证数据异常「%v」", assets[i].Name, err.Error())
return
}
if credential.Type == constant.Custom {
username = credential.Username
password = credential.Password
} else {
username = credential.Username
privateKey = credential.PrivateKey
passphrase = credential.Passphrase
}
}
go func() {
t1 := time.Now()
result, err := exec(metadataShell.Shell, asset.AccessGatewayId, ip, port, username, password, privateKey, passphrase)
elapsed := time.Since(t1)
var msg string
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
msg = fmt.Sprintf("资产「%v」Shell执行失败请检查资产所关联接入网关是否存在耗时「%v」", asset.Name, elapsed)
} else {
msg = fmt.Sprintf("资产「%v」Shell执行失败错误内容为「%v」耗时「%v」", asset.Name, err.Error(), elapsed)
}
log.Infof(msg)
} else {
msg = fmt.Sprintf("资产「%v」Shell执行成功返回值「%v」耗时「%v」", asset.Name, result, elapsed)
log.Infof(msg)
}
msgChan <- msg
}()
}
var message = ""
for i := 0; i < len(assets); i++ {
message += <-msgChan + "\n"
}
_ = r.jobService.jobRepository.UpdateLastUpdatedById(r.ID)
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Message: message,
}
_ = r.jobService.jobLogRepository.Create(&jobLog)
}
func exec(shell, accessGatewayId, ip string, port int, username, password, privateKey, passphrase string) (string, error) {
if accessGatewayId != "" && accessGatewayId != "-" {
g, err := accessGatewayService.GetGatewayAndReconnectById(accessGatewayId)
if err != nil {
return "", err
}
uuid := utils.UUID()
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
if err != nil {
return "", err
}
defer g.CloseSshTunnel(uuid)
return ExecCommandBySSH(shell, exposedIP, exposedPort, username, password, privateKey, passphrase)
} else {
return ExecCommandBySSH(shell, ip, port, username, password, privateKey, passphrase)
}
}
func ExecCommandBySSH(cmd, ip string, port int, username, password, privateKey, passphrase string) (result string, err error) {
sshClient, err := term.NewSshClient(ip, port, username, password, privateKey, passphrase)
if err != nil {
return "", err
}
session, err := sshClient.NewSession()
if err != nil {
return "", err
}
defer session.Close()
//执行远程命令
combo, err := session.CombinedOutput(cmd)
if err != nil {
return "", err
}
return string(combo), nil
}
func (r JobService) ExecJobById(id string) (err error) {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
j, err := getJob(&job, &r)
if err != nil {
return err
}
j.Run()
return nil
}
func (r JobService) InitJob() error {
jobs, _ := r.jobRepository.FindAll()
if len(jobs) == 0 {
job := model.Job{
ID: utils.UUID(),
Name: "资产状态检测",
Func: constant.FuncCheckAssetStatusJob,
Cron: "0 0/10 * * * ?",
Mode: constant.JobModeAll,
Status: constant.JobStatusRunning,
Created: utils.NowJsonTime(),
Updated: utils.NowJsonTime(),
}
if err := r.jobRepository.Create(&job); err != nil {
return err
}
log.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron)
} else {
for i := range jobs {
if jobs[i].Status == constant.JobStatusRunning {
err := r.ChangeStatusById(jobs[i].ID, constant.JobStatusRunning)
if err != nil {
return err
}
log.Debugf("启动计划任务「%v」cron「%v」", jobs[i].Name, jobs[i].Cron)
}
}
}
return nil
}
func (r JobService) Create(o *model.Job) (err error) {
if o.Status == constant.JobStatusRunning {
j, err := getJob(o, &r)
if err != nil {
return err
}
jobId, err := cron.GlobalCron.AddJob(o.Cron, j)
if err != nil {
return err
}
o.CronJobId = int(jobId)
}
return r.jobRepository.Create(o)
}
func (r JobService) DeleteJobById(id string) error {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
if job.Status == constant.JobStatusRunning {
if err := r.ChangeStatusById(id, constant.JobStatusNotRunning); err != nil {
return err
}
}
return r.jobRepository.DeleteJobById(id)
}
func (r JobService) UpdateById(m *model.Job) error {
if err := r.jobRepository.UpdateById(m); err != nil {
return err
}
if err := r.ChangeStatusById(m.ID, constant.JobStatusNotRunning); err != nil {
return err
}
if err := r.ChangeStatusById(m.ID, constant.JobStatusRunning); err != nil {
return err
}
return nil
}

42
server/service/mail.go Normal file
View File

@ -0,0 +1,42 @@
package service
import (
"net/smtp"
"next-terminal/server/constant"
"next-terminal/server/log"
"next-terminal/server/repository"
"github.com/jordan-wright/email"
)
type MailService struct {
propertyRepository *repository.PropertyRepository
}
func NewMailService(propertyRepository *repository.PropertyRepository) *MailService {
return &MailService{propertyRepository: propertyRepository}
}
func (r MailService) SendMail(to, subject, text string) {
propertiesMap := r.propertyRepository.FindAllMap()
host := propertiesMap[constant.MailHost]
port := propertiesMap[constant.MailPort]
username := propertiesMap[constant.MailUsername]
password := propertiesMap[constant.MailPassword]
if host == "" || port == "" || username == "" || password == "" {
log.Debugf("邮箱信息不完整,跳过发送邮件。")
return
}
e := email.NewEmail()
e.From = "Next Terminal <" + username + ">"
e.To = []string{to}
e.Subject = subject
e.Text = []byte(text)
err := e.Send(host+":"+port, smtp.PlainAuth("", username, password, host))
if err != nil {
log.Errorf("邮件发送失败: %v", err.Error())
}
}

180
server/service/property.go Normal file
View File

@ -0,0 +1,180 @@
package service
import (
"next-terminal/server/guacd"
"next-terminal/server/model"
"next-terminal/server/repository"
)
type PropertyService struct {
propertyRepository *repository.PropertyRepository
}
func NewPropertyService(propertyRepository *repository.PropertyRepository) *PropertyService {
return &PropertyService{propertyRepository: propertyRepository}
}
func (r PropertyService) InitProperties() error {
propertyMap := r.propertyRepository.FindAllMap()
if len(propertyMap[guacd.EnableRecording]) == 0 {
property := model.Property{
Name: guacd.EnableRecording,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.CreateRecordingPath]) == 0 {
property := model.Property{
Name: guacd.CreateRecordingPath,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.FontName]) == 0 {
property := model.Property{
Name: guacd.FontName,
Value: "menlo",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.FontSize]) == 0 {
property := model.Property{
Name: guacd.FontSize,
Value: "12",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.ColorScheme]) == 0 {
property := model.Property{
Name: guacd.ColorScheme,
Value: "gray-black",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableWallpaper]) == 0 {
property := model.Property{
Name: guacd.EnableWallpaper,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableTheming]) == 0 {
property := model.Property{
Name: guacd.EnableTheming,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFontSmoothing]) == 0 {
property := model.Property{
Name: guacd.EnableFontSmoothing,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFullWindowDrag]) == 0 {
property := model.Property{
Name: guacd.EnableFullWindowDrag,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableDesktopComposition]) == 0 {
property := model.Property{
Name: guacd.EnableDesktopComposition,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableMenuAnimations]) == 0 {
property := model.Property{
Name: guacd.EnableMenuAnimations,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableBitmapCaching]) == 0 {
property := model.Property{
Name: guacd.DisableBitmapCaching,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableOffscreenCaching]) == 0 {
property := model.Property{
Name: guacd.DisableOffscreenCaching,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableGlyphCaching]) == 0 {
property := model.Property{
Name: guacd.DisableGlyphCaching,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
return nil
}
func (r PropertyService) DeleteDeprecatedProperty() error {
propertyMap := r.propertyRepository.FindAllMap()
if propertyMap[guacd.EnableDrive] != "" {
if err := r.propertyRepository.DeleteByName(guacd.DriveName); err != nil {
return err
}
}
if propertyMap[guacd.DrivePath] != "" {
if err := r.propertyRepository.DeleteByName(guacd.DrivePath); err != nil {
return err
}
}
if propertyMap[guacd.DriveName] != "" {
if err := r.propertyRepository.DeleteByName(guacd.DriveName); err != nil {
return err
}
}
return nil
}

39
server/service/session.go Normal file
View File

@ -0,0 +1,39 @@
package service
import (
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type SessionService struct {
sessionRepository *repository.SessionRepository
}
func NewSessionService(sessionRepository *repository.SessionRepository) *SessionService {
return &SessionService{sessionRepository: sessionRepository}
}
func (r SessionService) FixSessionState() error {
sessions, err := r.sessionRepository.FindByStatus(constant.Connected)
if err != nil {
return err
}
if len(sessions) > 0 {
for i := range sessions {
session := model.Session{
Status: constant.Disconnected,
DisconnectedTime: utils.NowJsonTime(),
}
_ = r.sessionRepository.UpdateById(&session, sessions[i].ID)
}
}
return nil
}
func (r SessionService) EmptyPassword() error {
return r.sessionRepository.EmptyPassword()
}

151
server/service/storage.go Normal file
View File

@ -0,0 +1,151 @@
package service
import (
"errors"
"io/ioutil"
"os"
"path"
"next-terminal/server/config"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"gorm.io/gorm"
)
type StorageService struct {
storageRepository *repository.StorageRepository
userRepository *repository.UserRepository
propertyRepository *repository.PropertyRepository
}
func NewStorageService(storageRepository *repository.StorageRepository, userRepository *repository.UserRepository, propertyRepository *repository.PropertyRepository) *StorageService {
return &StorageService{storageRepository: storageRepository, userRepository: userRepository, propertyRepository: propertyRepository}
}
func (r StorageService) InitStorages() error {
users := r.userRepository.FindAll()
for i := range users {
userId := users[i].ID
_, err := r.storageRepository.FindByOwnerIdAndDefault(userId, true)
if errors.Is(err, gorm.ErrRecordNotFound) {
err = r.CreateStorageByUser(&users[i])
if err != nil {
return err
}
}
}
drivePath := r.GetBaseDrivePath()
storages := r.storageRepository.FindAll()
for i := 0; i < len(storages); i++ {
storage := storages[i]
// 判断是否为遗留的数据:磁盘空间在,但用户已删除
if storage.IsDefault {
var userExist = false
for j := range users {
if storage.ID == users[j].ID {
userExist = true
break
}
}
if !userExist {
if err := r.DeleteStorageById(storage.ID, true); err != nil {
return err
}
}
}
storageDir := path.Join(drivePath, storage.ID)
if !utils.FileExists(storageDir) {
if err := os.MkdirAll(storageDir, os.ModePerm); err != nil {
return err
}
log.Infof("创建storage:「%v」文件夹: %v", storage.Name, storageDir)
}
}
return nil
}
func (r StorageService) CreateStorageByUser(user *model.User) error {
drivePath := r.GetBaseDrivePath()
storage := model.Storage{
ID: user.ID,
Name: user.Nickname + "的默认空间",
IsShare: false,
IsDefault: true,
LimitSize: -1,
Owner: user.ID,
Created: utils.NowJsonTime(),
}
storageDir := path.Join(drivePath, storage.ID)
if err := os.MkdirAll(storageDir, os.ModePerm); err != nil {
return err
}
log.Infof("创建storage:「%v」文件夹: %v", storage.Name, storageDir)
err := r.storageRepository.Create(&storage)
if err != nil {
return err
}
return nil
}
type File struct {
Name string `json:"name"`
Path string `json:"path"`
IsDir bool `json:"isDir"`
Mode string `json:"mode"`
IsLink bool `json:"isLink"`
ModTime utils.JsonTime `json:"modTime"`
Size int64 `json:"size"`
}
func (r StorageService) Ls(drivePath, remoteDir string) ([]File, error) {
fileInfos, err := ioutil.ReadDir(path.Join(drivePath, remoteDir))
if err != nil {
return nil, err
}
var files = make([]File, 0)
for i := range fileInfos {
file := File{
Name: fileInfos[i].Name(),
Path: path.Join(remoteDir, fileInfos[i].Name()),
IsDir: fileInfos[i].IsDir(),
Mode: fileInfos[i].Mode().String(),
IsLink: fileInfos[i].Mode()&os.ModeSymlink == os.ModeSymlink,
ModTime: utils.NewJsonTime(fileInfos[i].ModTime()),
Size: fileInfos[i].Size(),
}
files = append(files, file)
}
return files, nil
}
func (r StorageService) GetBaseDrivePath() string {
return config.GlobalCfg.Guacd.Drive
}
func (r StorageService) DeleteStorageById(id string, force bool) error {
drivePath := r.GetBaseDrivePath()
storage, err := r.storageRepository.FindById(id)
if err != nil {
return err
}
if !force && storage.IsDefault {
return errors.New("默认空间不能删除")
}
// 删除对应的本地目录
if err := os.RemoveAll(path.Join(drivePath, id)); err != nil {
return err
}
if err := r.storageRepository.DeleteById(id); err != nil {
return err
}
return nil
}

129
server/service/user.go Normal file
View File

@ -0,0 +1,129 @@
package service
import (
"strings"
"next-terminal/server/constant"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type UserService struct {
userRepository *repository.UserRepository
loginLogRepository *repository.LoginLogRepository
}
func NewUserService(userRepository *repository.UserRepository, loginLogRepository *repository.LoginLogRepository) *UserService {
return &UserService{userRepository: userRepository, loginLogRepository: loginLogRepository}
}
func (r UserService) InitUser() (err error) {
users := r.userRepository.FindAll()
if len(users) == 0 {
initPassword := "admin"
var pass []byte
if pass, err = utils.Encoder.Encode([]byte(initPassword)); err != nil {
return err
}
user := model.User{
ID: utils.UUID(),
Username: "admin",
Password: string(pass),
Nickname: "超级管理员",
Type: constant.TypeAdmin,
Created: utils.NowJsonTime(),
}
if err := r.userRepository.Create(&user); err != nil {
return err
}
log.Infof("初始用户创建成功,账号:「%v」密码「%v」", user.Username, initPassword)
} else {
for i := range users {
// 修正默认用户类型为管理员
if users[i].Type == "" {
user := model.User{
Type: constant.TypeAdmin,
ID: users[i].ID,
}
if err := r.userRepository.Update(&user); err != nil {
return err
}
log.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID)
}
}
}
return nil
}
func (r UserService) FixUserOnlineState() error {
// 修正用户登录状态
onlineUsers, err := r.userRepository.FindOnlineUsers()
if err != nil {
return err
}
if len(onlineUsers) > 0 {
for i := range onlineUsers {
logs, err := r.loginLogRepository.FindAliveLoginLogsByUsername(onlineUsers[i].Username)
if err != nil {
return err
}
if len(logs) == 0 {
if err := r.userRepository.UpdateOnlineByUsername(onlineUsers[i].Username, false); err != nil {
return err
}
}
}
}
return nil
}
func (r UserService) Logout(token string) (err error) {
loginLog, err := r.loginLogRepository.FindById(token)
if err != nil {
log.Warnf("登录日志「%v」获取失败", token)
return
}
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
err = r.loginLogRepository.Update(loginLogForUpdate)
if err != nil {
return err
}
loginLogs, err := r.loginLogRepository.FindAliveLoginLogsByUsername(loginLog.Username)
if err != nil {
return
}
if len(loginLogs) == 0 {
err = r.userRepository.UpdateOnlineByUsername(loginLog.Username, false)
}
return
}
func (r UserService) BuildCacheKeyByToken(token string) string {
cacheKey := strings.Join([]string{constant.Token, token}, ":")
return cacheKey
}
func (r UserService) GetTokenFormCacheKey(cacheKey string) string {
token := strings.Split(cacheKey, ":")[1]
return token
}
func (r UserService) OnEvicted(key string, value interface{}) {
if strings.HasPrefix(key, constant.Token) {
token := r.GetTokenFormCacheKey(key)
log.Debugf("用户Token「%v」过期", token)
err := r.Logout(token)
if err != nil {
log.Errorf("退出登录失败 %v", err)
}
}
}