optimize the list of comments

This commit is contained in:
landaiqing
2024-09-24 21:37:37 +08:00
parent 5d86914cb7
commit bb2f49fe74
12 changed files with 411 additions and 258 deletions

View File

@@ -1,6 +1,6 @@
package dto
type RotateCaptchaRequest struct {
Angle int `json:"angle"`
Key string `json:"key"`
Angle int `json:"angle" binding:"required"`
Key string `json:"key" binding:"required"`
}

View File

@@ -1,14 +1,22 @@
package comment_api
import (
"encoding/base64"
"errors"
"io"
"regexp"
"schisandra-cloud-album/service"
"strings"
"sync"
"time"
)
type CommentAPI struct{}
var wg sync.WaitGroup
var commentReplyService = service.Service.CommentReplyService
// CommentImages 评论图片
type CommentImages struct {
TopicId string `json:"topic_id" bson:"topic_id" required:"true"`
CommentId int64 `json:"comment_id" bson:"comment_id" required:"true"`
@@ -17,6 +25,7 @@ type CommentImages struct {
CreatedAt string `json:"created_at" bson:"created_at" required:"true"`
}
// CommentContent 评论内容
type CommentContent struct {
NickName string `json:"nickname"`
Avatar string `json:"avatar"`
@@ -40,9 +49,88 @@ type CommentContent struct {
Images []string `json:"images,omitempty"`
}
// CommentResponse 评论返回值
type CommentResponse struct {
Size int `json:"size"`
Total int64 `json:"total"`
Current int `json:"current"`
Comments []CommentContent `json:"comments"`
}
// base64ToBytes 将base64字符串转换为字节数组
func base64ToBytes(base64Str string) ([]byte, error) {
reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64Str))
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to decode base64 string")
}
return data, nil
}
// processImages 处理图片,将 base64 字符串转换为字节数组
func processImages(images []string) ([][]byte, error) {
var imagesData [][]byte
dataChan := make(chan []byte, len(images)) // 创建一个带缓冲的 channel
re := regexp.MustCompile(`^data:image/\w+;base64,`)
for _, img := range images {
wg.Add(1) // 增加 WaitGroup 的计数
go func(img string) {
defer wg.Done() // 函数结束时减少计数
imgWithoutPrefix := re.ReplaceAllString(img, "")
data, err := base64ToBytes(imgWithoutPrefix)
if err != nil {
return // 出错时直接返回
}
dataChan <- data // 将结果发送到 channel
}(img)
}
wg.Wait() // 等待所有 goroutine 完成
close(dataChan) // 关闭 channel
for data := range dataChan { // 收集所有结果
imagesData = append(imagesData, data)
}
return imagesData, nil
}
// getMimeType 获取 MIME 类型
func getMimeType(data []byte) string {
if len(data) < 4 {
return "application/octet-stream" // 默认类型
}
// 判断 JPEG
if data[0] == 0xFF && data[1] == 0xD8 {
return "image/jpeg"
}
// 判断 PNG
if len(data) >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 &&
data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A {
return "image/png"
}
// 判断 GIF
if len(data) >= 6 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F' {
return "image/gif"
}
// 判断 WEBP
if len(data) >= 12 && data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 &&
data[8] == 0x57 && data[9] == 0x45 && data[10] == 0x42 && data[11] == 0x50 {
return "image/webp"
}
// 判断svg
if len(data) >= 4 && data[0] == '<' && data[1] == '?' && data[2] == 'x' && data[3] == 'm' {
return "image/svg+xml"
}
// 判断JPG
if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
return "image/jpeg"
}
return "application/octet-stream" // 默认类型
}

View File

