Skip to content

Commit d5e27cf

Browse files
committed
feat: 添加权限控制,以及评论和点赞功能,支持评论的创建和删除,点赞的新增和取消
1 parent af310fd commit d5e27cf

17 files changed

+656
-24
lines changed

handlers/blogs_handlers.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
// GetBlogsByTagsHandler 分页获取包含所有指定标签的文章
2+
func (h *BlogHandler) GetBlogsByTagsHandler(w http.ResponseWriter, r *http.Request) {
3+
tags := r.URL.Query()["tag"] // 支持多个 tag=xxx
4+
pageStr := r.URL.Query().Get("page")
5+
limitStr := r.URL.Query().Get("limit")
6+
page := int64(1)
7+
limit := int64(10)
8+
if pageStr != "" {
9+
if p, err := strconv.ParseInt(pageStr, 10, 64); err == nil && p > 0 {
10+
page = p
11+
}
12+
}
13+
if limitStr != "" {
14+
if l, err := strconv.ParseInt(limitStr, 10, 64); err == nil && l > 0 && l <= 100 {
15+
limit = l
16+
}
17+
}
18+
blogs, total, err := h.blogService.GetBlogsByTagsWithPagination(tags, page, limit)
19+
if err != nil {
20+
http.Error(w, "获取文章失败", http.StatusInternalServerError)
21+
return
22+
}
23+
resp := BlogListResponse{
24+
Data: blogs,
25+
Pagination: Pagination{Page: page, Limit: limit, Total: total},
26+
}
27+
w.Header().Set("Content-Type", "application/json")
28+
json.NewEncoder(w).Encode(resp)
29+
}
130
package handlers
231

