first commit
This commit is contained in:
45
internal/api/dashboard.go
Normal file
45
internal/api/dashboard.go
Normal 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
434
internal/api/device.go
Normal 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
169
internal/api/license.go
Normal 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
26
internal/api/monitor.go
Normal 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
121
internal/api/router.go
Normal 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
86
internal/api/site.go
Normal 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
121
internal/api/token.go
Normal 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
213
internal/api/upload.go
Normal 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
424
internal/api/user.go
Normal 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": "个人信息更新成功"})
|
||||
}
|
Reference in New Issue
Block a user