- Go + Gin + html/template 服务端渲染 - 主页:Google 风格搜索框 + 导航卡片 - 后台:卡片 CRUD、搜索引擎配置、主页背景/标题配置 - 图片上传:支持 jpg/jpeg/png/gif,自动压缩,缩略图参数 ?thumb=1 - 安全:登录日志、修改密码、IP 自动封禁、IP 白名单 - 访问统计:主页访问/卡片点击/搜索追踪、实时流量、IP 统计 - SQLite 存储(modernc.org/sqlite,纯 Go) - 内存 Session + bcrypt 密码哈希
259 lines
6.4 KiB
Go
259 lines
6.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"simple_portal/models"
|
|
"simple_portal/session"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// LoginLogsGet 渲染登录日志页面。
|
|
func LoginLogsGet(c *gin.Context) {
|
|
username, _ := c.Get("username")
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
pageSize := 20
|
|
|
|
logs, total, err := models.GetLoginLogs(page, pageSize)
|
|
if err != nil {
|
|
logs = []models.LoginLog{}
|
|
total = 0
|
|
}
|
|
|
|
// 获取活跃的封禁列表
|
|
bans, _ := models.GetAllActiveBans()
|
|
if bans == nil {
|
|
bans = []models.IPBan{}
|
|
}
|
|
|
|
totalPages := (total + pageSize - 1) / pageSize
|
|
if totalPages < 1 {
|
|
totalPages = 1
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "admin/logs.html", gin.H{
|
|
"Title": "登录日志",
|
|
"Username": username,
|
|
"Logs": logs,
|
|
"Bans": bans,
|
|
"Page": page,
|
|
"TotalPages": totalPages,
|
|
"Total": total,
|
|
})
|
|
}
|
|
|
|
// UnbanIP 处理手动解封IP的请求。
|
|
func UnbanIP(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.Redirect(http.StatusFound, "/admin/logs")
|
|
return
|
|
}
|
|
_ = models.DeleteIPBan(id)
|
|
c.Redirect(http.StatusFound, "/admin/logs")
|
|
}
|
|
|
|
// ChangePasswordGet 渲染修改密码页面。
|
|
func ChangePasswordGet(c *gin.Context) {
|
|
username, _ := c.Get("username")
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "",
|
|
"Message": "",
|
|
})
|
|
}
|
|
|
|
// ChangePasswordPost 处理修改密码表单提交。
|
|
func ChangePasswordPost(c *gin.Context) {
|
|
username, _ := c.Get("username")
|
|
adminID, _ := c.Get("adminID")
|
|
|
|
oldPassword := c.PostForm("old_password")
|
|
newPassword := c.PostForm("new_password")
|
|
confirmPassword := c.PostForm("confirm_password")
|
|
|
|
// 验证输入
|
|
if oldPassword == "" || newPassword == "" || confirmPassword == "" {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "请填写所有字段",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
if len(newPassword) < 6 {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "新密码长度不能少于6位",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
if newPassword != confirmPassword {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "两次输入的新密码不一致",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证旧密码
|
|
admin, err := models.GetAdminByUsername(username.(string))
|
|
if err != nil || admin == nil {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "用户不存在",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(oldPassword)); err != nil {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "旧密码不正确",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 生成新密码hash
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "密码加密失败",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 更新密码
|
|
if err := models.ChangePassword(adminID.(int), string(hashedPassword)); err != nil {
|
|
c.HTML(http.StatusOK, "admin/password.html", gin.H{
|
|
"Title": "修改密码",
|
|
"Username": username,
|
|
"Error": "密码修改失败: " + err.Error(),
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 记录登录日志
|
|
ip := c.ClientIP()
|
|
userAgent := c.Request.UserAgent()
|
|
adminIDInt := adminID.(int)
|
|
_ = models.CreateLoginLog(&adminIDInt, username.(string), ip, userAgent, true)
|
|
|
|
// 清除当前session,强制重新登录
|
|
sessionID, _ := c.Cookie("session_id")
|
|
if sessionID != "" {
|
|
store := c.MustGet("sessionStore").(*session.SessionStore)
|
|
store.Delete(sessionID)
|
|
}
|
|
c.SetCookie("session_id", "", -1, "/", "", false, true)
|
|
|
|
c.Redirect(http.StatusFound, "/admin/login")
|
|
}
|
|
|
|
// IPWhitelistGet 渲染IP白名单管理页面。
|
|
func IPWhitelistGet(c *gin.Context) {
|
|
username, _ := c.Get("username")
|
|
|
|
list, err := models.GetAllWhitelist()
|
|
if err != nil {
|
|
list = []models.IPWhitelist{}
|
|
}
|
|
|
|
hasWhitelist := len(list) > 0
|
|
|
|
c.HTML(http.StatusOK, "admin/ip_whitelist.html", gin.H{
|
|
"Title": "IP白名单",
|
|
"Username": username,
|
|
"Whitelist": list,
|
|
"HasWhitelist": hasWhitelist,
|
|
"Error": "",
|
|
"Message": "",
|
|
})
|
|
}
|
|
|
|
// IPWhitelistAdd 处理添加IP白名单的请求。
|
|
// 当白名单从空变为非空时,自动将当前操作者的IP也加入白名单,防止锁定自己。
|
|
func IPWhitelistAdd(c *gin.Context) {
|
|
username, _ := c.Get("username")
|
|
ip := c.PostForm("ip")
|
|
comment := c.PostForm("comment")
|
|
|
|
if ip == "" {
|
|
list, _ := models.GetAllWhitelist()
|
|
c.HTML(http.StatusOK, "admin/ip_whitelist.html", gin.H{
|
|
"Title": "IP白名单",
|
|
"Username": username,
|
|
"Whitelist": list,
|
|
"HasWhitelist": len(list) > 0,
|
|
"Error": "IP地址不能为空",
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 检查白名单是否之前为空(首次启用白名单时需自动加入当前操作者IP)
|
|
wasEmpty, _ := models.HasWhitelist()
|
|
|
|
if err := models.AddWhitelist(ip, comment); err != nil {
|
|
list, _ := models.GetAllWhitelist()
|
|
c.HTML(http.StatusOK, "admin/ip_whitelist.html", gin.H{
|
|
"Title": "IP白名单",
|
|
"Username": username,
|
|
"Whitelist": list,
|
|
"HasWhitelist": len(list) > 0,
|
|
"Error": "添加失败: " + err.Error(),
|
|
"Message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 首次启用白名单:自动把当前操作者IP也加入,防止锁定
|
|
if !wasEmpty {
|
|
c.Redirect(http.StatusFound, "/admin/ip-whitelist")
|
|
return
|
|
}
|
|
|
|
currentIP := c.ClientIP()
|
|
// 检查当前IP是否和刚添加的一样
|
|
isAlreadyAdded, _ := models.IsIPWhitelisted(currentIP)
|
|
if !isAlreadyAdded {
|
|
_ = models.AddWhitelist(currentIP, "自动添加(当前操作者)")
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, "/admin/ip-whitelist")
|
|
}
|
|
|
|
// IPWhitelistDelete 处理删除IP白名单的请求。
|
|
func IPWhitelistDelete(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.Redirect(http.StatusFound, "/admin/ip-whitelist")
|
|
return
|
|
}
|
|
_ = models.DeleteWhitelist(id)
|
|
c.Redirect(http.StatusFound, "/admin/ip-whitelist")
|
|
}
|