first commit

This commit is contained in:
JiXieShi
2024-11-14 22:55:43 +08:00
commit 421cfb8cfa
98 changed files with 12617 additions and 0 deletions

45
internal/api/dashboard.go Normal file
View File

@@ -0,0 +1,45 @@
package api
import (
"licserver/internal/model"
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type DashboardHandler struct {
db *gorm.DB
}
func NewDashboardHandler(db *gorm.DB) *DashboardHandler {
return &DashboardHandler{db: db}
}
func (h *DashboardHandler) GetStats(c *gin.Context) {
var stats struct {
TotalDevices int64 `json:"total_devices"`
TotalLicenses int64 `json:"total_licenses"`
TodayNew int64 `json:"today_new"`
OnlineDevices int64 `json:"online_devices"`
}
// 获取设备总数
h.db.Model(&model.Device{}).Count(&stats.TotalDevices)
// 获取授权码总数
h.db.Model(&model.LicenseCode{}).Count(&stats.TotalLicenses)
// 获取今日新增设备数
today := time.Now().Format("2006-01-02")
h.db.Model(&model.Device{}).Where("DATE(created_at) = ?", today).Count(&stats.TodayNew)
// 获取在线设备数最近30分钟内有活动的设备
thirtyMinutesAgo := time.Now().Add(-30 * time.Minute)
h.db.Model(&model.Device{}).
Where("last_active_at > ?", thirtyMinutesAgo).
Count(&stats.OnlineDevices)
c.JSON(http.StatusOK, stats)
}

434
internal/api/device.go Normal file
View File

@@ -0,0 +1,434 @@
package api
import (
"fmt"
"net/http"
"strconv"
"licserver/internal/model"
"licserver/internal/service"
"github.com/gin-gonic/gin"
)
type DeviceHandler struct {
deviceService *service.DeviceService
}
func NewDeviceHandler(deviceService *service.DeviceService) *DeviceHandler {
return &DeviceHandler{deviceService: deviceService}
}
func (h *DeviceHandler) CreateDevice(c *gin.Context) {
var input service.DeviceCreateInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.CreateDevice(&input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "设备创建成功"})
}
func (h *DeviceHandler) GetDevices(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
params := &service.DeviceQueryParams{
UID: c.Query("uid"),
DeviceType: c.Query("deviceType"),
Company: c.Query("company"),
LicenseType: c.Query("licenseType"),
Status: c.Query("status"),
Page: page,
PageSize: pageSize,
}
devices, total, err := h.deviceService.GetDevices(params)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "获取设备列表成功",
"count": total,
"data": devices,
})
}
func (h *DeviceHandler) UpdateStartCount(c *gin.Context) {
uid := c.Param("uid")
if err := h.deviceService.UpdateStartCount(uid); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "启动次数更新成功"})
}
func (h *DeviceHandler) UpdateDevice(c *gin.Context) {
uid := c.Param("uid")
var updates map[string]interface{}
if err := c.ShouldBindJSON(&updates); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.UpdateDevice(uid, updates); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "设备更新成功"})
}
func (h *DeviceHandler) DeleteDevice(c *gin.Context) {
uid := c.Param("uid")
if err := h.deviceService.DeleteDevice(uid); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "设备删除成功"})
}
func (h *DeviceHandler) RegisterDevice(c *gin.Context) {
var input service.DeviceRegisterInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.RegisterDevice(&input, c.ClientIP()); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
status := "未激活"
if input.LicenseCode != "" {
status = "已激活"
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": fmt.Sprintf("设备注册成功,当前状态:%s", status),
})
}
func (h *DeviceHandler) ValidateDevice(c *gin.Context) {
uid := c.Param("uid")
if err := h.deviceService.ValidateDevice(uid); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "设备验证通过"})
}
func (h *DeviceHandler) BindLicense(c *gin.Context) {
uid := c.Param("uid")
var input struct {
LicenseCode string `json:"license_code" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.BindLicense(uid, input.LicenseCode); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码绑定成功",
})
}
func (h *DeviceHandler) UnbindLicense(c *gin.Context) {
uid := c.Param("uid")
if err := h.deviceService.UnbindLicense(uid); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码解绑成功",
})
}
func (h *DeviceHandler) GetLicenseInfo(c *gin.Context) {
deviceUID := c.Param("uid")
device, err := h.deviceService.GetLicenseInfo(deviceUID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, device)
}
func (h *DeviceHandler) CheckLicenseStatus(c *gin.Context) {
deviceUID := c.Param("uid")
status, err := h.deviceService.CheckLicenseStatus(deviceUID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": status,
})
}
func (h *DeviceHandler) CheckUpdate(c *gin.Context) {
deviceUID := c.Param("uid")
currentVersion := c.Query("version")
update, err := h.deviceService.CheckUpdate(deviceUID, currentVersion)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, update)
}
func (h *DeviceHandler) CreateDeviceModel(c *gin.Context) {
var model model.DeviceModel
if err := c.ShouldBindJSON(&model); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
model.CreatedBy = c.GetUint("userID")
model.Status = "active"
if err := h.deviceService.CreateDeviceModel(&model); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "设备型号创建成功",
"data": model,
})
}
func (h *DeviceHandler) GetDeviceModels(c *gin.Context) {
modelName := c.Query("model_name")
deviceType := c.Query("device_type")
company := c.Query("company")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
models, total, err := h.deviceService.GetDeviceModels(modelName, deviceType, company, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"count": total,
"data": models,
})
}
func (h *DeviceHandler) UpdateDeviceModel(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
return
}
var model model.DeviceModel
if err := c.ShouldBindJSON(&model); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.UpdateDeviceModel(uint(id), &model); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "设备型号更新成功",
})
}
func (h *DeviceHandler) DeleteDeviceModel(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
return
}
if err := h.deviceService.DeleteDeviceModel(uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "设备型号删除成功",
})
}
func (h *DeviceHandler) BatchDeleteDeviceModels(c *gin.Context) {
var input struct {
IDs []uint `json:"ids" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.deviceService.BatchDeleteDeviceModels(input.IDs); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "设备型号批量删除成功",
})
}
func (h *DeviceHandler) GetRegisteredDevices(c *gin.Context) {
uid := c.Query("uid")
deviceModel := c.Query("device_model")
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
devices, total, err := h.deviceService.GetRegisteredDevices(uid, deviceModel, status, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"count": total,
"data": devices,
})
}
func (h *DeviceHandler) GetDeviceLogs(c *gin.Context) {
uid := c.Param("uid")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
logs, total, err := h.deviceService.GetDeviceLogs(uid, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"count": total,
"data": logs,
})
}
// GetDashboardStats 获取仪表盘统计数据
func (h *DeviceHandler) GetDashboardStats(c *gin.Context) {
stats, err := h.deviceService.GetDashboardStats()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": stats,
})
}

