This commit is contained in:
2026-02-23 23:50:04 +08:00
commit 084d3b0faf
45 changed files with 4090 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
package controllers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"lv8girl/internal/middleware"
"lv8girl/internal/services"
)
type AdminController struct {
adminSvc *services.AdminService
messageSvc *services.MessageService
}
func NewAdminController() *AdminController {
return &AdminController{
adminSvc: services.NewAdminService(),
messageSvc: services.NewMessageService(),
}
}
func (c *AdminController) Dashboard(ctx *gin.Context) {
username, _ := ctx.Get("username")
stats, _ := c.adminSvc.GetStats()
ctx.HTML(http.StatusOK, "admin_dashboard.html", gin.H{
"Username": username,
"Stats": stats,
"Page": "dashboard",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) PendingPosts(ctx *gin.Context) {
username, _ := ctx.Get("username")
posts, _ := c.adminSvc.GetPendingPosts()
ctx.HTML(http.StatusOK, "admin_pending_posts.html", gin.H{
"Username": username,
"Posts": posts,
"Page": "pending_posts",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) ApprovePost(ctx *gin.Context) {
postID := parseUint(ctx.Param("id"))
action := ctx.Param("action")
if action == "approve" {
c.adminSvc.ApprovePost(postID)
ctx.Redirect(http.StatusFound, "/admin/pending_posts?msg=帖子已通过审核")
} else if action == "reject" {
c.adminSvc.RejectPost(postID)
ctx.Redirect(http.StatusFound, "/admin/pending_posts?msg=帖子已拒绝")
} else {
ctx.Redirect(http.StatusFound, "/admin/pending_posts")
}
}
func (c *AdminController) PendingUsers(ctx *gin.Context) {
username, _ := ctx.Get("username")
users, _ := c.adminSvc.GetPendingUsers()
ctx.HTML(http.StatusOK, "admin_pending_users.html", gin.H{
"Username": username,
"Users": users,
"Page": "pending_users",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) ApproveUser(ctx *gin.Context) {
userID := parseUint(ctx.Param("id"))
action := ctx.Param("action")
adminID, _ := ctx.Get("user_id")
if action == "approve" {
c.adminSvc.ApproveUser(userID)
c.messageSvc.NotifyUserApproved(adminID.(uint), userID)
ctx.Redirect(http.StatusFound, "/admin/pending_users?msg=用户已通过审核")
} else if action == "reject" {
c.adminSvc.RejectUser(userID)
c.messageSvc.NotifyUserRejected(adminID.(uint), userID)
ctx.Redirect(http.StatusFound, "/admin/pending_users?msg=用户已拒绝")
} else {
ctx.Redirect(http.StatusFound, "/admin/pending_users")
}
}
func (c *AdminController) Posts(ctx *gin.Context) {
username, _ := ctx.Get("username")
posts, _ := c.adminSvc.GetAllPosts()
ctx.HTML(http.StatusOK, "admin_posts.html", gin.H{
"Username": username,
"Posts": posts,
"Page": "posts",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) DeletePost(ctx *gin.Context) {
postID := parseUint(ctx.Param("id"))
c.adminSvc.DeletePost(postID)
ctx.Redirect(http.StatusFound, "/admin/posts?msg=帖子已删除")
}
func (c *AdminController) Users(ctx *gin.Context) {
username, _ := ctx.Get("username")
currentUserID, _ := ctx.Get("user_id")
users, _ := c.adminSvc.GetAllUsers()
ctx.HTML(http.StatusOK, "admin_users.html", gin.H{
"Username": username,
"Users": users,
"CurrentUserID": currentUserID,
"Page": "users",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) UpdateUserRole(ctx *gin.Context) {
userIDStr := ctx.PostForm("user_id")
newRole := ctx.PostForm("new_role")
currentUserID, _ := ctx.Get("user_id")
userID, _ := strconv.ParseUint(userIDStr, 10, 32)
if uint(userID) == currentUserID.(uint) {
ctx.Redirect(http.StatusFound, "/admin/users?msg=不能修改自己的角色")
return
}
c.adminSvc.UpdateUserRole(uint(userID), newRole)
if newRole == "banned" {
c.messageSvc.NotifyUserBanned(currentUserID.(uint), uint(userID))
}
ctx.Redirect(http.StatusFound, "/admin/users?msg=用户角色已更新")
}
func (c *AdminController) DeleteUser(ctx *gin.Context) {
userID := parseUint(ctx.Param("id"))
currentUserID, _ := ctx.Get("user_id")
if userID == currentUserID.(uint) {
ctx.Redirect(http.StatusFound, "/admin/users?msg=不能删除自己")
return
}
c.adminSvc.DeleteUser(userID)
ctx.Redirect(http.StatusFound, "/admin/users?msg=用户已删除")
}
func (c *AdminController) Comments(ctx *gin.Context) {
username, _ := ctx.Get("username")
comments, _ := c.adminSvc.GetAllComments()
ctx.HTML(http.StatusOK, "admin_comments.html", gin.H{
"Username": username,
"Comments": comments,
"Page": "comments",
"Message": ctx.Query("msg"),
})
}
func (c *AdminController) DeleteComment(ctx *gin.Context) {
commentID := parseUint(ctx.Param("id"))
c.adminSvc.DeleteComment(commentID)
ctx.Redirect(http.StatusFound, "/admin/comments?msg=评论已删除")
}
func init() {
_ = middleware.GetCurrentUser
}

View File

@@ -0,0 +1,99 @@
package controllers
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"lv8girl/internal/services"
)
type AuthController struct {
authService *services.AuthService
}
func NewAuthController() *AuthController {
return &AuthController{
authService: services.NewAuthService(),
}
}
func (c *AuthController) ShowLogin(ctx *gin.Context) {
session := sessions.Default(ctx)
if session.Get("user_id") != nil {
ctx.Redirect(http.StatusFound, "/")
return
}
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": ""})
}
func (c *AuthController) Login(ctx *gin.Context) {
session := sessions.Default(ctx)
if session.Get("user_id") != nil {
ctx.Redirect(http.StatusFound, "/")
return
}
login := ctx.PostForm("login")
password := ctx.PostForm("password")
if login == "" || password == "" {
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": "请输入用户名/邮箱和密码"})
return
}
result := c.authService.Login(login, password)
if !result.Success {
ctx.HTML(http.StatusOK, "login.html", gin.H{"Error": result.Error})
return
}
session.Set("user_id", result.User.ID)
session.Set("username", result.User.Username)
session.Set("user_role", result.User.Role)
session.Save()
ctx.Redirect(http.StatusFound, "/")
}
func (c *AuthController) ShowRegister(ctx *gin.Context) {
session := sessions.Default(ctx)
if session.Get("user_id") != nil {
ctx.Redirect(http.StatusFound, "/")
return
}
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "", "Success": ""})
}
func (c *AuthController) Register(ctx *gin.Context) {
session := sessions.Default(ctx)
if session.Get("user_id") != nil {
ctx.Redirect(http.StatusFound, "/")
return
}
username := ctx.PostForm("username")
email := ctx.PostForm("email")
password := ctx.PostForm("password")
confirmPassword := ctx.PostForm("confirm_password")
if password != confirmPassword {
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "两次输入的密码不一致", "Success": ""})
return
}
result := c.authService.Register(username, email, password)
if !result.Success {
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": result.Error, "Success": ""})
return
}
ctx.HTML(http.StatusOK, "register.html", gin.H{"Error": "", "Success": "注册成功!您的账号正在等待管理员审核,请耐心等待。"})
}
func (c *AuthController) Logout(ctx *gin.Context) {
session := sessions.Default(ctx)
session.Clear()
session.Save()
ctx.Redirect(http.StatusFound, "/")
}

View File

@@ -0,0 +1,121 @@
package controllers
import (
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"lv8girl/internal/middleware"
"lv8girl/internal/services"
)
type DiscussionController struct {
discussionSvc *services.DiscussionService
}
func NewDiscussionController() *DiscussionController {
return &DiscussionController{
discussionSvc: services.NewDiscussionService(),
}
}
func (c *DiscussionController) ShowNewPost(ctx *gin.Context) {
userID, username, userRole, _ := middleware.GetCurrentUser(ctx)
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "",
"Success": "",
})
}
func (c *DiscussionController) CreatePost(ctx *gin.Context) {
userID, username, userRole, _ := middleware.GetCurrentUser(ctx)
title := strings.TrimSpace(ctx.PostForm("title"))
content := strings.TrimSpace(ctx.PostForm("content"))
if title == "" || content == "" {
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "标题和内容不能为空",
"Success": "",
})
return
}
var imagePath string
file, err := ctx.FormFile("image")
if err == nil {
if file.Size > 2*1024*1024 {
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "图片大小不能超过2MB",
"Success": "",
})
return
}
ext := strings.ToLower(filepath.Ext(file.Filename))
filename := "post_" + time.Now().Format("20060102150405") + "_" + randomString(8) + ext
uploadDir := "uploads/posts"
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
os.MkdirAll(uploadDir, 0755)
}
imagePath = filepath.Join(uploadDir, filename)
if err := ctx.SaveUploadedFile(file, imagePath); err != nil {
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "图片保存失败",
"Success": "",
})
return
}
}
if err := c.discussionSvc.CreatePost(userID, title, content, imagePath); err != nil {
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "发帖失败,请稍后重试",
"Success": "",
})
return
}
ctx.HTML(http.StatusOK, "post_discussion.html", gin.H{
"IsLoggedIn": true,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Error": "",
"Success": "帖子已提交,等待管理员审核。",
})
}
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
}
return string(b)
}