332
import (
@@ -11,11 +40,90 @@ import (
1140
"github.com/gorilla/mux"
1241
)
1342

43+
// 标签相关请求体
44+
type TagRequest struct {
45+
Tag string `json:"tag"`
46+
}
47+
1448
// BlogHandler 处理博客文章的HTTP请求
1549
type BlogHandler struct {
1650
blogService *services.BlogService
1751
}
1852

53+
// SearchBlogsHandler 支持模糊搜索和标签筛选
54+
func (h *BlogHandler) SearchBlogsHandler(w http.ResponseWriter, r *http.Request) {
55+
keyword := r.URL.Query().Get("keyword")
56+
tagsParam := r.URL.Query()["tag"] // 支持多个 tag=xxx
57+
pageStr := r.URL.Query().Get("page")
58+
limitStr := r.URL.Query().Get("limit")
59+
page := int64(1)
60+
limit := int64(10)
61+
if pageStr != "" {
62+
if p, err := strconv.ParseInt(pageStr, 10, 64); err == nil && p > 0 {
63+
page = p
64+
}
65+
}
66+
if limitStr != "" {
67+
if l, err := strconv.ParseInt(limitStr, 10, 64); err == nil && l > 0 && l <= 100 {
68+
limit = l
69+
}
70+
}
71+
blogs, total, err := h.blogService.SearchBlogs(keyword, tagsParam, page, limit)
72+
if err != nil {
73+
http.Error(w, "搜索失败", http.StatusInternalServerError)
74+
return
75+
}
76+
resp := BlogListResponse{
77+
Data: blogs,
78+
Pagination: Pagination{Page: page, Limit: limit, Total: total},
79+
}
80+
w.Header().Set("Content-Type", "application/json")
81+
json.NewEncoder(w).Encode(resp)
82+
}
83+
84+
// GetAllTagsHandler 获取所有标签
85+
func (h *BlogHandler) GetAllTagsHandler(w http.ResponseWriter, r *http.Request) {
86+
tags, err := h.blogService.GetAllTags()
87+
if err != nil {
88+
http.Error(w, "获取标签失败", http.StatusInternalServerError)
89+
return
90+
}
91+
w.Header().Set("Content-Type", "application/json")
92+
json.NewEncoder(w).Encode(struct {
93+
Data []string `json:"data"`
94+
}{Data: tags})
95+
}
96+
97+
// AddTagHandler 新增标签(如有专门标签集合时用)
98+
func (h *BlogHandler) AddTagHandler(w http.ResponseWriter, r *http.Request) {
99+
var req TagRequest
100+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Tag == "" {
101+
http.Error(w, "无效的标签", http.StatusBadRequest)
102+
return
103+
}
104+
err := h.blogService.AddTag(req.Tag)
105+
if err != nil {
106+
http.Error(w, "添加标签失败", http.StatusInternalServerError)
107+
return
108+
}
109+
w.WriteHeader(http.StatusCreated)
110+
}
111+
112+
// DeleteTagHandler 删除标签(会从所有博客中移除该标签)
113+
func (h *BlogHandler) DeleteTagHandler(w http.ResponseWriter, r *http.Request) {
114+
var req TagRequest
115+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Tag == "" {
116+
http.Error(w, "无效的标签", http.StatusBadRequest)
117+
return
118+
}
119+
err := h.blogService.DeleteTag(req.Tag)
120+
if err != nil {
121+
http.Error(w, "删除标签失败", http.StatusInternalServerError)
122+
return
123+
}
124+
w.WriteHeader(http.StatusNoContent)
125+
}
126+
19127
// NewBlogHandler 创建新的BlogHandler实例
20128
func NewBlogHandler(blogService *services.BlogService) *BlogHandler {
21129
return &BlogHandler{

handlers/comment_handlers.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package handlers
2+
3+
import (
4+
"blog/middleware"
5+
"blog/services"
6+
"encoding/json"
7+
"net/http"
8+
9+
"go.mongodb.org/mongo-driver/bson/primitive"
10+
)
11+
12+
type CommentHandler struct {
13+
commentService *services.CommentService
14+
}
15+
16+
func NewCommentHandler(commentService *services.CommentService) *CommentHandler {
17+
return &CommentHandler{commentService: commentService}
18+
}
19+
20+
// CreateCommentHandler 新增评论
21+
func (h *CommentHandler) CreateCommentHandler(w http.ResponseWriter, r *http.Request) {
22+
var req struct {
23+
BlogID string `json:"blog_id"`
24+
Content string `json:"content"`
25+
}
26+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
27+
http.Error(w, "无效请求", http.StatusBadRequest)
28+
return
29+
}
30+
blogID, err := primitive.ObjectIDFromHex(req.BlogID)
31+
if err != nil {
32+
http.Error(w, "无效博客ID", http.StatusBadRequest)
33+
return
34+
}
35+
userIDHex := middleware.GetUserID(r)
36+
userID, err := primitive.ObjectIDFromHex(userIDHex)
37+
if err != nil {
38+
http.Error(w, "无效用户ID", http.StatusUnauthorized)
39+
return
40+
}
41+
username := middleware.GetUsername(r)
42+
comment, err := h.commentService.CreateComment(blogID, userID, username, req.Content)
43+
if err != nil {
44+
http.Error(w, "评论失败", http.StatusInternalServerError)
45+
return
46+
}
47+
w.Header().Set("Content-Type", "application/json")
48+
json.NewEncoder(w).Encode(comment)
49+
}
50+
51+
// DeleteCommentHandler 删除评论(只能本人或管理员)
52+
func (h *CommentHandler) DeleteCommentHandler(w http.ResponseWriter, r *http.Request) {
53+
commentIDHex := r.URL.Query().Get("id")
54+
commentID, err := primitive.ObjectIDFromHex(commentIDHex)
55+
if err != nil {
56+
http.Error(w, "无效评论ID", http.StatusBadRequest)
57+
return
58+
}
59+
err = h.commentService.DeleteComment(commentID)
60+
if err != nil {
61+
http.Error(w, "删除失败", http.StatusInternalServerError)
62+
return
63+
}
64+
w.WriteHeader(http.StatusNoContent)
65+
}
66+
67+
// GetCommentUserID 用于权限中间件,获取评论的 user_id
68+
func (h *CommentHandler) GetCommentUserID(r *http.Request) (primitive.ObjectID, error) {
69+
commentIDHex := r.URL.Query().Get("id")
70+
commentID, err := primitive.ObjectIDFromHex(commentIDHex)
71+
if err != nil {
72+
return primitive.NilObjectID, err
73+
}
74+
comment, err := h.commentService.GetCommentByID(commentID)
75+
if err != nil {
76+
return primitive.NilObjectID, err
77+
}
78+
return comment.UserID, nil
79+
}

handlers/like_handlers.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package handlers
2+
3+
import (
4+
"blog/middleware"
5+
"blog/services"
6+
"encoding/json"
7+
"net/http"
8+
9+
"go.mongodb.org/mongo-driver/bson/primitive"
10+
)
11+
12+
type LikeHandler struct {
13+
likeService *services.LikeService
14+
}
15+
16+
func NewLikeHandler(likeService *services.LikeService) *LikeHandler {
17+
return &LikeHandler{likeService: likeService}
18+
}
19+
20+
// AddLikeHandler 新增点赞
21+
func (h *LikeHandler) AddLikeHandler(w http.ResponseWriter, r *http.Request) {
22+
var req struct {
23+
BlogID string `json:"blog_id"`
24+
}
25+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
26+
http.Error(w, "无效请求", http.StatusBadRequest)
27+
return
28+
}
29+
blogID, err := primitive.ObjectIDFromHex(req.BlogID)
30+
if err != nil {
31+
http.Error(w, "无效博客ID", http.StatusBadRequest)
32+
return
33+
}
34+
userIDHex := middleware.GetUserID(r)
35+
userID, err := primitive.ObjectIDFromHex(userIDHex)
36+
if err != nil {
37+
http.Error(w, "无效用户ID", http.StatusUnauthorized)
38+
return
39+
}
40+
if err := h.likeService.AddLike(blogID, userID); err != nil {
41+
http.Error(w, "点赞失败", http.StatusInternalServerError)
42+
return
43+
}
44+
w.WriteHeader(http.StatusCreated)
45+
}
46+
47+
// RemoveLikeHandler 取消点赞(只能本人或管理员)
48+
func (h *LikeHandler) RemoveLikeHandler(w http.ResponseWriter, r *http.Request) {
49+
var req struct {
50+
BlogID string `json:"blog_id"`
51+
}
52+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
53+
http.Error(w, "无效请求", http.StatusBadRequest)
54+
return
55+
}
56+
blogID, err := primitive.ObjectIDFromHex(req.BlogID)
57+
if err != nil {
58+
http.Error(w, "无效博客ID", http.StatusBadRequest)
59+
return
60+
}
61+
userIDHex := middleware.GetUserID(r)
62+
userID, err := primitive.ObjectIDFromHex(userIDHex)
63+
if err != nil {
64+
http.Error(w, "无效用户ID", http.StatusUnauthorized)
65+
return
66+
}
67+
if err := h.likeService.RemoveLike(blogID, userID); err != nil {
68+
http.Error(w, "取消点赞失败", http.StatusInternalServerError)
69+
return
70+
}
71+
w.WriteHeader(http.StatusNoContent)
72+
}
73+
74+
// GetLikeUserID 用于权限中间件,获取点赞的 user_id
75+
func (h *LikeHandler) GetLikeUserID(r *http.Request) (primitive.ObjectID, error) {
76+
var req struct {
77+
BlogID string `json:"blog_id"`
78+
}
79+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
80+
return primitive.NilObjectID, err
81+
}
82+
blogID, err := primitive.ObjectIDFromHex(req.BlogID)
83+
if err != nil {
84+
return primitive.NilObjectID, err
85+
}
86+
userIDHex := middleware.GetUserID(r)
87+
userID, err := primitive.ObjectIDFromHex(userIDHex)
88+
if err != nil {
89+
return primitive.NilObjectID, err
90+
}
91+
return userID, nil
92+
}

