refactor: replace standard log with zap logger and optimize code patterns
- Migrate all log.Printf/log.Println calls to structured zap logging - Use cmp.Or for cleaner default value handling - Replace manual loops with slices.ContainsFunc/IndexFunc - Add batch query methods (IsLikedBatch, IsFavoritedBatch) to solve N+1 problem - Update JSON tags from omitempty to omitzero for numeric fields - Use errgroup-style wg.Go for worker goroutines - Remove duplicate casbin/v2 dependency (keeping v3) - Add plans/ to gitignore
This commit is contained in:
@@ -224,34 +224,34 @@ type TextSegmentData struct {
|
||||
// ImageSegmentData 图片数据
|
||||
type ImageSegmentData struct {
|
||||
URL string `json:"url"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Width int `json:"width,omitzero"`
|
||||
Height int `json:"height,omitzero"`
|
||||
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||
FileSize int64 `json:"file_size,omitempty"`
|
||||
FileSize int64 `json:"file_size,omitzero"`
|
||||
}
|
||||
|
||||
// VoiceSegmentData 语音数据
|
||||
type VoiceSegmentData struct {
|
||||
URL string `json:"url"`
|
||||
Duration int `json:"duration,omitempty"` // 秒
|
||||
FileSize int64 `json:"file_size,omitempty"`
|
||||
Duration int `json:"duration,omitzero"` // 秒
|
||||
FileSize int64 `json:"file_size,omitzero"`
|
||||
}
|
||||
|
||||
// VideoSegmentData 视频数据
|
||||
type VideoSegmentData struct {
|
||||
URL string `json:"url"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Duration int `json:"duration,omitempty"` // 秒
|
||||
Width int `json:"width,omitzero"`
|
||||
Height int `json:"height,omitzero"`
|
||||
Duration int `json:"duration,omitzero"` // 秒
|
||||
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||
FileSize int64 `json:"file_size,omitempty"`
|
||||
FileSize int64 `json:"file_size,omitzero"`
|
||||
}
|
||||
|
||||
// FileSegmentData 文件数据
|
||||
type FileSegmentData struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Size int64 `json:"size,omitzero"`
|
||||
MimeType string `json:"mime_type,omitempty"`
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ type ReplySegmentData struct {
|
||||
|
||||
// FaceSegmentData 表情数据
|
||||
type FaceSegmentData struct {
|
||||
ID int `json:"id"`
|
||||
ID int `json:"id,omitzero"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
@@ -458,7 +458,7 @@ type DeviceTokenResponse struct {
|
||||
DeviceType string `json:"device_type"`
|
||||
IsActive bool `json:"is_active"`
|
||||
DeviceName string `json:"device_name"`
|
||||
LastUsedAt time.Time `json:"last_used_at,omitempty"`
|
||||
LastUsedAt time.Time `json:"last_used_at,omitzero"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -470,9 +470,9 @@ type PushRecordResponse struct {
|
||||
MessageID string `json:"message_id"`
|
||||
PushChannel string `json:"push_channel"`
|
||||
PushStatus string `json:"push_status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
PushedAt time.Time `json:"pushed_at,omitempty"`
|
||||
DeliveredAt time.Time `json:"delivered_at,omitempty"`
|
||||
RetryCount int `json:"retry_count,omitzero"`
|
||||
PushedAt time.Time `json:"pushed_at,omitzero"`
|
||||
DeliveredAt time.Time `json:"delivered_at,omitzero"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -859,7 +859,7 @@ type VoteResultDTO struct {
|
||||
Options []VoteOptionDTO `json:"options"`
|
||||
TotalVotes int `json:"total_votes"`
|
||||
HasVoted bool `json:"has_voted"`
|
||||
VotedOptionID string `json:"voted_option_id,omitempty"`
|
||||
VotedOptionID string `json:"voted_option_id,omitzero"`
|
||||
}
|
||||
|
||||
// ==================== WebSocket Response DTOs ====================
|
||||
@@ -882,10 +882,10 @@ type AdminPostListResponse struct {
|
||||
Content string `json:"content"`
|
||||
Images []PostImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
FavoritesCount int `json:"favorites_count"`
|
||||
ViewsCount int `json:"views_count"`
|
||||
LikesCount int `json:"likes_count,omitzero"`
|
||||
CommentsCount int `json:"comments_count,omitzero"`
|
||||
FavoritesCount int `json:"favorites_count,omitzero"`
|
||||
ViewsCount int `json:"views_count,omitzero"`
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
@@ -906,11 +906,11 @@ type AdminPostDetailResponse struct {
|
||||
Content string `json:"content"`
|
||||
Images []PostImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
FavoritesCount int `json:"favorites_count"`
|
||||
SharesCount int `json:"shares_count"`
|
||||
ViewsCount int `json:"views_count"`
|
||||
LikesCount int `json:"likes_count,omitzero"`
|
||||
CommentsCount int `json:"comments_count,omitzero"`
|
||||
FavoritesCount int `json:"favorites_count,omitzero"`
|
||||
SharesCount int `json:"shares_count,omitzero"`
|
||||
ViewsCount int `json:"views_count,omitzero"`
|
||||
HotScore float64 `json:"hot_score"`
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
|
||||
@@ -5,9 +5,9 @@ type ScheduleCourseResponse struct {
|
||||
Name string `json:"name"`
|
||||
Teacher string `json:"teacher,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
DayOfWeek int `json:"day_of_week"`
|
||||
StartSection int `json:"start_section"`
|
||||
EndSection int `json:"end_section"`
|
||||
DayOfWeek int `json:"day_of_week,omitzero"`
|
||||
StartSection int `json:"start_section,omitzero"`
|
||||
EndSection int `json:"end_section,omitzero"`
|
||||
Weeks []int `json:"weeks"`
|
||||
Color string `json:"color,omitempty"`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package dto
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"carrot_bbs/internal/model"
|
||||
)
|
||||
@@ -232,25 +233,27 @@ func ExtractMentionedUsers(segments model.MessageSegments) []string {
|
||||
|
||||
// IsAtAll 检查消息是否@了所有人
|
||||
func IsAtAll(segments model.MessageSegments) bool {
|
||||
for _, segment := range segments {
|
||||
return slices.ContainsFunc(segments, func(segment model.MessageSegment) bool {
|
||||
if segment.Type == string(SegmentTypeAt) {
|
||||
if userID, ok := segment.Data["user_id"].(string); ok && userID == "all" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// GetReplyMessageID 从消息链中获取被回复的消息ID
|
||||
// 如果没有回复segment,返回空字符串
|
||||
func GetReplyMessageID(segments model.MessageSegments) string {
|
||||
for _, segment := range segments {
|
||||
if segment.Type == string(SegmentTypeReply) {
|
||||
if id, ok := segment.Data["id"].(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool {
|
||||
return segment.Type == string(SegmentTypeReply)
|
||||
})
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
if id, ok := segments[idx].Data["id"].(string); ok {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -291,12 +294,9 @@ func BuildSegmentsFromContent(contentType, content string, mediaURL *string) mod
|
||||
|
||||
// HasSegmentType 检查消息链中是否包含指定类型的segment
|
||||
func HasSegmentType(segments model.MessageSegments, segmentType SegmentType) bool {
|
||||
for _, segment := range segments {
|
||||
if segment.Type == string(segmentType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.ContainsFunc(segments, func(segment model.MessageSegment) bool {
|
||||
return segment.Type == string(segmentType)
|
||||
})
|
||||
}
|
||||
|
||||
// GetSegmentsByType 获取消息链中所有指定类型的segment
|
||||
@@ -313,12 +313,14 @@ func GetSegmentsByType(segments model.MessageSegments, segmentType SegmentType)
|
||||
// GetFirstImageURL 获取消息链中第一张图片的URL
|
||||
// 如果没有图片,返回空字符串
|
||||
func GetFirstImageURL(segments model.MessageSegments) string {
|
||||
for _, segment := range segments {
|
||||
if segment.Type == string(SegmentTypeImage) {
|
||||
if url, ok := segment.Data["url"].(string); ok {
|
||||
return url
|
||||
}
|
||||
}
|
||||
idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool {
|
||||
return segment.Type == string(SegmentTypeImage)
|
||||
})
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
if url, ok := segments[idx].Data["url"].(string); ok {
|
||||
return url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -326,13 +328,18 @@ func GetFirstImageURL(segments model.MessageSegments) string {
|
||||
// GetFirstMediaURL 获取消息链中第一个媒体文件的URL(图片/视频/语音/文件)
|
||||
// 用于兼容旧版本API
|
||||
func GetFirstMediaURL(segments model.MessageSegments) string {
|
||||
for _, segment := range segments {
|
||||
idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool {
|
||||
switch segment.Type {
|
||||
case string(SegmentTypeImage), string(SegmentTypeVideo), string(SegmentTypeVoice), string(SegmentTypeFile):
|
||||
if url, ok := segment.Data["url"].(string); ok {
|
||||
return url
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
if url, ok := segments[idx].Data["url"].(string); ok {
|
||||
return url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user