View File

@@ -0,0 +1,150 @@
package controllers
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"lv8girl/internal/middleware"
"lv8girl/internal/repositories"
"lv8girl/internal/services"
)
type HomeController struct {
discussionSvc *services.DiscussionService
userSvc *services.UserService
messageSvc *services.MessageService
discussionRepo *repositories.DiscussionRepository
}
func NewHomeController() *HomeController {
return &HomeController{
discussionSvc: services.NewDiscussionService(),
userSvc: services.NewUserService(),
messageSvc: services.NewMessageService(),
discussionRepo: repositories.NewDiscussionRepository(),
}
}
func (c *HomeController) Index(ctx *gin.Context) {
session := sessions.Default(ctx)
userID, _ := session.Get("user_id").(uint)
username, _ := session.Get("username").(string)
userRole, _ := session.Get("user_role").(string)
isLoggedIn := userID != 0
if isLoggedIn {
userRepo := repositories.NewUserRepository()
userRepo.UpdateLastActive(userID)
}
posts, _ := c.discussionSvc.GetApprovedPosts(30)
var postCount, userCount, onlineCount int64
postCount, _ = c.discussionRepo.CountByStatus("approved")
userCount, onlineCount, _ = c.userSvc.GetUserStats()
var unreadCount int64
if isLoggedIn {
unreadCount, _ = c.messageSvc.GetUnreadCount(userID)
}
ctx.HTML(http.StatusOK, "index.html", gin.H{
"IsLoggedIn": isLoggedIn,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Posts": posts,
"PostCount": postCount,
"UserCount": userCount,
"OnlineCount": onlineCount,
"UnreadCount": unreadCount,
})
}
func (c *HomeController) ShowPost(ctx *gin.Context) {
postID := parseUint(ctx.Param("id"))
userID, username, userRole, isLoggedIn := middleware.GetCurrentUser(ctx)
session := sessions.Default(ctx)
viewedKey := "viewed_posts"
viewedPosts := session.Get(viewedKey)
var viewedMap map[uint]bool
if viewedPosts == nil {
viewedMap = make(map[uint]bool)
} else {
viewedMap = viewedPosts.(map[uint]bool)
}
if !viewedMap[postID] {
c.discussionSvc.IncrementViews(postID)
viewedMap[postID] = true
session.Set(viewedKey, viewedMap)
session.Save()
}
detail, err := c.discussionSvc.GetPostDetail(postID, userID)
if err != nil {
ctx.String(http.StatusNotFound, "帖子不存在")
return
}
comments, _ := c.discussionSvc.GetComments(postID)
avatar := ""
if detail.Post.User.Avatar != "" {
avatar = "/" + detail.Post.User.Avatar
}
ctx.HTML(http.StatusOK, "post.html", gin.H{
"IsLoggedIn": isLoggedIn,
"UserID": userID,
"Username": username,
"UserRole": userRole,
"Post": detail.Post,
"PostAvatar": avatar,
"Comments": comments,
"LikeCount": detail.LikeCount,
"UserLiked": detail.UserLiked,
"AuthorPostCount": detail.AuthorPostCount,
})
}
func (c *HomeController) LikePost(ctx *gin.Context) {
userID, _, _, isLoggedIn := middleware.GetCurrentUser(ctx)
if !isLoggedIn {
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
return
}
postID := parseUint(ctx.Param("id"))
c.discussionSvc.AddLike(postID, userID)
ctx.Redirect(http.StatusFound, "/post/"+ctx.Param("id"))
}
func (c *HomeController) AddComment(ctx *gin.Context) {
userID, _, _, isLoggedIn := middleware.GetCurrentUser(ctx)
if !isLoggedIn {
ctx.Redirect(http.StatusFound, "/login")
return
}
postID := parseUint(ctx.Param("id"))
content := ctx.PostForm("content")
if content != "" {
c.discussionSvc.AddComment(postID, userID, content)
}
ctx.Redirect(http.StatusFound, "/post/"+ctx.Param("id"))
}
func parseUint(s string) uint {
var result uint
for _, c := range s {
if c >= '0' && c <= '9' {
result = result*10 + uint(c-'0')
}
}
return result
}