169
internal/api/license.go Normal file
View File

@@ -0,0 +1,169 @@
package api
import (
"fmt"
"licserver/internal/service"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type LicenseHandler struct {
licenseService *service.LicenseService
}
func NewLicenseHandler(licenseService *service.LicenseService) *LicenseHandler {
return &LicenseHandler{licenseService: licenseService}
}
// 创建授权码
func (h *LicenseHandler) CreateLicenses(c *gin.Context) {
var input service.LicenseCreateInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := c.GetUint("userID")
licenses, err := h.licenseService.CreateLicenses(&input, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码创建成功",
"data": licenses,
})
}
// 使用授权码
func (h *LicenseHandler) UseLicense(c *gin.Context) {
var input struct {
Code string `json:"code" binding:"required"`
DeviceUID string `json:"device_uid" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
license, err := h.licenseService.UseLicense(input.Code, input.DeviceUID, c.ClientIP())
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码使用成功",
"data": license,
})
}
// 获取授权码列表
func (h *LicenseHandler) GetLicenses(c *gin.Context) {
status := c.Query("status")
licenseType := c.Query("license_type")
batchNo := c.Query("batch_no")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
licenses, total, err := h.licenseService.GetLicenses(status, licenseType, batchNo, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 确保返回格式符合 layui table 的要求
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"count": total,
"data": licenses,
})
}
// 获取授权码使用日志
func (h *LicenseHandler) GetLicenseLogs(c *gin.Context) {
licenseID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的授权码ID"})
return
}
// 检查是否为导出请求
if c.Query("export") == "1" {
data, err := h.licenseService.ExportLogs(uint(licenseID))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 设置响应头
filename := fmt.Sprintf("license_logs_%d_%s.csv", licenseID, time.Now().Format("20060102150405"))
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
c.Data(http.StatusOK, "text/csv", data)
return
}
// 常规日志查询
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
logs, total, err := h.licenseService.GetLicenseLogs(uint(licenseID), page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "获取授权码使用日志成功",
"count": total,
"data": logs,
})
}
// 添加撤销授权码的处理方法
func (h *LicenseHandler) RevokeLicense(c *gin.Context) {
code := c.Param("code")
userID := c.GetUint("userID")
if err := h.licenseService.RevokeLicense(code, userID); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码撤销成功",
})
}
// 添加批量撤销处理方法
func (h *LicenseHandler) RevokeLicenses(c *gin.Context) {
var input struct {
Codes []string `json:"codes" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := c.GetUint("userID")
if err := h.licenseService.RevokeLicenses(input.Codes, userID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "授权码批量撤销成功",
})
}

