diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..24026da --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (if not using vendor) +vendor/ + +# Go workspace file +go.work + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo + +# Environment files +.env + +# Git files +.git/ +.gitignore + +# Docker files +Dockerfile +.dockerignore + +# README and documentation +README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d936 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories +vendor/ +go/pkg/ + +# Go workspace file +go.work + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo + +# Environment files +.env + +# Docker files +quincy_admin.tar + +# Quincy related build files +quincy*/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a469f06 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +# 使用 golang:1.25.0-alpine 作为基础镜像,并命名为 builder 阶段 +FROM golang:1.25.0-alpine AS builder + +# 设置工作目录为 /app +WORKDIR /app + +# 创建日志/配置文件目录 +RUN mkdir -p /home/app/quincy/login + +# 复制 go.mod 和 go.sum 文件到工作目录 +COPY go.mod go.sum ./ + +# 添加国内代理解决网络问题 +ENV GOPROXY=https://mirrors.aliyun.com/goproxy/,direct + +# 下载 Go 依赖包 +RUN go mod download + +# 复制当前目录下所有文件到容器中 +COPY . . + +# 编译 Go 应用,禁用 CGO,目标平台为 linux,输出可执行文件 main +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# 使用 alpine:latest 作为基础镜像开始新的阶段 +FROM alpine:latest + +# 设置环境变量 TZ 为 Asia/Shanghai(北京时间) +ENV TZ=Asia/Shanghai + +# 安装 ca-certificates 和 tzdata 包,用于证书验证和时区设置 +RUN apk --no-cache add ca-certificates tzdata + +# 设置本地时区文件为上海时区 +RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 设置时区配置文件内容为 Asia/Shanghai +RUN echo "Asia/Shanghai" > /etc/timezone + +# 设置工作目录为 /root/ +WORKDIR /root/ + +# 从 builder 阶段复制编译好的可执行文件 main 到当前镜像 +COPY --from=builder /app/main . + +# 声明暴露端口 8080 +EXPOSE 8080 + +# 设置容器启动时执行的命令 +CMD ["./main"] diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..75dff6b --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,63 @@ +# Taskfile.yaml +version: '3' + +vars: + APP_NAME: quincy-admin + VERSION: + sh: git describe --tags --always --dirty + BUILD_TIME: + sh: date +%FT%T%z + LDFLAGS: + sh: echo "-ldflags \"-X main.Version={{.VERSION}} -X main.BuildTime={{.BUILD_TIME}}\"" + +tasks: + # 构建 Windows 版本 + windows: + cmds: + - GOOS=windows GOARCH=amd64 go build {{.LDFLAGS}} -o {{.APP_NAME}}.exe + + # 构建 Linux 版本 + linux: + cmds: + - GOOS=linux GOARCH=amd64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-linux + + # 为所有平台构建 + release: + deps: + - linux-amd64 + - linux-arm64 + - darwin-amd64 + - darwin-arm64 + - windows-amd64 + + # 多平台构建模板 + linux-amd64: + cmds: + - GOOS=linux GOARCH=amd64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-linux-amd64 + linux-arm64: + cmds: + - GOOS=linux GOARCH=arm64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-linux-arm64 + darwin-amd64: + cmds: + - GOOS=darwin GOARCH=amd64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-darwin-amd64 + darwin-arm64: + cmds: + - GOOS=darwin GOARCH=arm64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-darwin-arm64 + windows-amd64: + cmds: + - GOOS=windows GOARCH=amd64 go build {{.LDFLAGS}} -o {{.APP_NAME}}-windows-amd64.exe + + # 清理构建产物 + clean: + cmds: + - rm -f {{.APP_NAME}} {{.APP_NAME}}-* {{.APP_NAME}}.exe + + # 运行程序 + run: + cmds: + - go run . + + # 安装依赖 + deps: + cmds: + - go mod download diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..7a606c0 --- /dev/null +++ b/config/config.go @@ -0,0 +1,107 @@ +package config + +import ( + "os" + + "gopkg.in/yaml.v2" +) + +// Config 结构体定义了应用程序的所有配置项 +type Config struct { + // 数据库配置 + DB struct { + Host string `yaml:"host"` // 数据库主机地址 + Port string `yaml:"port"` // 数据库端口 + User string `yaml:"user"` // 数据库用户名 + Password string `yaml:"password"` // 数据库密码 + Name string `yaml:"name"` // 数据库名称 + } `yaml:"db"` + + Redis struct { + Host string `yaml:"host"` // Redis主机地址 + Port string `yaml:"port"` // Redis端口 + Password string `yaml:"password"` // Redis密码 + DB string `yaml:"db"` // Redis数据库索引 + } `yaml:"redis"` + + // 服务器配置 + Server struct { + Port string `yaml:"port"` // 服务器监听端口 + Mode string `yaml:"mode"` // 运行模式 + } `yaml:"server"` + + Jwt struct { + Secret string `yaml:"secret"` // JWT 密钥 + Expire string `yaml:"expire"` // JWT 过期时间(单位:小时) + } `yaml:"jwt"` + + // mmdb 配置 + Mmdb struct { + Path string `yaml:"path"` // mmdb 文件路径 + } `yaml:"mmdb"` + + // 日志配置 + Log struct { + Path string `yaml:"path"` // 日志目录路径 + } `yaml:"log"` +} + +// LoadConfig 加载并返回应用程序配置 +func LoadConfig() *Config { + // 首先尝试从配置文件加载 + if cfg := loadConfigFromFile(); cfg != nil { + return cfg + } + + // 如果没有配置文件,则使用环境变量或默认值 + cfg := &Config{} + + // 数据库配置 + cfg.DB.Host = getEnv("DB_HOST", "localhost") + cfg.DB.Port = getEnv("DB_PORT", "3306") + cfg.DB.User = getEnv("DB_USER", "root") + cfg.DB.Password = getEnv("DB_PASSWORD", "123456") + cfg.DB.Name = getEnv("DB_NAME", "quincy") + + // Redis 配置 + cfg.Redis.Host = getEnv("REDIS_HOST", "localhost") + cfg.Redis.Port = getEnv("REDIS_PORT", "6379") + cfg.Redis.Password = getEnv("REDIS_PASSWORD", "") + cfg.Redis.DB = getEnv("REDIS_DB", "0") + + // 服务器配置 + cfg.Server.Port = getEnv("SERVER_PORT", "8080") + cfg.Server.Mode = getEnv("SERVER_MODE", "debug") + + // Jwt 配置 + cfg.Jwt.Secret = getEnv("JWT_SECRET", "your-secret-key") + cfg.Jwt.Expire = getEnv("JWT_EXPIRE", "24") + + // mmdb 配置 + cfg.Mmdb.Path = getEnv("MMDB_PATH", "./GeoLite2-City.mmdb") + + cfg.Log.Path = getEnv("LOG_DIR", "./") + + return cfg +} + +// loadConfigFromFile 从配置文件加载配置 +func loadConfigFromFile() *Config { + // 尝试加载 yaml 配置文件 + if data, err := os.ReadFile("/home/app/quin_admin/default.yaml"); err == nil { + cfg := &Config{} + if err := yaml.Unmarshal(data, cfg); err == nil { + return cfg + } + } + return nil +} + +// getEnv 获取环境变量,如果不存在则返回默认值 +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} diff --git a/config/default.yaml b/config/default.yaml new file mode 100644 index 0000000..56f7c1c --- /dev/null +++ b/config/default.yaml @@ -0,0 +1,33 @@ +# 服务器配置 +server: + port: "8080" + mode: "debug" + +# JWT配置 +jwt: + secret: "admin-test" + expire: 24h + +# 数据库配置 +db: + host: "localhost" + port: "3306" + user: "root" + password: "123456" + name: "quincy" + +# Redis配置 +redis: + host: "localhost" + port: "6379" + password: "" + db: "0" + +# mmdb文件位置 +mmdb: + path: "/home/app/quincy/GeoLite2-City.mmdb" + +# 日志文件位置 +log: + path: "/home/app/quincy" + \ No newline at end of file diff --git a/controllers/base_utils.go b/controllers/base_utils.go new file mode 100644 index 0000000..3912a64 --- /dev/null +++ b/controllers/base_utils.go @@ -0,0 +1,21 @@ +package controllers + +import ( + "errors" + + "github.com/gin-gonic/gin" +) + +func getCode(ctx *gin.Context) (string, error) { + userIDInterface, exists := ctx.Get("code") + if !exists { + return "", errors.New("用户ID不存在") + } + + code, ok := userIDInterface.(string) + if !ok { + return "", errors.New("用户ID类型错误") + } + + return code, nil +} diff --git a/controllers/com_controller.go b/controllers/com_controller.go new file mode 100644 index 0000000..f4cf633 --- /dev/null +++ b/controllers/com_controller.go @@ -0,0 +1,171 @@ +package controllers + +import ( + "Quincy_admin/config" + "Quincy_admin/schemas" + "Quincy_admin/services" + "Quincy_admin/utils" + "fmt" + "net/http" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +type CommonController struct { + service *services.CommonService + config *config.Config +} + +func NewCommonController(service *services.CommonService) *CommonController { + return &CommonController{service: service, config: config.LoadConfig()} +} + +// GetLoginLogList 获取系统登录日志 +// @Summary 系统登录日志 +// @Description 系统登录日志 +// @Tags 系统管理 +// @Produce json +// @Param role body schemas.LoginLogListRequest true "req" +// @Success 200 {object} utils.Response{data=schemas.LoginLog} +// @Failure 500 {object} utils.Response +// @Router /system/log/login/list [post] +// @Security ApiKeyAuth +func (c *CommonController) GetLoginLogList(ctx *gin.Context) { + var req schemas.LoginLogListRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + items, total, err := c.service.GetLoginLogList(&req) + if err != nil { + fmt.Println("获取用户列表时出错:", err) + utils.Error(ctx, http.StatusInternalServerError, "获取日志列表失败") + return + } + + response := schemas.LoginLogListResponse{ + Item: items, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} + +// DownloadLogFile 下载日志文件 +// @Summary 下载日志文件 +// @Description 下载指定的日志文件内容 +// @Tags 系统管理 +// @Produce application/octet-stream +// @Param filename query string true "日志文件名" +// @Success 200 {file} file "日志文件内容" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /system/log/download [get] +// @Security ApiKeyAuth +// DownloadLogFile 下载日志文件 +func (c *CommonController) DownloadLogFile(ctx *gin.Context) { + filename := ctx.Query("filename") + if filename == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 filename 不能为空") + return + } + + // 验证文件名安全性 + if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") { + utils.Error(ctx, http.StatusBadRequest, "无效的文件名") + return + } + + // 使用 filepath.Join 处理跨平台路径分隔符 + filePath := filepath.Join(c.config.Log.Path, filename) + + // 检查文件是否存在 + if _, err := os.Stat(filePath); os.IsNotExist(err) { + utils.Error(ctx, http.StatusNotFound, "日志文件不存在") + return + } + + // 对文件名进行编码以支持中文 + encodedFilename := url.QueryEscape(filename) + + // 设置响应头 + ctx.Header("Content-Description", "File Transfer") + ctx.Header("Content-Transfer-Encoding", "binary") + ctx.Header("Content-Disposition", "attachment; filename*=UTF-8''"+encodedFilename) + ctx.Header("Content-Type", "application/octet-stream") + + // 返回文件内容 + ctx.File(filePath) +} + +// ViewLogFile 查看日志文件内容 +// @Summary 查看日志文件内容 +// @Description 返回指定日志文件的内容 +// @Tags 系统管理 +// @Produce json +// @Param filename query string true "日志文件名" +// @Param lines query int false "返回行数,默认100行" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /system/log/view [get] +// @Security ApiKeyAuth +// ViewLogFile 查看日志文件内容 +func (c *CommonController) ViewLogFile(ctx *gin.Context) { + filename := ctx.Query("filename") + if filename == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 filename 不能为空") + return + } + + linesStr := ctx.Query("lines") + lines := 100 // 默认返回100行 + if linesStr != "" { + var err error + lines, err = strconv.Atoi(linesStr) + if err != nil || lines <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 lines 必须是正整数") + return + } + if lines > 1000 { + lines = 1000 // 限制最大行数 + } + } + + // 验证文件名安全性 + if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") { + utils.Error(ctx, http.StatusBadRequest, "无效的文件名") + return + } + + // 使用配置文件中的日志目录 + f := c.config.Log.Path + "/" + filename + + // 读取文件内容 + content, err := c.service.ReadLastLines(f, lines) + if err != nil { + if os.IsNotExist(err) { + utils.Error(ctx, http.StatusNotFound, "日志文件不存在") + } else { + utils.Error(ctx, http.StatusInternalServerError, "读取日志文件失败: "+err.Error()) + } + return + } + + utils.Success(ctx, content) +} diff --git a/controllers/cron_controller.go b/controllers/cron_controller.go new file mode 100644 index 0000000..020586d --- /dev/null +++ b/controllers/cron_controller.go @@ -0,0 +1,263 @@ +// Package controllers/pms_controller.go +package controllers + +import ( + "Quincy_admin/schemas" + "Quincy_admin/services" + "Quincy_admin/utils" + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type CronController struct { + CronService *services.CronService +} + +func NewCronController(CronService *services.CronService) *CronController { + return &CronController{CronService: CronService} +} + +// GetCronList 定时任务列表 +// @Summary 定时任务列表 +// @Description 定时任务列表 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Param req body schemas.CronListRequest true "定时任务" +// @Success 200 {object} schemas.CronListResponse "定时任务" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/list [post] +// @Security ApiKeyAuth +func (c *CronController) GetCronList(ctx *gin.Context) { + var req schemas.CronListRequest + + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + items, total, err := c.CronService.GetCronList(&req) + if err != nil { + fmt.Println("获取用户列表时出错:", err) + utils.Error(ctx, http.StatusInternalServerError, "获取用户列表失败: "+err.Error()) + return + } + + response := schemas.CronListResponse{ + Item: items, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} + +// HandleCron 新增/更新任务 +// @Summary 新增/更新任务 +// @Description 新增/更新任务 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Param req body schemas.CronJobUpdateRequest true "新增/更新任务" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/hand [post] +// @Security ApiKeyAuth +func (c *CronController) HandleCron(ctx *gin.Context) { + var req schemas.CronJobUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + // 根据ID值判断是新增还是更新 + if req.ID > 0 { + // 更新操作 + err := c.CronService.UpdateCron(&req) + if err != nil { + utils.Error(ctx, http.StatusInternalServerError, "更新任务失败: "+err.Error()) + return + } + + utils.Success(ctx, "更新成功") + } else { + // 新增操作 + if _, err := c.CronService.AddCron(&req); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "添加任务失败: "+err.Error()) + return + } + utils.Success(ctx, "新增成功") + } + +} + +// StopOrStartCron 启用/停用任务 +// @Summary 启用/停用任务 +// @Description 启用/停用任务 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param enabled query int false "启用状态(1:启用,0:停用)" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/{id} [put] +// @Security ApiKeyAuth +func (c *CronController) StopOrStartCron(ctx *gin.Context) { + var idstr = ctx.Param("id") + + id, err := strconv.Atoi(idstr) + if err != nil || id <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 id 必须是有效的整数") + return + } + + enableStr := ctx.Query("enabled") + if enableStr == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 enabled 不能为空") + return + } + + enableValue, err := strconv.Atoi(enableStr) + if err != nil || (enableValue != 0 && enableValue != 1) { + utils.Error(ctx, http.StatusBadRequest, "参数 enabled 必须是0或1") + return + } + + // 直接传递整数值到服务层 + if err := c.CronService.UpdateCronStatus(id, enableValue); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "更新任务状态失败: "+err.Error()) + return + } + + if enableValue == 1 { + utils.Success(ctx, "启用成功") + } else { + utils.Success(ctx, "停用成功") + } +} + +// DeleteCron 删除定时任务 +// @Summary 删除定时任务 +// @Description 删除定时任务 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Param id path string true "id(任务ID)" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/{id} [delete] +// @Security ApiKeyAuth +func (c *CronController) DeleteCron(ctx *gin.Context) { + var idstr = ctx.Param("id") + + id, err := strconv.Atoi(idstr) + if err != nil || id <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 id 必须是有效的正整数") + return + } + + if err := c.CronService.DeleteCron(id); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "删除任务失败: "+err.Error()) + return + } + + utils.Success(ctx, "删除成功") +} + +// RestartCron 重启定时任务 +// @Summary 重启定时任务 +// @Description 重启定时任务 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/restart [get] +// @Security ApiKeyAuth +func (c *CronController) RestartCron(ctx *gin.Context) { + if err := c.CronService.RestartCron(); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "重启调度器失败: "+err.Error()) + return + } + + utils.Success(ctx, "重启成功") +} + +// StopCron 停止所有任务 +// @Summary 停止所有任务 +// @Description 停止所有任务 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/stop [get] +// @Security ApiKeyAuth +func (c *CronController) StopCron(ctx *gin.Context) { + if err := c.CronService.StopCron(); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "调度器停止失败: "+err.Error()) + return + } + + utils.Success(ctx, "停止成功") +} + +// GetCronLogList 查看日志运行记录 +// @Summary 查看日志运行记录 +// @Description 查看日志运行记录 +// @Tags 任务管理 +// @Accept json +// @Produce json +// @Param req body schemas.CronJobLogListRequest true "req" +// @Success 200 {object} schemas.CronJobLog "res" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /cron/loglist [post] +// @Security ApiKeyAuth +func (c *CronController) GetCronLogList(ctx *gin.Context) { + var req schemas.CronJobLogListRequest + + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + items, total, err := c.CronService.GetCronLogList(&req) + if err != nil { + fmt.Println("获取列表时出错:", err) + utils.Error(ctx, http.StatusInternalServerError, "获取列表失败: "+err.Error()) + return + } + + response := schemas.CronJobLogListResponse{ + Item: items, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} diff --git a/controllers/pms_controller.go b/controllers/pms_controller.go new file mode 100644 index 0000000..4a3b96e --- /dev/null +++ b/controllers/pms_controller.go @@ -0,0 +1,320 @@ +// Package controllers/pms_controller.go +package controllers + +import ( + "Quincy_admin/schemas" + "Quincy_admin/services" + "Quincy_admin/utils" + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type PermissionController struct { + PmsService *services.PermissionService +} + +func NewPermissionController(PmsService *services.PermissionService) *PermissionController { + return &PermissionController{PmsService: PmsService} +} + +// GetRoutes 侧边菜单权限 +// @Summary 侧边菜单权限 +// @Description 根据用户Session获取侧边栏菜单权限 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Success 200 {object} schemas.MenuItems "菜单信息" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/routes [get] +// @Security ApiKeyAuth +func (c *PermissionController) GetRoutes(ctx *gin.Context) { + // 从请求头获取 X-Access-Token + scode := ctx.GetHeader("X-Access-Token") + if scode == "" { + fmt.Println("无效的用户编码", scode) + utils.Fail(ctx, http.StatusBadRequest, "无效的用户编码") + return + } + + roleIDInterface, exists := ctx.Get("role_id") + if !exists { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID不存在") + ctx.Abort() + return + } + + roleID, ok := roleIDInterface.(int) + if !ok { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID类型错误") + ctx.Abort() + return + } + + items, err := c.PmsService.GetRoutesByID(roleID) + if err != nil { + fmt.Println("获取路由权限失败", err) + utils.Fail(ctx, http.StatusNotFound, "获取失败") + return + } + + utils.Success(ctx, items.Children) +} + +// GetMenuList 获取菜单列表 +// @Summary 获取菜单列表 +// @Description 获取管理后台菜单列表 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Param role body schemas.MenuListRequest true "req" +// @Success 200 {object} schemas.MenuItems "菜单信息" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/menus [post] +// @Security ApiKeyAuth +func (c *PermissionController) GetMenuList(ctx *gin.Context) { + var req schemas.MenuListRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + items, total, err := c.PmsService.GetMenuList(req.PageIndex, req.PageSize) + if err != nil { + utils.Error(ctx, http.StatusInternalServerError, err.Error()) + return + } + + response := schemas.MenuListResponse{ + Item: items, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} + +// UpdateMenuStatus 启用停用菜单 +// @Summary 启用停用菜单 +// @Description 启用停用菜单 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Param id path string true "id" +// @Param status query int false "启用状态(1:启用,0:停用)" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/menus/{id} [put] +// @Security ApiKeyAuth +func (c *PermissionController) UpdateMenuStatus(ctx *gin.Context) { + var idstr = ctx.Param("id") + + id, err := strconv.Atoi(idstr) + if err != nil || id <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 id 必须是有效的整数") + return + } + + statusStr := ctx.Query("status") + if statusStr == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 status 不能为空") + return + } + + statusValue, err := strconv.Atoi(statusStr) + if err != nil || (statusValue != 0 && statusValue != 1) { + utils.Error(ctx, http.StatusBadRequest, "参数 status 必须是0或1") + return + } + + // 直接传递整数值到服务层 + if err := c.PmsService.UpdateMenuStatus(id, statusValue); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "更新任务状态失败: "+err.Error()) + return + } + + if statusValue == 1 { + utils.Success(ctx, "启用成功") + } else { + utils.Success(ctx, "停用成功") + } +} + +// GetPermission 获取权限列表 +// @Summary 获取权限列表 +// @Description 根据用户Session和权限type获取权限列表 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Param type query string false "all-全部 button-按钮,route-路由,menu-菜单,api-接口" +// @Success 200 {object} []string "权限列表" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/permission [get] +// @Security ApiKeyAuth +func (c *PermissionController) GetPermission(ctx *gin.Context) { + // 获取角色 ID + roleIDInterface, exists := ctx.Get("role_id") + if !exists { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID不存在") + ctx.Abort() + return + } + + roleID, ok := roleIDInterface.(int) + if !ok { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID类型错误") + ctx.Abort() + return + } + + typeStr := ctx.Query("type") + if typeStr == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 type 不能为空") + return + } + + items, err := c.PmsService.GetPermission(roleID, typeStr) + if err != nil { + fmt.Println("获取路由权限失败", err) + utils.Fail(ctx, http.StatusNotFound, "获取失败") + return + } + + utils.Success(ctx, items) +} + +// GetAllPermission 获取所有权限 +// @Summary 获取所有权限 +// @Description 获取所有权限 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Success 200 {object} schemas.PermissionItems "权限列表" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/permission/all [get] +// @Security ApiKeyAuth +// @Security ApiKeyAuth +func (c *PermissionController) GetAllPermission(ctx *gin.Context) { + items, err := c.PmsService.GetAllPermission() + if err != nil { + fmt.Println("获取路由权限失败", err) + utils.Fail(ctx, http.StatusNotFound, "获取失败") + return + } + + // 返回树形结构,包含所有层级的 children + utils.Success(ctx, items.Children) +} + +// GetRolePermission 获取角色权限 +// @Summary 获取角色权限 +// @Description 获取角色权限 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @param role_id query int true "角色ID" +// @Param type query string false "all-全部 button-按钮,route-路由,menu-菜单,api-接口" +// @Success 200 {object} utils.Response{data=[]int} "权限列表" +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/permission/role [get] +// @Security ApiKeyAuth +func (c *PermissionController) GetRolePermission(ctx *gin.Context) { + // 获取角色 ID + var roleIDStr = ctx.Query("role_id") + roleID, err := strconv.Atoi(roleIDStr) + if err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数 role_id 错误") + return + } + + typeStr := ctx.Query("type") + if typeStr == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 type 不能为空") + return + } + + items, err := c.PmsService.GetRolePermission(roleID, typeStr) + if err != nil { + fmt.Println("获取路由权限失败", err) + utils.Fail(ctx, http.StatusNotFound, "获取失败") + return + } + + utils.Success(ctx, items) +} + +// AssignPermission 分配角色权限 +// @Summary 分配角色权限 +// @Description 为指定角色分配权限 +// @Tags 权限模块 +// @Accept json +// @Produce json +// @Param role_id path int true "角色ID" +// @Param permission_ids query []int true "权限ID数组" +// @Success 200 {object} utils.Response "权限分配成功" +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /pms/permission/assign/{role_id} [put] +// @Security ApiKeyAuth +func (c *PermissionController) AssignPermission(ctx *gin.Context) { + // 从 path 参数获取角色 ID + roleIDStr := ctx.Param("role_id") + roleID, err := strconv.Atoi(roleIDStr) + if err != nil || roleID <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 role_id 必须是有效的正整数") + return + } + + if roleID == 1 { + utils.Error(ctx, http.StatusForbidden, "无权修改超级管理员角色") + return + } + + // 从请求体解析权限ID数组 + var permissionIDs []int + if err := ctx.ShouldBindJSON(&permissionIDs); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数格式错误: "+err.Error()) + return + } + + // 验证权限ID数组不为空 + if len(permissionIDs) == 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 permission_ids 不能为空") + return + } + + // 验证每个权限ID的有效性 + for _, id := range permissionIDs { + if id <= 0 { + utils.Error(ctx, http.StatusBadRequest, fmt.Sprintf("权限ID '%d' 必须是有效的正整数", id)) + return + } + } + + // 调用服务层处理权限分配 + if err := c.PmsService.AssignPermission(roleID, permissionIDs); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "权限分配失败: "+err.Error()) + return + } + + utils.Success(ctx, "权限分配成功") +} diff --git a/controllers/user_controller.go b/controllers/user_controller.go new file mode 100644 index 0000000..150b61f --- /dev/null +++ b/controllers/user_controller.go @@ -0,0 +1,508 @@ +package controllers + +import ( + "Quincy_admin/config" + "Quincy_admin/schemas" + "Quincy_admin/services" + "Quincy_admin/utils" + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type UserController struct { + service *services.UserService + config *config.Config +} + +func NewUserController(service *services.UserService) *UserController { + return &UserController{service: service, config: config.LoadConfig()} +} + +// Create 创建用户 +// @Summary 创建用户 +// @Description 创建用户 +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Param user body schemas.CreateUser true "用户信息" +// @Success 200 {object} utils.Response{data=schemas.UserResponse} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/register [post] +// @Security ApiKeyAuth +func (c *UserController) Create(ctx *gin.Context) { + var user schemas.CreateUser + if err := ctx.ShouldBindJSON(&user); err != nil { + utils.Error(ctx, http.StatusBadRequest, "无效的请求数据,JSON解析错误") + return + } + + // 检查邮箱是否重复 + email, err := c.service.IsEmailExists(user.Email) + if err != nil { + utils.Error(ctx, http.StatusInternalServerError, err.Error()) + return + } + if email > 0 { + utils.Error(ctx, http.StatusBadRequest, "邮箱已存在") + return + } + + if err := c.service.CreateUser(&user); err != nil { + utils.Error(ctx, http.StatusInternalServerError, err.Error()) + return + } + + user.Password = "" + response := map[string]string{ + "sessioncode": user.SessionCode, + } + utils.Success(ctx, response) +} + +// GetByID 获取用户信息 +// @Summary 获取用户信息 +// @Description 获取用户信息 +// @Tags 用户管理 +// @Produce json +// @Success 200 {object} utils.Response{data=schemas.UserInfo} +// @Failure 400 {object} utils.Response +// @Failure 404 {object} utils.Response +// @Router /user/getinfo [get] +// @Security ApiKeyAuth +func (c *UserController) GetByID(ctx *gin.Context) { + scode, err := getCode(ctx) + if err != nil { + utils.Error(ctx, http.StatusUnauthorized, "用户ID不存在") + return + } + + if scode == "" { + utils.Error(ctx, http.StatusBadRequest, "无效的用户编码") + return + } + + user, err := c.service.GetUserByID(scode) + if err != nil { + utils.Error(ctx, http.StatusNotFound, "用户不存在") + return + } + + if user.RoleCode == 0 { + utils.Fail(ctx, http.StatusForbidden, "未分配权限") + return + } + + // 不返回密码等敏感信息 + user.Password = "" + utils.Success(ctx, user) +} + +// Update 更新用户信息 +// @Summary 更新用户信息 +// @Description 更新用户信息 +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Param user body schemas.UpdateUser true "用户信息" +// @Success 200 {object} utils.Response{data=string} +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/update [put] +// @Security ApiKeyAuth +func (c *UserController) Update(ctx *gin.Context) { + + var user schemas.UpdateUser + if err := ctx.ShouldBindJSON(&user); err != nil { + utils.Error(ctx, http.StatusBadRequest, "无效的请求数据") + return + } + + // 防止修改超级管理员账户(ID=1) + if user.ID == 1 { + utils.Error(ctx, http.StatusForbidden, "无权限修改超级管理员账户") + return + } + + if _, err := c.service.UpdateUser(&user); err != nil { + utils.Error(ctx, http.StatusInternalServerError, err.Error()) + return + } + + user.Password = "" + utils.Success(ctx, user.ID) +} + +// UpdateStatus 禁用/启用用户 +// @Summary 禁用/启用用户 +// @Description 禁用/启用用户 +// @Tags 用户管理 +// @Param id path string true "用户ID" +// @Param status query int false "启用状态(1:启用,0:停用)" +// @Success 200 {object} utils.Response +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/{id} [put] +// @Security ApiKeyAuth +func (c *UserController) UpdateStatus(ctx *gin.Context) { + idstr := ctx.Param("id") + + id, err := strconv.Atoi(idstr) + if err != nil || id <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 id 必须是有效的整数") + return + } + + // 防止修改超级管理员账户(ID=1) + if id == 1 { + utils.Error(ctx, http.StatusForbidden, "无权限修改超级管理员账户") + return + } + + status := ctx.Query("status") + if status == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 status 不能为空") + return + } + + statusValue, err := strconv.Atoi(status) + if err != nil || (statusValue != 0 && statusValue != 1) { + utils.Error(ctx, http.StatusBadRequest, "参数 status 必须是0或1") + return + } + + // 直接传递整数值到服务层 + if err := c.service.UpdateStatus(id, statusValue); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "操作失败: "+err.Error()) + return + } + + if statusValue == 1 { + utils.Success(ctx, "启用成功") + } else { + utils.Success(ctx, "停用成功") + } +} + +// Login 用户登录 +// @Summary 用户登录 +// @Description 用户登录验证 +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Param login body schemas.LoginRequest true "req" +// @Success 200 {object} utils.Response{data=schemas.UserResponse} +// @Failure 400 {object} utils.Response +// @Failure 401 {object} utils.Response +// @Failure 403 {object} utils.Response +// @Router /user/login [post] +func (c *UserController) Login(ctx *gin.Context) { + var loginReq schemas.LoginRequest + + if err := ctx.ShouldBindJSON(&loginReq); err != nil { + utils.Fail(ctx, http.StatusBadRequest, "无效的登录数据") + return + } + + user, err := c.service.GetUserByUsername(loginReq.Username) + if err != nil { + // 记录失败登录日志 + err := c.service.RecordLoginLog(0, loginReq.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 0, "用户不存在") + if err != nil { + return + } + utils.Fail(ctx, http.StatusUnauthorized, "用户不存在") + return + } + + // 更新最后登录时间 + if err := c.service.UpdateLastLoginTime(user.SessionCode); err != nil { + // 记录失败登录日志 + err := c.service.RecordLoginLog(0, loginReq.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 0, "系统错误") + if err != nil { + return + } + utils.Fail(ctx, http.StatusInternalServerError, "系统错误") + return + } + + if user.Status == schemas.UserStatusDisabled { + // 记录失败登录日志 + err := c.service.RecordLoginLog(user.ID, user.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 0, "用户已被锁定") + if err != nil { + return + } + utils.Fail(ctx, http.StatusForbidden, "用户已被锁定") + return + } + + if user.RoleCode == 0 { + // 记录失败登录日志 + err := c.service.RecordLoginLog(user.ID, user.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 0, "未分配权限") + if err != nil { + return + } + utils.Fail(ctx, http.StatusForbidden, "未分配权限") + return + } + + if !c.service.VerifyPassword(loginReq.Password, user.Password) { + // 记录失败登录日志 + err := c.service.RecordLoginLog(user.ID, user.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 0, "密码错误") + if err != nil { + return + } + utils.Fail(ctx, http.StatusUnauthorized, "密码错误") + return + } + + // 记录成功登录日志 + err = c.service.RecordLoginLog(user.ID, user.Username, ctx.ClientIP(), ctx.Request.UserAgent(), 1, "") + if err != nil { + return + } + + user.Password = "" + + //err = c.service.StoreLoginSession(user.SessionCode, user, 24*time.Hour) + //if err != nil { + // utils.Error(ctx, http.StatusInternalServerError, "登录失败") + // return + //} + + // 生成 JWT token + token, err := utils.GenerateToken(user.ID, user.Username, user.SessionCode, user.RoleCode) + if err != nil { + utils.Error(ctx, http.StatusInternalServerError, "生成令牌失败") + return + } + + // 返回 token 和用户信息 + response := map[string]interface{}{ + "Authorization": token, + } + utils.Success(ctx, response) +} + +// UserList 获取用户列表 +// @Summary 获取用户列表 +// @Description 获取用户列表 +// @Tags 用户管理 +// @Produce json +// @Param user body schemas.UserListRequest true "请求体" +// @Success 200 {object} utils.Response{data=schemas.UserListResponse} +// @Failure 500 {object} utils.Response +// @Router /user/list [post] +// @Security ApiKeyAuth +func (c *UserController) UserList(ctx *gin.Context) { + var req schemas.UserListRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + users, total, err := c.service.GetUserList(&req) + if err != nil { + fmt.Println("获取用户列表时出错:", err) + utils.Error(ctx, http.StatusInternalServerError, "获取用户列表失败") + return + } + + // 清除用户密码信息 + for _, user := range users { + user.Password = "" + } + + response := schemas.UserListResponse{ + Item: users, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} + +// UserRoleList 获取角色列表 +// @Summary 获取角色列表 +// @Description 获取角色列表 +// @Tags 用户管理 +// @Produce json +// @Param role body schemas.RoleListRequest true "req" +// @Success 200 {object} utils.Response{data=schemas.RoleResponseList} +// @Failure 500 {object} utils.Response +// @Router /user/rolelist [post] +// @Security ApiKeyAuth +func (c *UserController) UserRoleList(ctx *gin.Context) { + var req schemas.RoleListRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.PageIndex <= 0 || req.PageSize <= 0 || req.PageSize > 100 { + utils.Error(ctx, http.StatusBadRequest, "分页参数错误") + return + } + + items, total, err := c.service.GetRoleList(&req) + if err != nil { + fmt.Println("获取用户列表时出错:", err) + utils.Error(ctx, http.StatusInternalServerError, "获取用户列表失败") + return + } + + response := schemas.RoleListResponse{ + Item: items, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + } + + utils.Success(ctx, response) +} + +// AssignRole 用户角色分配 +// @Summary 用户角色分配 +// @Description 用户角色分配 +// @Tags 用户管理 +// @Param user_id query string true "用户ID" +// @Param role_id query int false "角色ID" +// @Success 200 {object} utils.Response +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/assign [put] +// @Security ApiKeyAuth +func (c *UserController) AssignRole(ctx *gin.Context) { + userId := ctx.Query("user_id") + userIdValue, err := strconv.Atoi(userId) + if err != nil || userIdValue <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 user_id 必须是有效的整数") + return + } + + if userIdValue == 1 { + utils.Error(ctx, http.StatusForbidden, "无权限修改超级管理员账户") + return + } + + roleId := ctx.Query("role_id") + roleIdValue, err := strconv.Atoi(roleId) + if err != nil || roleIdValue <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 role_id 必须是有效的整数") + return + } + + if roleIdValue == 1 { + utils.Error(ctx, http.StatusForbidden, "禁止分配超级管理员权限") + return + } + + // 直接传递整数值到服务层 + if err := c.service.AssignRole(userIdValue, roleIdValue); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "操作失败: "+err.Error()) + return + } + + utils.Success(ctx, "") +} + +// UpdateRoleStatus 禁用/启用角色 +// @Summary 禁用/启用角色 +// @Description 禁用/启用角色 +// @Tags 用户管理 +// @Param id path string true "角色ID" +// @Param status query int false "启用状态(1:启用,0:停用)" +// @Success 200 {object} utils.Response +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/role/{id} [put] +// @Security ApiKeyAuth +func (c *UserController) UpdateRoleStatus(ctx *gin.Context) { + idstr := ctx.Param("id") + + id, err := strconv.Atoi(idstr) + if err != nil || id <= 0 { + utils.Error(ctx, http.StatusBadRequest, "参数 id 必须是有效的整数") + return + } + + // 防止修改超级管理员账户(ID=1) + if id == 1 { + utils.Error(ctx, http.StatusForbidden, "无权限修改超级管理员账户") + return + } + + status := ctx.Query("status") + if status == "" { + utils.Error(ctx, http.StatusBadRequest, "参数 status 不能为空") + return + } + + statusValue, err := strconv.Atoi(status) + if err != nil || (statusValue != 0 && statusValue != 1) { + utils.Error(ctx, http.StatusBadRequest, "参数 status 必须是0或1") + return + } + + // 直接传递整数值到服务层 + if err := c.service.UpdateRoleStatus(id, statusValue); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "操作失败: "+err.Error()) + return + } + + if statusValue == 1 { + utils.Success(ctx, "启用成功") + } else { + utils.Success(ctx, "停用成功") + } +} + +// CreateRole 创建角色 +// @Summary 创建角色 +// @Description 创建角色 +// @Tags 用户管理 +// @Produce json +// @Param role body schemas.CreateRole true "req" +// @Success 200 {object} utils.Response +// @Failure 400 {object} utils.Response +// @Failure 500 {object} utils.Response +// @Router /user/role [post] +// @Security ApiKeyAuth +func (c *UserController) CreateRole(ctx *gin.Context) { + var req schemas.CreateRole + if err := ctx.ShouldBindJSON(&req); err != nil { + utils.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + if req.ID == 1 { + utils.Error(ctx, http.StatusForbidden, "无权限修改超级管理员账户") + return + } + + // 判断ID是否传入,传了更新,没传新增 + if req.ID > 0 { + // 更新角色 + if err := c.service.UpdateRole(&req); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "更新失败: "+err.Error()) + return + } + utils.Success(ctx, "更新成功") + } else { + // 新增角色 + if err := c.service.CreateRole(&req); err != nil { + utils.Error(ctx, http.StatusInternalServerError, "创建失败: "+err.Error()) + return + } + utils.Success(ctx, "创建成功") + } +} diff --git a/crontask/cron_manager.go b/crontask/cron_manager.go new file mode 100644 index 0000000..fe38dbe --- /dev/null +++ b/crontask/cron_manager.go @@ -0,0 +1,171 @@ +// Package crontask/cron_manager.go 主管理器文件 +package crontask + +import ( + "context" + "fmt" + "log" + + "github.com/jmoiron/sqlx" + "github.com/robfig/cron/v3" +) + +type JobHandler func() error + +type CronManager struct { + cron *cron.Cron + db *sqlx.DB + handlers map[string]JobHandler + entries map[int]cron.EntryID + cancel context.CancelFunc + ctx context.Context +} + +func NewCronManager(db *sqlx.DB) *CronManager { + ctx, cancel := context.WithCancel(context.Background()) + cm := &CronManager{ + cron: cron.New(), + db: db, + handlers: make(map[string]JobHandler), + entries: make(map[int]cron.EntryID), + cancel: cancel, + ctx: ctx, + } + + // 注册默认任务处理器 + cm.registerDefaultHandlers() + + // 加载数据库中的定时任务 + if err := cm.LoadJobs(); err != nil { + log.Printf("Failed to load cron jobs: %v", err) + } + + return cm +} + +// Start 开始所有定时任务 OK +func (cm *CronManager) Start() { + cm.cron.Start() + log.Println("定时任务已启动") +} + +// Stop 停止所有定时任务 OK +func (cm *CronManager) Stop() error { + log.Println("调度器正在停止...") + if cm.cron != nil && cm.cancel != nil { + // 取消所有任务的上下文 + cm.cancel() + + // 停止调度器 + cm.cron.Stop() + + // 移除所有已注册的任务条目 + for _, entryID := range cm.entries { + cm.cron.Remove(entryID) + } + log.Printf("已移除 %d 个任务条目", len(cm.entries)) + + // 清空条目映射 + cm.entries = make(map[int]cron.EntryID) + + // 重新创建上下文,为下次启动做准备 + ctx, cancel := context.WithCancel(context.Background()) + cm.ctx = ctx + cm.cancel = cancel + } + log.Println("调度器停止完成") + return nil +} + +// RemoveJob 移除指定任务 OK +func (cm *CronManager) RemoveJob(jobID int) error { + if entryID, exists := cm.entries[jobID]; exists { + cm.cron.Remove(entryID) + delete(cm.entries, jobID) + log.Printf("已移除任务 ID: %d", jobID) + } + return nil +} + +// AddJobByID 添加指定任务 OK +func (cm *CronManager) AddJobByID(jobID int) error { + var job CronJob + err := cm.db.Get(&job, "SELECT id, name, schedule, handler, enabled FROM admin_cron_jobs WHERE id = ? AND enabled = 1 AND isdel = 0", jobID) + if err != nil { + return fmt.Errorf("job with ID %d not found", jobID) + } + + // 检查 handler 是否存在 + handler, exists := cm.handlers[job.Handler] + if !exists { + return fmt.Errorf("handler %s not found for job %d", job.Handler, jobID) + } + + // 如果该任务已存在,先移除旧的任务 + if entryID, exists := cm.entries[jobID]; exists { + cm.cron.Remove(entryID) + delete(cm.entries, jobID) + log.Printf("移除已存在的任务: %s (ID: %d)", job.Name, jobID) + } + + // 添加新任务 + entryID, err := cm.cron.AddFunc(job.Schedule, func() { + cm.executeJob(job, handler) + }) + if err != nil { + return err + } + + cm.entries[jobID] = entryID + log.Printf("Added job %s with ID %d", job.Name, jobID) + return nil +} + +// Restart 重启定时任务管理器(移除当前所有任务并停止后再次启动) OK +func (cm *CronManager) Restart() error { + log.Println("调度器正在重启...") + + // 停止当前所有任务 + if err := cm.Stop(); err != nil { + return err + } + + // 清空处理器和条目映射 + cm.handlers = make(map[string]JobHandler) + cm.entries = make(map[int]cron.EntryID) + + // 重新初始化 cron 实例 + cm.cron = cron.New() + + // 重新注册默认处理器 + cm.registerDefaultHandlers() + + // 重新加载数据库中的任务 + if err := cm.LoadJobs(); err != nil { + return err + } + + // 启动任务(如果未暂停) + cm.Start() + + log.Println("调度器重启完成") + return nil +} + +// RefreshJobs 刷新任务列表 OK +func (cm *CronManager) RefreshJobs() error { + // 停止当前所有任务 + if err := cm.Stop(); err != nil { + return err + } + cm.handlers = make(map[string]JobHandler) + cm.entries = make(map[int]cron.EntryID) + cm.cron = cron.New() + cm.registerDefaultHandlers() + err := cm.LoadJobs() + if err != nil { + return err + } + cm.Start() + return nil +} diff --git a/crontask/job_handlers.go b/crontask/job_handlers.go new file mode 100644 index 0000000..6b46b3f --- /dev/null +++ b/crontask/job_handlers.go @@ -0,0 +1,55 @@ +// Package crontask/job_handlers 任务处理器文件 +package crontask + +import ( + "log" + "os/exec" +) + +func (cm *CronManager) registerDefaultHandlers() { + // mysql数据库备份任务 + cm.RegisterHandler("backup_data", func() error { + err := ExecuteShellCommand("backup_data", "./backup_mysql.sh start") + if err != nil { + return err + } + + err = ExecuteShellCommand("backup_mysql", "./backup_mysql.sh clean") + if err != nil { + return err + } + + return nil + }) + + // 系统监控 + cm.RegisterHandler("system_monitor", func() error { + log.Printf("system_monitor !!!") + return nil + }) + + // 执行日志轮转任务 + cm.RegisterHandler("cleanup_log", func() error { + return ExecuteShellCommand("cleanup_log", "./gin-app.sh cleanlogs") + }) + +} + +// ExecuteShellCommand 执行shell命令的公共方法 +func ExecuteShellCommand(name, command string) error { + cmd := exec.Command("/bin/bash", "-c", command) + output, err := cmd.CombinedOutput() + if err != nil { + log.Printf("[%s] Shell command execution failed: %v", name, err) + log.Printf("[%s] Command output: %s", name, output) + return err + } + log.Printf("[%s] Shell command executed successfully", name) + log.Printf("[%s] Command output: %s", name, output) + return nil +} + +// RegisterHandler 注册任务处理器 +func (cm *CronManager) RegisterHandler(name string, handler JobHandler) { + cm.handlers[name] = handler +} diff --git a/crontask/job_manager.go b/crontask/job_manager.go new file mode 100644 index 0000000..16691a9 --- /dev/null +++ b/crontask/job_manager.go @@ -0,0 +1,108 @@ +// Package crontask/job_manager.go 任务管理文件 +package crontask + +import ( + "fmt" + "log" + "time" +) + +// LoadJobs 加载数据库中的定时任务 +func (cm *CronManager) LoadJobs() error { + const query = ` + SELECT id, name, schedule, handler, enabled + FROM admin_cron_jobs + WHERE enabled = ? + AND isdel = 0 + ` + + var jobs []CronJob + err := cm.db.Select(&jobs, query, 1) + if err != nil { + return err + } + + log.Printf("Loaded %d enabled cron jobs", len(jobs)) + + for _, job := range jobs { + if err := cm.AddJob(job); err != nil { + log.Printf("Failed to add job %s: %v", job.Name, err) + } + } + return nil +} + +// AddJob 添加定时任务 +func (cm *CronManager) AddJob(job CronJob) error { + // 如果该任务已存在,先移除旧的任务 + if entryID, exists := cm.entries[job.ID]; exists { + cm.cron.Remove(entryID) + delete(cm.entries, job.ID) + log.Printf("移除已存在的任务: %s (ID: %d)", job.Name, job.ID) + } + + handler, exists := cm.handlers[job.Handler] + if !exists { + return fmt.Errorf("handler %s not found", job.Handler) + } + + entryID, err := cm.cron.AddFunc(job.Schedule, func() { + cm.executeJob(job, handler) + }) + if err != nil { + return err + } + + cm.entries[job.ID] = entryID + log.Printf("Added job %s with ID %d", job.Name, job.ID) + return nil +} + +// executeJob 执行定时任务 +func (cm *CronManager) executeJob(job CronJob, handler JobHandler) { + // 检查上下文是否已取消 + select { + case <-cm.ctx.Done(): + log.Printf("Job %s cancelled before execution", job.Name) + return + default: + } + + startTime := time.Now() + status := "success" + var message string + + // 执行任务(如果任务支持上下文取消,应传递 cm.ctx) + err := handler() + if err != nil { + status = "failed" + message = err.Error() + log.Printf("Job %s execution failed: %v", job.Name, err) + } + + // 检查任务是否在执行过程中被取消 + select { + case <-cm.ctx.Done(): + log.Printf("Job %s was cancelled during execution", job.Name) + return + default: + } + + // 创建 endTime 变量以便获取其地址 + endTime := time.Now() + + // 记录执行日志 + logEntry := CronJobLog{ + JobID: job.ID, + Status: status, + Message: message, + StartTime: &startTime, + EndTime: &endTime, + } + + _, err = cm.db.NamedExec(`INSERT INTO admin_cron_job_logs (job_id, status, message, start_time, end_time) + VALUES (:job_id, :status, :message, :start_time, :end_time)`, logEntry) + if err != nil { + log.Printf("Failed to log job execution: %v", err) + } +} diff --git a/crontask/models.go b/crontask/models.go new file mode 100644 index 0000000..23c4ac2 --- /dev/null +++ b/crontask/models.go @@ -0,0 +1,26 @@ +// Package crontask/models.go 数据模型文件 +package crontask + +import ( + "time" +) + +type CronJob struct { + ID int `db:"id"` + Name string `db:"name"` + Schedule string `db:"schedule"` + Handler string `db:"handler"` + Enabled int `db:"enabled"` + Description string `db:"description"` + CreatedAt *time.Time `db:"create_time"` + UpdatedAt *time.Time `db:"update_time"` +} + +type CronJobLog struct { + ID int `db:"id"` + JobID int `db:"job_id"` + Status string `db:"status"` + Message string `db:"message"` + StartTime *time.Time `db:"start_time"` + EndTime *time.Time `db:"end_time"` +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..eca060e --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,2251 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/cron/hand": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "新增/更新任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "新增/更新任务", + "parameters": [ + { + "description": "新增/更新任务", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronJobUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "定时任务列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "定时任务列表", + "parameters": [ + { + "description": "定时任务", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronListRequest" + } + } + ], + "responses": { + "200": { + "description": "定时任务", + "schema": { + "$ref": "#/definitions/schemas.CronListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/loglist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "查看日志运行记录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "查看日志运行记录", + "parameters": [ + { + "description": "req", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronJobLogListRequest" + } + } + ], + "responses": { + "200": { + "description": "res", + "schema": { + "$ref": "#/definitions/schemas.CronJobLog" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/restart": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "重启定时任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "重启定时任务", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/stop": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "停止所有任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "停止所有任务", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "启用/停用任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "启用/停用任务", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "enabled", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "删除定时任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "删除定时任务", + "parameters": [ + { + "type": "string", + "description": "id(任务ID)", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/menus": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取管理后台菜单列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取菜单列表", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.MenuListRequest" + } + } + ], + "responses": { + "200": { + "description": "菜单信息", + "schema": { + "$ref": "#/definitions/schemas.MenuItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/menus/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "启用停用菜单", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "启用停用菜单", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "根据用户Session和权限type获取权限列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取权限列表", + "parameters": [ + { + "type": "string", + "description": "all-全部 button-按钮,route-路由,menu-菜单,api-接口", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "权限列表", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/all": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "获取所有权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取所有权限", + "responses": { + "200": { + "description": "权限列表", + "schema": { + "$ref": "#/definitions/schemas.PermissionItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/assign/{role_id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "为指定角色分配权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "分配角色权限", + "parameters": [ + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "path", + "required": true + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "权限ID数组", + "name": "permission_ids", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "权限分配成功", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/role": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取角色权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取角色权限", + "parameters": [ + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "all-全部 button-按钮,route-路由,menu-菜单,api-接口", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "权限列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/routes": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "根据用户Session获取侧边栏菜单权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "侧边菜单权限", + "responses": { + "200": { + "description": "菜单信息", + "schema": { + "$ref": "#/definitions/schemas.MenuItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/download": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "下载指定的日志文件内容", + "produces": [ + "application/octet-stream" + ], + "tags": [ + "系统管理" + ], + "summary": "下载日志文件", + "parameters": [ + { + "type": "string", + "description": "日志文件名", + "name": "filename", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "日志文件内容", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/login/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "系统登录日志", + "produces": [ + "application/json" + ], + "tags": [ + "系统管理" + ], + "summary": "系统登录日志", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.LoginLogListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.LoginLog" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/view": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "返回指定日志文件的内容", + "produces": [ + "application/json" + ], + "tags": [ + "系统管理" + ], + "summary": "查看日志文件内容", + "parameters": [ + { + "type": "string", + "description": "日志文件名", + "name": "filename", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "返回行数,默认100行", + "name": "lines", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/assign": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "用户角色分配", + "tags": [ + "用户管理" + ], + "summary": "用户角色分配", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "user_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/getinfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取用户信息", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserInfo" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取用户列表", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取用户列表", + "parameters": [ + { + "description": "请求体", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.UserListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserListResponse" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "用户登录验证", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "req", + "name": "login", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/register": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建用户", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户信息", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CreateUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/role": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建角色", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CreateRole" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/role/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "禁用/启用角色", + "tags": [ + "用户管理" + ], + "summary": "禁用/启用角色", + "parameters": [ + { + "type": "string", + "description": "角色ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/rolelist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取角色列表", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取角色列表", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.RoleListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.RoleResponseList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/update": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "更新用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "更新用户信息", + "parameters": [ + { + "description": "用户信息", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.UpdateUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "禁用/启用用户", + "tags": [ + "用户管理" + ], + "summary": "禁用/启用用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + } + }, + "definitions": { + "schemas.CreateRole": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "schemas.CreateUser": { + "type": "object", + "required": [ + "email", + "nickname", + "password" + ], + "properties": { + "avatar": { + "description": "Avatar 头像URL", + "type": "string" + }, + "email": { + "description": "Email 邮箱地址,用于联系和找回密码", + "type": "string" + }, + "id": { + "description": "ID 用户ID,数据库自动生成, 不传", + "type": "integer" + }, + "last_login_time": { + "description": "LastLoginTime 最后登录时间,系统生成", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "nickname": { + "description": "Nickname 用户昵称,显示名称", + "type": "string" + }, + "password": { + "description": "Password 密码,将被加密存储", + "type": "string" + }, + "register_time": { + "description": "RegisterTime 注册时间,系统生成", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "rolecode": { + "description": "RoleCode 角色编码, 不传, 默认0", + "type": "integer" + }, + "rolename": { + "description": "RoleName 角色名称, 不传", + "type": "string" + }, + "sessioncode": { + "description": "SessionCode 用户编码 系统生成", + "type": "string" + }, + "status": { + "description": "Status 用户状态,不传", + "type": "integer" + }, + "username": { + "description": "Username 用户名,系统唯一标识,系统生成, 不传", + "type": "string" + } + } + }, + "schemas.CronJob": { + "type": "object", + "properties": { + "create_time": { + "description": "创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "description": { + "description": "任务描述", + "type": "string" + }, + "enabled": { + "description": "任务是否启用", + "type": "integer" + }, + "handler": { + "description": "任务处理程序", + "type": "string" + }, + "id": { + "description": "任务ID", + "type": "integer" + }, + "name": { + "description": "任务名称", + "type": "string" + }, + "schedule": { + "description": "任务计划", + "type": "string" + }, + "update_time": { + "description": "更新时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + } + } + }, + "schemas.CronJobLog": { + "type": "object", + "properties": { + "end_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "handler": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "job_id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "schedule": { + "type": "string" + }, + "start_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "status": { + "type": "string" + } + } + }, + "schemas.CronJobLogListRequest": { + "type": "object", + "required": [ + "id", + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "id": { + "type": "integer" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.CronJobUpdateRequest": { + "type": "object", + "properties": { + "description": { + "description": "任务是否启用\nEnabled int ` + "`" + `json:\"enabled\" db:\"enabled\"` + "`" + `\n任务描述", + "type": "string" + }, + "handler": { + "description": "任务处理程序", + "type": "string" + }, + "id": { + "description": "任务ID 传参代表修改 不传参代表新增", + "type": "integer" + }, + "name": { + "description": "任务名称", + "type": "string" + }, + "schedule": { + "description": "任务计划", + "type": "string" + } + } + }, + "schemas.CronListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "name": { + "type": "string" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.CronListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.CronJob" + } + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "schemas.CustomTime": { + "type": "object", + "properties": { + "time": { + "type": "string" + } + } + }, + "schemas.LoginLog": { + "type": "object", + "properties": { + "create_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "failure_reason": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ip_address": { + "type": "string" + }, + "isdel": { + "type": "integer" + }, + "location": { + "type": "string" + }, + "login_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "status": { + "type": "integer" + }, + "update_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "user_agent": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "schemas.LoginLogListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "description": "Password 密码,用于登录验证", + "type": "string" + }, + "username": { + "description": "Username 用户名,用于登录验证", + "type": "string" + } + } + }, + "schemas.MenuItems": { + "type": "object", + "properties": { + "component": { + "description": "组件名称", + "type": "string" + }, + "create_time": { + "description": "创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "icon": { + "description": "菜单项图标", + "type": "string" + }, + "id": { + "description": "ID 菜单项ID", + "type": "integer" + }, + "parent_id": { + "description": "父级菜单项ID", + "type": "integer" + }, + "path": { + "description": "菜单项路径", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "title": { + "description": "菜单项标题", + "type": "string" + }, + "visible": { + "description": "是否可见", + "type": "integer" + } + } + }, + "schemas.MenuListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + } + } + }, + "schemas.PermissionItems": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.PermissionItems" + } + }, + "id": { + "type": "integer" + }, + "module": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schemas.RoleListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.RoleResponseList": { + "type": "object", + "properties": { + "code": { + "description": "Code 角色编码", + "type": "string" + }, + "create_time": { + "description": "CreateTime 创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "description": { + "description": "Desc 角色描述", + "type": "string" + }, + "id": { + "description": "ID 角色ID", + "type": "integer" + }, + "name": { + "description": "Name 角色名称", + "type": "string" + }, + "status": { + "description": "Status 角色状态", + "type": "integer" + } + } + }, + "schemas.UpdateUser": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "avatar": { + "description": "Avatar 头像URL", + "type": "string" + }, + "email": { + "description": "Email 邮箱地址,用于联系和找回密码", + "type": "string" + }, + "id": { + "description": "ID 用户编码,用于定位用户", + "type": "integer" + }, + "nickname": { + "description": "Nickname 用户昵称,显示名称", + "type": "string" + }, + "password": { + "description": "Password 密码,将被加密存储", + "type": "string" + } + } + }, + "schemas.UserInfo": { + "type": "object", + "required": [ + "email", + "nickname", + "password" + ], + "properties": { + "avatar": { + "description": "头像URL", + "type": "string" + }, + "email": { + "description": "邮箱地址", + "type": "string" + }, + "id": { + "description": "用户ID", + "type": "integer" + }, + "last_login_time": { + "description": "最后登录时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "register_time": { + "description": "注册时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "rolecode": { + "description": "角色编码", + "type": "integer" + }, + "rolename": { + "description": "角色名称", + "type": "string" + }, + "sessioncode": { + "description": "用户编码", + "type": "string" + }, + "status": { + "description": "用户状态", + "type": "integer" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "schemas.UserListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "nickname": { + "description": "昵称(用户名)", + "type": "string" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_time": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.UserListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.UserInfo" + } + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "schemas.UserResponse": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + } + } + }, + "utils.Response": { + "type": "object", + "properties": { + "data": {}, + "message": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "X-Access-Token", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/quin", + Schemes: []string{}, + Title: "Quincy_admin", + Description: "打造一款综合性接口平台.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..867871e --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,2227 @@ +{ + "swagger": "2.0", + "info": { + "description": "打造一款综合性接口平台.", + "title": "Quincy_admin", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/quin", + "paths": { + "/cron/hand": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "新增/更新任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "新增/更新任务", + "parameters": [ + { + "description": "新增/更新任务", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronJobUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "定时任务列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "定时任务列表", + "parameters": [ + { + "description": "定时任务", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronListRequest" + } + } + ], + "responses": { + "200": { + "description": "定时任务", + "schema": { + "$ref": "#/definitions/schemas.CronListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/loglist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "查看日志运行记录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "查看日志运行记录", + "parameters": [ + { + "description": "req", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CronJobLogListRequest" + } + } + ], + "responses": { + "200": { + "description": "res", + "schema": { + "$ref": "#/definitions/schemas.CronJobLog" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/restart": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "重启定时任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "重启定时任务", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/stop": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "停止所有任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "停止所有任务", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/cron/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "启用/停用任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "启用/停用任务", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "enabled", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "删除定时任务", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "任务管理" + ], + "summary": "删除定时任务", + "parameters": [ + { + "type": "string", + "description": "id(任务ID)", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/menus": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取管理后台菜单列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取菜单列表", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.MenuListRequest" + } + } + ], + "responses": { + "200": { + "description": "菜单信息", + "schema": { + "$ref": "#/definitions/schemas.MenuItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/menus/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "启用停用菜单", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "启用停用菜单", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "根据用户Session和权限type获取权限列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取权限列表", + "parameters": [ + { + "type": "string", + "description": "all-全部 button-按钮,route-路由,menu-菜单,api-接口", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "权限列表", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/all": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "获取所有权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取所有权限", + "responses": { + "200": { + "description": "权限列表", + "schema": { + "$ref": "#/definitions/schemas.PermissionItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/assign/{role_id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "为指定角色分配权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "分配角色权限", + "parameters": [ + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "path", + "required": true + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "权限ID数组", + "name": "permission_ids", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "权限分配成功", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/permission/role": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取角色权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "获取角色权限", + "parameters": [ + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "all-全部 button-按钮,route-路由,menu-菜单,api-接口", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "权限列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/pms/routes": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "根据用户Session获取侧边栏菜单权限", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权限模块" + ], + "summary": "侧边菜单权限", + "responses": { + "200": { + "description": "菜单信息", + "schema": { + "$ref": "#/definitions/schemas.MenuItems" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/download": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "下载指定的日志文件内容", + "produces": [ + "application/octet-stream" + ], + "tags": [ + "系统管理" + ], + "summary": "下载日志文件", + "parameters": [ + { + "type": "string", + "description": "日志文件名", + "name": "filename", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "日志文件内容", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/login/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "系统登录日志", + "produces": [ + "application/json" + ], + "tags": [ + "系统管理" + ], + "summary": "系统登录日志", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.LoginLogListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.LoginLog" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/system/log/view": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "返回指定日志文件的内容", + "produces": [ + "application/json" + ], + "tags": [ + "系统管理" + ], + "summary": "查看日志文件内容", + "parameters": [ + { + "type": "string", + "description": "日志文件名", + "name": "filename", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "返回行数,默认100行", + "name": "lines", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/assign": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "用户角色分配", + "tags": [ + "用户管理" + ], + "summary": "用户角色分配", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "user_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "角色ID", + "name": "role_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/getinfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取用户信息", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserInfo" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取用户列表", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取用户列表", + "parameters": [ + { + "description": "请求体", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.UserListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserListResponse" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "用户登录验证", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "req", + "name": "login", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/register": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建用户", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户信息", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CreateUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.UserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/role": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "创建角色", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.CreateRole" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/role/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "禁用/启用角色", + "tags": [ + "用户管理" + ], + "summary": "禁用/启用角色", + "parameters": [ + { + "type": "string", + "description": "角色ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/rolelist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取角色列表", + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "获取角色列表", + "parameters": [ + { + "description": "req", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.RoleListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schemas.RoleResponseList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/update": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "更新用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户管理" + ], + "summary": "更新用户信息", + "parameters": [ + { + "description": "用户信息", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schemas.UpdateUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + }, + "/user/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "禁用/启用用户", + "tags": [ + "用户管理" + ], + "summary": "禁用/启用用户", + "parameters": [ + { + "type": "string", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "启用状态(1:启用,0:停用)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.Response" + } + } + } + } + } + }, + "definitions": { + "schemas.CreateRole": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "schemas.CreateUser": { + "type": "object", + "required": [ + "email", + "nickname", + "password" + ], + "properties": { + "avatar": { + "description": "Avatar 头像URL", + "type": "string" + }, + "email": { + "description": "Email 邮箱地址,用于联系和找回密码", + "type": "string" + }, + "id": { + "description": "ID 用户ID,数据库自动生成, 不传", + "type": "integer" + }, + "last_login_time": { + "description": "LastLoginTime 最后登录时间,系统生成", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "nickname": { + "description": "Nickname 用户昵称,显示名称", + "type": "string" + }, + "password": { + "description": "Password 密码,将被加密存储", + "type": "string" + }, + "register_time": { + "description": "RegisterTime 注册时间,系统生成", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "rolecode": { + "description": "RoleCode 角色编码, 不传, 默认0", + "type": "integer" + }, + "rolename": { + "description": "RoleName 角色名称, 不传", + "type": "string" + }, + "sessioncode": { + "description": "SessionCode 用户编码 系统生成", + "type": "string" + }, + "status": { + "description": "Status 用户状态,不传", + "type": "integer" + }, + "username": { + "description": "Username 用户名,系统唯一标识,系统生成, 不传", + "type": "string" + } + } + }, + "schemas.CronJob": { + "type": "object", + "properties": { + "create_time": { + "description": "创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "description": { + "description": "任务描述", + "type": "string" + }, + "enabled": { + "description": "任务是否启用", + "type": "integer" + }, + "handler": { + "description": "任务处理程序", + "type": "string" + }, + "id": { + "description": "任务ID", + "type": "integer" + }, + "name": { + "description": "任务名称", + "type": "string" + }, + "schedule": { + "description": "任务计划", + "type": "string" + }, + "update_time": { + "description": "更新时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + } + } + }, + "schemas.CronJobLog": { + "type": "object", + "properties": { + "end_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "handler": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "job_id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "schedule": { + "type": "string" + }, + "start_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "status": { + "type": "string" + } + } + }, + "schemas.CronJobLogListRequest": { + "type": "object", + "required": [ + "id", + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "id": { + "type": "integer" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.CronJobUpdateRequest": { + "type": "object", + "properties": { + "description": { + "description": "任务是否启用\nEnabled int `json:\"enabled\" db:\"enabled\"`\n任务描述", + "type": "string" + }, + "handler": { + "description": "任务处理程序", + "type": "string" + }, + "id": { + "description": "任务ID 传参代表修改 不传参代表新增", + "type": "integer" + }, + "name": { + "description": "任务名称", + "type": "string" + }, + "schedule": { + "description": "任务计划", + "type": "string" + } + } + }, + "schemas.CronListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "name": { + "type": "string" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.CronListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.CronJob" + } + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "schemas.CustomTime": { + "type": "object", + "properties": { + "time": { + "type": "string" + } + } + }, + "schemas.LoginLog": { + "type": "object", + "properties": { + "create_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "failure_reason": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ip_address": { + "type": "string" + }, + "isdel": { + "type": "integer" + }, + "location": { + "type": "string" + }, + "login_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "status": { + "type": "integer" + }, + "update_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "user_agent": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "schemas.LoginLogListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "description": "Password 密码,用于登录验证", + "type": "string" + }, + "username": { + "description": "Username 用户名,用于登录验证", + "type": "string" + } + } + }, + "schemas.MenuItems": { + "type": "object", + "properties": { + "component": { + "description": "组件名称", + "type": "string" + }, + "create_time": { + "description": "创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "icon": { + "description": "菜单项图标", + "type": "string" + }, + "id": { + "description": "ID 菜单项ID", + "type": "integer" + }, + "parent_id": { + "description": "父级菜单项ID", + "type": "integer" + }, + "path": { + "description": "菜单项路径", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "title": { + "description": "菜单项标题", + "type": "string" + }, + "visible": { + "description": "是否可见", + "type": "integer" + } + } + }, + "schemas.MenuListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + } + } + }, + "schemas.PermissionItems": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.PermissionItems" + } + }, + "id": { + "type": "integer" + }, + "module": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schemas.RoleListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_date": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_date": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.RoleResponseList": { + "type": "object", + "properties": { + "code": { + "description": "Code 角色编码", + "type": "string" + }, + "create_time": { + "description": "CreateTime 创建时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "description": { + "description": "Desc 角色描述", + "type": "string" + }, + "id": { + "description": "ID 角色ID", + "type": "integer" + }, + "name": { + "description": "Name 角色名称", + "type": "string" + }, + "status": { + "description": "Status 角色状态", + "type": "integer" + } + } + }, + "schemas.UpdateUser": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "avatar": { + "description": "Avatar 头像URL", + "type": "string" + }, + "email": { + "description": "Email 邮箱地址,用于联系和找回密码", + "type": "string" + }, + "id": { + "description": "ID 用户编码,用于定位用户", + "type": "integer" + }, + "nickname": { + "description": "Nickname 用户昵称,显示名称", + "type": "string" + }, + "password": { + "description": "Password 密码,将被加密存储", + "type": "string" + } + } + }, + "schemas.UserInfo": { + "type": "object", + "required": [ + "email", + "nickname", + "password" + ], + "properties": { + "avatar": { + "description": "头像URL", + "type": "string" + }, + "email": { + "description": "邮箱地址", + "type": "string" + }, + "id": { + "description": "用户ID", + "type": "integer" + }, + "last_login_time": { + "description": "最后登录时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "register_time": { + "description": "注册时间", + "allOf": [ + { + "$ref": "#/definitions/schemas.CustomTime" + } + ] + }, + "rolecode": { + "description": "角色编码", + "type": "integer" + }, + "rolename": { + "description": "角色名称", + "type": "string" + }, + "sessioncode": { + "description": "用户编码", + "type": "string" + }, + "status": { + "description": "用户状态", + "type": "integer" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "schemas.UserListRequest": { + "type": "object", + "required": [ + "page_index", + "page_size" + ], + "properties": { + "end_time": { + "$ref": "#/definitions/schemas.CustomTime" + }, + "nickname": { + "description": "昵称(用户名)", + "type": "string" + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "start_time": { + "$ref": "#/definitions/schemas.CustomTime" + } + } + }, + "schemas.UserListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/schemas.UserInfo" + } + }, + "page_index": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "schemas.UserResponse": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + } + } + }, + "utils.Response": { + "type": "object", + "properties": { + "data": {}, + "message": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "X-Access-Token", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..9267f4c --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1400 @@ +basePath: /quin +definitions: + schemas.CreateRole: + properties: + description: + type: string + id: + type: integer + name: + type: string + required: + - name + type: object + schemas.CreateUser: + properties: + avatar: + description: Avatar 头像URL + type: string + email: + description: Email 邮箱地址,用于联系和找回密码 + type: string + id: + description: ID 用户ID,数据库自动生成, 不传 + type: integer + last_login_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: LastLoginTime 最后登录时间,系统生成 + nickname: + description: Nickname 用户昵称,显示名称 + type: string + password: + description: Password 密码,将被加密存储 + type: string + register_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: RegisterTime 注册时间,系统生成 + rolecode: + description: RoleCode 角色编码, 不传, 默认0 + type: integer + rolename: + description: RoleName 角色名称, 不传 + type: string + sessioncode: + description: SessionCode 用户编码 系统生成 + type: string + status: + description: Status 用户状态,不传 + type: integer + username: + description: Username 用户名,系统唯一标识,系统生成, 不传 + type: string + required: + - email + - nickname + - password + type: object + schemas.CronJob: + properties: + create_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: 创建时间 + description: + description: 任务描述 + type: string + enabled: + description: 任务是否启用 + type: integer + handler: + description: 任务处理程序 + type: string + id: + description: 任务ID + type: integer + name: + description: 任务名称 + type: string + schedule: + description: 任务计划 + type: string + update_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: 更新时间 + type: object + schemas.CronJobLog: + properties: + end_time: + $ref: '#/definitions/schemas.CustomTime' + handler: + type: string + id: + type: integer + job_id: + type: integer + message: + type: string + name: + type: string + schedule: + type: string + start_time: + $ref: '#/definitions/schemas.CustomTime' + status: + type: string + type: object + schemas.CronJobLogListRequest: + properties: + end_date: + $ref: '#/definitions/schemas.CustomTime' + id: + type: integer + page_index: + type: integer + page_size: + type: integer + start_date: + $ref: '#/definitions/schemas.CustomTime' + required: + - id + - page_index + - page_size + type: object + schemas.CronJobUpdateRequest: + properties: + description: + description: |- + 任务是否启用 + Enabled int `json:"enabled" db:"enabled"` + 任务描述 + type: string + handler: + description: 任务处理程序 + type: string + id: + description: 任务ID 传参代表修改 不传参代表新增 + type: integer + name: + description: 任务名称 + type: string + schedule: + description: 任务计划 + type: string + type: object + schemas.CronListRequest: + properties: + end_date: + $ref: '#/definitions/schemas.CustomTime' + name: + type: string + page_index: + type: integer + page_size: + type: integer + start_date: + $ref: '#/definitions/schemas.CustomTime' + required: + - page_index + - page_size + type: object + schemas.CronListResponse: + properties: + items: + items: + $ref: '#/definitions/schemas.CronJob' + type: array + page_index: + type: integer + page_size: + type: integer + total: + type: integer + type: object + schemas.CustomTime: + properties: + time: + type: string + type: object + schemas.LoginLog: + properties: + create_time: + $ref: '#/definitions/schemas.CustomTime' + failure_reason: + type: string + id: + type: integer + ip_address: + type: string + isdel: + type: integer + location: + type: string + login_time: + $ref: '#/definitions/schemas.CustomTime' + status: + type: integer + update_time: + $ref: '#/definitions/schemas.CustomTime' + user_agent: + type: string + user_id: + type: integer + username: + type: string + type: object + schemas.LoginLogListRequest: + properties: + end_date: + $ref: '#/definitions/schemas.CustomTime' + page_index: + type: integer + page_size: + type: integer + start_date: + $ref: '#/definitions/schemas.CustomTime' + required: + - page_index + - page_size + type: object + schemas.LoginRequest: + properties: + password: + description: Password 密码,用于登录验证 + type: string + username: + description: Username 用户名,用于登录验证 + type: string + required: + - password + - username + type: object + schemas.MenuItems: + properties: + component: + description: 组件名称 + type: string + create_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: 创建时间 + icon: + description: 菜单项图标 + type: string + id: + description: ID 菜单项ID + type: integer + parent_id: + description: 父级菜单项ID + type: integer + path: + description: 菜单项路径 + type: string + sort: + description: 排序 + type: integer + status: + description: 状态 + type: integer + title: + description: 菜单项标题 + type: string + visible: + description: 是否可见 + type: integer + type: object + schemas.MenuListRequest: + properties: + page_index: + type: integer + page_size: + type: integer + required: + - page_index + - page_size + type: object + schemas.PermissionItems: + properties: + action: + type: string + children: + items: + $ref: '#/definitions/schemas.PermissionItems' + type: array + id: + type: integer + module: + type: string + name: + type: string + parent_id: + type: integer + path: + type: string + sort: + type: integer + status: + type: integer + title: + type: string + type: + type: string + type: object + schemas.RoleListRequest: + properties: + end_date: + $ref: '#/definitions/schemas.CustomTime' + page_index: + type: integer + page_size: + type: integer + start_date: + $ref: '#/definitions/schemas.CustomTime' + required: + - page_index + - page_size + type: object + schemas.RoleResponseList: + properties: + code: + description: Code 角色编码 + type: string + create_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: CreateTime 创建时间 + description: + description: Desc 角色描述 + type: string + id: + description: ID 角色ID + type: integer + name: + description: Name 角色名称 + type: string + status: + description: Status 角色状态 + type: integer + type: object + schemas.UpdateUser: + properties: + avatar: + description: Avatar 头像URL + type: string + email: + description: Email 邮箱地址,用于联系和找回密码 + type: string + id: + description: ID 用户编码,用于定位用户 + type: integer + nickname: + description: Nickname 用户昵称,显示名称 + type: string + password: + description: Password 密码,将被加密存储 + type: string + required: + - id + type: object + schemas.UserInfo: + properties: + avatar: + description: 头像URL + type: string + email: + description: 邮箱地址 + type: string + id: + description: 用户ID + type: integer + last_login_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: 最后登录时间 + nickname: + description: 昵称 + type: string + password: + description: 密码 + type: string + register_time: + allOf: + - $ref: '#/definitions/schemas.CustomTime' + description: 注册时间 + rolecode: + description: 角色编码 + type: integer + rolename: + description: 角色名称 + type: string + sessioncode: + description: 用户编码 + type: string + status: + description: 用户状态 + type: integer + username: + description: 用户名 + type: string + required: + - email + - nickname + - password + type: object + schemas.UserListRequest: + properties: + end_time: + $ref: '#/definitions/schemas.CustomTime' + nickname: + description: 昵称(用户名) + type: string + page_index: + type: integer + page_size: + type: integer + start_time: + $ref: '#/definitions/schemas.CustomTime' + required: + - page_index + - page_size + type: object + schemas.UserListResponse: + properties: + items: + items: + $ref: '#/definitions/schemas.UserInfo' + type: array + page_index: + type: integer + page_size: + type: integer + total: + type: integer + type: object + schemas.UserResponse: + properties: + authorization: + type: string + type: object + utils.Response: + properties: + data: {} + message: + type: string + status: + type: integer + type: object +host: localhost:8080 +info: + contact: + email: support@swagger.io + name: API Support + url: http://www.swagger.io/support + description: 打造一款综合性接口平台. + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + termsOfService: http://swagger.io/terms/ + title: Quincy_admin + version: "1.0" +paths: + /cron/{id}: + delete: + consumes: + - application/json + description: 删除定时任务 + parameters: + - description: id(任务ID) + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 删除定时任务 + tags: + - 任务管理 + put: + consumes: + - application/json + description: 启用/停用任务 + parameters: + - description: id + in: path + name: id + required: true + type: string + - description: 启用状态(1:启用,0:停用) + in: query + name: enabled + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 启用/停用任务 + tags: + - 任务管理 + /cron/hand: + post: + consumes: + - application/json + description: 新增/更新任务 + parameters: + - description: 新增/更新任务 + in: body + name: req + required: true + schema: + $ref: '#/definitions/schemas.CronJobUpdateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 新增/更新任务 + tags: + - 任务管理 + /cron/list: + post: + consumes: + - application/json + description: 定时任务列表 + parameters: + - description: 定时任务 + in: body + name: req + required: true + schema: + $ref: '#/definitions/schemas.CronListRequest' + produces: + - application/json + responses: + "200": + description: 定时任务 + schema: + $ref: '#/definitions/schemas.CronListResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 定时任务列表 + tags: + - 任务管理 + /cron/loglist: + post: + consumes: + - application/json + description: 查看日志运行记录 + parameters: + - description: req + in: body + name: req + required: true + schema: + $ref: '#/definitions/schemas.CronJobLogListRequest' + produces: + - application/json + responses: + "200": + description: res + schema: + $ref: '#/definitions/schemas.CronJobLog' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 查看日志运行记录 + tags: + - 任务管理 + /cron/restart: + get: + consumes: + - application/json + description: 重启定时任务 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 重启定时任务 + tags: + - 任务管理 + /cron/stop: + get: + consumes: + - application/json + description: 停止所有任务 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 停止所有任务 + tags: + - 任务管理 + /pms/menus: + post: + consumes: + - application/json + description: 获取管理后台菜单列表 + parameters: + - description: req + in: body + name: role + required: true + schema: + $ref: '#/definitions/schemas.MenuListRequest' + produces: + - application/json + responses: + "200": + description: 菜单信息 + schema: + $ref: '#/definitions/schemas.MenuItems' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取菜单列表 + tags: + - 权限模块 + /pms/menus/{id}: + put: + consumes: + - application/json + description: 启用停用菜单 + parameters: + - description: id + in: path + name: id + required: true + type: string + - description: 启用状态(1:启用,0:停用) + in: query + name: status + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 启用停用菜单 + tags: + - 权限模块 + /pms/permission: + get: + consumes: + - application/json + description: 根据用户Session和权限type获取权限列表 + parameters: + - description: all-全部 button-按钮,route-路由,menu-菜单,api-接口 + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: 权限列表 + schema: + items: + type: string + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取权限列表 + tags: + - 权限模块 + /pms/permission/all: + get: + consumes: + - application/json + description: 获取所有权限 + produces: + - application/json + responses: + "200": + description: 权限列表 + schema: + $ref: '#/definitions/schemas.PermissionItems' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: [] + summary: 获取所有权限 + tags: + - 权限模块 + /pms/permission/assign/{role_id}: + put: + consumes: + - application/json + description: 为指定角色分配权限 + parameters: + - description: 角色ID + in: path + name: role_id + required: true + type: integer + - collectionFormat: csv + description: 权限ID数组 + in: query + items: + type: integer + name: permission_ids + required: true + type: array + produces: + - application/json + responses: + "200": + description: 权限分配成功 + schema: + $ref: '#/definitions/utils.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 分配角色权限 + tags: + - 权限模块 + /pms/permission/role: + get: + consumes: + - application/json + description: 获取角色权限 + parameters: + - description: 角色ID + in: query + name: role_id + required: true + type: integer + - description: all-全部 button-按钮,route-路由,menu-菜单,api-接口 + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: 权限列表 + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + items: + type: integer + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取角色权限 + tags: + - 权限模块 + /pms/routes: + get: + consumes: + - application/json + description: 根据用户Session获取侧边栏菜单权限 + produces: + - application/json + responses: + "200": + description: 菜单信息 + schema: + $ref: '#/definitions/schemas.MenuItems' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 侧边菜单权限 + tags: + - 权限模块 + /system/log/download: + get: + description: 下载指定的日志文件内容 + parameters: + - description: 日志文件名 + in: query + name: filename + required: true + type: string + produces: + - application/octet-stream + responses: + "200": + description: 日志文件内容 + schema: + type: file + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 下载日志文件 + tags: + - 系统管理 + /system/log/login/list: + post: + description: 系统登录日志 + parameters: + - description: req + in: body + name: role + required: true + schema: + $ref: '#/definitions/schemas.LoginLogListRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.LoginLog' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 系统登录日志 + tags: + - 系统管理 + /system/log/view: + get: + description: 返回指定日志文件的内容 + parameters: + - description: 日志文件名 + in: query + name: filename + required: true + type: string + - description: 返回行数,默认100行 + in: query + name: lines + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 查看日志文件内容 + tags: + - 系统管理 + /user/{id}: + put: + description: 禁用/启用用户 + parameters: + - description: 用户ID + in: path + name: id + required: true + type: string + - description: 启用状态(1:启用,0:停用) + in: query + name: status + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/utils.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 禁用/启用用户 + tags: + - 用户管理 + /user/assign: + put: + description: 用户角色分配 + parameters: + - description: 用户ID + in: query + name: user_id + required: true + type: string + - description: 角色ID + in: query + name: role_id + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/utils.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 用户角色分配 + tags: + - 用户管理 + /user/getinfo: + get: + description: 获取用户信息 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.UserInfo' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取用户信息 + tags: + - 用户管理 + /user/list: + post: + description: 获取用户列表 + parameters: + - description: 请求体 + in: body + name: user + required: true + schema: + $ref: '#/definitions/schemas.UserListRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.UserListResponse' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取用户列表 + tags: + - 用户管理 + /user/login: + post: + consumes: + - application/json + description: 用户登录验证 + parameters: + - description: req + in: body + name: login + required: true + schema: + $ref: '#/definitions/schemas.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.UserResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/utils.Response' + "403": + description: Forbidden + schema: + $ref: '#/definitions/utils.Response' + summary: 用户登录 + tags: + - 用户管理 + /user/register: + post: + consumes: + - application/json + description: 创建用户 + parameters: + - description: 用户信息 + in: body + name: user + required: true + schema: + $ref: '#/definitions/schemas.CreateUser' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.UserResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 创建用户 + tags: + - 用户管理 + /user/role: + post: + description: 创建角色 + parameters: + - description: req + in: body + name: role + required: true + schema: + $ref: '#/definitions/schemas.CreateRole' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/utils.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 创建角色 + tags: + - 用户管理 + /user/role/{id}: + put: + description: 禁用/启用角色 + parameters: + - description: 角色ID + in: path + name: id + required: true + type: string + - description: 启用状态(1:启用,0:停用) + in: query + name: status + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/utils.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 禁用/启用角色 + tags: + - 用户管理 + /user/rolelist: + post: + description: 获取角色列表 + parameters: + - description: req + in: body + name: role + required: true + schema: + $ref: '#/definitions/schemas.RoleListRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + $ref: '#/definitions/schemas.RoleResponseList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 获取角色列表 + tags: + - 用户管理 + /user/update: + put: + consumes: + - application/json + description: 更新用户信息 + parameters: + - description: 用户信息 + in: body + name: user + required: true + schema: + $ref: '#/definitions/schemas.UpdateUser' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/utils.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.Response' + security: + - ApiKeyAuth: [] + summary: 更新用户信息 + tags: + - 用户管理 +securityDefinitions: + ApiKeyAuth: + in: header + name: X-Access-Token + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7ff4e1e --- /dev/null +++ b/go.mod @@ -0,0 +1,70 @@ +module Quincy_admin + +go 1.25 + +require ( + github.com/gin-gonic/gin v1.10.1 + github.com/go-redis/redis/v8 v8.11.5 + github.com/go-sql-driver/mysql v1.9.3 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/jmoiron/sqlx v1.4.0 + github.com/oschwald/geoip2-golang v1.13.0 + github.com/robfig/cron/v3 v3.0.1 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.1 + github.com/swaggo/swag v1.16.6 + golang.org/x/crypto v0.45.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/bytedance/sonic v1.13.3 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.22.0 // indirect + github.com/go-openapi/jsonreference v0.21.1 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.24.1 // indirect + github.com/go-openapi/swag/cmdutils v0.24.0 // indirect + github.com/go-openapi/swag/conv v0.24.0 // indirect + github.com/go-openapi/swag/fileutils v0.24.0 // indirect + github.com/go-openapi/swag/jsonname v0.24.0 // indirect + github.com/go-openapi/swag/jsonutils v0.24.0 // indirect + github.com/go-openapi/swag/loading v0.24.0 // indirect + github.com/go-openapi/swag/mangling v0.24.0 // indirect + github.com/go-openapi/swag/netutils v0.24.0 // indirect + github.com/go-openapi/swag/stringutils v0.24.0 // indirect + github.com/go-openapi/swag/typeutils v0.24.0 // indirect + github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/oschwald/maxminddb-golang v1.13.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + golang.org/x/arch v0.18.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3ffcb04 --- /dev/null +++ b/go.sum @@ -0,0 +1,203 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= +github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= +github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= +github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= +github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= +github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= +github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= +github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= +github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= +github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= +github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= +github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= +github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= +github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= +github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= +github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= +github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= +github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= +github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= +github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= +github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= +github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= +github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= +github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= +github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI= +github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= +github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= +github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= +golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a73c0ba --- /dev/null +++ b/main.go @@ -0,0 +1,57 @@ +// main.go +package main + +import ( + "Quincy_admin/config" + _ "Quincy_admin/docs" + "Quincy_admin/register" + "log" + + "github.com/gin-gonic/gin" +) + +// @title Quincy_admin +// @version 1.0 +// @description 打造一款综合性接口平台. +// @termsOfService http://swagger.io/terms/ +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @host localhost:8080 +// @BasePath /quin +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name X-Access-Token +func main() { + //===================================加载配置==================================== + cfg := config.LoadConfig() + + // 根据配置决定运行模式 + if cfg.Server.Mode == "release" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(gin.DebugMode) + } + + // =================================初始化服务==================================== + services, err := register.InitializeServices(cfg) + if err != nil { + log.Fatalf("Failed to initialize services: %v", err) + } + defer services.Cleanup() + + // =================================创建Gin引擎==================================== + router := gin.New() + + router.Use(gin.Recovery()) + + // 注册所有中间件和路由 + register.MiddlewaresAndRoutes(services.DB, router, services.CronManager) + + // 启动服务器 + if err := router.Run(":" + cfg.Server.Port); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/middle/middle_auth.go b/middle/middle_auth.go new file mode 100644 index 0000000..19851c2 --- /dev/null +++ b/middle/middle_auth.go @@ -0,0 +1,97 @@ +// Package middle auth_middleware.go +package middle + +import ( + "Quincy_admin/utils" + "net/http" + + "github.com/gin-gonic/gin" +) + +type AuthMiddleware struct { + Service *MService +} + +func NewAuthMiddleware(Service *MService) *AuthMiddleware { + return &AuthMiddleware{ + Service: Service, + } +} + +// Auth 验证 X-Access-Token 的中间件 +func (m *AuthMiddleware) Auth() gin.HandlerFunc { + return func(ctx *gin.Context) { + + // 获取 X-Access-Token 头部 + token := ctx.GetHeader("Authorization") + if token == "" { + utils.Error(ctx, http.StatusUnauthorized, "缺少访问令牌") + ctx.Abort() + return + } + + user, err := utils.ParseToken(token) + if err != nil { + utils.Error(ctx, http.StatusUnauthorized, "无效的访问令牌") + ctx.Abort() + return + } + + // 验证用户是否存在 + newUser, err := m.Service.GetUserID(user.SessionCode) + if err != nil { + utils.Error(ctx, http.StatusUnauthorized, "用户不存在") + ctx.Abort() + return + } + + if newUser.Status != 1 { + utils.Error(ctx, http.StatusUnauthorized, "用户已被锁定") + ctx.Abort() + return + } + + // 使用 ctx.Set() 存储用户 ID,角色ID,供后续中间件读取 + ctx.Set("user_id", newUser.ID) + ctx.Set("role_id", newUser.RoleCode) + ctx.Set("code", newUser.SessionCode) + + // token 验证成功,继续处理请求 + ctx.Next() + } +} + +// Perm 验证权限 +func (m *AuthMiddleware) Perm(perm string) gin.HandlerFunc { + return func(ctx *gin.Context) { + + userIDInterface, exists := ctx.Get("user_id") + if !exists { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID不存在") + ctx.Abort() + return + } + + userID, ok := userIDInterface.(int) + if !ok { + utils.Error(ctx, http.StatusUnauthorized, "权限校验错误:用户ID类型错误") + ctx.Abort() + return + } + + // 检查是否为超级管理员,如果是则跳过权限校验 + if m.Service.IsSuperAdmin(userID) { + ctx.Next() + return + } + + // 非超管账号检查权限 + if err := m.Service.HasPermission(userID, perm); err != nil { + utils.Error(ctx, http.StatusForbidden, "没有权限") + ctx.Abort() + return + } + + ctx.Next() + } +} diff --git a/middle/middle_cors.go b/middle/middle_cors.go new file mode 100644 index 0000000..e192d36 --- /dev/null +++ b/middle/middle_cors.go @@ -0,0 +1,23 @@ +// Package middle auth_middleware.go +package middle + +import ( + "github.com/gin-gonic/gin" +) + +// SetupCORS 返回CORS中间件函数 +func SetupCORS() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization") + c.Header("Access-Control-Allow-Credentials", "true") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} diff --git a/middle/middle_logger.go b/middle/middle_logger.go new file mode 100644 index 0000000..13a82cd --- /dev/null +++ b/middle/middle_logger.go @@ -0,0 +1,72 @@ +// Package middle middle_logger.go +package middle + +import ( + "bytes" + "fmt" + "io" + "log" + "time" + + "github.com/gin-gonic/gin" +) + +func RequestResponseLogger() gin.HandlerFunc { + return func(ctx *gin.Context) { + // 保存原始请求体 + var bodyBytes []byte + if ctx.Request.Body != nil { + bodyBytes, _ = io.ReadAll(ctx.Request.Body) + // 恢复请求体,供后续处理使用 + ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + + // 获取查询参数 + queryParams := ctx.Request.URL.Query().Encode() + + // 获取请求头信息 + var headers string + Token := ctx.GetHeader("X-Access-Token") + + headers = fmt.Sprintf("X-Access-Token: %s; ", Token) + + // 记录开始时间 + startTime := time.Now() + + // 继续处理请求 + ctx.Next() + + // 计算处理时间 + latency := time.Since(startTime) + + // 格式化日志内容 + timestamp := time.Now().Format("2006/01/02 15:04:05") + + // 格式化日志内容 + logMessage := fmt.Sprintf("[GIN] Request | %3d | %13v | %15s | %-7s %s", + ctx.Writer.Status(), + latency, + ctx.ClientIP(), + ctx.Request.Method, + ctx.Request.URL.Path, + ) + + // 单独打印查询参数(带时间戳) + if queryParams != "" { + logMessage += fmt.Sprintf("\n%s [GIN] Params: %s", timestamp, queryParams) + } + + // 单独打印请求体(带时间戳) + if len(bodyBytes) > 0 { + logMessage += fmt.Sprintf("\n%s [GIN] Body: %s", timestamp, string(bodyBytes)) + } + + // 单独打印请求头(带时间戳) + if headers != "" { + logMessage += fmt.Sprintf("\n%s [GIN] Headers: %s", timestamp, headers) + } + + // 输出到控制台 + log.Println(logMessage) + } +} diff --git a/middle/middle_service.go b/middle/middle_service.go new file mode 100644 index 0000000..711eedc --- /dev/null +++ b/middle/middle_service.go @@ -0,0 +1,112 @@ +// Package middle/middle_service.go +package middle + +import ( + "Quincy_admin/schemas" + "fmt" + + "github.com/jmoiron/sqlx" +) + +type MService struct { + db *sqlx.DB +} + +func NewMiddleService(db *sqlx.DB) *MService { + return &MService{db: db} +} + +// GetUserID 验证令牌并返回用户信息 +//func (s *MService) GetUserID(scode string) (*schemas.UserInfo, error) { +// // 从Redis获取用户信息 +// InitRedis := utils.GetRedis() +// ctx := utils.GetContext() +// +// // 尝试从Redis获取用户会话信息 +// userInfoStr, err := InitRedis.Get(ctx, scode).Result() +// if err != nil { +// // Redis中未找到会话信息,视为登录过期 +// return nil, fmt.Errorf("登录已过期") +// } +// +// // 解析Redis中的用户信息 +// var user schemas.UserInfo +// err = json.Unmarshal([]byte(userInfoStr), &user) +// if err != nil { +// return nil, fmt.Errorf("会话信息解析失败") +// } +// +// return &user, nil +//} + +// GetUserID 验证令牌并返回用户ID +func (s *MService) GetUserID(scode string) (*schemas.UserInfo, error) { + // 实现令牌验证逻辑 + query := ` + SELECT + tb1.id, + COALESCE(tb2.role_id, 0) AS rolecode, + tb1.sessioncode, + tb1.username, + tb1.password, + tb1.nickname, + tb1.email, + tb1.avatar, + tb1.status, + tb1.register_time, + tb1.last_login_time + FROM + admin_user tb1 + JOIN admin_user_role tb2 ON tb1.id = tb2.user_id + WHERE + sessioncode = ? + ORDER BY + tb1.id + LIMIT 1; + ` + + user := &schemas.UserInfo{} + err := s.db.Get(user, query, scode) + if err != nil { + return nil, err + } + + return user, nil +} + +// HasPermission 检查用户是否有特定权限 +func (s *MService) HasPermission(userID int, permission string) error { + query := ` + SELECT + COUNT(*) > 0 AS has_permission + FROM + admin_user au + JOIN admin_user_role aur ON au.id = aur.user_id + JOIN admin_role ar ON aur.role_id = ar.id + JOIN admin_role_permission arp ON ar.id = arp.role_id + JOIN admin_permission ap ON arp.permission_id = ap.id + WHERE + au.id = ? + AND ap.name = ? + ` + + var hasPermission bool + err := s.db.Get(&hasPermission, query, userID, permission) + if err != nil { + return err + } + + fmt.Println(hasPermission) + + // 根据权限检查结果返回相应错误 + if !hasPermission { + return fmt.Errorf("user %d does not have permission %s", userID, permission) + } + + return nil +} + +// IsSuperAdmin 检查是否为超级管理员 +func (s *MService) IsSuperAdmin(userID int) bool { + return userID == 1 +} diff --git a/register/register_middle_routes.go b/register/register_middle_routes.go new file mode 100644 index 0000000..0870e1e --- /dev/null +++ b/register/register_middle_routes.go @@ -0,0 +1,30 @@ +package register + +import ( + "Quincy_admin/crontask" + middleware "Quincy_admin/middle" + "Quincy_admin/routes" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" +) + +func MiddlewaresAndRoutes(db *sqlx.DB, router *gin.Engine, cronManager *crontask.CronManager) { + MiddlewaresRegister(router) + RoutesRegister(db, router, cronManager) +} + +// MiddlewaresRegister 中间件注册入口 +func MiddlewaresRegister(router *gin.Engine) { + // 配置自定义日志中间件,记录请求和响应 + router.Use(middleware.RequestResponseLogger()) + + // 配置CORS跨域中间件 + router.Use(middleware.SetupCORS()) +} + +// RoutesRegister 路由注册入口 +func RoutesRegister(db *sqlx.DB, router *gin.Engine, cronManager *crontask.CronManager) { + // 设置路由 - 传递白名单配置 + routes.SetupRoutes(db, router, cronManager) +} diff --git a/register/register_services.go b/register/register_services.go new file mode 100644 index 0000000..e9bff6b --- /dev/null +++ b/register/register_services.go @@ -0,0 +1,83 @@ +package register + +import ( + "Quincy_admin/config" + "Quincy_admin/crontask" + "Quincy_admin/utils" + "context" + "log" + + "github.com/go-redis/redis/v8" + "github.com/jmoiron/sqlx" +) + +// Services 服务注册 +type Services struct { + DB *sqlx.DB + Redis *redis.Client + CronManager *crontask.CronManager + Context context.Context +} + +// InitializeServices 初始化所有服务 +func InitializeServices(cfg *config.Config) (*Services, error) { + // 初始化数据库 + db, err := utils.InitDB(cfg) + if err != nil { + return nil, err + } + + // 初始化 Redis + redisClient := utils.InitRedis(cfg) + ctx := utils.GetContext() + if _, err := redisClient.Ping(ctx).Result(); err != nil { + log.Printf("Failed to connect to Redis: %v", err) + } + + // 初始化 GeoIP + if err := utils.InitGeoIP(cfg.Mmdb.Path); err != nil { + log.Printf("Failed to initialize GeoIP: %v", err) + } + + // 初始化定时任务管理器 + cronManager := crontask.NewCronManager(db) + cronManager.Start() + + return &Services{ + DB: db, + Redis: redisClient, + CronManager: cronManager, + Context: ctx, + }, nil +} + +// Cleanup 清理所有服务资源 +func (s *Services) Cleanup() { + // 清理数据库连接 + if s.DB != nil { + if err := s.DB.Close(); err != nil { + log.Printf("Failed to close database: %v", err) + } + log.Printf("Database connection closed") + } + + // 清理 Redis 连接 + if s.Redis != nil { + if err := s.Redis.Close(); err != nil { + log.Printf("Failed to close Redis connection: %v", err) + } + log.Printf("Redis connection closed") + } + + // 停止定时任务 + if s.CronManager != nil { + if err := s.CronManager.Stop(); err != nil { + log.Printf("Failed to stop cron manager: %v", err) + } + } + + // 关闭 GeoIP + if err := utils.CloseGeoIP(); err != nil { + log.Printf("Failed to close GeoIP: %v", err) + } +} diff --git a/repositories/base_utils.go b/repositories/base_utils.go new file mode 100644 index 0000000..01b212b --- /dev/null +++ b/repositories/base_utils.go @@ -0,0 +1,35 @@ +package repositories + +import ( + "fmt" + "time" +) + +// FormatTimeFlexible 灵活格式化时间字符串 +// 支持输入格式: "2006", "2006-01", "2006-01-02" +// 输出格式按照指定的格式进行输出 +func (r *UserRepository) FormatTimeFlexible(timeStr string, outputFormat string) (string, error) { + // 修正:使用正确的格式模板 + inputFormats := []string{ + "2006-01-02", // 年-月-日(正确格式) + "2006-01", // 年-月 + "2006", // 年 + } + + var parsedTime time.Time + var err error + + // 尝试解析不同格式 + for _, format := range inputFormats { + parsedTime, err = time.Parse(format, timeStr) + if err == nil { + break + } + } + + if err != nil { + return "", fmt.Errorf("无法解析时间字符串 '%s'", timeStr) + } + + return parsedTime.Format(outputFormat), nil +} diff --git a/repositories/com_repositories.go b/repositories/com_repositories.go new file mode 100644 index 0000000..884420a --- /dev/null +++ b/repositories/com_repositories.go @@ -0,0 +1,54 @@ +package repositories + +import ( + "Quincy_admin/schemas" + + "github.com/jmoiron/sqlx" +) + +type CommonRepository struct { + db *sqlx.DB +} + +func NewCommonRepository(db *sqlx.DB) *CommonRepository { + return &CommonRepository{db: db} +} + +// GetLoginLogList 获取登录日志列表 +func (r *CommonRepository) GetLoginLogList(req *schemas.LoginLogListRequest) ([]*schemas.LoginLog, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := ` + SELECT COUNT(*) FROM admin_login_logs WHERE isdel = 0 AND (? IS NULL OR create_time >= ?) AND (? IS NULL OR create_time < ?) +` + var total int64 + err := r.db.QueryRow(countQuery, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time).Scan(&total) + + if err != nil { + return nil, 0, err + } + + query := ` + SELECT + tb1.id, tb1.username, tb1.user_id, tb1.ip_address, tb1.location, tb1.user_agent, tb1.status, tb1.failure_reason, tb1.login_time + FROM + admin_login_logs tb1 + WHERE + tb1.isdel = 0 + AND (? IS NULL OR tb1.create_time >= ?) + AND (? IS NULL OR tb1.create_time < ?) + ORDER BY + id DESC + LIMIT ? OFFSET ? + ` + + var items []*schemas.LoginLog + err = r.db.Select(&items, query, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time, req.PageSize, offset) + + if err != nil { + return nil, 0, err + } + + return items, total, nil +} diff --git a/repositories/cron_repository.go b/repositories/cron_repository.go new file mode 100644 index 0000000..e9c7232 --- /dev/null +++ b/repositories/cron_repository.go @@ -0,0 +1,227 @@ +// Package repositories/pms_repository.go +package repositories + +import ( + "Quincy_admin/schemas" + "fmt" + "time" + + "github.com/jmoiron/sqlx" +) + +type CronRepository struct { + db *sqlx.DB +} + +func NewCronRepository(db *sqlx.DB) *CronRepository { + return &CronRepository{db: db} +} + +// GetCronList 获取定时任务列表 +func (r *CronRepository) GetCronList(req *schemas.CronListRequest) ([]*schemas.CronJob, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := ` + SELECT COUNT(*) FROM admin_cron_jobs WHERE isdel = 0 AND (name LIKE ? OR ? = '') AND (? IS NULL OR create_time >= ?) AND (? IS NULL OR create_time < ?) +` + var total int64 + err := r.db.QueryRow(countQuery, "%"+req.Name+"%", req.Name, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time).Scan(&total) + + if err != nil { + return nil, 0, err + } + + query := ` + SELECT + tb1.id, tb1.name, tb1.schedule, tb1.handler, tb1.enabled, tb1.description, tb1.create_time, tb1.update_time + FROM + admin_cron_jobs tb1 + WHERE + tb1.isdel = 0 + AND (tb1.name LIKE ? OR ? = '') + AND (? IS NULL OR tb1.create_time >= ?) + AND (? IS NULL OR tb1.create_time < ?) + ORDER BY + id + LIMIT ? OFFSET ? + ` + + var items []*schemas.CronJob + err = r.db.Select(&items, query, "%"+req.Name+"%", req.Name, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time, req.PageSize, offset) + + if err != nil { + return nil, 0, err + } + + return items, total, nil +} + +// AddCron 添加定时任务 +func (r *CronRepository) AddCron(req *schemas.CronJobUpdateRequest) (int64, error) { + query := ` + INSERT INTO admin_cron_jobs ( + name, schedule, handler, description, create_time, update_time + ) VALUES (?, ?, ?, ?, ?, ?) + ` + + now := time.Now() + + result, err := r.db.Exec(query, req.Name, req.Schedule, req.Handler, req.Description, now, now) + if err != nil { + return 0, err + } + + // 获取插入的 ID + id, err := result.LastInsertId() + if err != nil { + return 0, fmt.Errorf("failed to get last insert id: %w", err) + } + + return id, nil +} + +// UpdateCron 更新定时任务 +func (r *CronRepository) UpdateCron(req *schemas.CronJobUpdateRequest) error { + // 构建动态更新语句 + setClause := "" + args := map[string]interface{}{ + "id": req.ID, + } + + // 只有当字段非空时才添加到更新语句中 + if req.Name != "" { + setClause += "name=:name, " + args["name"] = req.Name + } + if req.Schedule != "" { + setClause += "schedule=:schedule, " + args["schedule"] = req.Schedule + } + if req.Handler != "" { + setClause += "handler=:handler, " + args["handler"] = req.Handler + } + if req.Description != "" { + setClause += "description=:description, " + args["description"] = req.Description + } + + // 如果没有要更新的字段,直接返回 + if setClause == "" { + return fmt.Errorf("没有要更新的字段") + } + + // 构建完整查询语句,确保SET子句格式正确 + query := fmt.Sprintf("UPDATE admin_cron_jobs SET %s WHERE id=:id", setClause[:len(setClause)-2]) + + _, err := r.db.NamedExec(query, args) + if err != nil { + return fmt.Errorf("SQL执行错误: %w", err) + } + + return nil +} + +// UpdateCronStatus 启用/停用定时任务 +func (r *CronRepository) UpdateCronStatus(id int, enable int) error { + query := ` + UPDATE admin_cron_jobs SET enabled = ?, update_time = ? WHERE id = ? + ` + + result, err := r.db.Exec(query, enable, time.Now(), id) + if err != nil { + return err + } + + // 检查是否有行被影响,如果没有说明任务不存在 + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + + if rowsAffected == 0 { + return fmt.Errorf("任务不存在") + } + + return nil +} + +// DeleteCron 删除定时任务 +func (r *CronRepository) DeleteCron(id int) error { + query := ` + UPDATE admin_cron_jobs SET isdel = 1, update_time = ? WHERE id = ? + ` + result, err := r.db.Exec(query, time.Now(), id) + if err != nil { + return err + } + + // 检查是否有行被影响,如果没有说明任务不存在 + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + + if rowsAffected == 0 { + return fmt.Errorf("任务不存在") + } + + return nil +} + +// UpdateAllCronStatus 批量停用定时任务 +func (r *CronRepository) UpdateAllCronStatus(enabled int) error { + query := ` + UPDATE admin_cron_jobs SET enabled = ?, update_time = ? WHERE enabled != ? AND isdel = 0 + ` + + _, err := r.db.Exec(query, enabled, time.Now(), enabled) + if err != nil { + return err + } + + return nil +} + +// GetCronLogList 获取定时任务日志列表 +func (r *CronRepository) GetCronLogList(req *schemas.CronJobLogListRequest) ([]*schemas.CronJobLog, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := ` + SELECT COUNT(*) FROM admin_cron_job_logs tb1 + LEFT JOIN admin_cron_jobs tb2 ON tb1.job_id = tb2.id + WHERE tb1.job_id = ? + AND (? IS NULL OR DATE(tb1.start_time) = DATE(?)) + ` + + var total int64 + err := r.db.QueryRow(countQuery, req.Id, req.StartDate, req.StartDate).Scan(&total) + + if err != nil { + return nil, 0, err + } + + // 主查询 + query := ` + SELECT + tb1.id, tb2.name AS name, tb1.job_id, tb2.handler, tb2.schedule, tb1.status AS status, tb1.start_time + FROM + admin_cron_job_logs tb1 + LEFT JOIN admin_cron_jobs tb2 ON tb1.job_id = tb2.id + WHERE tb1.job_id = ? + AND (? IS NULL OR DATE(tb1.start_time) = DATE(?)) + ORDER BY id DESC + LIMIT ? OFFSET ? + ` + + var items []*schemas.CronJobLog + err = r.db.Select(&items, query, req.Id, req.StartDate, req.StartDate, req.PageSize, offset) + + if err != nil { + return nil, 0, err + } + + return items, total, nil +} diff --git a/repositories/pms_repository.go b/repositories/pms_repository.go new file mode 100644 index 0000000..145dd5a --- /dev/null +++ b/repositories/pms_repository.go @@ -0,0 +1,303 @@ +// Package repositories/pms_repository.go +package repositories + +import ( + "Quincy_admin/schemas" + "database/sql" + "errors" + "fmt" + "strings" + + "github.com/jmoiron/sqlx" +) + +type PermissionRepository struct { + db *sqlx.DB +} + +func NewPermissionRepository(db *sqlx.DB) *PermissionRepository { + return &PermissionRepository{db: db} +} + +// GetRoleIDByCode 根据scode获取权限角色 ID +func (r *PermissionRepository) GetRoleIDByCode(scode string) (int, error) { + query := ` + SELECT + COALESCE(tb2.role_id, 0) AS rolecode + FROM + admin_user tb1 + LEFT JOIN admin_user_role tb2 ON tb1.id = tb2.user_id + WHERE + tb1.STATUS != 0 + AND tb1.sessioncode = ? + ORDER BY + id + LIMIT 1 + ` + + var rolecode int + err := r.db.QueryRow(query, scode).Scan(&rolecode) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + return 0, err + } + + return rolecode, nil +} + +// GetRoutesByID 获取侧边栏菜单权限 +func (r *PermissionRepository) GetRoutesByID(roleID int) ([]*schemas.MenuItems, error) { + query := ` + SELECT + m.id, + m.parent_id, + m.title, + m.path, + m.component, + m.icon, + m.sort, + m.visible, + m.status + FROM admin_menu m + INNER JOIN admin_role_menu arm ON m.id = arm.menu_id + WHERE arm.role_id = ? + AND m.status = 1 + AND m.visible = 1 + ORDER BY m.parent_id, m.sort + ` + + var menuItems []*schemas.MenuItems + err := r.db.Select(&menuItems, query, roleID) + if err != nil { + return nil, err + } + + return menuItems, nil +} + +// GetRoutes 获取路由权限 +func (r *PermissionRepository) GetRoutes() ([]*schemas.MenuItems, error) { + query := ` + SELECT + m.id, + m.parent_id, + m.title, + m.path, + m.component, + m.icon, + m.sort, + m.visible, + m.status + FROM admin_menu m + WHERE m.status = 1 + AND m.visible = 1 + ORDER BY m.parent_id, m.sort + ` + + var menuItems []*schemas.MenuItems + err := r.db.Select(&menuItems, query) + if err != nil { + return nil, err + } + + return menuItems, nil +} + +// GetMenuList 获取菜单列表 +func (r *PermissionRepository) GetMenuList(page, pageSize int) ([]*schemas.MenuItems, int64, error) { + offset := (page - 1) * pageSize + + // 查询总数 + countQuery := `SELECT COUNT(*) FROM admin_menu` + var total int64 + err := r.db.QueryRow(countQuery).Scan(&total) + if err != nil { + return nil, 0, err + } + + query := ` + SELECT + m.id, + m.parent_id, + m.title, + m.path, + m.component, + m.icon, + m.sort, + m.visible, + m.status, + m.create_time + FROM admin_menu m + ORDER BY m.parent_id, m.sort + LIMIT ? OFFSET ? + ` + + var menuItems []*schemas.MenuItems + err = r.db.Select(&menuItems, query, pageSize, offset) + if err != nil { + return nil, 0, err + } + + return menuItems, total, nil +} + +// UpdateMenuStatus 启用/停用菜单 +func (r *PermissionRepository) UpdateMenuStatus(id int, status int) error { + query := ` + UPDATE admin_menu SET status = ? WHERE id = ? + ` + + result, err := r.db.Exec(query, status, id) + if err != nil { + return err + } + + // 检查是否有行被影响,如果没有说明数据不存在 + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + + if rowsAffected == 0 { + return fmt.Errorf("数据不存在") + } + + return nil +} + +// GetPermission 获取权限信息 +func (r *PermissionRepository) GetPermission(roleID int, typeStr string) (*[]string, error) { + query := ` + SELECT + tb2.name + FROM + admin_role_permission tb1 + JOIN admin_permission tb2 ON tb1.permission_id = tb2.id + WHERE + tb1.role_id = ? + AND (? = 'all' OR tb2.type = ?) + AND tb2.status != 0 + ` + var permissionNames []string + err := r.db.Select(&permissionNames, query, roleID, typeStr, typeStr) + if err != nil { + return nil, err + } + return &permissionNames, nil +} + +func (r *PermissionRepository) GetRolePermission(roleID int, typeStr string) (*[]int, error) { + query := ` + SELECT + tb2.id + FROM + admin_role_permission tb1 + JOIN admin_permission tb2 ON tb1.permission_id = tb2.id + WHERE + tb1.role_id = ? + AND (? = 'all' OR tb2.type = ?) + AND tb2.status != 0 + ` + var permissionID []int + err := r.db.Select(&permissionID, query, roleID, typeStr, typeStr) + if err != nil { + return nil, err + } + return &permissionID, nil +} + +// GetAllPermission 获取全部权限信息 +func (r *PermissionRepository) GetAllPermission(typeStr string) (*[]string, error) { + query := ` + SELECT + tb1.name + FROM + admin_permission tb1 + WHERE (? = 'all' OR tb1.type = ?) + AND tb1.status != 0 + ` + var permissionNames []string + err := r.db.Select(&permissionNames, query, typeStr, typeStr) + if err != nil { + return nil, err + } + return &permissionNames, nil +} + +// GetAllNewPermission 获取全部权限信息 +func (r *PermissionRepository) GetAllNewPermission() ([]*schemas.PermissionItems, error) { + query := ` + SELECT + id, name, title, path, type, module, action, parent_id, sort, status + FROM admin_permission + WHERE status = 1 + ORDER BY parent_id, sort + ` + + var PermissionItems []*schemas.PermissionItems + err := r.db.Select(&PermissionItems, query) + if err != nil { + return nil, err + } + return PermissionItems, nil +} + +// GetRolePermissionIDs 获取角色已有的权限ID列表 +func (r *PermissionRepository) GetRolePermissionIDs(roleID int) ([]int, error) { + query := ` + SELECT permission_id + FROM admin_role_permission + WHERE role_id = ? + ` + var permissionIDs []int + err := r.db.Select(&permissionIDs, query, roleID) + return permissionIDs, err +} + +// InsertRolePermissions 批量插入角色权限关联 +func (r *PermissionRepository) InsertRolePermissions(roleID int, permissionIDs []int) error { + if len(permissionIDs) == 0 { + return nil + } + + // 构造批量插入SQL + query := ` + INSERT INTO admin_role_permission (role_id, permission_id) + VALUES (?, ?) + ` + + // 批量插入 + for _, permID := range permissionIDs { + _, err := r.db.Exec(query, roleID, permID) + if err != nil { + return err + } + } + + return nil +} + +// DeleteRolePermissions 批量删除角色权限关联 +func (r *PermissionRepository) DeleteRolePermissions(roleID int, permissionIDs []int) error { + if len(permissionIDs) == 0 { + return nil + } + + // 构造IN查询的占位符 + placeholders := make([]string, len(permissionIDs)) + args := make([]interface{}, 0, len(permissionIDs)+1) + args = append(args, roleID) // role_id + + for i, permID := range permissionIDs { + placeholders[i] = "?" + args = append(args, permID) + } + + query := "DELETE FROM admin_role_permission WHERE role_id = ? AND permission_id IN (" + + strings.Join(placeholders, ",") + ")" + + _, err := r.db.Exec(query, args...) + return err +} diff --git a/repositories/user_repositories.go b/repositories/user_repositories.go new file mode 100644 index 0000000..6a4877b --- /dev/null +++ b/repositories/user_repositories.go @@ -0,0 +1,429 @@ +package repositories + +import ( + "Quincy_admin/schemas" + "fmt" + "time" + + "github.com/jmoiron/sqlx" +) + +type UserRepository struct { + db *sqlx.DB +} + +func NewUserRepository(db *sqlx.DB) *UserRepository { + return &UserRepository{db: db} +} + +// Create 创建用户 +func (r *UserRepository) Create(user *schemas.CreateUser) error { + query := ` + INSERT INTO admin_user ( + rolecode, sessioncode, username, password, nickname, + email, avatar, status, register_time, last_login_time + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ` + + now := time.Now() + + _, err := r.db.Exec(query, user.RoleCode, user.SessionCode, user.Username, user.Password, user.Nickname, user.Email, user.Avatar, user.Status, now, now) + + if err != nil { + return err + } + + user.RegisterTime = schemas.NewCustomTime(&now) + user.LastLoginTime = schemas.NewCustomTime(&now) + + return nil +} + +// IsUserCodeExists 检查用户编码是否已存在 +func (r *UserRepository) IsUserCodeExists(scode string) (int, error) { + var count int + query := `SELECT COUNT(*) FROM admin_user WHERE sessioncode = ?` + err := r.db.Get(&count, query, scode) + return count, err +} + +// IsEmailExists 检查邮箱是否已存在 +func (r *UserRepository) IsEmailExists(email string) (int, error) { + var count int + query := `SELECT COUNT(*) FROM admin_user WHERE email = ?` + err := r.db.Get(&count, query, email) + return count, err +} + +// IsUsernameExists 检查用户名是否已存在 +func (r *UserRepository) IsUsernameExists(username string) (int, error) { + var count int + query := `SELECT COUNT(*) FROM admin_user WHERE username = ?` + err := r.db.Get(&count, query, username) + return count, err +} + +// UpdateLastLoginTime 更新用户最后登录时间 +func (r *UserRepository) UpdateLastLoginTime(scode string, loginTime time.Time) error { + query := `UPDATE admin_user SET last_login_time = ? WHERE sessioncode = ?` + _, err := r.db.Exec(query, loginTime, scode) + return err +} + +// FindByID 通过用户ID查找用户 +func (r *UserRepository) FindByID(scode string) (*schemas.UserInfo, error) { + query := ` + SELECT + tb1.id, + COALESCE(tb2.role_id, 0) AS rolecode, + tb1.sessioncode, + tb1.username, + tb1.password, + tb1.nickname, + tb1.email, + tb1.avatar, + tb1.status, + tb1.register_time, + tb1.last_login_time + FROM + admin_user tb1 + LEFT JOIN admin_user_role tb2 ON tb1.id = tb2.user_id + WHERE + sessioncode = ? + AND tb1.STATUS != 0 + ORDER BY + tb1.id + LIMIT 1; + ` + + user := &schemas.UserInfo{} + err := r.db.Get(user, query, scode) + if err != nil { + return nil, err + } + + return user, nil +} + +// FindByUsername 通过用户名查找用户 +func (r *UserRepository) FindByUsername(username string) (*schemas.UserInfo, error) { + query := ` + SELECT + tb1.id, + tb1.sessioncode, + COALESCE(tb2.role_id, 0) AS rolecode, + tb1.username, + tb1.password, + tb1.nickname, + tb1.email, + tb1.avatar, + tb1.status, + tb1.register_time, + tb1.last_login_time + FROM + admin_user tb1 + LEFT JOIN admin_user_role tb2 ON tb1.id = tb2.user_id + WHERE + username = ? + ORDER BY + tb1.id DESC + LIMIT 1; + ` + + user := &schemas.UserInfo{} + err := r.db.Get(user, query, username) + if err != nil { + return nil, err + } + + return user, nil +} + +// Update 更新用户 +func (r *UserRepository) Update(user *schemas.UpdateUser) (int, error) { + // 检查 sessioncode 是否为空 + if user.ID == 0 { + return 0, fmt.Errorf("用户编码不能为空") + } + + // 构建动态更新语句 + setClause := "" + args := map[string]interface{}{ + "id": user.ID, + } + if user.Password != "" && len(user.Password) > 0 { + setClause += "password=:password, " + args["password"] = user.Password + } + if user.Nickname != "" { + setClause += "nickname=:nickname, " + args["nickname"] = user.Nickname + } + if user.Email != "" { + setClause += "email=:email, " + args["email"] = user.Email + } + if user.Avatar != "" { + setClause += "avatar=:avatar, " + args["avatar"] = user.Avatar + } + + // 如果没有要更新的字段,直接返回 + if setClause == "" { + return user.ID, nil + } + + // 构建完整查询语句 + query := "UPDATE admin_user SET " + setClause[:len(setClause)-2] + " WHERE id=:id" + + _, err := r.db.NamedExec(query, args) + if err != nil { + return user.ID, err + } + + return user.ID, nil +} + +// UpdateStatus 禁用用户 +func (r *UserRepository) UpdateStatus(id int, status int) error { + query := ` + UPDATE admin_user SET status = ? WHERE id = ? + ` + + _, err := r.db.Exec(query, status, id) + if err != nil { + return err + } + + return nil +} + +// UpdateRoleStatus 启用/禁用角色 +func (r *UserRepository) UpdateRoleStatus(id int, status int) error { + query := ` + UPDATE admin_role SET status = ? WHERE id = ? + ` + + _, err := r.db.Exec(query, status, id) + if err != nil { + return err + } + + return nil +} + +// GetUserList 用户列表 +func (r *UserRepository) GetUserList(req *schemas.UserListRequest) ([]*schemas.UserInfo, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := `SELECT COUNT(*) FROM admin_user WHERE (nickname LIKE ? OR ? = '') AND (? IS NULL OR register_time >= ?) AND (? IS NULL OR register_time < ?)` + var total int64 + err := r.db.QueryRow(countQuery, "%"+req.NickName+"%", req.NickName, req.StartDate, req.StartDate, req.EndDate, req.EndDate).Scan(&total) + if err != nil { + return nil, 0, err + } + + // 查询列表 - 使用sqlx的Select方法直接映射到结构体切片 + query := ` + SELECT + tb1.id as id, + COALESCE(tb3.id, 0) AS rolecode, + COALESCE(tb3.name, '') AS rolename, + tb1.sessioncode as sessioncode, + tb1.username as username, + tb1.password as password, + tb1.nickname as nickname, + tb1.email as email, + tb1.avatar as avatar, + tb1.status as status, + tb1.register_time as register_time, + tb1.last_login_time as last_login_time + FROM + admin_user tb1 + LEFT JOIN admin_user_role tb2 ON tb1.id = tb2.user_id + LEFT JOIN admin_role tb3 ON tb2.role_id = tb3.id + WHERE + (tb1.nickname LIKE ? OR ? = '') + AND (? IS NULL OR tb1.register_time >= ?) + AND (? IS NULL OR tb1.register_time < ?) + ORDER BY + id + LIMIT ? OFFSET ? + ` + + var users []*schemas.UserInfo + err = r.db.Select(&users, query, "%"+req.NickName+"%", req.NickName, req.StartDate, req.StartDate, req.EndDate, req.EndDate, req.PageSize, offset) + if err != nil { + return nil, 0, err + } + + return users, total, nil +} + +// GetRoleList 角色列表 +func (r *UserRepository) GetRoleList(req *schemas.RoleListRequest) ([]*schemas.RoleResponseList, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := `SELECT COUNT(*) FROM admin_role WHERE (? IS NULL OR create_time >= ?) AND (? IS NULL OR create_time < ?)` + var total int64 + err := r.db.QueryRow(countQuery, req.StartDate, req.StartDate, req.EndDate, req.EndDate).Scan(&total) + if err != nil { + return nil, 0, err + } + + // 查询列表 - 使用sqlx的Select方法直接映射到结构体切片 + query := ` + SELECT + id, name, description, status, create_time + FROM admin_role + WHERE (? IS NULL OR create_time >= ?) AND (? IS NULL OR create_time < ?) + ORDER BY id + LIMIT ? OFFSET ? + ` + + var items []*schemas.RoleResponseList + err = r.db.Select(&items, query, req.StartDate, req.StartDate, req.EndDate, req.EndDate, req.PageSize, offset) + if err != nil { + return nil, 0, err + } + + return items, total, nil +} + +// RecordLoginLog 记录登录日志 +func (r *UserRepository) RecordLoginLog(log *schemas.LoginLog) error { + query := ` + INSERT INTO admin_login_logs ( + user_id, username, ip_address, location, user_agent, login_time, + status, failure_reason, create_time + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ` + + now := time.Now() + + _, err := r.db.Exec(query, + log.UserID, + log.Username, + log.IPAddress, + log.Location, + log.UserAgent, + log.LoginTime, + log.Status, + log.FailureReason, + now, + ) + + if err != nil { + return err + } + + return nil +} + +// GetLoginLogList 获取登录日志列表 +func (r *UserRepository) GetLoginLogList(req *schemas.LoginLogListRequest) ([]*schemas.LoginLog, int64, error) { + offset := (req.PageIndex - 1) * req.PageSize + + // 查询总数 + countQuery := ` + SELECT COUNT(*) FROM admin_login_logs WHERE isdel = 0 AND (? IS NULL OR create_time >= ?) AND (? IS NULL OR create_time < ?) +` + var total int64 + err := r.db.QueryRow(countQuery, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time).Scan(&total) + + if err != nil { + return nil, 0, err + } + + query := ` + SELECT + tb1.id, tb1.username, tb1.user_id, tb1.ip_address, tb1.user_agent, tb1.status, tb1.failure_reason, tb1.login_time + FROM + admin_login_logs tb1 + WHERE + tb1.isdel = 0 + AND (? IS NULL OR tb1.create_time >= ?) + AND (? IS NULL OR tb1.create_time < ?) + ORDER BY + id DESC + LIMIT ? OFFSET ? + ` + + var items []*schemas.LoginLog + err = r.db.Select(&items, query, req.StartDate.Time, req.StartDate.Time, req.EndDate.Time, req.EndDate.Time, req.PageSize, offset) + + if err != nil { + return nil, 0, err + } + + return items, total, nil +} + +// AssignRoleExists 检查用户是否已分配角色 +func (r *UserRepository) AssignRoleExists(userIdValue int) (bool, error) { + var count int + query := `SELECT COUNT(1) FROM admin_user_role WHERE user_id = ?` + + err := r.db.Get(&count, query, userIdValue) + if err != nil { + return false, err + } + + return count > 0, nil +} + +// AssignRole 角色分配 +func (r *UserRepository) AssignRole(userIdValue int, roleIdValue int) error { + query := ` + INSERT INTO admin_user_role (user_id, role_id) VALUES (?, ?) + ` + + _, err := r.db.Exec(query, userIdValue, roleIdValue) + if err != nil { + return err + } + + return nil +} + +// UpdateAssignRole 更新角色分配 +func (r *UserRepository) UpdateAssignRole(userIdValue int, roleIdValue int) error { + query := ` + UPDATE admin_user_role SET Role_id = ? WHERE user_id = ? + ` + + _, err := r.db.Exec(query, roleIdValue, userIdValue) + if err != nil { + return err + } + + return nil +} + +// CreateRole 创建角色 +func (r *UserRepository) CreateRole(req *schemas.CreateRole) error { + query := ` + INSERT INTO admin_role (name, description) VALUES (?, ?) + ` + _, err := r.db.Exec(query, req.Name, req.Description) + + if err != nil { + return err + } + + return nil +} + +// UpdateRole 更新角色 +func (r *UserRepository) UpdateRole(req *schemas.CreateRole) error { + query := ` + UPDATE admin_role SET name = ?, description = ? WHERE id = ? + ` + _, err := r.db.Exec(query, req.Name, req.Description, req.ID) + if err != nil { + return err + } + return nil +} diff --git a/routes/common_routes.go b/routes/common_routes.go new file mode 100644 index 0000000..819de83 --- /dev/null +++ b/routes/common_routes.go @@ -0,0 +1,26 @@ +// Package routes/user_routes.go +package routes + +import ( + "Quincy_admin/controllers" + "Quincy_admin/middle" + "Quincy_admin/repositories" + "Quincy_admin/services" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" +) + +func SetupCommonRoutes(router *gin.RouterGroup, db *sqlx.DB, m *middle.AuthMiddleware) { + // 初始化用户模块依赖 + conRepo := repositories.NewCommonRepository(db) + conService := services.NewCommonService(conRepo) + conController := controllers.NewCommonController(conService) + + system := router.Group("/system") + { + system.POST("/log/login/list", m.Auth(), conController.GetLoginLogList) + system.GET("/log/download", conController.DownloadLogFile) + system.GET("/log/view", m.Auth(), conController.ViewLogFile) + } +} diff --git a/routes/cron_routes.go b/routes/cron_routes.go new file mode 100644 index 0000000..fc3617f --- /dev/null +++ b/routes/cron_routes.go @@ -0,0 +1,31 @@ +// Package routes/cron_routes.go +package routes + +import ( + "Quincy_admin/controllers" + "Quincy_admin/crontask" + "Quincy_admin/middle" + "Quincy_admin/repositories" + "Quincy_admin/services" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" +) + +func SetupCronRoutes(router *gin.RouterGroup, db *sqlx.DB, cronManager *crontask.CronManager, m *middle.AuthMiddleware) { + // 初始化定时任务模块依赖 + cronRepo := repositories.NewCronRepository(db) + cronService := services.NewCronService(cronRepo, cronManager) + cronController := controllers.NewCronController(cronService) + + cron := router.Group("/cron") + { + cron.POST("/list", m.Auth(), cronController.GetCronList) + cron.POST("/hand", m.Auth(), cronController.HandleCron) + cron.PUT("/:id", m.Auth(), cronController.StopOrStartCron) + cron.DELETE("/:id", m.Auth(), cronController.DeleteCron) + cron.GET("/restart", m.Auth(), cronController.RestartCron) + cron.GET("/stop", m.Auth(), cronController.StopCron) + cron.POST("/loglist", m.Auth(), cronController.GetCronLogList) + } +} diff --git a/routes/perm_routes.go b/routes/perm_routes.go new file mode 100644 index 0000000..ff55a6b --- /dev/null +++ b/routes/perm_routes.go @@ -0,0 +1,30 @@ +// Package routes/perm_routes.go +package routes + +import ( + "Quincy_admin/controllers" + "Quincy_admin/middle" + "Quincy_admin/repositories" + "Quincy_admin/services" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" +) + +func SetupPermissionRoutes(router *gin.RouterGroup, db *sqlx.DB, m *middle.AuthMiddleware) { + // 初始化权限模块依赖 + permissionRepo := repositories.NewPermissionRepository(db) + permissionService := services.NewPermissionService(permissionRepo) + permissionController := controllers.NewPermissionController(permissionService) + + pms := router.Group("/pms") + { + pms.GET("/routes", m.Auth(), permissionController.GetRoutes) + pms.POST("/menus", m.Auth(), permissionController.GetMenuList) + pms.PUT("/menus/:id", m.Auth(), permissionController.UpdateMenuStatus) + pms.GET("/permission", m.Auth(), permissionController.GetPermission) + pms.GET("/permission/all", m.Auth(), permissionController.GetAllPermission) + pms.GET("/permission/role", m.Auth(), permissionController.GetRolePermission) + pms.PUT("/permission/assign/:role_id", m.Auth(), permissionController.AssignPermission) + } +} diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..3a38552 --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,34 @@ +// Package routes routes.go +package routes + +import ( + "Quincy_admin/crontask" + "Quincy_admin/middle" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +// SetupRoutes 设置路由 +func SetupRoutes(db *sqlx.DB, router *gin.Engine, cronManager *crontask.CronManager) { + // 初始化中间件初始依赖 + middleService := middle.NewMiddleService(db) + + // 创建认证中间件 + m := middle.NewAuthMiddleware(middleService) + + // API路由组 + api := router.Group("/quin") + { + // 注册 Swagger 路由 + api.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + // 构建各模块路由(传入db和cronManager等基础依赖) + SetupUserRoutes(api, db, m) + SetupPermissionRoutes(api, db, m) + SetupCronRoutes(api, db, cronManager, m) + SetupCommonRoutes(api, db, m) + } +} diff --git a/routes/user_routes.go b/routes/user_routes.go new file mode 100644 index 0000000..13bd075 --- /dev/null +++ b/routes/user_routes.go @@ -0,0 +1,33 @@ +// Package routes/user_routes.go +package routes + +import ( + "Quincy_admin/controllers" + "Quincy_admin/middle" + "Quincy_admin/repositories" + "Quincy_admin/services" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" +) + +func SetupUserRoutes(router *gin.RouterGroup, db *sqlx.DB, m *middle.AuthMiddleware) { + // 初始化用户模块依赖 + userRepo := repositories.NewUserRepository(db) + userService, _ := services.NewUserService(userRepo) + userController := controllers.NewUserController(userService) + + users := router.Group("/user") + { + users.POST("/login", userController.Login) + users.POST("/register", m.Auth(), m.Perm("user.create"), userController.Create) + users.GET("/getinfo", m.Auth(), userController.GetByID) + users.PUT("/update", m.Auth(), userController.Update) + users.PUT("/:id", m.Auth(), userController.UpdateStatus) + users.POST("list", m.Auth(), userController.UserList) + users.POST("/rolelist", m.Auth(), userController.UserRoleList) + users.PUT("/assign", m.Auth(), userController.AssignRole) + users.PUT("/role/:id", userController.UpdateRoleStatus) + users.POST("/role", m.Auth(), userController.CreateRole) + } +} diff --git a/schemas/base_util.go b/schemas/base_util.go new file mode 100644 index 0000000..0af8a55 --- /dev/null +++ b/schemas/base_util.go @@ -0,0 +1,71 @@ +package schemas + +import ( + "database/sql/driver" + "encoding/json" + "time" +) + +type CustomTime struct { + Time *time.Time +} + +// MarshalJSON 实现 JSON 序列化 +func (ct CustomTime) MarshalJSON() ([]byte, error) { + if ct.Time == nil { + return []byte(`""`), nil + } + return json.Marshal(ct.Time.Format("2006-01-02 15:04:05")) +} + +// UnmarshalJSON 实现 JSON 反序列化 +func (ct *CustomTime) UnmarshalJSON(data []byte) error { + var timeStr string + if err := json.Unmarshal(data, &timeStr); err != nil { + return err + } + + if timeStr == "" { + ct.Time = nil + return nil + } + + parsedTime, err := time.Parse("2006-01-02 15:04:05", timeStr) + if err != nil { + return err + } + + ct.Time = &parsedTime + return nil +} + +// Scan 实现 sql.Scanner 接口,用于数据库扫描 +func (ct *CustomTime) Scan(value interface{}) error { + if value == nil { + ct.Time = nil + return nil + } + + switch v := value.(type) { + case time.Time: + ct.Time = &v + case *time.Time: + ct.Time = v + default: + ct.Time = nil + } + return nil +} + +// Value 实现 driver.Valuer 接口,用于数据库存储 +func (ct CustomTime) Value() (driver.Value, error) { + if ct.Time == nil { + return nil, nil + } + return *ct.Time, nil +} + +// NewCustomTime 创建新的 CustomTime 实例 +func NewCustomTime(t *time.Time) CustomTime { + return CustomTime{Time: t} +} diff --git a/schemas/cron_schemas.go b/schemas/cron_schemas.go new file mode 100644 index 0000000..f05e196 --- /dev/null +++ b/schemas/cron_schemas.go @@ -0,0 +1,78 @@ +// Package schemas/cron_repository.go +package schemas + +type CronListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` + Name string `json:"name" form:"name"` + StartDate CustomTime `json:"start_date" form:"start_date"` + EndDate CustomTime `json:"end_date" form:"end_date"` +} + +type CronListResponse struct { + Item []*CronJob `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} + +type CronJob struct { + // 任务ID + ID int `json:"id" db:"id"` + // 任务名称 + Name string `json:"name" db:"name"` + // 任务计划 + Schedule string `json:"schedule" db:"schedule"` + // 任务处理程序 + Handler string `json:"handler" db:"handler"` + // 任务是否启用 + Enabled int `json:"enabled" db:"enabled"` + // 任务描述 + Description string `json:"description" db:"description"` + // 创建时间 + CreatedAt CustomTime `json:"create_time" db:"create_time"` + // 更新时间 + UpdatedAt CustomTime `json:"update_time" db:"update_time"` +} + +type CronJobLogListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` + Id int `json:"id" form:"id" binding:"required"` + StartDate CustomTime `json:"start_date" form:"start_date"` + EndDate CustomTime `json:"end_date" form:"end_date"` +} + +type CronJobLogListResponse struct { + Item []*CronJobLog `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} + +type CronJobLog struct { + ID int `json:"id" db:"id"` + JobID int `json:"job_id" db:"job_id"` + Name string `json:"name" db:"name"` + Handler string `json:"handler" db:"handler"` + Schedule string `json:"schedule" db:"schedule"` + Message string `json:"message" db:"message"` + Status string `json:"status" db:"status"` + StartTime CustomTime `json:"start_time" db:"start_time"` + EndTime CustomTime `json:"end_time" db:"end_time"` +} + +type CronJobUpdateRequest struct { + // 任务ID 传参代表修改 不传参代表新增 + ID int `json:"id" db:"id"` + // 任务名称 + Name string `json:"name" db:"name"` + // 任务计划 + Schedule string `json:"schedule" db:"schedule"` + // 任务处理程序 + Handler string `json:"handler" db:"handler"` + // 任务是否启用 + //Enabled int `json:"enabled" db:"enabled"` + // 任务描述 + Description string `json:"description" db:"description"` +} diff --git a/schemas/pms_schemas.go b/schemas/pms_schemas.go new file mode 100644 index 0000000..f3d1e1d --- /dev/null +++ b/schemas/pms_schemas.go @@ -0,0 +1,66 @@ +// Package schemas/note.go +package schemas + +// MenuItems 表示菜单项 +type MenuItems struct { + // ID 菜单项ID + ID int `json:"id" db:"id"` + // 父级菜单项ID + ParentID int `json:"parent_id" db:"parent_id"` + // 菜单项标题 + Title string `json:"title" db:"title"` + // 菜单项路径 + Path string `json:"path" db:"path"` + // 组件名称 + Component string `json:"component" db:"component"` + // 菜单项图标 + Icon string `json:"icon" db:"icon"` + // 排序 + Sort int `json:"sort" db:"sort"` + // 是否可见 + Visible int `json:"visible" db:"visible"` + // 状态 + Status int `json:"status" db:"status"` + // 创建时间 + CreateTime CustomTime `json:"create_time" db:"create_time"` +} + +// Routes 路由 +type Routes struct { + ID int `json:"id" db:"id"` + ParentID int `json:"parent_id" db:"parent_id"` + Path string `json:"index" db:"path"` + Icon string `json:"icon" db:"icon"` + Title string `json:"title" db:"title"` + Component string `json:"component" db:"component"` + Sort int `json:"sort" db:"sort"` + Visible int `json:"visible" db:"visible"` + Status int `json:"status" db:"status"` + Children []*Routes `json:"children,omitempty"` +} + +type MenuListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` +} + +type MenuListResponse struct { + Item []*MenuItems `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} + +type PermissionItems struct { + ID int64 `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Title string `json:"title" db:"title"` + Path string `json:"path" db:"path"` + Type string `json:"type" db:"type"` + Module string `json:"module" db:"module"` + Action string `json:"action" db:"action"` + ParentID int64 `json:"parent_id" db:"parent_id"` + Sort int `json:"sort" db:"sort"` + Status int `json:"status" db:"status"` + Children []*PermissionItems `json:"children"` +} diff --git a/schemas/user_schemas.go b/schemas/user_schemas.go new file mode 100644 index 0000000..24573c9 --- /dev/null +++ b/schemas/user_schemas.go @@ -0,0 +1,187 @@ +// Package schemas user_schemas.go +package schemas + +// LoginRequest 登录请求参数 +type LoginRequest struct { + // Username 用户名,用于登录验证 + Username string `json:"username" binding:"required"` + + // Password 密码,用于登录验证 + Password string `json:"password" binding:"required"` +} + +// UserInfo 用户信息 +type UserInfo struct { + // 用户ID + ID int `json:"id" db:"id"` + // 角色编码 + RoleCode int `json:"rolecode" db:"rolecode"` + // 角色名称 + RoleName string `json:"rolename" db:"rolename"` + // 用户编码 + SessionCode string `json:"sessioncode" db:"sessioncode"` + // 用户名 + Username string `json:"username" db:"username"` + // 密码 + Password string `json:"password" binding:"required" db:"password"` + // 昵称 + Nickname string `json:"nickname" binding:"required" db:"nickname"` + // 邮箱地址 + Email string `json:"email" binding:"required" db:"email"` + // 头像URL + Avatar string `json:"avatar" db:"avatar"` + // 用户状态 + Status int `json:"status" db:"status"` + // 注册时间 + RegisterTime CustomTime `json:"register_time" db:"register_time"` + // 最后登录时间 + LastLoginTime CustomTime `json:"last_login_time" db:"last_login_time"` +} + +// CreateUser 创建用户请求参数 +type CreateUser struct { + // ID 用户ID,数据库自动生成, 不传 + ID int `json:"id" db:"id"` + + // RoleCode 角色编码, 不传, 默认0 + RoleCode int `json:"rolecode" db:"rolecode"` + + // RoleName 角色名称, 不传 + RoleName string `json:"rolename" db:"rolename"` + + // SessionCode 用户编码 系统生成 + SessionCode string `json:"sessioncode" db:"sessioncode"` + + // Username 用户名,系统唯一标识,系统生成, 不传 + Username string `json:"username" db:"username"` + + // Password 密码,将被加密存储 + Password string `json:"password" binding:"required" db:"password"` + + // Nickname 用户昵称,显示名称 + Nickname string `json:"nickname" binding:"required" db:"nickname"` + + // Email 邮箱地址,用于联系和找回密码 + Email string `json:"email" binding:"required" db:"email"` + + // Avatar 头像URL + Avatar string `json:"avatar" db:"avatar"` + + // Status 用户状态,不传 + Status int `json:"status" db:"status"` + + // RegisterTime 注册时间,系统生成 + RegisterTime CustomTime `json:"register_time" db:"register_time"` + + // LastLoginTime 最后登录时间,系统生成 + LastLoginTime CustomTime `json:"last_login_time" db:"last_login_time"` +} + +// UserResponse 用户创建响应模型 +type UserResponse struct { + Authorization string `json:"authorization"` +} + +// UpdateUser 用户选择性更新请求参数 +type UpdateUser struct { + // ID 用户编码,用于定位用户 + ID int `json:"id" binding:"required"` + + // Password 密码,将被加密存储 + Password string `json:"password"` + + // Nickname 用户昵称,显示名称 + Nickname string `json:"nickname"` + + // Email 邮箱地址,用于联系和找回密码 + Email string `json:"email"` + + // Avatar 头像URL + Avatar string `json:"avatar"` +} + +// UserStatus 用户状态常量 +const ( + UserStatusDisabled = 0 // 禁用 + UserStatusNormal = 1 // 正常 +) + +type UserListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` + // 昵称(用户名) + NickName string `json:"nickname" form:"nickname"` + StartDate CustomTime `json:"start_time" form:"register_time"` + EndDate CustomTime `json:"end_time" form:"register_time"` +} + +type UserListResponse struct { + Item []*UserInfo `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} +type RoleListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` + StartDate CustomTime `json:"start_date" form:"start_date"` + EndDate CustomTime `json:"end_date" form:"end_date"` +} + +type RoleListResponse struct { + Item []*RoleResponseList `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} + +type RoleResponseList struct { + // ID 角色ID + ID int `json:"id" db:"id"` + // Name 角色名称 + Name string `json:"name" db:"name"` + // Code 角色编码 + Code string `json:"code" db:"code"` + // Desc 角色描述 + Desc string `json:"description" db:"description"` + // Status 角色状态 + Status int `json:"status" db:"status"` + // CreateTime 创建时间 + CreateTime CustomTime `json:"create_time" db:"create_time"` +} + +// LoginLog 登录日志结构体 +type LoginLog struct { + ID int64 `json:"id" db:"id"` + UserID int `json:"user_id" db:"user_id"` + Username string `json:"username" db:"username"` + IPAddress string `json:"ip_address" db:"ip_address"` + Location string `json:"location" db:"location"` + UserAgent string `json:"user_agent" db:"user_agent"` + LoginTime CustomTime `json:"login_time" db:"login_time"` + Status int `json:"status" db:"status"` + FailureReason string `json:"failure_reason" db:"failure_reason"` + CreateTime CustomTime `json:"create_time" db:"create_time"` + UpdateTime CustomTime `json:"update_time" db:"update_time"` + IsDel int `json:"isdel" db:"isdel"` +} + +type LoginLogListRequest struct { + PageIndex int `json:"page_index" form:"page_index" binding:"required"` + PageSize int `json:"page_size" form:"page_size" binding:"required"` + StartDate CustomTime `json:"start_date" form:"start_date"` + EndDate CustomTime `json:"end_date" form:"end_date"` +} + +type LoginLogListResponse struct { + Item []*LoginLog `json:"items"` + Total int64 `json:"total"` + PageIndex int `json:"page_index"` + PageSize int `json:"page_size"` +} + +type CreateRole struct { + Name string `json:"name" binding:"required"` + ID int `json:"id"` + Description string `json:"description"` +} diff --git a/services/com_service.go b/services/com_service.go new file mode 100644 index 0000000..5c20611 --- /dev/null +++ b/services/com_service.go @@ -0,0 +1,59 @@ +package services + +import ( + "Quincy_admin/repositories" + "Quincy_admin/schemas" + "bufio" + "log" + "os" + "strings" +) + +type CommonService struct { + repo *repositories.CommonRepository +} + +func NewCommonService(repo *repositories.CommonRepository) *CommonService { + return &CommonService{repo: repo} +} + +// GetLoginLogList 获取登录日志列表 +func (s *CommonService) GetLoginLogList(req *schemas.LoginLogListRequest) ([]*schemas.LoginLog, int64, error) { + return s.repo.GetLoginLogList(req) +} + +// ReadLastLines 读取文件最后几行 +func (s *CommonService) ReadLastLines(filepath string, lines int) (string, error) { + file, err := os.Open(filepath) + if err != nil { + return "", err + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Println("Error closing file:", err) + } + }(file) + + // 使用Scanner按行读取 + scanner := bufio.NewScanner(file) + var allLines []string + + for scanner.Scan() { + allLines = append(allLines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return "", err + } + + // 获取最后几行 + totalLines := len(allLines) + start := totalLines - lines + if start < 0 { + start = 0 + } + + result := allLines[start:] + return strings.Join(result, "\n"), nil +} diff --git a/services/cron_service.go b/services/cron_service.go new file mode 100644 index 0000000..ec704da --- /dev/null +++ b/services/cron_service.go @@ -0,0 +1,122 @@ +// Package services/cron_service.go +package services + +import ( + "Quincy_admin/crontask" + "Quincy_admin/repositories" + "Quincy_admin/schemas" + "fmt" + "log" +) + +type CronService struct { + cronRepo *repositories.CronRepository + cronManager *crontask.CronManager +} + +func NewCronService(cronRepo *repositories.CronRepository, cronManager *crontask.CronManager) *CronService { + return &CronService{ + cronRepo: cronRepo, + cronManager: cronManager, + } +} + +// GetCronList 获取定时任务列表 +func (s *CronService) GetCronList(req *schemas.CronListRequest) ([]*schemas.CronJob, int64, error) { + return s.cronRepo.GetCronList(req) +} + +// AddCron 添加定时任务 +func (s *CronService) AddCron(req *schemas.CronJobUpdateRequest) (int64, error) { + return s.cronRepo.AddCron(req) +} + +// UpdateCron 修改定时任务 +func (s *CronService) UpdateCron(req *schemas.CronJobUpdateRequest) error { + // 更新数据库 + err := s.cronRepo.UpdateCron(req) + if err != nil { + return err + } + + // 先移除现有的任务 + err = s.cronManager.RemoveJob(int(req.ID)) + if err != nil { + return err + } + err = s.cronManager.AddJobByID(int(req.ID)) + return nil +} + +// UpdateCronStatus 启用/停用定时任务 +func (s *CronService) UpdateCronStatus(id int, enable int) error { + // 先更新数据库状态 + err := s.cronRepo.UpdateCronStatus(id, enable) + if err != nil { + return err + } + + // 根据 enabled 状态决定是否移除或添加任务 + // 数据库更新成功后再处理任务管理器 + var jobErr error + if enable == 0 { + // 停用:移除任务 + jobErr = s.cronManager.RemoveJob(id) + } else if enable == 1 { + // 启用:添加任务 + jobErr = s.cronManager.AddJobByID(id) + } + + // 如果任务管理器操作失败,回滚数据库状态 + if jobErr != nil { + rollbackEnable := 1 - enable + rollbackErr := s.cronRepo.UpdateCronStatus(id, rollbackEnable) + if rollbackErr != nil { + log.Printf("failed to rollback job status: %v\n", rollbackErr) + } + return fmt.Errorf("failed to update job status: %v", jobErr) + } + + return nil +} + +// DeleteCron 删除定时任务 +func (s *CronService) DeleteCron(id int) error { + // 删除数据库记录 + err := s.cronRepo.DeleteCron(id) + if err != nil { + return err + } + + // 触发任务刷新(需传入 CronManager 实例) + go func() { + err := s.cronManager.RemoveJob(id) + if err != nil { + } + }() + + return nil +} + +// RestartCron 重启定时任务 +func (s *CronService) RestartCron() error { + err := s.cronRepo.UpdateAllCronStatus(1) + if err != nil { + return err + } + return s.cronManager.Restart() +} + +// StopCron 停止定时任务 +func (s *CronService) StopCron() error { + err := s.cronRepo.UpdateAllCronStatus(0) + if err != nil { + return err + } + return s.cronManager.Stop() +} + +// GetCronLogList 获取定时任务日志列表 +func (s *CronService) GetCronLogList(req *schemas.CronJobLogListRequest) ([]*schemas.CronJobLog, int64, error) { + return s.cronRepo.GetCronLogList(req) +} diff --git a/services/pms_service.go b/services/pms_service.go new file mode 100644 index 0000000..8ccc0df --- /dev/null +++ b/services/pms_service.go @@ -0,0 +1,196 @@ +// Package services/pms_service.go +package services + +import ( + "Quincy_admin/repositories" + "Quincy_admin/schemas" +) + +type PermissionService struct { + pmsRepo *repositories.PermissionRepository +} + +func NewPermissionService(repo *repositories.PermissionRepository) *PermissionService { + return &PermissionService{pmsRepo: repo} +} + +// GetRoutesByID 获取侧边栏菜单权限 +func (s *PermissionService) GetRoutesByID(roleID int) (*schemas.Routes, error) { + // 从仓库获取菜单数据 + var menuItems []*schemas.MenuItems + var err error + // 超级管理员ID为1(超管),获取所有菜单数据 + if roleID == 1 { + menuItems, err = s.pmsRepo.GetRoutes() + } else { + // 从仓库获取菜单数据 + menuItems, err = s.pmsRepo.GetRoutesByID(roleID) + } + + if err != nil { + return nil, err + } + + // 构建树形结构 + var rootNodes []*schemas.Routes + // 存储所有节点的映射,便于查找子节点 + nodeMap := make(map[int]*schemas.Routes) + + for _, item := range menuItems { + node := &schemas.Routes{ + ID: item.ID, + ParentID: item.ParentID, + Title: item.Title, + Path: item.Path, + Component: item.Component, + Icon: item.Icon, + Sort: item.Sort, + Visible: item.Visible, + Status: item.Status, + } + + nodeMap[item.ID] = node + + // 如果是根节点(parent_id = 0) + if item.ParentID == 0 { + rootNodes = append(rootNodes, node) + } + } + + // 为每个节点添加子节点 + for _, node := range nodeMap { + if node.ParentID != 0 { + parentNode, exists := nodeMap[node.ParentID] + if exists { + if parentNode.Children == nil { + parentNode.Children = []*schemas.Routes{} + } + parentNode.Children = append(parentNode.Children, node) + } + } + } + + // 返回根节点数组 + return &schemas.Routes{Children: rootNodes}, nil +} + +// GetMenuList 获取菜单列表 +func (s *PermissionService) GetMenuList(page, pageSize int) ([]*schemas.MenuItems, int64, error) { + return s.pmsRepo.GetMenuList(page, pageSize) +} + +// UpdateMenuStatus 启用/停用菜单 +func (s *PermissionService) UpdateMenuStatus(id int, status int) error { + return s.pmsRepo.UpdateMenuStatus(id, status) +} + +// GetPermission 获取权限信息 +func (s *PermissionService) GetPermission(roleID int, typeStr string) (*[]string, error) { + // 超级管理员ID为1(超管),获取所有菜单数据 + if roleID == 1 { + return s.pmsRepo.GetAllPermission(typeStr) + } else { + return s.pmsRepo.GetPermission(roleID, typeStr) + } +} + +// GetAllPermission 获取系统所有权限信息 +func (s *PermissionService) GetAllPermission() (*schemas.PermissionItems, error) { + // 获取所有权限数据 + items, err := s.pmsRepo.GetAllNewPermission() + if err != nil { + return nil, err + } + + // 构建树形结构 + root := &schemas.PermissionItems{ + ID: 0, + Name: "root", + Title: "根节点", + Children: make([]*schemas.PermissionItems, 0), // 使用指针切片 + } + nodeMap := make(map[int64]*schemas.PermissionItems) + + // 先建立映射 + for i := range items { + nodeMap[items[i].ID] = items[i] // 直接使用指针 + } + + // 遍历所有节点,挂载到父节点下 + for _, item := range items { + if item.ParentID == 0 { + root.Children = append(root.Children, item) // 使用指针 + } else { + if parent, exists := nodeMap[item.ParentID]; exists { + parent.Children = append(parent.Children, item) // 使用指针 + } + } + } + + return root, nil +} + +// GetRolePermission 获取角色权限信息 +func (s *PermissionService) GetRolePermission(roleID int, typeStr string) (*[]int, error) { + return s.pmsRepo.GetRolePermission(roleID, typeStr) +} + +// AssignPermission 为角色分配权限 +func (s *PermissionService) AssignPermission(roleID int, permissionIDs []int) error { + // 获取角色现有的权限ID列表 + existingPermIDs, err := s.GetRolePermissionIDs(roleID) + if err != nil { + return err + } + + // 构建现有权限ID映射 + existingPermMap := make(map[int]bool) + for _, pid := range existingPermIDs { + existingPermMap[pid] = true + } + + // 构建请求权限ID映射 + requestPermMap := make(map[int]bool) + for _, pid := range permissionIDs { + requestPermMap[pid] = true + } + + // 分离需要新增和需要删除的权限ID + var toInsert []int + var toDelete []int + + // 找出需要新增的权限(存在于请求中但不存在于现有权限中) + for _, pid := range permissionIDs { + if !existingPermMap[pid] { + toInsert = append(toInsert, pid) + } + } + + // 找出需要删除的权限(存在于现有权限中但不存在于请求中) + for _, pid := range existingPermIDs { + if !requestPermMap[pid] { + toDelete = append(toDelete, pid) + } + } + + // 执行插入操作 + if len(toInsert) > 0 { + if err := s.pmsRepo.InsertRolePermissions(roleID, toInsert); err != nil { + return err + } + } + + // 执行删除操作 + if len(toDelete) > 0 { + if err := s.pmsRepo.DeleteRolePermissions(roleID, toDelete); err != nil { + return err + } + } + + return nil +} + +// GetRolePermissionIDs 获取角色已有的权限ID列表 +func (s *PermissionService) GetRolePermissionIDs(roleID int) ([]int, error) { + return s.pmsRepo.GetRolePermissionIDs(roleID) +} diff --git a/services/user_service.go b/services/user_service.go new file mode 100644 index 0000000..2648712 --- /dev/null +++ b/services/user_service.go @@ -0,0 +1,294 @@ +package services + +import ( + "Quincy_admin/repositories" + "Quincy_admin/schemas" + "Quincy_admin/utils" + "fmt" + "net" + "time" + + "golang.org/x/crypto/bcrypt" +) + +type UserService struct { + repo *repositories.UserRepository +} + +func NewUserService(repo *repositories.UserRepository) (*UserService, error) { + service := &UserService{repo: repo} + return service, nil +} + +// CreateUser 创建用户 +func (s *UserService) CreateUser(userReq *schemas.CreateUser) error { + // 只有当密码不为空时才进行加密处理 + if userReq.Password != "" { + hashedPassword, err := s.HashPassword(userReq.Password) + if err != nil { + return fmt.Errorf("密码加密失败: %v", err) + } + userReq.Password = hashedPassword + } else { + return fmt.Errorf("密码不能为空") + } + + //添加默认值 + userReq.RoleCode = 0 + userReq.Status = schemas.UserStatusDisabled + + // 如果 UserCode 为空,生成一个唯一的 UserCode,确保与数据库中已有的不重复 + foundUniqueCode := false + for i := 0; i < 5; i++ { // 最多尝试5次 + userReq.SessionCode = utils.GenerateUserCode("S10", 7) + // 检查数据库是否已存在该 sessioncode + count, err := s.repo.IsUserCodeExists(userReq.SessionCode) + if err != nil { + return err + } + if count == 0 { + foundUniqueCode = true + break + } + } + + // 如果5次尝试后仍未找到唯一code,返回错误 + if !foundUniqueCode { + return fmt.Errorf("无法生成唯一的用户编码,已尝试5次") + } + + // 生成唯一的 Username,确保与数据库中已有的不重复 + foundUniqueName := false + for i := 0; i < 5; i++ { + userReq.Username, _ = utils.GenerateUsername(8) + // 检查数据库是否已存在该 Username + count, err := s.repo.IsUsernameExists(userReq.Username) + if err != nil { + return err + } + if count == 0 { + foundUniqueName = true + break + } + } + + // 如果5次尝试后仍未找到唯一username,返回错误 + if !foundUniqueName { + return fmt.Errorf("无法生成唯一的用户名,已尝试5次") + } + + return s.repo.Create(userReq) +} + +// IsEmailExists 检查邮箱是否已存在 +func (s *UserService) IsEmailExists(email string) (int, error) { + return s.repo.IsEmailExists(email) +} + +// GetUserByID 通过用户ID查找用户 +func (s *UserService) GetUserByID(scode string) (*schemas.UserInfo, error) { + return s.repo.FindByID(scode) +} + +// GetUserByUsername 通过用户名查找用户 +func (s *UserService) GetUserByUsername(username string) (*schemas.UserInfo, error) { + return s.repo.FindByUsername(username) +} + +// GetUserLocation 获取用户地理位置信息 +func (s *UserService) GetUserLocation(ipAddress string) (map[string]string, error) { + geoDB := utils.GetGeoIP() + if geoDB == nil { + return nil, fmt.Errorf("GeoIP database not initialized") + } + + // 解析 IP 地址 + ip := net.ParseIP(ipAddress) + if ip == nil { + return nil, fmt.Errorf("invalid IP address: %s", ipAddress) + } + + // 查询地理位置 + record, err := geoDB.City(ip) + if err != nil { + return nil, fmt.Errorf("failed to query GeoIP database: %v", err) + } + + // 构造返回结果 + location := map[string]string{ + "ip": ipAddress, + "country": record.Country.Names["zh-CN"], + "country_code": record.Country.IsoCode, + "province": "", + "city": "", + "latitude": fmt.Sprintf("%f", record.Location.Latitude), + "longitude": fmt.Sprintf("%f", record.Location.Longitude), + } + + // 获取省份/州信息 + if len(record.Subdivisions) > 0 { + location["province"] = record.Subdivisions[0].Names["zh-CN"] + } + + // 获取城市信息 + if record.City.Names["zh-CN"] != "" { + location["city"] = record.City.Names["zh-CN"] + } + + //fmt.Println("location:", location) + + return location, nil +} + +// StoreLoginSession 存储登录会话 +//func (s *UserService) StoreLoginSession(sessionKey string, userInfo *schemas.UserInfo, expireTime time.Duration) error { +// InitRedis := utils.GetRedis() +// ctx := utils.GetContext() +// +// // 将用户信息序列化后存储到Redis +// userInfoBytes, err := json.Marshal(userInfo) +// if err != nil { +// return err +// } +// +// err = InitRedis.Set(ctx, sessionKey, userInfoBytes, expireTime).Err() +// if err != nil { +// return err +// } +// +// return nil +//} + +// RecordLoginLog 记录登录日志 +func (s *UserService) RecordLoginLog(userID int, username string, ipAddress string, userAgent string, status int, failureReason string) error { + // 获取地理位置信息 + locationInfo, err := s.GetUserLocation("60.255.157.229") + var locationStr string + if err == nil { + locationStr = fmt.Sprintf("%s%s%s", + locationInfo["country"], + locationInfo["province"], + locationInfo["city"]) + } else { + locationStr = "未知位置" + } + + // 创建登录日志记录 + now := time.Now() + loginLog := &schemas.LoginLog{ + UserID: userID, + Username: username, + IPAddress: ipAddress, + Location: locationStr, + UserAgent: userAgent, + LoginTime: schemas.NewCustomTime(&now), + Status: status, + FailureReason: failureReason, + CreateTime: schemas.NewCustomTime(&now), + } + + // 插入数据库 + return s.repo.RecordLoginLog(loginLog) +} + +// UpdateUser 更新用户信息 +func (s *UserService) UpdateUser(user *schemas.UpdateUser) (int, error) { + // 检查是否尝试修改超级管理员账户(ID=1) + if user.ID == 1 { + return 0, fmt.Errorf("无法修改超级管理员账户信息") + } + + // 只有当密码不为空时才进行加密处理 + if user.Password != "" { + hashedPassword, err := s.HashPassword(user.Password) + if err != nil { + return 0, fmt.Errorf("密码加密失败: %v", err) + } + user.Password = hashedPassword + } else { + // 如果密码为空,则不更新密码字段 + user.Password = "" + } + + return s.repo.Update(user) +} + +// UpdateLastLoginTime 更新用户最后登录时间 +func (s *UserService) UpdateLastLoginTime(scode string) error { + return s.repo.UpdateLastLoginTime(scode, time.Now()) +} + +// UpdateStatus 删除用户 +func (s *UserService) UpdateStatus(id int, enable int) error { + // 防止修改超级管理员账户(ID=1) + if id == 1 { + return fmt.Errorf("无权限修改超级管理员账户") + } + return s.repo.UpdateStatus(id, enable) +} + +// HashPassword 对密码进行加密 +func (s *UserService) HashPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedPassword), nil +} + +// VerifyPassword 验证密码 +func (s *UserService) VerifyPassword(plainPassword, hashedPassword string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword)) + return err == nil +} + +// GetUserList 获取用户列表 +func (s *UserService) GetUserList(req *schemas.UserListRequest) ([]*schemas.UserInfo, int64, error) { + return s.repo.GetUserList(req) +} + +// GetRoleList 获取角色列表 +func (s *UserService) GetRoleList(req *schemas.RoleListRequest) ([]*schemas.RoleResponseList, int64, error) { + return s.repo.GetRoleList(req) +} + +// GetLoginLogList 获取登录日志列表 +func (s *UserService) GetLoginLogList(req *schemas.LoginLogListRequest) ([]*schemas.LoginLog, int64, error) { + return s.repo.GetLoginLogList(req) +} + +// AssignRole 分配角色 +func (s *UserService) AssignRole(userIdValue, roleIdValue int) error { + // 检查用户是否已分配角色 + exists, err := s.repo.AssignRoleExists(userIdValue) + if err != nil { + return err + } + + if !exists { + // 用户未分配角色,执行插入操作 + return s.repo.AssignRole(userIdValue, roleIdValue) + } else { + // 用户已分配角色,执行更新操作 + return s.repo.UpdateAssignRole(userIdValue, roleIdValue) + } +} + +// UpdateRoleStatus 删除用户 +func (s *UserService) UpdateRoleStatus(id int, enable int) error { + // 防止修改超级管理员账户(ID=1) + if id == 1 { + return fmt.Errorf("无权限修改超级管理员账户") + } + return s.repo.UpdateRoleStatus(id, enable) +} + +// CreateRole 创建角色 +func (s *UserService) CreateRole(req *schemas.CreateRole) error { + return s.repo.CreateRole(req) +} + +// UpdateRole 更新角色 +func (s *UserService) UpdateRole(req *schemas.CreateRole) error { + return s.repo.UpdateRole(req) +} diff --git a/utils/generator_util.go b/utils/generator_util.go new file mode 100644 index 0000000..0369201 --- /dev/null +++ b/utils/generator_util.go @@ -0,0 +1,126 @@ +// Package utils generator_util.go +package utils + +import ( + "fmt" + "math/rand" + "strings" + "time" +) + +// GenerateUserCode 生成指定前缀 + 指定长度随机数字的编码 +// prefix: 字符串前缀,如 "S", "U" 等 +// length: 数字部分长度,默认为 9 位 +// 返回带前缀的 usercode,例如 S123456789 +func GenerateUserCode(prefix string, length int) string { + // 设置随机种子 + rand.Seed(time.Now().UnixNano()) + + // 计算数字范围 + mini := 1 + maxi := 9 + for i := 1; i < length; i++ { + mini *= 10 + maxi = maxi*10 + 9 + } + + // 生成随机数 + randomNum := rand.Intn(maxi-mini+1) + mini + return fmt.Sprintf("%s%d", prefix, randomNum) +} + +// GenerateEmail 生成随机电子邮箱地址 +// domain: 邮箱域名,默认为 "example.com" +// usernameLength: 用户名部分长度,默认为 8 位 +// 返回随机邮箱地址,如 "a1b2c3d4@example.com" +func GenerateEmail(domain string, usernameLength int) (string, error) { + // 设置随机种子 + rand.Seed(time.Now().UnixNano()) + + // 如果域名为空,使用默认域名 + if domain == "" { + domain = "example.com" + } + + // 如果用户名长度小于1,使用默认长度 + if usernameLength < 1 { + usernameLength = 8 + } + + // 定义字符集(小写字母和数字) + charset := "abcdefghijklmnopqrstuvwxyz0123456789" + + // 最多重试 5 次 + for i := 0; i < 5; i++ { + // 生成指定长度的用户名 + username := make([]byte, usernameLength) + for j := range username { + username[j] = charset[rand.Intn(len(charset))] + } + + email := fmt.Sprintf("%s@%s", string(username), domain) + + // 检查邮箱有效性 + if isValidEmail(email) { + return email, nil + } + } + + return "", fmt.Errorf("无法生成有效邮箱地址,请调整参数") +} + +// isValidEmail 简单验证邮箱地址有效性 +func isValidEmail(email string) bool { + parts := strings.Split(email, "@") + if len(parts) != 2 { + return false + } + + username := parts[0] + domain := parts[1] + + // 检查用户名和域名是否非空 + if username == "" || domain == "" { + return false + } + + // 检查域名是否包含点 + if !strings.Contains(domain, ".") { + return false + } + + // 检查域名的点是否在正确位置 + domainParts := strings.Split(domain, ".") + for _, part := range domainParts { + if part == "" { + return false + } + } + + return true +} + +// GenerateUsername 生成全英文随机用户名 +// length: 用户名长度,默认为 8 +// 返回全小写英文用户名,例如 "abcdefgh" +// 如果长度参数无效会返回错误 +func GenerateUsername(length int) (string, error) { + // 设置随机种子 + rand.Seed(time.Now().UnixNano()) + + // 验证长度参数 + if length < 4 || length > 20 { + return "", fmt.Errorf("用户名长度需在4-20个字符之间") + } + + // 定义字符集(仅小写字母) + charset := "abcdefghijklmnopqrstuvwxyz" + + // 生成指定长度的用户名 + username := make([]byte, length) + for i := range username { + username[i] = charset[rand.Intn(len(charset))] + } + + return string(username), nil +} diff --git a/utils/geoip_util.go b/utils/geoip_util.go new file mode 100644 index 0000000..5704266 --- /dev/null +++ b/utils/geoip_util.go @@ -0,0 +1,37 @@ +// Package utils/geoip_util.go +package utils + +import ( + "sync" + + "github.com/oschwald/geoip2-golang" +) + +var ( + geoDB *geoip2.Reader + geoOnce sync.Once +) + +// InitGeoIP 初始化 GeoIP 数据库 +func InitGeoIP(geoDBPath string) error { + var err error + geoOnce.Do(func() { + if geoDBPath != "" { + geoDB, err = geoip2.Open(geoDBPath) + } + }) + return err +} + +// GetGeoIP 获取 GeoIP 实例 +func GetGeoIP() *geoip2.Reader { + return geoDB +} + +// CloseGeoIP 关闭 GeoIP 数据库 +func CloseGeoIP() error { + if geoDB != nil { + return geoDB.Close() + } + return nil +} diff --git a/utils/jwt_util.go b/utils/jwt_util.go new file mode 100644 index 0000000..23a4138 --- /dev/null +++ b/utils/jwt_util.go @@ -0,0 +1,77 @@ +package utils + +import ( + "Quincy_admin/config" + "errors" + "strconv" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// Claims JWT 声明结构 +type Claims struct { + UserID int `json:"user_id"` + Username string `json:"username"` + SessionCode string `json:"sessioncode"` + RoleCode int `json:"rolecode"` + jwt.RegisteredClaims +} + +// GenerateToken 生成 JWT token +func GenerateToken(userID int, username, sessionCode string, roleCode int) (string, error) { + // 从配置文件或其他地方获取密钥,这里先使用硬编码的密钥 + cfg := config.LoadConfig() + + secretKey := []byte(cfg.Jwt.Secret) + + expireHours, _ := strconv.Atoi(cfg.Jwt.Expire) + if expireHours <= 1 { + expireHours = 1 + } + + expirationTime := time.Now().Add(time.Duration(expireHours) * time.Hour) + + // 创建 JWT 声明 + claims := &Claims{ + UserID: userID, + Username: username, + SessionCode: sessionCode, + RoleCode: roleCode, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + IssuedAt: jwt.NewNumericDate(time.Now()), + Issuer: "Quincy_admin", + }, + } + + // 创建 token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // 签名并获取完整的 token 字符串 + return token.SignedString(secretKey) +} + +// ParseToken 解析 JWT token +func ParseToken(tokenString string) (*Claims, error) { + secretKey := []byte("hcq") + + // 解析 token + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("unexpected signing method") + } + return secretKey, nil + }) + + if err != nil { + return nil, err + } + + // 验证并返回 claims + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New("invalid token") +} diff --git a/utils/mysql_util.go b/utils/mysql_util.go new file mode 100644 index 0000000..b660e8d --- /dev/null +++ b/utils/mysql_util.go @@ -0,0 +1,48 @@ +package utils + +import ( + "Quincy_admin/config" + "fmt" + "log" + "sync" + + "github.com/gin-gonic/gin" + _ "github.com/go-sql-driver/mysql" // MySQL驱动 + "github.com/jmoiron/sqlx" +) + +var db *sqlx.DB // 全局数据库连接实例,使用sqlx.DB替代sql.DB +var once sync.Once // 确保初始化只执行一次 + +// InitDB 初始化数据库连接 +func InitDB(cfg *config.Config) (*sqlx.DB, error) { + var err error + once.Do(func() { + // 构建数据库连接字符串 - 添加时区参数 + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=Asia%%2FShanghai", + cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Port, cfg.DB.Name) + + // 使用sqlx.Open替代sql.Open + db, err = sqlx.Open("mysql", dsn) + if err != nil { + log.Fatalf("Error opening database: %v", err) + } + + // 测试数据库连接 + err = db.Ping() + if err != nil { + log.Fatalf("Error connecting to database: %v", err) + } + + // 设置连接池参数 + db.SetMaxOpenConns(20) // 最大打开连接数 + db.SetMaxIdleConns(10) // 最大空闲连接数 + }) + + return db, err +} + +// GetDB 从上下文中获取数据库连接 +func GetDB(c *gin.Context) *sqlx.DB { + return db +} diff --git a/utils/redis_util.go b/utils/redis_util.go new file mode 100644 index 0000000..dc06e3b --- /dev/null +++ b/utils/redis_util.go @@ -0,0 +1,55 @@ +package utils + +import ( + "Quincy_admin/config" + "context" + "fmt" + "log" + "strconv" + "sync" + + "github.com/go-redis/redis/v8" +) + +// Redis客户端全局变量 +var redisClient *redis.Client +var redisOnce sync.Once + +// InitRedis 初始化Redis连接 +func InitRedis(cfg *config.Config) *redis.Client { + redisOnce.Do(func() { + // 将字符串类型的DB转换为整数 + redisDB, err := strconv.Atoi(cfg.Redis.DB) + if err != nil { + log.Printf("Invalid Redis DB value '%s', using default 0: %v", cfg.Redis.DB, err) + redisDB = 0 + } + + // 创建Redis客户端 + redisClient = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%s", cfg.Redis.Host, cfg.Redis.Port), + Password: cfg.Redis.Password, // 没有密码则留空 + DB: redisDB, // 使用转换后的整数DB + }) + + // 测试Redis连接 + ctx := context.Background() + _, err = redisClient.Ping(ctx).Result() + if err != nil { + log.Printf("Error connecting to Redis: %v", err) + } else { + } + }) + + return redisClient +} + +// GetRedis 获取Redis客户端 +func GetRedis() *redis.Client { + return redisClient +} + +// GetContext 获取上下文 +func GetContext() context.Context { + return context.Background() +} diff --git a/utils/response.go b/utils/response.go new file mode 100644 index 0000000..fe76b43 --- /dev/null +++ b/utils/response.go @@ -0,0 +1,33 @@ +package utils + +import "github.com/gin-gonic/gin" + +type Response struct { + Status int `json:"status"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +func Success(c *gin.Context, data interface{}) { + c.JSON(200, Response{ + Status: 1, + Message: "success", + Data: data, + }) +} + +func Fail(c *gin.Context, code int, message string) { + c.JSON(200, Response{ + Status: code, + Message: message, + Data: nil, + }) +} + +func Error(c *gin.Context, code int, message string) { + c.JSON(code, Response{ + Status: code, + Message: message, + Data: nil, + }) +}