View File

@@ -0,0 +1,89 @@
package controllers
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"lv8girl/internal/middleware"
"lv8girl/internal/repositories"
"lv8girl/internal/services"
)
type MessageController struct {
messageSvc *services.MessageService
userRepo *repositories.UserRepository
}
func NewMessageController() *MessageController {
return &MessageController{
messageSvc: services.NewMessageService(),
userRepo: repositories.NewUserRepository(),
}
}
func (c *MessageController) ShowMessages(ctx *gin.Context) {
currentUserID, currentUsername, currentUserRole, _ := middleware.GetCurrentUser(ctx)
conversations, _ := c.messageSvc.GetConversations(currentUserID)
ctx.HTML(http.StatusOK, "messages.html", gin.H{
"IsLoggedIn": true,
"UserID": currentUserID,
"Username": currentUsername,
"UserRole": currentUserRole,
"Conversations": conversations,
})
}
func (c *MessageController) ShowSendMessage(ctx *gin.Context) {
currentUserID, currentUsername, currentUserRole, _ := middleware.GetCurrentUser(ctx)
toUserID := parseUint(ctx.Query("to"))
if toUserID == 0 {
ctx.Redirect(http.StatusFound, "/")
return
}
receiver, err := c.userRepo.FindByID(toUserID)
if err != nil {
ctx.String(http.StatusNotFound, "用户不存在")
return
}
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
"IsLoggedIn": true,
"UserID": currentUserID,
"Username": currentUsername,
"UserRole": currentUserRole,
"Receiver": receiver,
"Error": "",
"Success": "",
})
}
func (c *MessageController) SendMessage(ctx *gin.Context) {
currentUserID, _, _, _ := middleware.GetCurrentUser(ctx)
toUserID := parseUint(ctx.Query("to"))
content := strings.TrimSpace(ctx.PostForm("content"))
receiver, _ := c.userRepo.FindByID(toUserID)
if content == "" {
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
"Receiver": receiver,
"Error": "消息内容不能为空",
"Success": "",
})
return
}
c.messageSvc.SendMessage(currentUserID, toUserID, content)
ctx.HTML(http.StatusOK, "send_message.html", gin.H{
"Receiver": receiver,
"Error": "",
"Success": "消息已发送!",
})
}