main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func main() {
3434

3535
// 初始化服务
3636
blogService := services.NewBlogService(mongoCfg.Client, mongoCfg.Database, getEnv("COLLECTION_NAME", "blogs-dev"))
37+
commentService := services.NewCommentService(mongoCfg.Client, mongoCfg.Database, "comments")
38+
likeService := services.NewLikeService(mongoCfg.Client, mongoCfg.Database, "likes")
3739

3840
// 初始化认证服务
3941
jwtSecret := getEnv("JWT_SECRET", "default-jwt-secret") // 在生产环境中请通过环境变量注入
@@ -42,6 +44,8 @@ func main() {
4244
// 初始化处理器
4345
blogHandler := handlers.NewBlogHandler(blogService)
4446
authHandler := handlers.NewAuthHandler(authService)
47+
commentHandler := handlers.NewCommentHandler(commentService)
48+
likeHandler := handlers.NewLikeHandler(likeService)
4549

4650
// 初始化中间件
4751
jwtMiddleware := middleware.NewJWTMiddleware(authService)
@@ -50,7 +54,7 @@ func main() {
5054
r := mux.NewRouter()
5155

5256
// 注册路由(集中管理)
53-
routes.RegisterRoutes(r, blogHandler, authHandler, jwtMiddleware)
57+
routes.RegisterRoutes(r, blogHandler, authHandler, jwtMiddleware, commentHandler, likeHandler)
5458

5559
// 健康检查端点
5660
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {

middleware/jwt_middleware.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,25 @@ func (m *JWTMiddleware) Authenticate(next http.HandlerFunc) http.HandlerFunc {
5353
}
5454

5555
// 从 token 中提取用户信息
56-
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
57-
userID := claims["user_id"].(string)
58-
username := claims["username"].(string)
59-
60-
// 将用户信息添加到请求上下文
61-
ctx := context.WithValue(r.Context(), "user_id", userID)
62-
ctx = context.WithValue(ctx, "username", username)
63-
r = r.WithContext(ctx)
64-
} else {
65-
http.Error(w, "无效的认证令牌", http.StatusUnauthorized)
66-
return
67-
}
56+
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
57+
userID := claims["user_id"].(string)
58+
username := claims["username"].(string)
59+
role, _ := claims["role"].(string)
60+
ctx := context.WithValue(r.Context(), "user_id", userID)
61+
ctx = context.WithValue(ctx, "username", username)
62+
ctx = context.WithValue(ctx, "role", role)
63+
r = r.WithContext(ctx)
64+
} else {
65+
http.Error(w, "无效的认证令牌", http.StatusUnauthorized)
66+
return
67+
}
68+
// GetUserRole 从请求上下文中获取用户角色
69+
func GetUserRole(r *http.Request) string {
70+
if role, ok := r.Context().Value("role").(string); ok {
71+
return role
72+
}
73+
return ""
74+
}
6875

6976
next(w, r)
7077
}

middleware/role_middleware.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package middleware
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
// RequireAdmin 只允许管理员访问的中间件
8+
func RequireAdmin(next http.HandlerFunc) http.HandlerFunc {
9+
return func(w http.ResponseWriter, r *http.Request) {
10+
role := GetUserRole(r)
11+
if role != "admin" {
12+
http.Error(w, "无权限,管理员专用接口", http.StatusForbidden)
13+
return
14+
}
15+
next(w, r)
16+
}
17+
}

0 commit comments

Comments
 (0)