26
internal/api/monitor.go Normal file
View File

@@ -0,0 +1,26 @@
package api
import (
"licserver/internal/service"
"net/http"
"github.com/gin-gonic/gin"
)
type MonitorHandler struct {
monitorService *service.MonitorService
}
func NewMonitorHandler(monitorService *service.MonitorService) *MonitorHandler {
return &MonitorHandler{monitorService: monitorService}
}
func (h *MonitorHandler) GetSystemStatus(c *gin.Context) {
status, err := h.monitorService.GetSystemStatus()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, status)
}

121
internal/api/router.go Normal file
View File

@@ -0,0 +1,121 @@
package api
import (
"licserver/internal/middleware"
"licserver/internal/utils"
"github.com/gin-gonic/gin"
)
func SetupRouter(
userHandler *UserHandler,
deviceHandler *DeviceHandler,
monitorHandler *MonitorHandler,
config *utils.Config,
uploadHandler *UploadHandler,
siteHandler *SiteHandler,
tokenHandler *TokenHandler,
licenseHandler *LicenseHandler,
) *gin.Engine {
r := gin.Default()
// 添加错误处理中间件
r.Use(middleware.ErrorHandler())
// 静态文件服务
r.Static("/static", "./web/static")
// 首页和登录页面
r.StaticFile("/", "./web/templates/index.html")
r.StaticFile("/login", "./web/templates/login.html")
// Admin页面路由组
admin := r.Group("/admin")
admin.Use(middleware.JWTAuth(&config.JWT))
{
// 使用StaticFile处理包含Layui模板的页面
admin.StaticFile("/dashboard", "./web/templates/admin/dashboard.html")
admin.StaticFile("/devices", "./web/templates/admin/devices.html")
admin.StaticFile("/device-files", "./web/templates/admin/device-files.html")
admin.StaticFile("/device-license", "./web/templates/admin/device-license.html")
admin.StaticFile("/licenses", "./web/templates/admin/licenses.html")
admin.StaticFile("/license-logs", "./web/templates/admin/license-logs.html")
admin.StaticFile("/tokens", "./web/templates/admin/tokens.html")
admin.StaticFile("/token-logs", "./web/templates/admin/token-logs.html")
admin.StaticFile("/monitor", "./web/templates/admin/monitor.html")
admin.StaticFile("/site-settings", "./web/templates/admin/site-settings.html")
admin.StaticFile("/users", "./web/templates/admin/users.html")
admin.StaticFile("/user-edit", "./web/templates/admin/user-edit.html")
admin.StaticFile("/change-password", "./web/templates/admin/change-password.html")
}
// API路由
api := r.Group("/api")
{
// 公开API
api.GET("/captcha", userHandler.GetCaptcha)
api.POST("/captcha/verify", userHandler.VerifyCaptcha)
api.POST("/login", userHandler.Login)
api.POST("/register", userHandler.Register)
api.POST("/reset-password", userHandler.ResetPassword)
api.POST("/reset-password/confirm", userHandler.ResetPasswordWithToken)
api.POST("/captcha/register", userHandler.SendRegisterCaptcha)
api.POST("/captcha/reset-password", userHandler.SendResetPasswordCaptcha)
api.POST("/validate-token", tokenHandler.ValidateToken)
// 需要认证的API
authorized := api.Group("")
authorized.Use(middleware.JWTAuth(&config.JWT))
{
// 设备型号管理
authorized.POST("/devices/models", middleware.AdminRequired(), deviceHandler.CreateDeviceModel)
authorized.GET("/devices/models", deviceHandler.GetDeviceModels)
authorized.PUT("/devices/models/:id", middleware.AdminRequired(), deviceHandler.UpdateDeviceModel)
authorized.DELETE("/devices/models/:id", middleware.AdminRequired(), deviceHandler.DeleteDeviceModel)
authorized.POST("/devices/models/batch", middleware.AdminRequired(), deviceHandler.BatchDeleteDeviceModels)
// 设备管理
authorized.POST("/devices/register", deviceHandler.RegisterDevice)
authorized.GET("/devices/registered", deviceHandler.GetRegisteredDevices)
authorized.POST("/devices/:uid/license", middleware.AdminRequired(), deviceHandler.BindLicense)
authorized.DELETE("/devices/:uid/license", middleware.AdminRequired(), deviceHandler.UnbindLicense)
authorized.GET("/devices/:uid/logs", deviceHandler.GetDeviceLogs)
// 其他API路由...
// 用户管理
authorized.GET("/users", middleware.AdminRequired(), userHandler.GetUsers)
authorized.POST("/users", middleware.AdminRequired(), userHandler.CreateUser)
authorized.GET("/users/:id", middleware.AdminRequired(), userHandler.GetUserInfo)
authorized.PUT("/users/:id", middleware.AdminRequired(), userHandler.UpdateUser)
authorized.DELETE("/users/:id", middleware.AdminRequired(), userHandler.DeleteUser)
authorized.GET("/users/profile", userHandler.GetProfile)
authorized.PUT("/users/profile", userHandler.UpdateProfile)
authorized.POST("/users/change-password", userHandler.ChangePassword)
// 系统监控
authorized.GET("/monitor/status", middleware.AdminRequired(), monitorHandler.GetSystemStatus)
// 站点设置
authorized.GET("/site/settings", middleware.AdminRequired(), siteHandler.GetSettings)
authorized.PUT("/site/settings", middleware.AdminRequired(), siteHandler.UpdateSettings)
// Token管理
authorized.POST("/tokens", middleware.AdminRequired(), tokenHandler.CreateToken)
authorized.GET("/tokens", tokenHandler.GetTokens)
authorized.GET("/tokens/:id/logs", tokenHandler.GetTokenLogs)
authorized.DELETE("/tokens/:token", middleware.AdminRequired(), tokenHandler.RevokeToken)
// 授权码管理
authorized.POST("/licenses", middleware.AdminRequired(), licenseHandler.CreateLicenses)
authorized.GET("/licenses", licenseHandler.GetLicenses)
authorized.GET("/licenses/:id/logs", licenseHandler.GetLicenseLogs)
authorized.POST("/licenses/use", licenseHandler.UseLicense)
// 仪表盘统计
authorized.GET("/dashboard/stats", deviceHandler.GetDashboardStats)
}
}
return r
}