View File

@@ -0,0 +1,98 @@
package controllers
import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"lv8girl/internal/middleware"
"lv8girl/internal/services"
)
type UserController struct {
userSvc *services.UserService
}
func NewUserController() *UserController {
return &UserController{
userSvc: services.NewUserService(),
}
}
func (c *UserController) ShowProfile(ctx *gin.Context) {
currentUserID, currentUsername, currentUserRole, isLoggedIn := middleware.GetCurrentUser(ctx)
userIDStr := ctx.Param("id")
var userID uint
if userIDStr == "" {
userID = currentUserID
} else {
userID = parseUint(userIDStr)
}
if userID == 0 {
ctx.Redirect(http.StatusFound, "/")
return
}
profile, err := c.userSvc.GetUserProfile(currentUserID, userID)
if err != nil {
ctx.String(http.StatusNotFound, "用户不存在")
return
}
avatar := ""
if profile.User.Avatar != "" {
avatar = "/" + profile.User.Avatar
}
ctx.HTML(http.StatusOK, "profile.html", gin.H{
"IsLoggedIn": isLoggedIn,
"CurrentUserID": currentUserID,
"CurrentUsername": currentUsername,
"CurrentUserRole": currentUserRole,
"User": profile.User,
"UserAvatar": avatar,
"Posts": profile.Posts,
"PostCount": profile.PostCount,
"IsOwner": profile.IsOwner,
"UnreadCount": profile.UnreadCount,
"Message": "",
})
}
func (c *UserController) UploadAvatar(ctx *gin.Context) {
userID, _, _, _ := middleware.GetCurrentUser(ctx)
file, err := ctx.FormFile("avatar")
if err != nil {
ctx.Redirect(http.StatusFound, "/profile?error=上传失败")
return
}
if file.Size > 2*1024*1024 {
ctx.Redirect(http.StatusFound, "/profile?error=图片大小不能超过2MB")
return
}
ext := strings.ToLower(filepath.Ext(file.Filename))
filename := "avatar_" + strconv.FormatUint(uint64(userID), 10) + "_" + time.Now().Format("20060102150405") + ext
uploadDir := "uploads/avatars"
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
os.MkdirAll(uploadDir, 0755)
}
imagePath := filepath.Join(uploadDir, filename)
if err := ctx.SaveUploadedFile(file, imagePath); err != nil {
ctx.Redirect(http.StatusFound, "/profile?error=保存失败")
return
}
c.userSvc.UpdateAvatar(userID, imagePath)
ctx.Redirect(http.StatusFound, "/profile?success=头像更新成功")
}