package community import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" "hongze/hongze_yb/controller/response" "hongze/hongze_yb/global" userLogic "hongze/hongze_yb/logic/user" "hongze/hongze_yb/logic/yb_community_question" "hongze/hongze_yb/models/request" "hongze/hongze_yb/models/tables/yb_resource" "strings" respond "hongze/hongze_yb/models/response" responseModel "hongze/hongze_yb/models/response" "hongze/hongze_yb/services" "hongze/hongze_yb/services/community" "hongze/hongze_yb/services/user" userService "hongze/hongze_yb/services/user" "hongze/hongze_yb/services/wx_app" "hongze/hongze_yb/utils" "io/ioutil" "os" "path" "strconv" "time" ) // QuestionList 问答列表 // @Tags 问答社区模块 // @Description 获取问答列表 // @Param page_index query int false "页码" // @Param page_size query int false "每页数量" // @Param only_mine query int false "只看我的" // @Param variety_tag_id query int false "标签ID" // @Param reply_status query int false "回复状态 0-全部 2-未回答 3-已回答,4-待回答(研究员)" // @Success 200 {object} []respond.CommunityQuestionItem // @failure 400 {string} string "获取失败" // @Router /question/list [get] func QuestionList(c *gin.Context) { var req request.QuestionListReq if err := c.Bind(&req); err != nil { response.Fail("参数有误", c) return } if req.PageIndex == 0 { req.PageIndex = 1 } if req.PageSize == 0 { req.PageSize = utils.PageSize20 } userinfo := user.GetInfoByClaims(c) list, err := community.GetQuestionList(req.PageIndex, req.PageSize, req.OnlyMine, req.VarietyTagId, req.ReplyStatus, req.GroupId, userinfo) if err != nil { response.FailMsg("获取失败", "QuestionList ErrMsg:"+err.Error(), c) return } // 点赞/吐槽数据 err = yb_community_question.HandleLikeOrTeaseByCommunityQuestionItemList(userinfo.UserID, list) if err != nil { response.FailMsg("获取失败", "QuestionList ErrMsg:"+err.Error(), c) return } // 评论数据 err = yb_community_question.HandleCommentByCommunityQuestionItemList(userinfo.UserID, list) if err != nil { response.FailMsg("获取失败", "QuestionList ErrMsg:"+err.Error(), c) return } response.OkData("获取成功", list, c) } // QuestionDetail 问答详情 // @Tags 问答社区模块 // @Description 获取问答详情 // @Param question_id query int true "问答ID" // @Success 200 {object} respond.CommunityQuestionItem // @failure 400 {string} string "获取失败" // @Router /question/detail [get] func QuestionDetail(c *gin.Context) { var req request.QuestionDetailReq if err := c.Bind(&req); err != nil { response.Fail("参数有误", c) return } if req.QuestionId == 0 { response.Fail("参数有误", c) return } userinfo := user.GetInfoByClaims(c) item, errMsg, err := community.GetQuestionDetail(req.QuestionId, userinfo) if err != nil { response.FailMsg(errMsg, "QuestionDetail ErrMsg:"+err.Error(), c) return } response.OkData("获取成功", item, c) } // QuestionAsk 发布提问 // @Tags 问答社区模块 // @Description 发布提问 // @Param question_content query string true "问题内容" // @Success 200 {string} string "操作成功" // @failure 400 {string} string "操作失败" // @Router /question/ask [post] func QuestionAsk(c *gin.Context) { var req request.QuestionAskReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.QuestionContent == "" { response.Fail("内容不可为空", c) return } contentRune := []rune(req.QuestionContent) if len(contentRune) > 50 { response.Fail("内容不可超过50个字符", c) return } if req.VarietyClassifyId <= 0 { response.Fail("请选择品种分类", c) return } if req.VarietyTagId <= 0 { response.Fail("请选择品种", c) return } // 敏感词校验, 只有小程序的用户才能走敏感词过滤接口 userinfo := user.GetInfoByClaims(c) if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 { checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.QuestionContent) if e == nil { if checkResult.Result != nil && checkResult.Result.Suggest != "pass" { errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label) response.FailMsg("含有违禁词, 不允许发布", errMsg, c) return } } } if err := community.CreateQuestion(int(userinfo.UserID), req.VarietyClassifyId, req.VarietyTagId, userinfo.Mobile, userinfo.RealName, req.QuestionContent); err != nil { response.FailMsg("提交失败", "QuestionAsk ErrMsg:"+err.Error(), c) return } response.Ok("操作成功", c) } // QuestionReply 发布回复 // @Tags 问答社区模块 // @Description 发布回复 // @Param question_id query int true "问答ID" // @Param audio_list query object true "音频列表" // @Success 200 {string} string "操作成功" // @failure 400 {string} string "操作失败" // @Router /question/reply [post] func QuestionReply(c *gin.Context) { var req request.QuestionReplyReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.QuestionId == 0 { response.Fail("参数有误", c) return } if len(req.AudioList) == 0 { response.Fail("音频不可为空", c) return } for i := range req.AudioList { if req.AudioList[i].AudioUrl == "" || req.AudioList[i].AudioPlaySeconds == "" || req.AudioList[i].AudioSize == "" { response.Fail("音频信息有误, 请重新上传", c) return } if !strings.Contains(req.AudioList[i].AudioUrl, utils.ALIYUN_OSS_HOST) { response.Fail("音频地址有误, 请重新上传", c) return } } userinfo := user.GetInfoByClaims(c) msg, err := community.ReplyUserQuestion(int(userinfo.UserID), req.QuestionId, req.AudioList) if err != nil { response.FailMsg(msg, "QuestionReply ErrMsg:"+err.Error(), c) return } response.Ok("操作成功", c) } // QuestionReplyRead 已读回复 // @Tags 问答社区模块 // @Description 已读回复 // @Param question_ids query string true "问答IDs" // @Success 200 {string} string "操作成功" // @failure 400 {string} string "操作失败" // @Router /question/reply/read [post] func QuestionReplyRead(c *gin.Context) { var req request.QuestionReadReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.QuestionIds == "" { response.Fail("参数有误", c) return } userinfo := user.GetInfoByClaims(c) if err := community.ReadQuestionReply(req.QuestionIds, userinfo); err != nil { response.FailMsg("操作失败", "QuestionReplyRead ErrMsg:"+err.Error(), c) return } response.Ok("操作成功", c) } // QuestionListTotal 问答列表数量统计 // @Tags 问答社区模块 // @Description 问答列表数量统计 // @Success 200 {object} respond.CommunityQuestionListTotal // @failure 400 {string} string "获取失败" // @Router /question/list/total [get] func QuestionListTotal(c *gin.Context) { userinfo := user.GetInfoByClaims(c) resp, err := community.GetQuestionListTotal(userinfo) if err != nil { response.FailMsg("获取失败", "QuestionReplyTotal ErrMsg:"+err.Error(), c) return } response.OkData("获取成功", resp, c) } // QuestionUploadAudio 上传回复音频 // @Tags 问答社区模块 // @Description 上传回复音频 // @Param file query string true "音频文件" // @Success 200 {string} string "上传成功" // @failure 400 {string} string "上传失败" // @Router /question/reply/upload_audio [post] func QuestionUploadAudio(c *gin.Context) { file, err := c.FormFile("file") if err != nil { response.FailMsg("获取资源失败", "获取资源失败, Err:"+err.Error(), c) return } ext := path.Ext(file.Filename) if ext != ".mp3" { response.Fail("暂仅支持mp3格式", c) return } dateDir := time.Now().Format("20060102") localDir := global.CONFIG.Serve.StaticDir + "hongze/" + dateDir if err := os.MkdirAll(localDir, 0766); err != nil { response.FailMsg("存储目录创建失败", "QuestionUploadAudio 存储目录创建失败, Err:"+err.Error(), c) return } randStr := utils.GetRandStringNoSpecialChar(28) filtName := randStr + ext fpath := localDir + "/" + filtName defer func() { _ = os.Remove(fpath) }() // 生成文件至指定目录 if err := c.SaveUploadedFile(file, fpath); err != nil { response.FailMsg("文件生成失败", "QuestionUploadAudio 文件生成失败, Err:"+err.Error(), c) return } // 获取音频文件时长 fByte, err := ioutil.ReadFile(fpath) if err != nil { response.FailMsg("读取本地文件失败", "QuestionUploadAudio 读取本地文件失败", c) return } if len(fByte) <= 0 { response.FailMsg("文件大小有误", "QuestionUploadAudio 文件大小有误", c) return } seconds, err := services.GetMP3PlayDuration(fByte) if err != nil { response.FailMsg("读取文件时长失败", "QuestionUploadAudio 读取文件时长失败", c) return } // 音频大小MB fi, err := os.Stat(fpath) if err != nil { response.FailMsg("读取文件大小失败", "QuestionUploadAudio 读取文件大小失败", c) return } mb := utils.Bit2MB(fi.Size(), 2) // 上传文件至阿里云 ossDir := "yb_wx/community_question_audio/" resourceUrl, err := services.UploadAliyunToDir(filtName, fpath, ossDir) if err != nil { response.FailMsg("文件上传失败", "QuestionUploadAudio 文件上传失败, Err:"+err.Error(), c) return } resp := &respond.CommunityQuestionAudioUpload{ AudioURL: resourceUrl, AudioPlaySeconds: fmt.Sprint(seconds), AudioSize: fmt.Sprint(mb), } // 记录文件 userInfo := user.GetInfoByClaims(c) go func() { extendData := struct { FileName string AudioURL string AudioPlaySeconds string AudioSize string UserId int }{ file.Filename, resourceUrl, fmt.Sprint(seconds), fmt.Sprint(mb), int(userInfo.UserID), } dataByte, e := json.Marshal(extendData) if e != nil { return } re := new(yb_resource.YbResource) re.ResourceUrl = resourceUrl re.ResourceType = yb_resource.ResourceTypeAudio re.ExtendData = string(dataByte) re.CreateTime = time.Now().Local() if e = re.Create(); e != nil { return } }() response.OkData("上传成功", resp, c) } // QuestionUnread 我的问答未读数 // @Tags 问答社区模块 // @Description 我的问答未读数 // @Success 200 {int} int "获取成功" // @failure 400 {string} string "获取失败" // @Router /question/unread [get] func QuestionUnread(c *gin.Context) { userinfo := user.GetInfoByClaims(c) total, err := community.GetMyQuestionUnread(userinfo) if err != nil { fmt.Println(err.Error()) response.FailMsg("获取失败", "QuestionUnread ErrMsg:"+err.Error(), c) return } response.OkData("获取成功", total, c) } // ResearchGroupList 研究方向分组列表 // @Tags 问答社区模块 // @Description 研究方向分组列表 // @Success 200 {int} int "获取成功" // @failure 400 {string} string "获取失败" // @Router /question/research_group [get] func ResearchGroupList(c *gin.Context) { list, err := community.GetResearchGroupTree() if err != nil { response.FailMsg("获取失败", "ResearchGroupList ErrMsg:"+err.Error(), c) return } response.OkData("获取成功", list, c) } // AddAudioLog 添加用户点击音频日志 // @Tags 问答社区模块 // @Description 添加用户点击音频日志 // @Param community_question_audio_id query int true "音频ID" // @Param source_agent query int true "操作来源,1:小程序,2:小程序 pc 3:弘则研究公众号,4:web pc" // @Success 200 {string} string "操作成功" // @failure 400 {string} string "操作失败" // @Router /AddAudioLog [post] func AddAudioLog(c *gin.Context) { var req request.CommunityAudioListenLogReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.CommunityQuestionAudioID == 0 { response.Fail("请输入音频ID", c) return } if req.SourceAgent == 0 { response.Fail("请输入操作来源", c) return } userinfo := user.GetInfoByClaims(c) newId, err := community.AddAudioListenLog(userinfo, req.CommunityQuestionAudioID, req.SourceAgent) if err != nil { response.Fail("操作失败: "+err.Error(), c) return } response.OkData("操作成功", newId, c) } // SetLikeOrTease 问答点赞/吐槽设置 func SetLikeOrTease(c *gin.Context) { var req request.ReqCommunityQuestionLikeTease if c.ShouldBind(&req) != nil { response.Fail("参数异常", c) return } userinfo := userService.GetInfoByClaims(c) // 不传来源默认为问答社区 if req.Source == 0 { req.Source = 1 } communityQuestionLikeTease, likeNum, teaseNum, err, errMsg := yb_community_question.SetLikeOrTease(userinfo.UserID, req.CommunityQuestionId, req.OpType, req.Enable, req.SourceAgent, req.Source) if err != nil { response.FailMsg(errMsg, err.Error(), c) return } response.OkData("操作成功", responseModel.RespCommunityQuestionLikeTease{ LikeTotal: likeNum, TeaseTotal: teaseNum, OpType: communityQuestionLikeTease.OpType, Enabled: communityQuestionLikeTease.Enabled, }, c) return } // QuestionTransfer 转移问答 // @Tags 问答社区模块 // @Description 转移问答 // @Param request body request.QuestionTransferReq true "type json string" // @Success 200 {string} string "操作成功" // @Failure 400 {string} string "操作失败" // @Router /question/transfer [post] func QuestionTransfer(c *gin.Context) { var req request.QuestionTransferReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.CommunityQuestionID <= 0 { response.Fail("参数有误", c) return } if req.VarietyClassifyId <= 0 { response.Fail("请选择品种分类", c) return } if req.VarietyTagId <= 0 { response.Fail("请选择品种", c) return } if req.AdminId <= 0 { response.Fail("请选择研究员", c) return } userInfo := user.GetInfoByClaims(c) isInner, isResearcher, _, adminInfo, e := userLogic.GetUserIdentity(int(userInfo.CompanyID), userInfo.Mobile, userInfo.Email) if e != nil { response.FailMsg("操作失败", "QuestionTransfer ErrMsg:"+e.Error(), c) return } if isInner == 0 || isResearcher == 0 { response.Fail("无权操作", c) return } errMsg, e := community.TransferQuestion(req.CommunityQuestionID, int(userInfo.UserID), int(adminInfo.AdminID), req.VarietyClassifyId, req.VarietyTagId, req.AdminId, adminInfo.RealName) if e != nil { response.FailMsg(errMsg, "QuestionTransfer ErrMsg:"+e.Error(), c) return } response.Ok("操作成功", c) } // QuestionStop 终止问答 // @Tags 问答社区模块 // @Description 终止问答 // @Param request body request.QuestionStopReq true "type json string" // @Success 200 {string} string "操作成功" // @failure 400 {string} string "操作失败" // @Router /question/stop [post] func QuestionStop(c *gin.Context) { var req request.QuestionStopReq if err := c.ShouldBind(&req); err != nil { response.Fail("参数有误", c) return } if req.CommunityQuestionID <= 0 { response.Fail("参数有误", c) return } if req.Reason == "" { response.Fail("请输入终止原因", c) return } // 敏感词校验, 只有小程序的用户才能走敏感词过滤接口 userinfo := user.GetInfoByClaims(c) if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 { checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.Reason) if e == nil { if checkResult.Result != nil && checkResult.Result.Suggest != "pass" { errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label) response.FailMsg("含有违禁词, 不允许发布", errMsg, c) return } } } userInfo := user.GetInfoByClaims(c) isInner, isResearcher, _, _, e := userLogic.GetUserIdentity(int(userInfo.CompanyID), userInfo.Mobile, userInfo.Email) if e != nil { response.FailMsg("操作失败", "QuestionTransfer ErrMsg:"+e.Error(), c) return } if isInner == 0 || isResearcher == 0 { response.Fail("无权操作", c) return } errMsg, e := community.StopQuestion(req.CommunityQuestionID, int(userInfo.UserID), req.Reason) if e != nil { response.FailMsg(errMsg, "QuestionStop ErrMsg:"+e.Error(), c) return } response.Ok("操作成功", c) }