From bc8f980e912825eaaadaf9fdd2367f443a56de02 Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Sat, 07 Mar 2026 10:17:19 +0800
Subject: [PATCH] 2026-03-07 标记功能接口,支持点、线、面、圆标记存储

---
 ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationDirectoryMapper.java            |    9 
 ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationServiceImpl.java          |   64 ++++
 ard-sample/src/main/java/com/ard/sample/annotation/util/JsonSerializeUtil.java                      |   32 ++
 ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationDirectoryService.java         |   19 +
 ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationService.java                  |   18 +
 ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationMapper.java                     |   14 +
 ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java                     |   37 ++
 ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDirectoryDTO.java            |   31 ++
 ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationEntity.java               |   50 +++
 ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationPointsDTO.java               |   26 ++
 ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationDirectoryEntity.java      |   33 ++
 ard-sample/src/main/java/com/ard/sample/annotation/util/ConvertUtil.java                            |   57 ++++
 ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationDirectoryServiceImpl.java |  147 +++++++++++
 ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java                     |  197 +++++++++++++++
 14 files changed, 734 insertions(+), 0 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java b/ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java
new file mode 100644
index 0000000..a6c0f77
--- /dev/null
+++ b/ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java
@@ -0,0 +1,197 @@
+package com.ard.web.controller.sample;
+
+import com.ard.sample.annotation.model.dto.AnnotationDTO;
+import com.ard.sample.annotation.model.dto.AnnotationDirectoryDTO;
+import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity;
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import com.ard.sample.annotation.service.IAnnotationDirectoryService;
+import com.ard.sample.annotation.service.IAnnotationService;
+import com.ard.sample.annotation.util.ConvertUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Api(tags = "标注管理")
+@Slf4j
+@RestController
+@RequestMapping("/annotation")
+@RequiredArgsConstructor
+public class AnnotationController {
+
+    private final IAnnotationDirectoryService directoryService;
+    private final IAnnotationService annotationService;
+
+    // ====================== 目录接口 ======================
+    @ApiOperation("新增目录")
+    @PostMapping("/directory")
+    public ResponseEntity<Map<String, Object>> addDirectory(@RequestBody AnnotationDirectoryEntity entity) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            //AnnotationDirectoryEntity entity = ConvertUtil.directoryDtoToEntity(dto);
+            boolean success = directoryService.addDirectory(entity);
+            result.put("success", success);
+            result.put("msg", success ? "目录新增成功" : "目录新增失败");
+            result.put("data", entity.getId()); // 返回新增ID
+        } catch (Exception e) {
+            log.error("新增目录异常", e);
+            result.put("success", false);
+            result.put("msg", "新增目录失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation("修改目录")
+    @PutMapping("/directory")
+    public ResponseEntity<Map<String, Object>> editDirectory(@RequestBody AnnotationDirectoryEntity entity) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+//            AnnotationDirectoryEntity entity = ConvertUtil.directoryDtoToEntity(dto);
+            boolean success = directoryService.editDirectory(entity);
+            result.put("success", success);
+            result.put("msg", success ? "目录修改成功" : "目录修改失败");
+        } catch (Exception e) {
+            log.error("修改目录异常", e);
+            result.put("success", false);
+            result.put("msg", "修改目录失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation("删除目录及子项")
+    @DeleteMapping("/directory/{id}")
+    public ResponseEntity<Map<String, Object>> deleteDirectory(@PathVariable Integer id) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            boolean success = directoryService.deleteDirectory(id);
+            result.put("success", success);
+            result.put("msg", success ? "目录删除成功" : "目录删除失败");
+        } catch (Exception e) {
+            log.error("删除目录异常", e);
+            result.put("success", false);
+            result.put("msg", "删除目录失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+
+    // ====================== 标记接口 ======================
+    @ApiOperation("新增标记")
+    @PostMapping
+    public ResponseEntity<Map<String, Object>> addAnnotation(@RequestBody AnnotationDTO dto) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            AnnotationEntity entity = ConvertUtil.annotationDtoToEntity(dto);
+            boolean success = annotationService.addAnnotation(entity);
+            result.put("success", success);
+            result.put("msg", success ? "标记新增成功" : "标记新增失败");
+            result.put("data", entity.getId());
+        } catch (Exception e) {
+            log.error("新增标记异常", e);
+            result.put("success", false);
+            result.put("msg", "新增标记失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation("修改标记")
+    @PutMapping
+    public ResponseEntity<Map<String, Object>> editAnnotation(@RequestBody AnnotationDTO dto) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            AnnotationEntity entity = ConvertUtil.annotationDtoToEntity(dto);
+            boolean success = annotationService.editAnnotation(entity);
+            result.put("success", success);
+            result.put("msg", success ? "标记修改成功" : "标记修改失败");
+        } catch (Exception e) {
+            log.error("修改标记异常", e);
+            result.put("success", false);
+            result.put("msg", "修改标记失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation("删除标记")
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Map<String, Object>> deleteAnnotation(@PathVariable Integer id) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            boolean success = annotationService.deleteAnnotation(id);
+            result.put("success", success);
+            result.put("msg", success ? "标记删除成功" : "标记删除失败");
+        } catch (Exception e) {
+            log.error("删除标记异常", e);
+            result.put("success", false);
+            result.put("msg", "删除标记失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation("批量删除标记")
+    @DeleteMapping("/batch")
+    public ResponseEntity<Map<String, Object>> batchDeleteAnnotation(@RequestBody List<Integer> ids) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            boolean success = annotationService.batchDeleteAnnotation(ids);
+            result.put("success", success);
+            result.put("msg", success ? "标记批量删除成功" : "标记批量删除失败");
+        } catch (Exception e) {
+            log.error("批量删除标记异常", e);
+            result.put("success", false);
+            result.put("msg", "批量删除标记失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    // 层级查询:目录→标记→坐标
+    @ApiOperation("按目标id获取所有目录及子标记信息")
+    @GetMapping("/directory/{directoryId}")
+    public ResponseEntity<Map<String, Object>> getDirectoryWithAnnotations(@PathVariable Integer directoryId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            // 1. 查询目录
+            AnnotationDirectoryEntity dirEntity = directoryService.getById(directoryId);
+            if (dirEntity == null) {
+                result.put("success", false);
+                result.put("msg", "目录不存在");
+                return ResponseEntity.ok(result);
+            }
+            AnnotationDirectoryDTO dirDto = ConvertUtil.directoryEntityToDto(dirEntity);
+
+            // 2. 查询目录下标记
+            List<AnnotationEntity> annoEntities = annotationService.listByDirectoryId(directoryId);
+            List<AnnotationDTO> annoDtos = ConvertUtil.annotationEntityListToDtoList(annoEntities);
+            dirDto.setAnnotations(annoDtos);
+
+            result.put("success", true);
+            result.put("data", dirDto);
+        } catch (Exception e) {
+            log.error("查询目录及标记异常", e);
+            result.put("success", false);
+            result.put("msg", "查询失败:" + e.getMessage());
+        }
+        return ResponseEntity.ok(result);
+    }
+
+    /**
+     * 新增:获取全部目录及下属标注数据
+     */
+    @ApiOperation("获取全部目录及下属标注数据")
+    @GetMapping("/directory/all")
+    public ResponseEntity<Map<String, Object>> listAllDirectoryWithAnnotations() {
+        List<AnnotationDirectoryDTO> directoryList = directoryService.listAllDirectoryWithAnnotations();
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+        result.put("msg", "查询全部目录及标注成功");
+        result.put("data", directoryList);
+
+        return ResponseEntity.ok(result);
+    }
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationDirectoryMapper.java b/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationDirectoryMapper.java
new file mode 100644
index 0000000..b14dfcb
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationDirectoryMapper.java
@@ -0,0 +1,9 @@
+package com.ard.sample.annotation.mapper;
+
+import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AnnotationDirectoryMapper extends BaseMapper<AnnotationDirectoryEntity> {
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationMapper.java b/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationMapper.java
new file mode 100644
index 0000000..192ad4a
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/mapper/AnnotationMapper.java
@@ -0,0 +1,14 @@
+package com.ard.sample.annotation.mapper;
+
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface AnnotationMapper extends BaseMapper<AnnotationEntity> {
+    // 按目录ID查询标记列表
+    List<AnnotationEntity> selectByDirectoryId(@Param("directoryId") Integer directoryId);
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java
new file mode 100644
index 0000000..66ea70c
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java
@@ -0,0 +1,37 @@
+package com.ard.sample.annotation.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class AnnotationDTO {
+
+    private Integer id;
+
+    private Integer directoryId;
+
+    private String name;
+
+    private String color;
+
+    private String annotationType;
+
+    private List<AnnotationPointsDTO> coordinateInfo;
+
+    private String imageUrl;
+
+    private String drawer;
+
+    private String description;
+
+    private Long createTime;
+
+    private Long updateTime;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDirectoryDTO.java b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDirectoryDTO.java
new file mode 100644
index 0000000..01afa6b
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDirectoryDTO.java
@@ -0,0 +1,31 @@
+package com.ard.sample.annotation.model.dto;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 标注目录的DTO类
+ * */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class AnnotationDirectoryDTO {
+    private Integer id;
+
+    private String directoryName;
+
+    private List<AnnotationDTO> annotations; // 层级返回用
+
+    private String directoryRemark;
+
+    private Long createTime;
+
+    private Long updateTime;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationPointsDTO.java b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationPointsDTO.java
new file mode 100644
index 0000000..eab831b
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationPointsDTO.java
@@ -0,0 +1,26 @@
+package com.ard.sample.annotation.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 点、线、面、圆标注的DTO类
+ * */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AnnotationPointsDTO {
+
+    private Double longitude;
+
+    private Double latitude;
+
+    private Double height;
+
+    private Double radius;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationDirectoryEntity.java b/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationDirectoryEntity.java
new file mode 100644
index 0000000..50dd1c5
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationDirectoryEntity.java
@@ -0,0 +1,33 @@
+package com.ard.sample.annotation.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "annotation_directory")
+public class AnnotationDirectoryEntity implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    @TableField(value = "directory_name")
+    private String directoryName;
+
+    @TableField(value = "directory_remark")
+    private String directoryRemark;
+
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Long createTime;
+
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    private Long updateTime;
+
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationEntity.java b/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationEntity.java
new file mode 100644
index 0000000..3752036
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/model/entity/AnnotationEntity.java
@@ -0,0 +1,50 @@
+package com.ard.sample.annotation.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "annotation")
+public class AnnotationEntity implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    @TableField(value = "directory_id")
+    private Integer directoryId;
+
+    @TableField(value = "name")
+    private String name;
+
+    @TableField(value = "color")
+    private String color;
+
+    @TableField(value = "annotation_type")
+    private String annotationType;
+
+    @TableField(value = "coordinate_info")
+    private byte[] coordinateInfo;
+
+    @TableField(value = "image_url")
+    private String imageUrl;
+
+    @TableField(value = "drawer")
+    private String drawer;
+
+    @TableField(value = "description")
+    private String description;
+
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Long createTime;
+
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    private Long updateTime;
+
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationDirectoryService.java b/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationDirectoryService.java
new file mode 100644
index 0000000..d302d19
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationDirectoryService.java
@@ -0,0 +1,19 @@
+package com.ard.sample.annotation.service;
+
+import com.ard.sample.annotation.model.dto.AnnotationDirectoryDTO;
+import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+public interface IAnnotationDirectoryService extends IService<AnnotationDirectoryEntity> {
+    // 新增目录
+    boolean addDirectory(AnnotationDirectoryEntity entity);
+    // 修改目录
+    boolean editDirectory(AnnotationDirectoryEntity entity);
+    // 删除目录(级联删除标记)
+    boolean deleteDirectory(Integer id);
+
+    // 新增:查询全部目录(含下属标注数据)
+    List<AnnotationDirectoryDTO> listAllDirectoryWithAnnotations();
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationService.java b/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationService.java
new file mode 100644
index 0000000..da2707f
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/service/IAnnotationService.java
@@ -0,0 +1,18 @@
+package com.ard.sample.annotation.service;
+
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+
+public interface IAnnotationService extends IService<AnnotationEntity> {
+    // 新增标记
+    boolean addAnnotation(AnnotationEntity entity);
+    // 修改标记
+    boolean editAnnotation(AnnotationEntity entity);
+    // 删除标记
+    boolean deleteAnnotation(Integer id);
+    // 批量删除标记
+    boolean batchDeleteAnnotation(List<Integer> ids);
+    // 按目录ID查询标记
+    List<AnnotationEntity> listByDirectoryId(Integer directoryId);
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationDirectoryServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationDirectoryServiceImpl.java
new file mode 100644
index 0000000..6ba5f04
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationDirectoryServiceImpl.java
@@ -0,0 +1,147 @@
+package com.ard.sample.annotation.service.impl;
+
+import com.ard.common.utils.bean.BeanUtils;
+import com.ard.sample.annotation.mapper.AnnotationMapper;
+import com.ard.sample.annotation.model.dto.AnnotationDTO;
+import com.ard.sample.annotation.model.dto.AnnotationDirectoryDTO;
+import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity;
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import com.ard.sample.annotation.service.IAnnotationDirectoryService;
+import com.ard.sample.annotation.service.IAnnotationService;
+import com.ard.sample.annotation.util.JsonSerializeUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ard.sample.annotation.mapper.AnnotationDirectoryMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AnnotationDirectoryServiceImpl extends ServiceImpl<AnnotationDirectoryMapper, AnnotationDirectoryEntity>
+        implements IAnnotationDirectoryService {
+
+    // 注入已实现的AnnotationService(而非直接用Mapper),复用其查询逻辑
+    private final IAnnotationService annotationService;
+
+    private final AnnotationMapper annotationMapper;
+
+    @Override
+    public boolean addDirectory(AnnotationDirectoryEntity entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean editDirectory(AnnotationDirectoryEntity entity) {
+        if (entity.getId() == null) {
+            log.error("修改目录失败:ID为空");
+            return false;
+        }
+        return updateById(entity);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class) // 事务保证级联删除
+    public boolean deleteDirectory(Integer id) {
+        if (id == null) {
+            log.error("删除目录失败:ID为空");
+            return false;
+        }
+        // 1. 删除目录下所有标记
+        annotationMapper.deleteByMap(Map.of("directory_id", id));
+        // 2. 删除目录
+        boolean result = removeById(id);
+        log.info("删除目录{},结果:{}", id, result);
+        return result;
+    }
+
+    /**
+     * 查询全部目录(含下属标注数据)
+     * 修复点:替换自定义Mapper方法为MyBatis-Plus通用查询,避免SQL映射缺失问题
+     */
+    @Override
+    public List<AnnotationDirectoryDTO> listAllDirectoryWithAnnotations() {
+        // 1. 查询所有目录
+        List<AnnotationDirectoryEntity> directoryEntities = list();
+        if (directoryEntities.isEmpty()) {
+            return List.of();
+        }
+
+        // 2. 批量查询所有标注,按目录ID分组(减少数据库查询次数)
+        List<AnnotationEntity> allAnnotations = annotationService.list();
+        Map<Integer, List<AnnotationEntity>> annotationMap = allAnnotations.stream()
+                .collect(Collectors.groupingBy(AnnotationEntity::getDirectoryId));
+
+        // 3. 遍历目录,组装DTO(关联下属标注)
+        return directoryEntities.stream().map(directoryEntity -> {
+            AnnotationDirectoryDTO directoryDTO = AnnotationDirectoryDTO.builder().build();
+            BeanUtils.copyProperties(directoryEntity, directoryDTO);
+
+            // 4. 获取当前目录下的标注(从分组Map中获取,避免循环查库)
+            List<AnnotationEntity> annotationEntities = annotationMap.getOrDefault(directoryEntity.getId(), List.of());
+
+            // 5. 标注实体转DTO(反序列化坐标数据)
+            List<AnnotationDTO> annotationDTOList = annotationEntities.stream().map(annotationEntity -> {
+                AnnotationDTO annotationDTO = AnnotationDTO.builder().build();
+                BeanUtils.copyProperties(annotationEntity, annotationDTO);
+                try {
+                    // 反序列化坐标数据
+                    annotationDTO.setCoordinateInfo(
+                            JsonSerializeUtil.deserialize(annotationEntity.getCoordinateInfo())
+                    );
+                } catch (Exception e) {
+                    log.error("反序列化标注坐标失败,标注ID:{}", annotationEntity.getId(), e);
+                    annotationDTO.setCoordinateInfo(null);
+                }
+                return annotationDTO;
+            }).collect(Collectors.toList());
+
+            // 6. 设置目录下的标注列表
+            directoryDTO.setAnnotations(annotationDTOList);
+            return directoryDTO;
+        }).collect(Collectors.toList());
+    }
+
+//    /**
+//     * 查询全部目录(含下属标注数据)
+//     */
+//    @Override
+//    public List<AnnotationDirectoryDTO> listAllDirectoryWithAnnotations() {
+//        // 1. 查询所有目录
+//        List<AnnotationDirectoryEntity> directoryEntities = list();
+//
+//        // 2. 遍历目录,关联查询标注数据并组装DTO
+//        return directoryEntities.stream().map(directoryEntity -> {
+//            AnnotationDirectoryDTO directoryDTO = AnnotationDirectoryDTO.builder().build();
+//            BeanUtils.copyProperties(directoryEntity, directoryDTO);
+//
+//            // 3. 查询当前目录下的所有标注
+//            List<AnnotationEntity> annotationEntities = annotationMapper.selectByDirectoryId(directoryEntity.getId());
+//
+//            // 4. 标注实体转DTO(反序列化坐标数据)
+//            List<AnnotationDTO> annotationDTOList = annotationEntities.stream().map(annotationEntity -> {
+//                AnnotationDTO annotationDTO = AnnotationDTO.builder().build();
+//                BeanUtils.copyProperties(annotationEntity, annotationDTO);
+//                try {
+//                    // 反序列化坐标数据
+//                    annotationDTO.setCoordinateInfo(
+//                            JsonSerializeUtil.deserialize(annotationEntity.getCoordinateInfo())
+//                    );
+//                } catch (Exception e) {
+//                    log.error("反序列化标注坐标失败,标注ID:{}", annotationEntity.getId(), e);
+//                    annotationDTO.setCoordinateInfo(null);
+//                }
+//                return annotationDTO;
+//            }).collect(Collectors.toList());
+//
+//            // 5. 给目录DTO设置下属标注列表(需先给AnnotationDirectoryDTO新增annotations字段)
+//            directoryDTO.setAnnotations(annotationDTOList);
+//            return directoryDTO;
+//        }).collect(Collectors.toList());
+//    }
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationServiceImpl.java
new file mode 100644
index 0000000..bf09f45
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/service/impl/AnnotationServiceImpl.java
@@ -0,0 +1,64 @@
+package com.ard.sample.annotation.service.impl;
+
+import com.ard.sample.annotation.mapper.AnnotationMapper;
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import com.ard.sample.annotation.service.IAnnotationService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AnnotationServiceImpl extends ServiceImpl<AnnotationMapper, AnnotationEntity>
+        implements IAnnotationService {
+
+    @Override
+    public boolean addAnnotation(AnnotationEntity entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean editAnnotation(AnnotationEntity entity) {
+        if (entity.getId() == null) {
+            log.error("修改标记失败:ID为空");
+            return false;
+        }
+        return updateById(entity);
+    }
+
+    @Override
+    public boolean deleteAnnotation(Integer id) {
+        if (id == null) {
+            log.error("删除标记失败:ID为空");
+            return false;
+        }
+        boolean result = removeById(id);
+        log.info("删除标记{},结果:{}", id, result);
+        return result;
+    }
+
+    @Override
+    public boolean batchDeleteAnnotation(List<Integer> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            log.error("批量删除标记失败:ID列表为空");
+            return false;
+        }
+        boolean result = removeByIds(ids);
+        log.info("批量删除标记{},结果:{}", ids, result);
+        return result;
+    }
+
+    @Override
+    public List<AnnotationEntity> listByDirectoryId(Integer directoryId) {
+        if (directoryId == null) {
+            log.error("按目录查询标记失败:目录ID为空");
+            return List.of();
+        }
+        return lambdaQuery().eq(AnnotationEntity::getDirectoryId, directoryId).list();
+    }
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/util/ConvertUtil.java b/ard-sample/src/main/java/com/ard/sample/annotation/util/ConvertUtil.java
new file mode 100644
index 0000000..e39e7bb
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/util/ConvertUtil.java
@@ -0,0 +1,57 @@
+package com.ard.sample.annotation.util;
+
+import com.ard.sample.annotation.model.dto.AnnotationDTO;
+import com.ard.sample.annotation.model.dto.AnnotationDirectoryDTO;
+import com.ard.sample.annotation.model.dto.AnnotationPointsDTO;
+import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity;
+import com.ard.sample.annotation.model.entity.AnnotationEntity;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ConvertUtil {
+    // 目录DTO → 目录Entity
+    public static AnnotationDirectoryEntity directoryDtoToEntity(AnnotationDirectoryDTO dto) {
+        AnnotationDirectoryEntity entity = new AnnotationDirectoryEntity();
+        BeanUtils.copyProperties(dto, entity);
+        return entity;
+    }
+
+    // 目录Entity → 目录DTO
+    public static AnnotationDirectoryDTO directoryEntityToDto(AnnotationDirectoryEntity entity) {
+        AnnotationDirectoryDTO dto = new AnnotationDirectoryDTO();
+        BeanUtils.copyProperties(entity, dto);
+        return dto;
+    }
+
+    // 标记DTO → 标记Entity(含坐标序列化)
+    public static AnnotationEntity annotationDtoToEntity(AnnotationDTO dto) throws Exception {
+        AnnotationEntity entity = new AnnotationEntity();
+        BeanUtils.copyProperties(dto, entity);
+        // 序列化坐标数据
+        entity.setCoordinateInfo(JsonSerializeUtil.serialize(dto.getCoordinateInfo()));
+        return entity;
+    }
+
+    // 标记Entity → 标记DTO(含坐标反序列化)
+    public static AnnotationDTO annotationEntityToDto(AnnotationEntity entity) throws Exception {
+        AnnotationDTO dto = new AnnotationDTO();
+        BeanUtils.copyProperties(entity, dto);
+        // 反序列化坐标数据
+        List<AnnotationPointsDTO> points = JsonSerializeUtil.deserialize(entity.getCoordinateInfo());
+        dto.setCoordinateInfo(points);
+        return dto;
+    }
+
+    // 标记Entity列表 → 标记DTO列表
+    public static List<AnnotationDTO> annotationEntityListToDtoList(List<AnnotationEntity> entityList) {
+        return entityList.stream().map(entity -> {
+            try {
+                return annotationEntityToDto(entity);
+            } catch (Exception e) {
+                throw new RuntimeException("标记Entity转DTO失败", e);
+            }
+        }).collect(Collectors.toList());
+    }
+}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/annotation/util/JsonSerializeUtil.java b/ard-sample/src/main/java/com/ard/sample/annotation/util/JsonSerializeUtil.java
new file mode 100644
index 0000000..4f6c3bd
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/annotation/util/JsonSerializeUtil.java
@@ -0,0 +1,32 @@
+package com.ard.sample.annotation.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ard.sample.annotation.model.dto.AnnotationPointsDTO;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class JsonSerializeUtil {
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    // 序列化:List<AnnotationPointsDTO> → byte[]
+    public static byte[] serialize(List<AnnotationPointsDTO> points) throws JsonProcessingException {
+        if (points == null) {
+            return null;
+        }
+        String json = objectMapper.writeValueAsString(points);
+        return json.getBytes();
+    }
+
+    // 反序列化:byte[] → List<AnnotationPointsDTO>
+    public static List<AnnotationPointsDTO> deserialize(byte[] bytes) throws JsonProcessingException {
+        if (bytes == null || bytes.length == 0) {
+            return null;
+        }
+        String json = new String(bytes);
+        return objectMapper.readValue(json,
+                objectMapper.getTypeFactory().constructCollectionType(List.class, AnnotationPointsDTO.class));
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3