@@ -11,16 +11,12 @@ import (
"github.com/mssola/useragent"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"io"
"regexp"
"schisandra-cloud-album/api/comment_api/dto"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"schisandra-cloud-album/utils"
"strconv"
"strings"
"time"
)
@@ -38,11 +34,6 @@ func (CommentAPI) CommentSubmit(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if commentRequest.Content == "" || commentRequest.UserID == "" || commentRequest.TopicId == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if len(commentRequest.Images) > 3 {
result.FailWithMessage(ginI18n.MustGetMessage(c, "TooManyImages"), c)
return
@@ -69,12 +60,14 @@ func (CommentAPI) CommentSubmit(c *gin.Context) {
if commentRequest.UserID == commentRequest.Author {
isAuthor = 1
}
tx := global.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
commentReply := model.ScaCommentReply{
Content: commentRequest.Content,
UserId: commentRequest.UserID,
@@ -88,26 +81,38 @@ func (CommentAPI) CommentSubmit(c *gin.Context) {
OperatingSystem: operatingSystem,
Agent: userAgent,
}
// 使用 goroutine 进行异步评论保存
errCh := make(chan error, 2)
go func() {
errCh <- commentReplyService.CreateCommentReply(&commentReply)
}()
if err = commentReplyService.CreateCommentReply(&commentReply); err != nil {
// 等待评论回复的创建
if err = <-errCh; err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
// 处理图片异步上传
if len(commentRequest.Images) > 0 {
var imagesData [][]byte
for _, img := range commentRequest.Images {
re := regexp.MustCompile(`^data:image/\w+;base64,`)
imgWithoutPrefix := re.ReplaceAllString(img, "")
data, err := base64ToBytes(imgWithoutPrefix)
imagesDataCh := make(chan [][]byte)
go func() {
imagesData, err := processImages(commentRequest.Images)
if err != nil {
global.LOG.Errorln(err)
tx.Rollback()
imagesDataCh <- nil // 发送失败信号
return
}
imagesData = append(imagesData, data)
imagesDataCh <- imagesData // 发送处理成功的数据
}()
imagesData := <-imagesDataCh
if imagesData == nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
commentImages := CommentImages{
@@ -117,28 +122,19 @@ func (CommentAPI) CommentSubmit(c *gin.Context) {
Images: imagesData,
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
// 使用 goroutine 进行异步图片保存
go func() {
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
}
}()
}
tx.Commit()
result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c)
return
}
// base64ToBytes 将base64字符串转换为字节数组
func base64ToBytes(base64Str string) ([]byte, error) {
reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64Str))
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to decode base64 string")
}
return data, nil
}
// ReplySubmit 提交回复
// @Summary 提交回复
// @Description 提交回复
@@ -154,14 +150,6 @@ func (CommentAPI) ReplySubmit(c *gin.Context) {
return
}
if replyCommentRequest.Content == "" ||
replyCommentRequest.UserID == "" ||
replyCommentRequest.TopicId == "" ||
strconv.FormatInt(replyCommentRequest.ReplyId, 10) == "" ||
replyCommentRequest.ReplyUser == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if len(replyCommentRequest.Images) > 3 {
result.FailWithMessage(ginI18n.MustGetMessage(c, "TooManyImages"), c)
return
@@ -172,8 +160,8 @@ func (CommentAPI) ReplySubmit(c *gin.Context) {
global.LOG.Errorln("user-agent is empty")
return
}
ua := useragent.New(userAgent)
ua := useragent.New(userAgent)
ip := utils.GetClientIP(c)
location, err := global.IP2Location.SearchByStr(ip)
if err != nil {
@@ -188,12 +176,14 @@ func (CommentAPI) ReplySubmit(c *gin.Context) {
if replyCommentRequest.UserID == replyCommentRequest.Author {
isAuthor = 1
}
tx := global.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
commentReply := model.ScaCommentReply{
Content: replyCommentRequest.Content,
UserId: replyCommentRequest.UserID,
@@ -209,33 +199,44 @@ func (CommentAPI) ReplySubmit(c *gin.Context) {
OperatingSystem: operatingSystem,
Agent: userAgent,
}
// 使用 goroutine 进行异步评论保存
errCh := make(chan error)
go func() {
if err = commentReplyService.CreateCommentReply(&commentReply); err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
err = commentReplyService.UpdateCommentReplyCount(replyCommentRequest.ReplyId)
if err != nil {
errCh <- commentReplyService.CreateCommentReply(&commentReply)
}()
go func() {
errCh <- commentReplyService.UpdateCommentReplyCount(replyCommentRequest.ReplyId)
}()
// 等待评论回复的创建
if err = <-errCh; err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
// 处理图片异步上传
if len(replyCommentRequest.Images) > 0 {
var imagesData [][]byte
for _, img := range replyCommentRequest.Images {
re := regexp.MustCompile(`^data:image/\w+;base64,`)
imgWithoutPrefix := re.ReplaceAllString(img, "")
data, err := base64ToBytes(imgWithoutPrefix)
imagesDataCh := make(chan [][]byte)
go func() {
imagesData, err := processImages(replyCommentRequest.Images)
if err != nil {
global.LOG.Errorln(err)
tx.Rollback()
imagesDataCh <- nil // 发送失败信号
return
}
imagesData = append(imagesData, data)
imagesDataCh <- imagesData // 发送处理成功的数据
}()
imagesData := <-imagesDataCh
if imagesData == nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
commentImages := CommentImages{
TopicId: replyCommentRequest.TopicId,
CommentId: commentReply.Id,
@@ -243,12 +244,13 @@ func (CommentAPI) ReplySubmit(c *gin.Context) {
Images: imagesData,
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
// 使用 goroutine 进行异步图片保存
go func() {
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
}
}()
}
tx.Commit()
result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c)
@@ -270,15 +272,6 @@ func (CommentAPI) ReplyReplySubmit(c *gin.Context) {
return
}
if replyReplyRequest.Content == "" ||
replyReplyRequest.UserID == "" ||
replyReplyRequest.TopicId == "" ||
replyReplyRequest.ReplyTo == 0 ||
replyReplyRequest.ReplyId == 0 ||
replyReplyRequest.ReplyUser == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if len(replyReplyRequest.Images) > 3 {
result.FailWithMessage(ginI18n.MustGetMessage(c, "TooManyImages"), c)
return
@@ -289,8 +282,8 @@ func (CommentAPI) ReplyReplySubmit(c *gin.Context) {
global.LOG.Errorln("user-agent is empty")
return
}
ua := useragent.New(userAgent)
ua := useragent.New(userAgent)
ip := utils.GetClientIP(c)
location, err := global.IP2Location.SearchByStr(ip)
if err != nil {
@@ -305,12 +298,14 @@ func (CommentAPI) ReplyReplySubmit(c *gin.Context) {
if replyReplyRequest.UserID == replyReplyRequest.Author {
isAuthor = 1
}
tx := global.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
commentReply := model.ScaCommentReply{
Content: replyReplyRequest.Content,
UserId: replyReplyRequest.UserID,
@@ -328,32 +323,40 @@ func (CommentAPI) ReplyReplySubmit(c *gin.Context) {
Agent: userAgent,
}
if err = commentReplyService.CreateCommentReply(&commentReply); err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
err = commentReplyService.UpdateCommentReplyCount(replyReplyRequest.ReplyId)
if err != nil {
errCh := make(chan error, 2)
go func() {
errCh <- commentReplyService.CreateCommentReply(&commentReply)
}()
go func() {
errCh <- commentReplyService.UpdateCommentReplyCount(replyReplyRequest.ReplyId)
}()
if err = <-errCh; err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
if len(replyReplyRequest.Images) > 0 {
var imagesData [][]byte
for _, img := range replyReplyRequest.Images {
re := regexp.MustCompile(`^data:image/\w+;base64,`)
imgWithoutPrefix := re.ReplaceAllString(img, "")
data, err := base64ToBytes(imgWithoutPrefix)
imagesDataCh := make(chan [][]byte)
go func() {
imagesData, err := processImages(replyReplyRequest.Images)
if err != nil {
global.LOG.Errorln(err)
tx.Rollback()
imagesDataCh <- nil
return
}
imagesData = append(imagesData, data)
imagesDataCh <- imagesData
}()
imagesData := <-imagesDataCh
if imagesData == nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
commentImages := CommentImages{
TopicId: replyReplyRequest.TopicId,
CommentId: commentReply.Id,
@@ -361,12 +364,13 @@ func (CommentAPI) ReplyReplySubmit(c *gin.Context) {
Images: imagesData,
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c)
tx.Rollback()
return
}
// 处理图片保存
go func() {
if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil {
global.LOG.Errorln(err)
}
}()
}
tx.Commit()
result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c)
@@ -388,38 +392,57 @@ func (CommentAPI) CommentList(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if commentListRequest.TopicId == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
query, u := gplus.NewQuery[model.ScaCommentReply]()
page := gplus.NewPage[model.ScaCommentReply](commentListRequest.Page, commentListRequest.Size)
query.Eq(&u.TopicId, commentListRequest.TopicId).Eq(&u.CommentType, enum.COMMENT).OrderByDesc(&u.Likes)
page, _ = gplus.SelectPage(page, query)
var commentsWithImages []CommentContent
userIds := make([]string, 0, len(page.Records))
for _, comment := range page.Records {
// 获取评论图片
commentImages := CommentImages{}
wrong := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").FindOne(context.Background(), bson.M{"comment_id": comment.Id}).Decode(&commentImages)
if wrong != nil && !errors.Is(wrong, mongo.ErrNoDocuments) {
global.LOG.Errorln(wrong)
}
// 将图片转换为base64
var imagesBase64 []string
for _, img := range commentImages.Images {
mimeType := getMimeType(img) // 动态获取 MIME 类型
base64Img := base64.StdEncoding.EncodeToString(img)
base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img)
imagesBase64 = append(imagesBase64, base64WithPrefix)
}
// 组装评论数据
queryUser, n := gplus.NewQuery[model.ScaAuthUser]()
queryUser.Eq(&n.UID, comment.UserId)
userInfo, _ := gplus.SelectOne(queryUser)
commentsWithImages = append(commentsWithImages,
CommentContent{
userIds = append(userIds, comment.UserId)
}
queryUser, n := gplus.NewQuery[model.ScaAuthUser]()
queryUser.In(&n.UID, userIds)
userInfos, _ := gplus.SelectList(queryUser)
userInfoMap := make(map[string]model.ScaAuthUser, len(userInfos))
for _, userInfo := range userInfos {
userInfoMap[*userInfo.UID] = *userInfo
}
commentChannel := make(chan CommentContent, len(page.Records))
imagesBase64S := make([][]string, len(page.Records)) // 存储每条评论的图片
for index, comment := range page.Records {
wg.Add(1)
go func(comment model.ScaCommentReply, index int) {
defer wg.Done()
// 使用 context 设置超时时间
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 设置超时2秒
defer cancel()
// 获取评论图片并处理
commentImages := CommentImages{}
wrong := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").FindOne(ctx, bson.M{"comment_id": comment.Id}).Decode(&commentImages)
if wrong != nil && !errors.Is(wrong, mongo.ErrNoDocuments) {
global.LOG.Errorf("Failed to get images for comment ID %s: %v", comment.Id, wrong)
return
}
// 将图片转换为base64
var imagesBase64 []string
for _, img := range commentImages.Images {
mimeType := getMimeType(img)
base64Img := base64.StdEncoding.EncodeToString(img)
base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img)
imagesBase64 = append(imagesBase64, base64WithPrefix)
}
imagesBase64S[index] = imagesBase64 // 保存到切片中
userInfo := userInfoMap[comment.UserId]
commentContent := CommentContent{
Avatar: *userInfo.Avatar,
NickName: *userInfo.Nickname,
Id: comment.Id,
@@ -435,8 +458,21 @@ func (CommentAPI) CommentList(c *gin.Context) {
Browser: comment.Browser,
OperatingSystem: comment.OperatingSystem,
Images: imagesBase64,
})
}
commentChannel <- commentContent
}(*comment, index)
}
go func() {
wg.Wait()
close(commentChannel)
}()
var commentsWithImages []CommentContent
for commentContent := range commentChannel {
commentsWithImages = append(commentsWithImages, commentContent)
}
response := CommentResponse{
Comments: commentsWithImages,
Size: page.Size,
@@ -447,30 +483,6 @@ func (CommentAPI) CommentList(c *gin.Context) {
return
}
func getMimeType(data []byte) string {
if len(data) < 4 {
return "application/octet-stream" // 默认类型
}
// 判断 JPEG
if data[0] == 0xFF && data[1] == 0xD8 {
return "image/jpeg"
}
// 判断 PNG
if len(data) >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 &&
data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A {
return "image/png"
}
// 判断 GIF
if len(data) >= 6 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F' {
return "image/gif"
}
return "application/octet-stream" // 默认类型
}
// ReplyList 获取回复列表
// @Summary 获取回复列表
// @Description 获取回复列表
@@ -486,65 +498,113 @@ func (CommentAPI) ReplyList(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if replyListRequest.TopicId == "" || strconv.FormatInt(replyListRequest.CommentId, 10) == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
query, u := gplus.NewQuery[model.ScaCommentReply]()
page := gplus.NewPage[model.ScaCommentReply](replyListRequest.Page, replyListRequest.Size)
query.Eq(&u.TopicId, replyListRequest.TopicId).Eq(&u.ReplyId, replyListRequest.CommentId).Eq(&u.CommentType, enum.REPLY).OrderByDesc(&u.Likes)
page, _ = gplus.SelectPage(page, query)
var commentsWithImages []CommentContent
userIds := make([]string, 0, len(page.Records))
replyUserIds := make([]string, 0, len(page.Records))
// 收集用户 ID 和回复用户 ID
for _, comment := range page.Records {
// 获取评论图片
commentImages := CommentImages{}
wrong := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").FindOne(context.Background(), bson.M{"comment_id": comment.Id}).Decode(&commentImages)
if wrong != nil && !errors.Is(wrong, mongo.ErrNoDocuments) {
global.LOG.Errorln(wrong)
userIds = append(userIds, comment.UserId)
if comment.ReplyUser != "" {
replyUserIds = append(replyUserIds, comment.ReplyUser)
}
// 将图片转换为base64
var imagesBase64 []string
for _, img := range commentImages.Images {
mimeType := getMimeType(img) // 动态获取 MIME 类型
base64Img := base64.StdEncoding.EncodeToString(img)
base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img)
imagesBase64 = append(imagesBase64, base64WithPrefix)
}
// 查询评论用户信息
queryUser, n := gplus.NewQuery[model.ScaAuthUser]()
queryUser.Eq(&n.UID, comment.UserId)
userInfo, _ := gplus.SelectOne(queryUser)
// 查询回复用户信息
}
// 查询评论用户信息
queryUser, n := gplus.NewQuery[model.ScaAuthUser]()
queryUser.In(&n.UID, userIds)
userInfos, _ := gplus.SelectList(queryUser)
userInfoMap := make(map[string]model.ScaAuthUser, len(userInfos))
for _, userInfo := range userInfos {
userInfoMap[*userInfo.UID] = *userInfo
}
// 查询回复用户信息
replyUserInfoMap := make(map[string]model.ScaAuthUser)
if len(replyUserIds) > 0 {
queryReplyUser, m := gplus.NewQuery[model.ScaAuthUser]()
queryReplyUser.Eq(&m.UID, comment.ReplyUser)
replyUserInfo, _ := gplus.SelectOne(queryReplyUser)
commentsWithImages = append(commentsWithImages,
CommentContent{
queryReplyUser.In(&m.UID, replyUserIds)
replyUserInfos, _ := gplus.SelectList(queryReplyUser)
for _, replyUserInfo := range replyUserInfos {
replyUserInfoMap[*replyUserInfo.UID] = *replyUserInfo
}
}
replyChannel := make(chan CommentContent, len(page.Records)) // 使用通道传递回复内容
imagesBase64S := make([][]string, len(page.Records)) // 存储每条回复的图片
for index, reply := range page.Records {
wg.Add(1)
go func(reply model.ScaCommentReply, index int) {
defer wg.Done()
// 使用 context 设置超时时间
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 设置超时2秒
defer cancel()
// 获取回复图片并处理
replyImages := CommentImages{}
wrong := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").FindOne(ctx, bson.M{"comment_id": reply.Id}).Decode(&replyImages)
if wrong != nil && !errors.Is(wrong, mongo.ErrNoDocuments) {
global.LOG.Errorf("Failed to get images for reply ID %s: %v", reply.Id, wrong)
return
}
// 将图片转换为base64
var imagesBase64 []string
for _, img := range replyImages.Images {
mimeType := getMimeType(img)
base64Img := base64.StdEncoding.EncodeToString(img)
base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img)
imagesBase64 = append(imagesBase64, base64WithPrefix)
}
imagesBase64S[index] = imagesBase64 // 保存到切片中
userInfo := userInfoMap[reply.UserId]
replyUserInfo := replyUserInfoMap[reply.ReplyUser]
commentContent := CommentContent{
Avatar: *userInfo.Avatar,
NickName: *userInfo.Nickname,
Id: comment.Id,
UserId: comment.UserId,
TopicId: comment.TopicId,
Dislikes: comment.Dislikes,
Content: comment.Content,
Id: reply.Id,
UserId: reply.UserId,
TopicId: reply.TopicId,
Dislikes: reply.Dislikes,
Content: reply.Content,
ReplyUsername: *replyUserInfo.Nickname,
ReplyCount: comment.ReplyCount,
Likes: comment.Likes,
CreatedTime: comment.CreatedTime,
ReplyUser: comment.ReplyUser,
ReplyId: comment.ReplyId,
ReplyTo: comment.ReplyTo,
Author: comment.Author,
Location: comment.Location,
Browser: comment.Browser,
OperatingSystem: comment.OperatingSystem,
ReplyCount: reply.ReplyCount,
Likes: reply.Likes,
CreatedTime: reply.CreatedTime,
ReplyUser: reply.ReplyUser,
ReplyId: reply.ReplyId,
ReplyTo: reply.ReplyTo,
Author: reply.Author,
Location: reply.Location,
Browser: reply.Browser,
OperatingSystem: reply.OperatingSystem,
Images: imagesBase64,
})
}
replyChannel <- commentContent // 发送到通道
}(*reply, index)
}
go func() {
wg.Wait()
close(replyChannel) // 关闭通道
}()
var repliesWithImages []CommentContent
for replyContent := range replyChannel {
repliesWithImages = append(repliesWithImages, replyContent) // 从通道获取回复内容
}
response := CommentResponse{
Comments: commentsWithImages,
Comments: repliesWithImages,
Size: page.Size,
Current: page.Current,
Total: page.Total,
@@ -552,3 +612,17 @@ func (CommentAPI) ReplyList(c *gin.Context) {
result.OkWithData(response, c)
return
}
// CommentLikes 点赞评论
func (CommentAPI) CommentLikes(c *gin.Context) {
likeRequest := dto.CommentLikeRequest{}
err := c.ShouldBindJSON(&likeRequest)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
}
func (CommentAPI) CommentDislikes(c *gin.Context) {
}

View File

@@ -1,41 +1,47 @@
package dto
type CommentRequest struct {
Content string `json:"content"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
UserID string `json:"user_id"`
TopicId string `json:"topic_id"`
Author string `json:"author"`
UserID string `json:"user_id" binding:"required"`
TopicId string `json:"topic_id" binding:"required"`
Author string `json:"author" binding:"required"`
}
type ReplyCommentRequest struct {
Content string `json:"content"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
UserID string `json:"user_id"`
TopicId string `json:"topic_id"`
ReplyId int64 `json:"reply_id"`
ReplyUser string `json:"reply_user"`
Author string `json:"author"`
UserID string `json:"user_id" binding:"required"`
TopicId string `json:"topic_id" binding:"required"`
ReplyId int64 `json:"reply_id" binding:"required"`
ReplyUser string `json:"reply_user" binding:"required"`
Author string `json:"author" binding:"required"`
}
type ReplyReplyRequest struct {
Content string `json:"content"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
UserID string `json:"user_id"`
TopicId string `json:"topic_id"`
ReplyTo int64 `json:"reply_to"`
ReplyId int64 `json:"reply_id"`
ReplyUser string `json:"reply_user"`
Author string `json:"author"`
UserID string `json:"user_id" binding:"required"`
TopicId string `json:"topic_id" binding:"required"`
ReplyTo int64 `json:"reply_to" binding:"required"`
ReplyId int64 `json:"reply_id" binding:"required"`
ReplyUser string `json:"reply_user" binding:"required"`
Author string `json:"author" binding:"required""`
}
type CommentListRequest struct {
TopicId string `json:"topic_id"`
TopicId string `json:"topic_id" binding:"required"`
Page int `json:"page" default:"1"`
Size int `json:"size" default:"5"`
}
type ReplyListRequest struct {
TopicId string `json:"topic_id"`
CommentId int64 `json:"comment_id"`
TopicId string `json:"topic_id" binding:"required"`
CommentId int64 `json:"comment_id" binding:"required"`
Page int `json:"page" default:"1"`
Size int `json:"size" default:"5"`
}
type CommentLikeRequest struct {
TopicId string `json:"topic_id" binding:"required"`
CommentId int64 `json:"comment_id" binding:"required"`
UserID string `json:"user_id" binding:"required"`
Type int `json:"type" binding:"required"`
}

View File

@@ -301,11 +301,11 @@ func handelUserLogin(userId string, clientId string) bool {
resultChan <- false
return
}
responseData := map[string]interface{}{
"code": 0,
"message": "success",
"data": data,
"success": true,
responseData := result.Response{
Data: data,
Message: "success",
Code: 200,
Success: true,
}
tokenData, err := json.Marshal(responseData)
if err != nil {

View File

@@ -1,11 +1,11 @@
package dto
type RoleRequestDto struct {
RoleName string `json:"role_name"`
RoleKey string `json:"role_key"`
RoleName string `json:"role_name" binding:"required"`
RoleKey string `json:"role_key" binding:"required"`
}
type AddRoleToUserRequestDto struct {
Uid string `json:"uid"`
RoleKey string `json:"role_key"`
Uid string `json:"uid" binding:"required"`
RoleKey string `json:"role_key" binding:"required"`
}

View File

@@ -4,36 +4,36 @@ import "encoding/json"
// RefreshTokenRequest 刷新token请求
type RefreshTokenRequest struct {
RefreshToken string `json:"refresh_token"`
RefreshToken string `json:"refresh_token" binding:"required"`
}
// PhoneLoginRequest 手机号登录请求
type PhoneLoginRequest struct {
Phone string `json:"phone"`
Captcha string `json:"captcha"`
AutoLogin bool `json:"auto_login"`
Phone string `json:"phone" binding:"required"`
Captcha string `json:"captcha" binding:"required"`
AutoLogin bool `json:"auto_login" binding:"required"`
}
// AccountLoginRequest 账号登录请求
type AccountLoginRequest struct {
Account string `json:"account"`
Password string `json:"password"`
AutoLogin bool `json:"auto_login"`
Account string `json:"account" binding:"required"`
Password string `json:"password" binding:"required"`
AutoLogin bool `json:"auto_login" binding:"required"`
}
// AddUserRequest 新增用户请求
type AddUserRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Phone string `json:"phone"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Phone string `json:"phone" binding:"required"`
}
// ResetPasswordRequest 重置密码请求
type ResetPasswordRequest struct {
Phone string `json:"phone"`
Captcha string `json:"captcha"`
Password string `json:"password"`
Repassword string `json:"repassword"`
Phone string `json:"phone" binding:"required"`
Captcha string `json:"captcha" binding:"required"`
Password string `json:"password" binding:"required"`
Repassword string `json:"repassword" binding:"required"`
}
// ResponseData 返回数据

View File

@@ -118,10 +118,6 @@ func (UserAPI) AccountLogin(c *gin.Context) {
}
account := accountLoginRequest.Account
password := accountLoginRequest.Password
if account == "" || password == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "AccountAndPasswordNotEmpty"), c)
return
}
var user model.ScaAuthUser
if utils.IsPhone(account) {
@@ -163,10 +159,6 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
phone := request.Phone
captcha := request.Captcha
autoLogin := request.AutoLogin
if phone == "" || captcha == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneAndCaptchaNotEmpty"), c)
return
}
if !utils.IsPhone(phone) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c)
return
@@ -273,10 +265,6 @@ func (UserAPI) RefreshHandler(c *gin.Context) {
return
}
refreshToken := request.RefreshToken
if refreshToken == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
parseRefreshToken, isUpd, err := utils.ParseRefreshToken(refreshToken)
if err != nil || !isUpd {
global.LOG.Errorln(err)
@@ -369,12 +357,6 @@ func (UserAPI) ResetPassword(c *gin.Context) {
captcha := resetPasswordRequest.Captcha
password := resetPasswordRequest.Password
repassword := resetPasswordRequest.Repassword
if phone == "" || captcha == "" || password == "" || repassword == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
if !utils.IsPhone(phone) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneError"), c)
return

View File

@@ -9,8 +9,9 @@ type MongoDB struct {
DB string `yaml:"db"`
User string `yaml:"user"`
Password string `yaml:"password"`
MaxOpenConn int `yaml:"max-open-conn"`
MaxIdleConn int `yaml:"max-idle-conn"`
MaxOpenConn uint64 `yaml:"max-open-conn"`
MaxIdleConn uint64 `yaml:"max-idle-conn"`
Timeout int `yaml:"timeout"`
}
func (m *MongoDB) MongoDsn() string {

View File

@@ -2,7 +2,6 @@ package core
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"schisandra-cloud-album/global"
@@ -24,6 +23,9 @@ func InitMongoDB() {
Password: global.CONFIG.MongoDB.Password,
PasswordSet: true,
})
clientOptions.SetConnectTimeout(time.Duration(global.CONFIG.MongoDB.Timeout) * time.Second)
clientOptions.SetMaxPoolSize(global.CONFIG.MongoDB.MaxOpenConn)
clientOptions.SetMaxConnecting(global.CONFIG.MongoDB.MaxIdleConn)
connect, err := mongo.Connect(ctx, clientOptions)
if err != nil {
global.LOG.Fatalf(err.Error())
@@ -36,5 +38,4 @@ func InitMongoDB() {
return
}
global.MongoDB = connect
fmt.Println("Connected to MongoDB!")
}

View File

@@ -5,6 +5,7 @@ const ScaCommentLikesTableName = "sca_comment_likes"
// ScaCommentLikes 评论点赞表
type ScaCommentLikes struct {
Id int64 `gorm:"column:id;type:bigint(20);primary_key" json:"id"`
TopicId string `gorm:"column:topic_id;type:varchar(20);NOT NULL;comment:主题ID" json:"topic_id"`
UserId string `gorm:"column:user_id;type:varchar(20);comment:用户ID;NOT NULL" json:"user_id"`
CommentId int64 `gorm:"column:comment_id;type:bigint(20);comment:评论ID;NOT NULL" json:"comment_id"`
Type int `gorm:"column:type;type:int(11);comment:类型0 点赞 1 踩;NOT NULL" json:"type"`

View File

@@ -45,7 +45,7 @@ func (CommentReplyService) GetCommentListOrderByLikesDesc(topicID uint, page, pa
func (CommentReplyService) UpdateCommentReplyCount(commentID int64) error {
// 使用事务处理错误
err := global.DB.Transaction(func(tx *gorm.DB) error {
result := tx.Model(&model.ScaCommentReply{}).Where("id = ?", commentID).Update("reply_count", gorm.Expr("reply_count + ?", 1))
result := tx.Model(&model.ScaCommentReply{}).Where("id = ? and deleted = 0", commentID).Update("reply_count", gorm.Expr("reply_count + ?", 1))
if result.Error != nil {
return result.Error // 返回更新错误
}