feat: 认证简化为固定进入密钥 + 页面增强(API 文档/主题切换/版权署名)
- 移除管理员初始化/登录/JWT 流程,改为固定进入密钥(19735500), 首次启动自动初始化到数据库 settings 表(可改库更新) - analyze 接口支持可选密钥鉴权:X-Access-Key 请求头或 access_key 字段, 带密钥强制校验(错误 401),不带密钥保持免鉴权 - 解锁后顶栏"复制 API 文档"按钮:以当前访问 URL 为基础地址动态生成 Markdown 文档(免鉴权/带密钥两种 curl 示例,供智能体直接调用) - 白昼/黑夜主题切换:CSS 变量驱动,localStorage 持久化,默认跟随系统 - 修复 hidden 属性被 display:flex 覆盖导致解锁层无法隐藏的问题 - 页面去除"免费/Agnes"文案,页脚署名青梧映星软件开发工作室 - build.bat/build.sh 简化为 Linux 交叉编译(CGO_ENABLED=0) - 依赖精简:移除 golang-jwt/bcrypt,直接依赖仅 gin/sqlite/gorm/yaml Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -10,79 +9,27 @@ import (
|
||||
"vision-tool/internal/service"
|
||||
)
|
||||
|
||||
// AuthHandler HTTP 处理器:管理员初始化 / 登录 / 用户信息。
|
||||
// AuthHandler HTTP 处理器:进入密钥校验。
|
||||
type AuthHandler struct {
|
||||
svc *service.AuthService
|
||||
svc *service.AccessService
|
||||
}
|
||||
|
||||
func NewAuthHandler(svc *service.AuthService) *AuthHandler {
|
||||
func NewAuthHandler(svc *service.AccessService) *AuthHandler {
|
||||
return &AuthHandler{svc: svc}
|
||||
}
|
||||
|
||||
type initRequest struct {
|
||||
Username string `json:"username" binding:"required,min=2,max=32"`
|
||||
Password string `json:"password" binding:"required,min=6,max=64"`
|
||||
}
|
||||
|
||||
type loginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// Check GET /api/v1/admin/check — 管理员是否已初始化
|
||||
func (h *AuthHandler) Check(c *gin.Context) {
|
||||
initialized, err := h.svc.CheckAdmin()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Err(model.CodeServerErr, err.Error()))
|
||||
return
|
||||
// Verify POST /api/v1/auth/verify — 校验进入密钥(固定密钥,存于数据库)
|
||||
func (h *AuthHandler) Verify(c *gin.Context) {
|
||||
var req struct {
|
||||
Key string `json:"key" binding:"required"`
|
||||
}
|
||||
c.JSON(http.StatusOK, model.OK(gin.H{"initialized": initialized}))
|
||||
}
|
||||
|
||||
// Init POST /api/v1/admin/init — 初始化管理员(仅首次)
|
||||
func (h *AuthHandler) Init(c *gin.Context) {
|
||||
var req initRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Err(model.CodeBadParam, "用户名 2-32 位,密码至少 6 位"))
|
||||
c.JSON(http.StatusBadRequest, model.Err(model.CodeBadParam, "请输入进入密钥"))
|
||||
return
|
||||
}
|
||||
if err := h.svc.InitAdmin(req.Username, req.Password); err != nil {
|
||||
if errors.Is(err, service.ErrAdminExists) {
|
||||
c.JSON(http.StatusConflict, model.Err(model.CodeConflict, err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, model.Err(model.CodeServerErr, err.Error()))
|
||||
if !h.svc.VerifyKey(req.Key) {
|
||||
c.JSON(http.StatusUnauthorized, model.Err(model.CodeUnauthorized, "密钥错误"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.OK(gin.H{"message": "管理员初始化成功"}))
|
||||
}
|
||||
|
||||
// Login POST /api/v1/auth/login — 登录获取 JWT token
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req loginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Err(model.CodeBadParam, "请输入用户名和密码"))
|
||||
return
|
||||
}
|
||||
token, err := h.svc.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInvalidCredentials) {
|
||||
c.JSON(http.StatusUnauthorized, model.Err(model.CodeUnauthorized, err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, model.Err(model.CodeServerErr, err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.OK(gin.H{"token": token}))
|
||||
}
|
||||
|
||||
// Profile GET /api/v1/user/profile — 当前用户信息(JWT 保护)
|
||||
func (h *AuthHandler) Profile(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
user, err := h.svc.Profile(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Err(model.CodeServerErr, err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.OK(user))
|
||||
c.JSON(http.StatusOK, model.OK(gin.H{"ok": true}))
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ import (
|
||||
// VisionHandler HTTP 处理器:在线图片识别。
|
||||
type VisionHandler struct {
|
||||
svc *service.VisionService
|
||||
access *service.AccessService
|
||||
upload config.UploadConfig
|
||||
}
|
||||
|
||||
func NewVisionHandler(svc *service.VisionService, upload config.UploadConfig) *VisionHandler {
|
||||
return &VisionHandler{svc: svc, upload: upload}
|
||||
func NewVisionHandler(svc *service.VisionService, access *service.AccessService, upload config.UploadConfig) *VisionHandler {
|
||||
return &VisionHandler{svc: svc, access: access, upload: upload}
|
||||
}
|
||||
|
||||
// Analyze POST /api/v1/vision/analyze — 上传 1-N 张图片并调用免费视觉模型识别。
|
||||
@@ -34,8 +35,19 @@ func NewVisionHandler(svc *service.VisionService, upload config.UploadConfig) *V
|
||||
// images 图片文件(可多个,支持 png/jpg/gif/webp/bmp/tiff)
|
||||
// prompt 可选,自定义分析提问(默认:结构化描述图片内容)
|
||||
//
|
||||
// 免鉴权 + 限流,方便 curl / Claude Code 直接调用。
|
||||
// 鉴权(可选):提供 X-Access-Key 请求头 或 access_key 表单字段时校验进入密钥,
|
||||
// 密钥错误返回 401;不提供时保持免鉴权调用,方便 curl / Claude Code 直接使用。
|
||||
func (h *VisionHandler) Analyze(c *gin.Context) {
|
||||
// 可选密钥校验:带密钥必须正确
|
||||
key := c.GetHeader("X-Access-Key")
|
||||
if key == "" {
|
||||
key = c.PostForm("access_key")
|
||||
}
|
||||
if key != "" && !h.access.VerifyKey(key) {
|
||||
c.JSON(http.StatusUnauthorized, model.Err(model.CodeUnauthorized, "密钥错误"))
|
||||
return
|
||||
}
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Err(model.CodeBadParam, "请使用 multipart/form-data 上传图片"))
|
||||
|
||||
Reference in New Issue
Block a user