Add files via upload

This commit is contained in:
QWQLwToo
2026-01-28 13:25:18 +08:00
committed by GitHub
parent 02f66ec447
commit 7030903eb6
77 changed files with 21573 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
package api
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"home-vue-go/internal/config"
"home-vue-go/internal/database"
"home-vue-go/internal/ent/user"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func Login(db *database.Database, cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户名和密码不能为空"})
return
}
ctx := c.Request.Context()
user, err := db.Client.User.Query().Where(user.UsernameEQ(req.Username)).First(ctx)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
return
}
// 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
return
}
// 获取登录IP和用户代理
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 查询IP地理位置
location := queryIPLocation(ip)
// 记录登录历史到内存
addLoginHistory(user.Username, ip, location, userAgent, true)
// 生成JWT token
claims := Claims{
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(cfg.JWTSecret))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成token失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"token": tokenString,
})
}
}
func ChangePassword(db *database.Database) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
OldPassword string `json:"oldPassword" binding:"required"`
NewPassword string `json:"newPassword" binding:"required,min=8"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "旧密码和新密码不能为空,且新密码至少8位"})
return
}
// 从JWT中获取用户名
username, exists := c.Get("username")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到用户信息"})
return
}
usernameStr := username.(string)
ctx := c.Request.Context()
// 查询用户
user, err := db.Client.User.Query().Where(user.UsernameEQ(usernameStr)).First(ctx)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户不存在"})
return
}
// 验证旧密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "旧密码错误"})
return
}
// 加密新密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
return
}
// 更新密码到数据库
updatedUser, err := db.Client.User.UpdateOneID(user.ID).SetPassword(string(hashedPassword)).Save(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码更新失败: " + err.Error()})
return
}
// 验证密码已保存(可选,用于调试)
if updatedUser == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码更新失败: 未返回更新后的用户"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"})
}
}
func JWTAuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证token"})
c.Abort()
return
}
// 移除 "Bearer " 前缀
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
tokenString = tokenString[7:]
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil || !token.Valid {
// Token失效时,记录登录IP(用于统计)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
location := queryIPLocation(ip)
// 记录为失败的登录尝试(token失效)
addLoginHistory("", ip, location, userAgent, false)
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的token"})
c.Abort()
return
}
c.Set("username", claims.Username)
c.Next()
}
}
// 查询IP地理位置(使用多个公共API)
func queryIPLocation(ip string) string {
if ip == "" || ip == "::1" || ip == "127.0.0.1" {
return "本地"
}
// 尝试多个IP查询服务
apis := []struct {
name string
url string
}{
{"ipapi", fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN", ip)},
{"ipapi.co", fmt.Sprintf("https://ipapi.co/%s/json/", ip)},
{"ip.sb", fmt.Sprintf("https://api.ip.sb/geoip/%s", ip)},
}
for _, api := range apis {
if location := queryIPFromAPI(api.url, api.name); location != "" {
return location
}
}
return "未知"
}
func queryIPFromAPI(url, apiName string) string {
client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(url)
if err != nil {
return ""
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return ""
}
switch apiName {
case "ipapi":
if country, ok := result["country"].(string); ok {
region := ""
if r, ok := result["regionName"].(string); ok {
region = r
}
city := ""
if c, ok := result["city"].(string); ok {
city = c
}
parts := []string{country}
if region != "" {
parts = append(parts, region)
}
if city != "" {
parts = append(parts, city)
}
return strings.Join(parts, " ")
}
case "ipapi.co":
if country, ok := result["country_name"].(string); ok {
city := ""
if c, ok := result["city"].(string); ok {
city = c
}
if city != "" {
return fmt.Sprintf("%s %s", country, city)
}
return country
}
case "ip.sb":
if country, ok := result["country"].(string); ok {
city := ""
if c, ok := result["city"].(string); ok {
city = c
}
if city != "" {
return fmt.Sprintf("%s %s", country, city)
}
return country
}
}
return ""
}
// 获取登录历史
func GetLoginHistory(db *database.Database) gin.HandlerFunc {
return func(c *gin.Context) {
limit := c.DefaultQuery("limit", "20")
limitInt := 20
if n, err := parseInt(limit); err == nil && n > 0 {
limitInt = n
if limitInt > 100 {
limitInt = 100
}
}
// 从内存获取登录历史
histories := getLoginHistoryRecords(limitInt)
result := make([]gin.H, 0, len(histories))
for _, h := range histories {
result = append(result, gin.H{
"username": h.Username,
"ip": h.IP,
"location": h.Location,
"userAgent": h.UserAgent,
"loginTime": h.LoginTime.Format("2006-01-02 15:04:05"),
"success": h.Success,
})
}
c.JSON(http.StatusOK, gin.H{
"data": result,
"count": len(result),
})
}
}
// 简单的整数解析
func parseInt(s string) (int, error) {
var n int
for _, char := range s {
if char >= '0' && char <= '9' {
n = n*10 + int(char-'0')
} else {
return 0, fmt.Errorf("invalid number")
}
}
return n, nil
}