feat(*): go 后端项目脚手架

This commit is contained in:
2025-11-15 18:20:30 +08:00
commit 5bb025c1aa
39 changed files with 2459 additions and 0 deletions

25
internal/api/api.go Normal file
View File

@@ -0,0 +1,25 @@
package api
import (
"github.com/gin-gonic/gin"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
docs "github.com/zhilv666/navsite/docs"
"github.com/zhilv666/navsite/internal/api/handler"
"github.com/zhilv666/navsite/internal/model"
)
func RegisterRouter(r *gin.Engine) {
model.Init()
docs.SwaggerInfo.BasePath = "/"
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
v1 := r.Group("/api/v1")
{
handler.RegisterRouterAuth(v1.Group("/auth"))
handler.RegisterRouterUser(v1.Group("/user"))
handler.RegisterRouterCateage(v1.Group("/cateage"))
v1.GET("/proxy", handler.Proxy)
}
}

View File

@@ -0,0 +1,102 @@
package handler
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/zhilv666/navsite/internal/model"
"github.com/zhilv666/navsite/internal/service"
"github.com/zhilv666/navsite/pkg/common"
"github.com/zhilv666/navsite/pkg/utils"
"gorm.io/gorm"
)
type Req struct {
Username string `json:"username" binding:"required" example:"admin"`
Password string `json:"password" binding:"required" example:"123456"`
OtpCode string `json:"otp_code" example:"123456"`
}
type LoginResp struct {
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...zYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA"`
}
// login
// @Summary 用户登录
// @Tag Auth
// @Accept json
// @Produce json
// @Param login body Req true "登录参数"
// @Success 200 {object} common.Response[LoginResp] "登录成功"
// @Failure 400 {object} common.Response[any] "登录失败"
// @Router /api/v1/auth/login [post]
func login(c *gin.Context) {
var req Req
if err := c.ShouldBindJSON(&req); err != nil {
common.Fail(c, err.Error())
return
}
user, err := service.GetUserByName(req.Username)
if err != nil {
common.Fail(c, err.Error())
return
}
if user == nil {
common.Fail(c, "账号或密码不正确")
return
}
if len(req.Password) < 32 {
req.Password = common.Sha1(req.Password)
}
if user.Password != req.Password {
common.Fail(c, "账号或密码不正确")
return
}
token := utils.GenerateJwtToken(user.ID, user.Email, user.SsoID)
common.Succ(c, LoginResp{Token: token})
}
// register
// @Summary 用户注册
// @Tag Auth
// @Accept json
// @Produce json
// @Param register body Req true "注册参数"
// @Success 200 {object} common.Response[Req] "注册成功"
// @Failure 400 {object} common.Response[any] "注册失败"
// @Router /api/v1/auth/register [post]
func register(c *gin.Context) {
var req Req
if err := c.ShouldBindJSON(&req); err != nil {
common.Fail(c, err.Error())
return
}
user, err := service.GetUserByName(req.Username)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
} else {
common.Fail(c, "查询用户失败")
return
}
}
if user != nil {
common.Fail(c, "该账号已注册")
return
}
if len(req.Password) < 32 {
req.Password = common.Sha1(req.Password)
}
err = service.AddUser(&model.User{
Username: req.Username,
Password: req.Password,
})
if err != nil {
common.Fail(c, err.Error())
return
}
common.Ok(c, "账号注册成功")
}
func RegisterRouterAuth(g *gin.RouterGroup) {
g.POST("/login", login)
g.POST("/register", register)
}

View File

@@ -0,0 +1,20 @@
package handler
import (
"github.com/gin-gonic/gin"
"github.com/zhilv666/navsite/internal/service"
"github.com/zhilv666/navsite/pkg/common"
)
func GetAllCateageWithSites(c *gin.Context) {
cateagies, err := service.GetAllCateageWithSites()
if err != nil {
common.Fail(c, err.Error())
return
}
common.Succ(c, cateagies)
}
func RegisterRouterCateage(g *gin.RouterGroup) {
g.GET("/", GetAllCateageWithSites)
}

View File

@@ -0,0 +1,42 @@
package handler
import (
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/zhilv666/navsite/pkg/common"
)
func Proxy(c *gin.Context) {
_url := c.Query("url")
if _url == "" {
common.Fail(c, "missing url")
return
}
// 请求目标 URL
resp, err := http.Get(_url)
if err != nil {
common.Fail(c, "unable to fetch image")
return
}
defer resp.Body.Close()
// 检查 Content-Type 是否为图片
contentType := resp.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "image/") {
common.Fail(c, "not an image file")
return
}
// 设置响应 Content-Type
c.Writer.Header().Set("Content-Type", contentType)
// 将图片数据直接写入响应
_, err = io.Copy(c.Writer, resp.Body)
if err != nil {
common.Fail(c, "failed to write image")
return
}
}

View File

@@ -0,0 +1,35 @@
package handler
import (
"github.com/gin-gonic/gin"
"github.com/zhilv666/navsite/internal/service"
"github.com/zhilv666/navsite/pkg/common"
"strconv"
)
// getUserByID
// @Summary 获取指定 ID 用户
// @Tag User
// @Accept json
// @Produce json
// @Param id path int true "用户 ID"
// @Success 200 {object} common.Response[model.User] "获取成功"
// @Failure 400 {object} common.Response[any] "获取失败"
// @Router /api/v1/user/{id} [get]
func getUserByID(c *gin.Context) {
id := c.Param("id")
idInt, err := strconv.Atoi(id)
if err != nil {
common.Fail(c, err.Error())
}
user, err := service.GetUserByID(uint(idInt))
if err != nil {
common.Fail(c, err.Error())
return
}
common.Succ(c, user)
}
func RegisterRouterUser(g *gin.RouterGroup) {
g.GET(":id", getUserByID)
}