86
internal/api/site.go Normal file
View File

@@ -0,0 +1,86 @@
package api
import (
"licserver/internal/service"
"licserver/internal/utils"
"net/http"
"github.com/gin-gonic/gin"
)
type SiteHandler struct {
siteService *service.SiteService
}
func NewSiteHandler(siteService *service.SiteService) *SiteHandler {
return &SiteHandler{siteService: siteService}
}
func (h *SiteHandler) GetSettings(c *gin.Context) {
settings := h.siteService.GetSettings()
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": settings,
"title": settings.Title,
"description": settings.Description,
"base_url": settings.BaseURL,
"icp": settings.ICP,
"copyright": settings.Copyright,
"logo": settings.Logo,
"favicon": settings.Favicon,
})
}
func (h *SiteHandler) UpdateSettings(c *gin.Context) {
var settings utils.SiteConfig
if err := c.ShouldBindJSON(&settings); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.siteService.ValidateSettings(settings); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.siteService.UpdateSettings(settings); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "站点设置更新成功",
"data": settings,
})
}
func (h *SiteHandler) BackupSettings(c *gin.Context) {
settings := h.siteService.GetSettings()
c.Header("Content-Disposition", "attachment; filename=site_settings.json")
c.JSON(http.StatusOK, settings)
}
func (h *SiteHandler) RestoreSettings(c *gin.Context) {
var settings utils.SiteConfig
if err := c.ShouldBindJSON(&settings); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.siteService.ValidateSettings(settings); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.siteService.UpdateSettings(settings); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "站点设置恢复成功",
"data": settings,
})
}

