新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
220 lines
6.4 KiB
Go
220 lines
6.4 KiB
Go
package handler
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/freefire/jiang13-bbs/middleware"
|
||
"github.com/freefire/jiang13-bbs/model"
|
||
"github.com/freefire/jiang13-bbs/service"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// UpdateProfileRequest 更新资料请求(头像不在此通道,见 /api/upload/avatar)
|
||
type UpdateProfileRequest struct {
|
||
Nickname string `json:"nickname"`
|
||
Email string `json:"email"`
|
||
Signature string `json:"signature"`
|
||
}
|
||
|
||
// UpdateProfile 更新当前登录用户的资料(昵称、邮箱、签名)
|
||
func (h *Handlers) UpdateProfile(c *gin.Context) {
|
||
claims := middleware.CurrentUser(c)
|
||
var req UpdateProfileRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数格式错误"})
|
||
return
|
||
}
|
||
|
||
user, err := h.Auth.UpdateProfile(claims.ID, req.Nickname, req.Email, req.Signature)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 返回更新后的完整资料(含 email/signature,供前端刷新展示)
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"user": gin.H{
|
||
"id": user.ID,
|
||
"username": user.Username,
|
||
"nickname": user.Nickname,
|
||
"avatar": user.Avatar,
|
||
"signature": user.Signature,
|
||
"email": user.Email,
|
||
"role": user.Role,
|
||
},
|
||
})
|
||
}
|
||
|
||
// UserProfile 获取用户资料:基本信息 + 统计 + 发帖列表(分页)
|
||
func (h *Handlers) UserProfile(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
|
||
return
|
||
}
|
||
|
||
user, err := h.Auth.GetUserByID(uint(id))
|
||
if err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||
return
|
||
}
|
||
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||
sort := c.DefaultQuery("sort", "new")
|
||
|
||
var viewerID uint
|
||
var actor *service.Actor
|
||
if claims := middleware.CurrentUser(c); claims != nil {
|
||
viewerID = claims.ID
|
||
if a, err := h.Auth.LoadActor(claims.ID); err == nil {
|
||
actor = a
|
||
}
|
||
}
|
||
|
||
posts, postsTotal, err := h.Post.ListByUser(user.ID, viewerID, actor, page, size, sort)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
// 填充点赞状态(仅登录用户)
|
||
if viewerID > 0 {
|
||
ids := make([]uint, 0, len(posts))
|
||
for _, p := range posts {
|
||
ids = append(ids, p.ID)
|
||
}
|
||
likedMap := h.Like.BatchHasLiked(ids, viewerID)
|
||
for i := range posts {
|
||
posts[i].Liked = likedMap[posts[i].ID]
|
||
}
|
||
}
|
||
|
||
postCount, _ := h.Post.CountByUser(user.ID)
|
||
commentCount, _ := h.Comment.CountByUser(user.ID)
|
||
favoriteCount := h.Favorite.CountByUser(user.ID)
|
||
followingCount := h.Follow.CountFollowing(user.ID)
|
||
isFollowing := viewerID > 0 && viewerID != user.ID && h.Follow.IsFollowing(viewerID, user.ID)
|
||
|
||
// 签到积分概览(失败不阻塞资料返回)
|
||
pointsTotal, streak := 0, 0
|
||
if cs, err := h.Checkin.Status(user.ID); err == nil && cs != nil {
|
||
pointsTotal = cs.TotalPoints
|
||
streak = cs.Streak
|
||
}
|
||
|
||
// 板块管理员公开其授权板块(角色徽章展示"板块管理员 · 板块名")
|
||
var boardIDs []uint
|
||
if user.Role == model.RoleBoardAdmin {
|
||
boardIDs, _ = h.Auth.GetUserBoardIDs(user.ID)
|
||
}
|
||
if boardIDs == nil {
|
||
boardIDs = []uint{}
|
||
}
|
||
|
||
userPayload := gin.H{
|
||
"id": user.ID,
|
||
"username": user.Username,
|
||
"nickname": user.Nickname,
|
||
"avatar": user.Avatar,
|
||
"signature": user.Signature,
|
||
"role": user.Role,
|
||
"board_ids": boardIDs,
|
||
"created_at": user.CreatedAt,
|
||
"level": user.Level,
|
||
"total_points": user.TotalPoints,
|
||
}
|
||
// 仅对具备用户管理权限的访客下发封禁态(避免信息公开)
|
||
if actor != nil && actor.HasPerm(service.PermUsers) {
|
||
userPayload["banned"] = user.Banned
|
||
}
|
||
// 访客与该用户的关注关系(关注按钮初始态)
|
||
userPayload["is_following"] = isFollowing
|
||
// 邮箱仅本人可见;仅本人访问时下发明文(用于账号信息展示与资料表单回填)
|
||
if viewerID == user.ID && user.Email != "" {
|
||
userPayload["email"] = user.Email
|
||
}
|
||
// 徽章墙(失败不阻塞资料返回)
|
||
if h.Badge != nil {
|
||
if badges, err := h.Badge.ForUser(user.ID); err == nil && badges != nil {
|
||
userPayload["badges"] = badges
|
||
}
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"user": userPayload,
|
||
"stats": gin.H{
|
||
"post_count": postCount,
|
||
"comment_count": commentCount,
|
||
"points": pointsTotal,
|
||
"streak": streak,
|
||
"favorite_count": favoriteCount,
|
||
"following_count": followingCount,
|
||
},
|
||
"posts": nonNilSlice(posts),
|
||
"posts_total": postsTotal,
|
||
"page": page,
|
||
"size": size,
|
||
})
|
||
}
|
||
|
||
// UserComments 获取用户发表的评论列表(分页,含帖子标题)
|
||
func (h *Handlers) UserComments(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
|
||
return
|
||
}
|
||
// 校验用户存在
|
||
if _, err := h.Auth.GetUserByID(uint(id)); err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||
return
|
||
}
|
||
|
||
if allow, err := h.Setting.AllowComments(); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "评论暂时不可用"})
|
||
return
|
||
} else if !allow {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"comments": []any{},
|
||
"total": 0,
|
||
"page": page,
|
||
"size": size,
|
||
})
|
||
return
|
||
}
|
||
if requireLogin, err := h.Setting.CommentsRequireLogin(); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "评论暂时不可用"})
|
||
return
|
||
} else if requireLogin && middleware.CurrentUser(c) == nil {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"comments": []any{},
|
||
"total": 0,
|
||
"page": page,
|
||
"size": size,
|
||
"require_login": true,
|
||
})
|
||
return
|
||
}
|
||
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||
|
||
comments, total, err := h.Comment.ListByUser(uint(id), page, size)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"comments": nonNilSlice(comments),
|
||
"total": total,
|
||
"page": page,
|
||
"size": size,
|
||
})
|
||
}
|