feat: implement batch move and delete functionality for documents and FAQs

This commit is contained in:
mlogclub
2026-06-02 16:13:08 +08:00
parent 1bdc1cb2b6
commit 842f827c86
12 changed files with 727 additions and 2 deletions
+4
View File
@@ -263,6 +263,8 @@ func registerDashboardKnowledgeDirectoryRoutes(group *gin.RouterGroup) {
func registerDashboardKnowledgeDocumentRoutes(group *gin.RouterGroup) {
group.GET("/:id", dashboard.KnowledgeDocumentGetBy)
group.POST("/batch_delete", dashboard.KnowledgeDocumentPostBatch_delete)
group.POST("/batch_move", dashboard.KnowledgeDocumentPostBatch_move)
group.POST("/create", dashboard.KnowledgeDocumentPostCreate)
group.POST("/delete", dashboard.KnowledgeDocumentPostDelete)
group.Any("/list", dashboard.KnowledgeDocumentAnyList)
@@ -273,6 +275,8 @@ func registerDashboardKnowledgeFAQRoutes(group *gin.RouterGroup) {
group.GET("/import_template", dashboard.KnowledgeFAQGetImport_template)
group.GET("/export", dashboard.KnowledgeFAQGetExport)
group.GET("/:id", dashboard.KnowledgeFAQGetBy)
group.POST("/batch_delete", dashboard.KnowledgeFAQPostBatch_delete)
group.POST("/batch_move", dashboard.KnowledgeFAQPostBatch_move)
group.POST("/create", dashboard.KnowledgeFAQPostCreate)
group.POST("/delete", dashboard.KnowledgeFAQPostDelete)
group.POST("/import", dashboard.KnowledgeFAQPostImport)
@@ -144,3 +144,38 @@ func KnowledgeDocumentPostDelete(ctx *gin.Context) {
}
httpx.WriteJSON(ctx, nil)
}
func KnowledgeDocumentPostBatch_move(ctx *gin.Context) {
operator, err := services.AuthService.RequirePermission(ctx, constants.PermissionKnowledgeDocumentUpdate)
if err != nil {
httpx.WriteJSON(ctx, err)
return
}
req := request.BatchMoveKnowledgeDocumentRequest{}
if err := params.ReadJSON(ctx, &req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
if err := services.KnowledgeDocumentService.BatchMoveKnowledgeDocuments(req, operator); err != nil {
httpx.WriteJSON(ctx, err)
return
}
httpx.WriteJSON(ctx, nil)
}
func KnowledgeDocumentPostBatch_delete(ctx *gin.Context) {
if _, err := services.AuthService.RequirePermission(ctx, constants.PermissionKnowledgeDocumentDelete); err != nil {
httpx.WriteJSON(ctx, err)
return
}
req := request.BatchDeleteKnowledgeDocumentRequest{}
if err := params.ReadJSON(ctx, &req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
if err := services.KnowledgeDocumentService.BatchDeleteKnowledgeDocuments(req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
httpx.WriteJSON(ctx, nil)
}
@@ -192,6 +192,41 @@ func KnowledgeFAQPostDelete(ctx *gin.Context) {
httpx.WriteJSON(ctx, nil)
}
func KnowledgeFAQPostBatch_move(ctx *gin.Context) {
operator, err := services.AuthService.RequirePermission(ctx, constants.PermissionKnowledgeFAQUpdate)
if err != nil {
httpx.WriteJSON(ctx, err)
return
}
req := request.BatchMoveKnowledgeFAQRequest{}
if err := params.ReadJSON(ctx, &req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
if err := services.KnowledgeFAQService.BatchMoveKnowledgeFAQs(req, operator); err != nil {
httpx.WriteJSON(ctx, err)
return
}
httpx.WriteJSON(ctx, nil)
}
func KnowledgeFAQPostBatch_delete(ctx *gin.Context) {
if _, err := services.AuthService.RequirePermission(ctx, constants.PermissionKnowledgeFAQDelete); err != nil {
httpx.WriteJSON(ctx, err)
return
}
req := request.BatchDeleteKnowledgeFAQRequest{}
if err := params.ReadJSON(ctx, &req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
if err := services.KnowledgeFAQService.BatchDeleteKnowledgeFAQs(req); err != nil {
httpx.WriteJSON(ctx, err)
return
}
httpx.WriteJSON(ctx, nil)
}
func writeKnowledgeFAQExcelFile(ctx *gin.Context, file *response.KnowledgeFAQExportedFile) {
ctx.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, file.Filename))
ctx.Header("Cache-Control", "no-store")
@@ -55,6 +55,16 @@ type UpdateKnowledgeDocumentRequest struct {
CreateKnowledgeDocumentRequest
}
type BatchMoveKnowledgeDocumentRequest struct {
KnowledgeBaseID int64 `json:"knowledgeBaseId"`
DirectoryID int64 `json:"directoryId"`
IDs []int64 `json:"ids"`
}
type BatchDeleteKnowledgeDocumentRequest struct {
IDs []int64 `json:"ids"`
}
type CreateKnowledgeFAQRequest struct {
KnowledgeBaseID int64 `json:"knowledgeBaseId"`
DirectoryID int64 `json:"directoryId"`
@@ -69,6 +79,16 @@ type UpdateKnowledgeFAQRequest struct {
CreateKnowledgeFAQRequest
}
type BatchMoveKnowledgeFAQRequest struct {
KnowledgeBaseID int64 `json:"knowledgeBaseId"`
DirectoryID int64 `json:"directoryId"`
IDs []int64 `json:"ids"`
}
type BatchDeleteKnowledgeFAQRequest struct {
IDs []int64 `json:"ids"`
}
type KnowledgeFAQImportMode string
const (
@@ -183,6 +183,73 @@ func (s *knowledgeDocumentService) DeleteKnowledgeDocument(id int64) error {
return rag.Index.RemoveDocumentIndex(context.Background(), id)
}
func (s *knowledgeDocumentService) BatchMoveKnowledgeDocuments(req request.BatchMoveKnowledgeDocumentRequest, operator *dto.AuthPrincipal) error {
if operator == nil {
return errorsx.Unauthorized("未登录或登录已过期")
}
ids := uniquePositiveIDs(req.IDs)
if len(ids) == 0 {
return errorsx.InvalidParam("请选择要移动的文档")
}
kb := KnowledgeBaseService.Get(req.KnowledgeBaseID)
if kb == nil {
return errorsx.InvalidParam("知识库不存在")
}
if kb.KnowledgeType == string(enums.KnowledgeBaseTypeFAQ) {
return errorsx.InvalidParam("FAQ知识库不支持文档")
}
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
return err
}
for _, id := range ids {
current := s.Get(id)
if current == nil || current.Status == enums.StatusDeleted {
return errorsx.InvalidParam("文档不存在")
}
if current.KnowledgeBaseID != req.KnowledgeBaseID {
return errorsx.InvalidParam("只能移动当前知识库下的文档")
}
}
now := time.Now()
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
for _, id := range ids {
if err := repositories.KnowledgeDocumentRepository.Updates(ctx.Tx, id, map[string]any{
"directory_id": req.DirectoryID,
"index_status": enums.KnowledgeDocumentIndexStatusPending,
"indexed_at": nil,
"index_error": "",
"update_user_id": operator.UserID,
"update_user_name": operator.Username,
"updated_at": now,
}); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
for _, id := range ids {
if err := rag.Index.IndexDocumentByID(context.Background(), id); err != nil {
slog.Error("failed to reindex moved knowledge document", "document_id", id, "error", err)
}
}
return nil
}
func (s *knowledgeDocumentService) BatchDeleteKnowledgeDocuments(req request.BatchDeleteKnowledgeDocumentRequest) error {
ids := uniquePositiveIDs(req.IDs)
if len(ids) == 0 {
return errorsx.InvalidParam("请选择要删除的文档")
}
for _, id := range ids {
if err := s.DeleteKnowledgeDocument(id); err != nil {
return err
}
}
return nil
}
func (s *knowledgeDocumentService) buildKnowledgeDocumentModel(req request.CreateKnowledgeDocumentRequest) (*models.KnowledgeDocument, error) {
if strs.IsBlank(string(req.ContentType)) {
req.ContentType = enums.KnowledgeDocumentContentTypeHTML
@@ -205,3 +272,19 @@ func (s *knowledgeDocumentService) buildKnowledgeDocumentModel(req request.Creat
}
return item, nil
}
func uniquePositiveIDs(ids []int64) []int64 {
result := make([]int64, 0, len(ids))
seen := make(map[int64]struct{}, len(ids))
for _, id := range ids {
if id <= 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
result = append(result, id)
}
return result
}
@@ -122,6 +122,69 @@ func (s *knowledgeFAQService) DeleteKnowledgeFAQ(id int64) error {
return rag.Index.RemoveFAQIndex(context.Background(), id)
}
func (s *knowledgeFAQService) BatchMoveKnowledgeFAQs(req request.BatchMoveKnowledgeFAQRequest, operator *dto.AuthPrincipal) error {
if operator == nil {
return errorsx.Unauthorized("未登录或登录已过期")
}
ids := uniquePositiveIDs(req.IDs)
if len(ids) == 0 {
return errorsx.InvalidParam("请选择要移动的FAQ")
}
if _, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID); err != nil {
return err
}
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
return err
}
for _, id := range ids {
current := s.Get(id)
if current == nil {
return errorsx.InvalidParam("FAQ不存在")
}
if current.KnowledgeBaseID != req.KnowledgeBaseID {
return errorsx.InvalidParam("只能移动当前知识库下的FAQ")
}
}
now := time.Now()
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
for _, id := range ids {
if err := repositories.KnowledgeFAQRepository.Updates(ctx.Tx, id, map[string]any{
"directory_id": req.DirectoryID,
"index_status": enums.KnowledgeDocumentIndexStatusPending,
"indexed_at": nil,
"index_error": "",
"update_user_id": operator.UserID,
"update_user_name": operator.Username,
"updated_at": now,
}); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
for _, id := range ids {
if err := rag.Index.IndexFAQByID(context.Background(), id); err != nil {
slog.Error("failed to reindex moved knowledge faq", "faq_id", id, "error", err)
}
}
return nil
}
func (s *knowledgeFAQService) BatchDeleteKnowledgeFAQs(req request.BatchDeleteKnowledgeFAQRequest) error {
ids := uniquePositiveIDs(req.IDs)
if len(ids) == 0 {
return errorsx.InvalidParam("请选择要删除的FAQ")
}
for _, id := range ids {
if err := s.DeleteKnowledgeFAQ(id); err != nil {
return err
}
}
return nil
}
func (s *knowledgeFAQService) buildKnowledgeFAQModel(req request.CreateKnowledgeFAQRequest) (*models.KnowledgeFAQ, error) {
if req.KnowledgeBaseID <= 0 {
return nil, errorsx.InvalidParam("知识库不存在")