121
internal/api/token.go Normal file
View File

@@ -0,0 +1,121 @@
package api
import (
"licserver/internal/service"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type TokenHandler struct {
tokenService *service.TokenService
}
func NewTokenHandler(tokenService *service.TokenService) *TokenHandler {
return &TokenHandler{tokenService: tokenService}
}
func (h *TokenHandler) CreateToken(c *gin.Context) {
var input struct {
DeviceUID string `json:"device_uid" binding:"required"`
TokenType string `json:"token_type" binding:"required,oneof=api device"`
ExpireDays int `json:"expire_days" binding:"required,min=1"`
IPList []string `json:"ip_list"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
expireTime := time.Now().AddDate(0, 0, input.ExpireDays)
userID := c.GetUint("userID")
token, err := h.tokenService.CreateToken(
input.DeviceUID,
input.TokenType,
expireTime,
input.IPList,
userID,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, token)
}
func (h *TokenHandler) ValidateToken(c *gin.Context) {
token := c.GetHeader("X-Access-Token")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供访问令牌"})
return
}
clientIP := c.ClientIP()
accessToken, err := h.tokenService.ValidateToken(token, clientIP)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, accessToken)
}
func (h *TokenHandler) RevokeToken(c *gin.Context) {
token := c.Param("token")
userID := c.GetUint("userID")
if err := h.tokenService.RevokeToken(token, userID); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "令牌已撤销"})
}
func (h *TokenHandler) GetTokens(c *gin.Context) {
deviceUID := c.Query("device_uid")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
tokens, total, err := h.tokenService.GetTokens(deviceUID, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "获取令牌列表成功",
"count": total,
"data": tokens,
})
}
func (h *TokenHandler) GetTokenLogs(c *gin.Context) {
tokenID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的令牌ID"})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
logs, total, err := h.tokenService.GetTokenLogs(uint(tokenID), page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "获取令牌使用日志成功",
"count": total,
"data": logs,
})
}

213
internal/api/upload.go Normal file
View File

@@ -0,0 +1,213 @@
package api
import (
"fmt"
"licserver/internal/service"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
type UploadHandler struct {
uploadService *service.UploadService
}
func NewUploadHandler(uploadService *service.UploadService) *UploadHandler {
return &UploadHandler{uploadService: uploadService}
}
func (h *UploadHandler) UploadFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未找到上传文件"})
return
}
deviceUID := c.PostForm("device_uid")
description := c.PostForm("description")
userID := c.GetUint("userID")
upload, err := h.uploadService.UploadFile(file, userID, deviceUID, description)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, upload)
}
func (h *UploadHandler) DownloadFile(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的文件ID"})
return
}
file, err := h.uploadService.DownloadFile(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
return
}
c.FileAttachment(file.FilePath, file.FileName)
}
func (h *UploadHandler) DeleteFile(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的文件ID"})
return
}
userID := c.GetUint("userID")
if err := h.uploadService.DeleteFile(uint(id), userID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "文件删除成功"})
}
func (h *UploadHandler) GetDeviceFiles(c *gin.Context) {
deviceUID := c.Param("uid")
files, err := h.uploadService.GetDeviceFiles(deviceUID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, files)
}
func (h *UploadHandler) UploadChunk(c *gin.Context) {
file, err := c.FormFile("chunk")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未找到上传文件"})
return
}
fileHash := c.PostForm("fileHash")
if fileHash == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "未提供文件哈希"})
return
}
chunkNumber, err := strconv.Atoi(c.PostForm("chunkNumber"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的分片序号"})
return
}
totalChunks, err := strconv.Atoi(c.PostForm("totalChunks"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的总分片数"})
return
}
totalSize, err := strconv.ParseInt(c.PostForm("totalSize"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的文件大小"})
return
}
filename := c.PostForm("filename")
deviceUID := c.PostForm("deviceUID")
userID := c.GetUint("userID")
err = h.uploadService.UploadChunk(
file,
fileHash,
chunkNumber,
totalChunks,
totalSize,
filename,
userID,
deviceUID,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 检查是否所有分片都已上传
completed, err := h.uploadService.CheckUploadStatus(fileHash)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "分片上传成功",
"completed": completed,
})
}
func (h *UploadHandler) MergeChunks(c *gin.Context) {
fileHash := c.PostForm("fileHash")
if fileHash == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "未提供文件哈希"})
return
}
upload, err := h.uploadService.MergeChunks(fileHash)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "文件合并成功",
"file": upload,
})
}
func (h *UploadHandler) UploadSiteFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "未找到上传文件"})
return
}
// 检查文件类型
ext := strings.ToLower(filepath.Ext(file.Filename))
allowedExts := map[string]bool{
".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
".ico": true, ".svg": true,
}
if !allowedExts[ext] {
c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的文件类型"})
return
}
// 生成文件名
filename := fmt.Sprintf("site_%s%s", time.Now().Format("20060102150405"), ext)
// 构建目标目录路径
uploadDir := filepath.Join("web", "static", "images")
if err := os.MkdirAll(uploadDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
return
}
// 构建完整的文件路径
filePath := filepath.Join(uploadDir, filename)
// 保存文件
if err := c.SaveUploadedFile(file, filePath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
return
}
// 返回文件URL使用正斜杠作为URL路径分隔符
fileURL := "/" + strings.Join([]string{"static", "images", filename}, "/")
c.JSON(http.StatusOK, gin.H{
"url": fileURL,
"message": "文件上传成功",
})
}

424
internal/api/user.go Normal file
View File

@@ -0,0 +1,424 @@
package api
import (
"licserver/internal/service"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type UserHandler struct {
userService *service.UserService
}
func NewUserHandler(userService *service.UserService) *UserHandler {
return &UserHandler{userService: userService}
}
func (h *UserHandler) Login(c *gin.Context) {
var input struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Captcha string `json:"captcha" binding:"required"`
CaptchaId string `json:"captchaId" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 验证验证码
if !h.userService.GetCaptchaService().VerifyImageCaptcha(input.CaptchaId, input.Captcha) {
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
return
}
token, err := h.userService.Login(input.Username, input.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
// 设置 cookie
// c.SetCookie("token", token, 86400, "/", "", false, true) // 24小时过期httpOnly=true
c.JSON(http.StatusOK, gin.H{"token": token})
}
func (h *UserHandler) Register(c *gin.Context) {
var input struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
Captcha string `json:"captcha" binding:"required,len=6"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.userService.Register(input.Username, input.Password, input.Email, input.Captcha); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
}
func (h *UserHandler) ResetPasswordWithToken(c *gin.Context) {
var input struct {
Token string `json:"token" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.userService.ResetPasswordWithToken(input.Token, input.NewPassword); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "密码重置成功"})
}
func (h *UserHandler) ResetPassword(c *gin.Context) {
var input struct {
Email string `json:"email" binding:"required,email"`
Captcha string `json:"captcha" binding:"required,len=6"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.userService.ResetPassword(input.Email, input.Captcha); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "重置密码邮件已发送"})
}
func (h *UserHandler) SendRegisterCaptcha(c *gin.Context) {
var input struct {
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.userService.SendRegisterCaptcha(input.Email); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
}
func (h *UserHandler) SendResetPasswordCaptcha(c *gin.Context) {
var input struct {
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.userService.SendResetPasswordCaptcha(input.Email); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
}
// 在 UserHandler 中添加以下方法
// 获取图片验证码
func (h *UserHandler) GetCaptcha(c *gin.Context) {
id, b64s, err := h.userService.GetCaptchaService().GenerateImageCaptcha()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "生成验证码失败: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"captchaId": id,
"imageBase64": b64s,
})
}
// 验证图片验证码
func (h *UserHandler) VerifyCaptcha(c *gin.Context) {
var input struct {
CaptchaId string `json:"captcha_id" binding:"required"`
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if !h.userService.GetCaptchaService().VerifyImageCaptcha(input.CaptchaId, input.Code) {
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "验证成功"})
}
// 获取用户列表
func (h *UserHandler) GetUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
username := c.Query("username")
role := c.Query("role")
users, total, err := h.userService.GetUsers(username, role, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "获取用户列表成功",
"count": total,
"data": users,
})
}
// 创建用户
func (h *UserHandler) CreateUser(c *gin.Context) {
var input struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Email string `json:"email" binding:"required,email"`
Role string `json:"role" binding:"required,oneof=admin user"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 检查权限
if c.GetString("role") != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
return
}
err := h.userService.CreateUser(input.Username, input.Password, input.Email, input.Role)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "用户创建成功"})
}
// 更新用户
func (h *UserHandler) UpdateUser(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
return
}
var input struct {
Email string `json:"email" binding:"required,email"`
Role string `json:"role" binding:"required,oneof=admin user"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 检查权限
if c.GetString("role") != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
return
}
err = h.userService.UpdateUser(uint(id), input.Email, input.Role)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "用户更新成功"})
}
// 删除用户
func (h *UserHandler) DeleteUser(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
return
}
// 检查权限
if c.GetString("role") != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
return
}
// 不能删除自己
if uint(id) == c.GetUint("userID") {
c.JSON(http.StatusBadRequest, gin.H{"error": "不能删除自己"})
return
}
err = h.userService.DeleteUser(uint(id))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "用户删除成功"})
}
// 获取用户信息
func (h *UserHandler) GetUserInfo(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
return
}
user, err := h.userService.GetUserByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusOK, user)
}
// 获取当前用户信息
func (h *UserHandler) GetProfile(c *gin.Context) {
userID := c.GetUint("userID")
user, err := h.userService.GetUserByID(userID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusOK, user)
}
// 修改密码
func (h *UserHandler) ChangePassword(c *gin.Context) {
var input struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := c.GetUint("userID")
err := h.userService.ChangePassword(userID, input.OldPassword, input.NewPassword)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"})
}
// 在 UserHandler 结构体中添加 UpdateProfile 方法
func (h *UserHandler) UpdateProfile(c *gin.Context) {
var input struct {
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := c.GetUint("userID")
if err := h.userService.UpdateProfile(userID, input.Email); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "个人信息更新成功"})
}