调整了相关字段信息,并1. 根据用户id获取用户未完成上传任务的作品的接口2. 根据用户id获取已完成上传任务的作品3. 根据用户id获取用户角色, 至少要有管理员,同时合并了帖子功能与作品

Change-Id: Id2c664cc212f2aef1f99054d386d28083fe13d92
diff --git a/src/main/java/edu/bjtu/groupone/backend/api/UserRoleController.java b/src/main/java/edu/bjtu/groupone/backend/api/UserRoleController.java
new file mode 100644
index 0000000..56abf19
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/api/UserRoleController.java
@@ -0,0 +1,31 @@
+package edu.bjtu.groupone.backend.api;
+
+import edu.bjtu.groupone.backend.domain.UserRole;
+import edu.bjtu.groupone.backend.service.UserRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/user-roles")
+public class UserRoleController {
+    @Autowired
+    private UserRoleService userRoleService;
+
+    @GetMapping("/{userId}")
+    public ResponseEntity<List<UserRole>> getUserRoles(@PathVariable Long userId) {
+        return ResponseEntity.ok(userRoleService.getUserRolesByUserId(userId));
+    }
+
+    @GetMapping("/{userId}/is-admin")
+    public ResponseEntity<Boolean> isAdmin(@PathVariable Long userId) {
+        return ResponseEntity.ok(userRoleService.isAdmin(userId));
+    }
+
+    @PostMapping
+    public ResponseEntity<Integer> addUserRole(@RequestBody UserRole userRole) {
+        return ResponseEntity.ok(userRoleService.addUserRole(userRole));
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/api/WorkController.java b/src/main/java/edu/bjtu/groupone/backend/api/WorkController.java
index d15c4ce..28cd43d 100644
--- a/src/main/java/edu/bjtu/groupone/backend/api/WorkController.java
+++ b/src/main/java/edu/bjtu/groupone/backend/api/WorkController.java
@@ -1,8 +1,11 @@
 package edu.bjtu.groupone.backend.api;
 
 import edu.bjtu.groupone.backend.domain.dto.WorkResponse;
+import edu.bjtu.groupone.backend.domain.dto.WorkDetailResponse;
 import edu.bjtu.groupone.backend.domain.entity.Result;
 import edu.bjtu.groupone.backend.domain.entity.Work;
+import edu.bjtu.groupone.backend.domain.entity.Version;
+import edu.bjtu.groupone.backend.domain.entity.Comment;
 import edu.bjtu.groupone.backend.service.WorkService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -34,36 +37,70 @@
             @Parameter(description = "每页数量", example = "20") @RequestParam(defaultValue = "20") int size) {
         return ResponseEntity.ok(workService.getWorks(categoryId, page, size));
     }
-    @PostMapping
-    @Operation(summary = "创建新作品")
-    public ResponseEntity<String> createWork(@RequestBody Work work) {
-        workService.addWork(work);
-        return ResponseEntity.ok("作品创建成功");
+
+    @GetMapping("/uncompleted/{userId}")
+    public ResponseEntity<List<Work>> getUncompletedWorks(@PathVariable Long userId) {
+        return ResponseEntity.ok(workService.getUncompletedWorksByUserId(userId));
     }
 
-    // 删除作品
-    @DeleteMapping("/{id}")
-    @Operation(summary = "删除作品")
-    public ResponseEntity<String> deleteWork(@PathVariable Long id) {
-        workService.deleteWork(id);
-        return ResponseEntity.ok("作品删除成功");
+    @GetMapping("/completed/{userId}")
+    public ResponseEntity<List<Work>> getCompletedWorks(@PathVariable Long userId) {
+        return ResponseEntity.ok(workService.getCompletedWorksByUserId(userId));
     }
 
-    // 更新作品
-    @PutMapping("/{id}")
-    @Operation(summary = "更新作品信息")
-    public ResponseEntity<String> updateWork(@PathVariable Long id, @RequestBody Work work) {
-        work.setId(id);
-        workService.updateWork(work);
-        return ResponseEntity.ok("作品更新成功");
-    }
-
-    // 获取单个作品
     @GetMapping("/{id}")
-    @Operation(summary = "获取作品详情")
-    public ResponseEntity<Work> getWork(@PathVariable Long id) {
+    public ResponseEntity<Work> getWorkById(@PathVariable Long id) {
         return ResponseEntity.ok(workService.getWorkById(id));
     }
+
+    @GetMapping("/detail/{id}")
+    public ResponseEntity<WorkDetailResponse> getWorkDetail(@PathVariable Long id) {
+        WorkDetailResponse dto = workService.getWorkDetailById(id);
+        if (dto == null) return ResponseEntity.notFound().build();
+        return ResponseEntity.ok(dto);
+    }
+
+    @PostMapping
+    @Operation(summary = "创建新作品")
+    public ResponseEntity<Integer> createWork(@RequestBody Work work) {
+        return ResponseEntity.ok(workService.createWork(work));
+    }
+
+    @PutMapping("/{id}")
+    @Operation(summary = "更新作品信息")
+    public ResponseEntity<Integer> updateWork(@PathVariable Long id, @RequestBody Work work) {
+        work.setId(id);
+        return ResponseEntity.ok(workService.updateWork(work));
+    }
+
+    @DeleteMapping("/{id}")
+    @Operation(summary = "删除作品")
+    public ResponseEntity<Integer> deleteWork(@PathVariable Long id) {
+        return ResponseEntity.ok(workService.deleteWork(id));
+    }
+
+    @PostMapping("/{workId}/versions")
+    public ResponseEntity<Integer> addVersion(@PathVariable Long workId, @RequestBody Version version) {
+        version.setWorkId(workId);
+        return ResponseEntity.ok(workService.addVersion(version));
+    }
+
+    @DeleteMapping("/versions/{id}")
+    public ResponseEntity<Integer> deleteVersion(@PathVariable Long id) {
+        return ResponseEntity.ok(workService.deleteVersion(id));
+    }
+
+    @PostMapping("/{workId}/comments")
+    public ResponseEntity<Integer> addComment(@PathVariable Long workId, @RequestBody Comment comment) {
+        comment.setPostId(workId);
+        return ResponseEntity.ok(workService.addComment(comment));
+    }
+
+    @DeleteMapping("/comments/{id}")
+    public ResponseEntity<Integer> deleteComment(@PathVariable Long id) {
+        return ResponseEntity.ok(workService.deleteComment(id));
+    }
+
     @Operation(
             summary = "根据作者查询所有作品",
             description = "根据作者名称查询其所有作品",
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/UserRole.java b/src/main/java/edu/bjtu/groupone/backend/domain/UserRole.java
new file mode 100644
index 0000000..18e7b6a
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/UserRole.java
@@ -0,0 +1,11 @@
+package edu.bjtu.groupone.backend.domain;
+
+import lombok.Data;
+
+@Data
+public class UserRole {
+    private Long id;
+    private Long userId;
+    private String role; // ADMIN, USER
+    private String createTime;
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/Work.java b/src/main/java/edu/bjtu/groupone/backend/domain/Work.java
new file mode 100644
index 0000000..673acb3
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/Work.java
@@ -0,0 +1,35 @@
+package edu.bjtu.groupone.backend.domain;
+
+import lombok.Data;
+import java.util.List;
+
+@Data
+public class Work {
+    private Long id;
+    private String title;
+    private String description;
+    private Long userId;
+    private String status; // UPLOADING, COMPLETED
+    private List<Version> versions;
+    private List<Comment> comments;
+    private String createTime;
+    private String updateTime;
+}
+
+@Data
+class Version {
+    private Long id;
+    private Long workId;
+    private String versionNumber;
+    private String fileUrl;
+    private String createTime;
+}
+
+@Data
+class Comment {
+    private Long id;
+    private Long workId;
+    private Long userId;
+    private String content;
+    private String createTime;
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/dto/WorkDetailResponse.java b/src/main/java/edu/bjtu/groupone/backend/domain/dto/WorkDetailResponse.java
new file mode 100644
index 0000000..84065b0
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/dto/WorkDetailResponse.java
@@ -0,0 +1,59 @@
+package edu.bjtu.groupone.backend.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import java.util.List;
+
+@Data
+public class WorkDetailResponse {
+    @JsonProperty("artworkId")
+    private String artworkId;
+    @JsonProperty("artworkCover")
+    private String artworkCover;
+    private String author;
+    @JsonProperty("authorId")
+    private String authorId;
+    @JsonProperty("artworkName")
+    private String artworkName;
+    @JsonProperty("artworkCategory")
+    private String artworkCategory;
+    @JsonProperty("comments")
+    private List<CommentDTO> comments;
+    @JsonProperty("artworkDescription")
+    private String artworkDescription;
+    @JsonProperty("versionList")
+    private List<VersionDTO> versionList;
+    @JsonProperty("usersSeedingCurrently")
+    private List<UserDTO> usersSeedingCurrently;
+    @JsonProperty("usersSeedingHistory")
+    private List<HistoryUserDTO> usersSeedingHistory;
+
+    @Data
+    public static class CommentDTO {
+        private String id;
+        private String content;
+        private String author;
+        private String authorId;
+        private String createdAt;
+        private List<CommentDTO> child;
+    }
+
+    @Data
+    public static class VersionDTO {
+        private String version;
+        private String seedFile;
+        private String versionDescription;
+    }
+
+    @Data
+    public static class UserDTO {
+        private String username;
+        private String userId;
+    }
+
+    @Data
+    public static class HistoryUserDTO {
+        private String username;
+        private String uploadTotal;
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/entity/Version.java b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Version.java
new file mode 100644
index 0000000..1cd1706
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Version.java
@@ -0,0 +1,13 @@
+package edu.bjtu.groupone.backend.domain.entity;
+
+import lombok.Data;
+
+@Data
+public class Version {
+    private Long id;
+    private Long workId;
+    private String versionNumber;
+    private String fileUrl;
+    private String description; // 版本描述
+    private String createTime;
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/domain/entity/Work.java b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Work.java
index ca3b365..1512c2f 100644
--- a/src/main/java/edu/bjtu/groupone/backend/domain/entity/Work.java
+++ b/src/main/java/edu/bjtu/groupone/backend/domain/entity/Work.java
@@ -34,4 +34,7 @@
 
     @Column(name = "create_time")
     private String createTime;
+
+    @Column(length = 255)
+    private String cover;
 }
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/UserRoleMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/UserRoleMapper.java
new file mode 100644
index 0000000..01efb71
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/UserRoleMapper.java
@@ -0,0 +1,16 @@
+package edu.bjtu.groupone.backend.mapper;
+
+import edu.bjtu.groupone.backend.domain.UserRole;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface UserRoleMapper {
+    @Select("SELECT * FROM user_role WHERE user_id = #{userId}")
+    List<UserRole> getUserRolesByUserId(Long userId);
+
+    @Insert("INSERT INTO user_role (user_id, role, create_time) VALUES (#{userId}, #{role}, NOW())")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insertUserRole(UserRole userRole);
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/WorkMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/WorkMapper.java
new file mode 100644
index 0000000..2ec1050
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/WorkMapper.java
@@ -0,0 +1,62 @@
+package edu.bjtu.groupone.backend.mapper;
+
+import edu.bjtu.groupone.backend.domain.entity.Work;
+import edu.bjtu.groupone.backend.domain.entity.Version;
+import edu.bjtu.groupone.backend.domain.entity.Comment;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface WorkMapper {
+    @Select("SELECT * FROM work WHERE user_id = #{userId} AND status = 'UPLOADING'")
+    List<Work> getUncompletedWorksByUserId(Long userId);
+
+    @Select("SELECT * FROM work WHERE user_id = #{userId} AND status = 'COMPLETED'")
+    List<Work> getCompletedWorksByUserId(Long userId);
+
+    @Select("SELECT * FROM work WHERE id = #{id}")
+    Work getWorkById(Long id);
+
+    @Insert("INSERT INTO work (title, description, user_id, status, create_time, update_time) " +
+            "VALUES (#{title}, #{description}, #{userId}, #{status}, NOW(), NOW())")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insertWork(Work work);
+
+    @Update("UPDATE work SET title = #{title}, description = #{description}, status = #{status}, " +
+            "update_time = NOW() WHERE id = #{id}")
+    int updateWork(Work work);
+
+    @Delete("DELETE FROM work WHERE id = #{id}")
+    int deleteWork(Long id);
+
+    @Insert("INSERT INTO version (work_id, version_number, file_url, description, create_time) " +
+            "VALUES (#{workId}, #{versionNumber}, #{fileUrl}, #{description}, NOW())")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insertVersion(Version version);
+
+    @Delete("DELETE FROM version WHERE id = #{id}")
+    int deleteVersion(Long id);
+
+    @Insert("INSERT INTO comment (work_id, user_id, content, create_time) " +
+            "VALUES (#{workId}, #{userId}, #{content}, NOW())")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insertComment(Comment comment);
+
+    @Delete("DELETE FROM comment WHERE id = #{id}")
+    int deleteComment(Long id);
+
+    // 新增聚合查询
+    @Select("SELECT content FROM comment WHERE work_id = #{workId}")
+    List<String> getCommentsByWorkId(Long workId);
+
+    @Select("SELECT version_number, file_url, description FROM version WHERE work_id = #{workId}")
+    List<Map<String, Object>> getVersionsByWorkId(Long workId);
+
+    @Select("SELECT u.username, u.user_id FROM seeding_user su JOIN user u ON su.user_id = u.user_id WHERE su.work_id = #{workId} AND su.status = 'seeding'")
+    List<Map<String, Object>> getSeedingUsersByWorkId(Long workId);
+
+    @Select("SELECT u.username, u.user_id, su.uploaded FROM seeding_user su JOIN user u ON su.user_id = u.user_id WHERE su.work_id = #{workId} AND su.status = 'history'")
+    List<Map<String, Object>> getHistorySeedingUsersByWorkId(Long workId);
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/UserRoleService.java b/src/main/java/edu/bjtu/groupone/backend/service/UserRoleService.java
new file mode 100644
index 0000000..6c303c0
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/UserRoleService.java
@@ -0,0 +1,27 @@
+package edu.bjtu.groupone.backend.service;
+
+import edu.bjtu.groupone.backend.domain.UserRole;
+import edu.bjtu.groupone.backend.mapper.UserRoleMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class UserRoleService {
+    @Autowired
+    private UserRoleMapper userRoleMapper;
+
+    public List<UserRole> getUserRolesByUserId(Long userId) {
+        return userRoleMapper.getUserRolesByUserId(userId);
+    }
+
+    public boolean isAdmin(Long userId) {
+        List<UserRole> roles = getUserRolesByUserId(userId);
+        return roles.stream().anyMatch(role -> "ADMIN".equals(role.getRole()));
+    }
+
+    public int addUserRole(UserRole userRole) {
+        return userRoleMapper.insertUserRole(userRole);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/WorkService.java b/src/main/java/edu/bjtu/groupone/backend/service/WorkService.java
index 7aeebf1..5693bb1 100644
--- a/src/main/java/edu/bjtu/groupone/backend/service/WorkService.java
+++ b/src/main/java/edu/bjtu/groupone/backend/service/WorkService.java
@@ -1,30 +1,57 @@
 package edu.bjtu.groupone.backend.service;
 
-//import edu.bjtu.groupone.backend.model.Work;
 import edu.bjtu.groupone.backend.domain.dto.WorkResponse;
 import edu.bjtu.groupone.backend.domain.entity.Work;
 import edu.bjtu.groupone.backend.mapper.WorkMybatisMapper;
+import edu.bjtu.groupone.backend.mapper.WorkMapper;
+//import edu.bjtu.groupone.backend.model.Work;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
+
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.stream.Collectors;
+
+import edu.bjtu.groupone.backend.domain.dto.WorkDetailResponse;
+import edu.bjtu.groupone.backend.domain.entity.Version;
+import edu.bjtu.groupone.backend.domain.entity.Comment;
+import edu.bjtu.groupone.backend.mapper.UserMapper;
+import edu.bjtu.groupone.backend.domain.entity.Post;
+import edu.bjtu.groupone.backend.mapper.PostMapper;
+import edu.bjtu.groupone.backend.mapper.CommentMapper;
+import edu.bjtu.groupone.backend.domain.entity.User;
+
 @Service
 public class WorkService {
     @Autowired
     private WorkMybatisMapper workMybatisMapper;
+
     @Autowired
     private CategoryService categoryService;
+
+    @Autowired
+    private WorkMapper workMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private PostMapper postMapper;
+
+    @Autowired
+    private CommentMapper commentMapper;
+
     public Page<WorkResponse> getWorks(Long categoryId, int page, int size) {
         List<Long> categoryIds = categoryService.getAllSubcategoryIds(categoryId);
         Pageable pageable = PageRequest.of(page-1, size);
         return workMybatisMapper.findByCategoryIdIn(categoryIds, pageable)
                 .map(this::convertToResponse);
     }
+
     private WorkResponse convertToResponse(Work work) {
         return new WorkResponse(
                 work.getId(),
@@ -36,27 +63,124 @@
                 work.getCreateTime()
         );
     }
+
     public void addWork(Work work) {
         work.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
         workMybatisMapper.save(work);
     }
-    public void deleteWork(Long id) {
-        workMybatisMapper.deleteById(id);
-    }
-    public void updateWork(Work work) {
-        Work existing = workMybatisMapper.findById(work.getId());
-        if (existing != null) {
-            work.setCreateTime(existing.getCreateTime());
-        }
-        workMybatisMapper.update(work);
-    }
+
+ 
+
     public Work getWorkById(Long id) {
         return workMybatisMapper.findById(id);
     }
+
     public List<WorkResponse> getWorksByAuthor(String author) {
         List<Work> works = workMybatisMapper.findByAuthor(author);
         return works.stream()
                 .map(this::convertToResponse)
                 .collect(Collectors.toList());
     }
+
+    public List<Work> getUncompletedWorksByUserId(Long userId) {
+        return workMapper.getUncompletedWorksByUserId(userId);
+    }
+
+    public List<Work> getCompletedWorksByUserId(Long userId) {
+        return workMapper.getCompletedWorksByUserId(userId);
+    }
+
+    public int createWork(Work work) {
+        return workMapper.insertWork(work);
+    }
+
+    public int updateWork(Work work) {
+        return workMapper.updateWork(work);
+    }
+
+    public int deleteWork(Long id) {
+        return workMapper.deleteWork(id);
+    }
+
+    public int addVersion(Version version) {
+        return workMapper.insertVersion(version);
+    }
+
+    public int deleteVersion(Long id) {
+        return workMapper.deleteVersion(id);
+    }
+
+    public int addComment(Comment comment) {
+        return workMapper.insertComment(comment);
+    }
+
+    public int deleteComment(Long id) {
+        return workMapper.deleteComment(id);
+    }
+
+    public WorkDetailResponse getWorkDetailById(Long workId) {
+        Work work = workMapper.getWorkById(workId);
+        if (work == null) return null;
+        WorkDetailResponse dto = new WorkDetailResponse();
+        // 基本字段映射
+        dto.setArtworkId(work.getId() != null ? work.getId().toString() : null);
+        dto.setArtworkCover(work.getCover());
+        dto.setAuthor(work.getAuthor());
+        dto.setAuthorId(work.getAuthor()); // 如需userId请调整
+        dto.setArtworkName(work.getTitle());
+        dto.setArtworkCategory(work.getCategory() != null ? work.getCategory().getName() : null);
+        dto.setArtworkDescription(work.getDescription());
+        // 版本列表
+        List<java.util.Map<String, Object>> versionList = workMapper.getVersionsByWorkId(workId);
+        List<WorkDetailResponse.VersionDTO> versions = new java.util.ArrayList<>();
+        for (java.util.Map<String, Object> v : versionList) {
+            WorkDetailResponse.VersionDTO vi = new WorkDetailResponse.VersionDTO();
+            vi.setVersion((String)v.get("version_number"));
+            vi.setSeedFile((String)v.get("file_url"));
+            vi.setVersionDescription((String)v.get("description"));
+            versions.add(vi);
+        }
+        dto.setVersionList(versions);
+        // 正在做种用户
+        List<java.util.Map<String, Object>> seedingList = workMapper.getSeedingUsersByWorkId(workId);
+        List<WorkDetailResponse.UserDTO> seedingUsers = new java.util.ArrayList<>();
+        for (java.util.Map<String, Object> u : seedingList) {
+            WorkDetailResponse.UserDTO su = new WorkDetailResponse.UserDTO();
+            su.setUsername((String)u.get("username"));
+            su.setUserId(String.valueOf(u.get("user_id")));
+            seedingUsers.add(su);
+        }
+        dto.setUsersSeedingCurrently(seedingUsers);
+        // 历史做种用户
+        List<java.util.Map<String, Object>> historyList = workMapper.getHistorySeedingUsersByWorkId(workId);
+        List<WorkDetailResponse.HistoryUserDTO> historyUsers = new java.util.ArrayList<>();
+        for (java.util.Map<String, Object> u : historyList) {
+            WorkDetailResponse.HistoryUserDTO hu = new WorkDetailResponse.HistoryUserDTO();
+            hu.setUsername((String)u.get("username"));
+            hu.setUploadTotal((String)u.get("uploaded"));
+            historyUsers.add(hu);
+        }
+        dto.setUsersSeedingHistory(historyUsers);
+        // 评论递归组装
+        Post post = postMapper.selectPostById(workId);
+        if (post != null) {
+            List<Comment> commentList = commentMapper.selectCommentsByPostId(post.getPostId());
+            List<WorkDetailResponse.CommentDTO> commentDTOList = new java.util.ArrayList<>();
+            for (Comment c : commentList) {
+                WorkDetailResponse.CommentDTO cdto = new WorkDetailResponse.CommentDTO();
+                cdto.setId(c.getCommentId() != null ? c.getCommentId().toString() : null);
+                cdto.setContent(c.getContent());
+                User user = userMapper.selectById((long)c.getUserId());
+                cdto.setAuthor(user != null ? user.getUsername() : null);
+                cdto.setAuthorId(String.valueOf(c.getUserId()));
+                cdto.setCreatedAt(c.getCreateTime());
+                cdto.setChild(new java.util.ArrayList<>()); // 如有子评论可递归
+                commentDTOList.add(cdto);
+            }
+            dto.setComments(commentDTOList);
+        } else {
+            dto.setComments(new java.util.ArrayList<>());
+        }
+        return dto;
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/edu/bjtu/groupone/backend/WorkServiceDetailTest.java b/src/test/java/edu/bjtu/groupone/backend/WorkServiceDetailTest.java
new file mode 100644
index 0000000..137e43f
--- /dev/null
+++ b/src/test/java/edu/bjtu/groupone/backend/WorkServiceDetailTest.java
@@ -0,0 +1,133 @@
+package edu.bjtu.groupone.backend;
+
+import edu.bjtu.groupone.backend.domain.dto.WorkDetailResponse;
+import edu.bjtu.groupone.backend.domain.entity.*;
+import edu.bjtu.groupone.backend.mapper.*;
+import edu.bjtu.groupone.backend.service.WorkService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class WorkServiceDetailTest {
+
+    @Mock
+    private WorkMapper workMapper;
+    @Mock
+    private UserMapper userMapper;
+    @Mock
+    private PostMapper postMapper;
+    @Mock
+    private CommentMapper commentMapper;
+
+    @InjectMocks
+    private WorkService workService;
+
+    private Work work;
+    private Post post;
+
+    @BeforeEach
+    void setUp() {
+        Category category = new Category(1L, "分区1", null);
+        work = Work.builder()
+                .id(12345L)
+                .title("作品标题")
+                .author("作者A")
+                .views(100)
+                .category(category)
+                .description("作品描述")
+                .createTime("2024-05-01")
+                .cover("https://via.placeholder.com/200/")
+                .build();
+
+        post = new Post();
+        post.setPostId(12345L);
+        post.setUserId(1);
+        post.setTitle("帖子标题");
+        post.setContent("帖子内容");
+        post.setCreateTime("2024-05-01");
+        post.setViews(10);
+    }
+
+    @Test
+    void getWorkDetailById_shouldReturnAggregatedDetail() {
+        // mock 作品
+        when(workMapper.getWorkById(12345L)).thenReturn(work);
+        // mock 版本
+        Map<String, Object> version1 = new HashMap<>();
+        version1.put("version_number", "1.0");
+        version1.put("file_url", "file1.torrent");
+        version1.put("description", "版本1描述");
+        Map<String, Object> version2 = new HashMap<>();
+        version2.put("version_number", "1.1");
+        version2.put("file_url", "file2.torrent");
+        version2.put("description", "版本1.1描述");
+        when(workMapper.getVersionsByWorkId(12345L)).thenReturn(Arrays.asList(version1, version2));
+        // mock 正在做种用户
+        Map<String, Object> seeder = new HashMap<>();
+        seeder.put("username", "用户A");
+        seeder.put("user_id", 1L);
+        when(workMapper.getSeedingUsersByWorkId(12345L)).thenReturn(Collections.singletonList(seeder));
+        // mock 历史做种用户
+        Map<String, Object> historySeeder = new HashMap<>();
+        historySeeder.put("username", "用户B");
+        historySeeder.put("user_id", 2L);
+        historySeeder.put("uploaded", "10GB");
+        when(workMapper.getHistorySeedingUsersByWorkId(12345L)).thenReturn(Collections.singletonList(historySeeder));
+        // mock 帖子
+        when(postMapper.selectPostById(anyLong())).thenReturn(post);
+        // mock 评论
+        Comment comment = new Comment(1L, 12345L, 2, "评论内容", "2024-05-01");
+        when(commentMapper.selectCommentsByPostId(12345L)).thenReturn(Collections.singletonList(comment));
+        User commentUser = new User();
+        commentUser.setUserId(2);
+        commentUser.setUsername("评论者");
+        when(userMapper.selectById(2L)).thenReturn(commentUser);
+
+        WorkDetailResponse detail = workService.getWorkDetailById(12345L);
+
+        assertNotNull(detail);
+        assertEquals("12345", detail.getArtworkId());
+        assertEquals("https://via.placeholder.com/200/", detail.getArtworkCover());
+        assertEquals("作者A", detail.getAuthor());
+        assertEquals("作者A", detail.getAuthorId()); // 如需userId请调整
+        assertEquals("作品标题", detail.getArtworkName());
+        assertEquals("分区1", detail.getArtworkCategory());
+        assertEquals("作品描述", detail.getArtworkDescription());
+        // 版本断言
+        assertEquals(2, detail.getVersionList().size());
+        assertEquals("1.0", detail.getVersionList().get(0).getVersion());
+        assertEquals("file1.torrent", detail.getVersionList().get(0).getSeedFile());
+        assertEquals("版本1描述", detail.getVersionList().get(0).getVersionDescription());
+        // 做种用户断言
+        assertEquals(1, detail.getUsersSeedingCurrently().size());
+        assertEquals("用户A", detail.getUsersSeedingCurrently().get(0).getUsername());
+        assertEquals("1", detail.getUsersSeedingCurrently().get(0).getUserId());
+        // 历史做种用户断言
+        assertEquals(1, detail.getUsersSeedingHistory().size());
+        assertEquals("用户B", detail.getUsersSeedingHistory().get(0).getUsername());
+        assertEquals("10GB", detail.getUsersSeedingHistory().get(0).getUploadTotal());
+        // 评论断言
+        assertEquals(1, detail.getComments().size());
+        assertEquals("1", detail.getComments().get(0).getId());
+        assertEquals("评论内容", detail.getComments().get(0).getContent());
+        assertEquals("评论者", detail.getComments().get(0).getAuthor());
+        assertEquals("2", detail.getComments().get(0).getAuthorId());
+        assertEquals("2024-05-01", detail.getComments().get(0).getCreatedAt());
+        assertTrue(detail.getComments().get(0).getChild().isEmpty());
+    }
+
+    @Test
+    void getWorkDetailById_shouldReturnNullIfWorkNotFound() {
+        when(workMapper.getWorkById(999L)).thenReturn(null);
+        assertNull(workService.getWorkDetailById(999L));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/bjtu/groupone/backend/WorkServiceTest.java b/src/test/java/edu/bjtu/groupone/backend/WorkServiceTest.java
index 44a05b6..118ff01 100644
--- a/src/test/java/edu/bjtu/groupone/backend/WorkServiceTest.java
+++ b/src/test/java/edu/bjtu/groupone/backend/WorkServiceTest.java
@@ -6,6 +6,7 @@
 import edu.bjtu.groupone.backend.domain.entity.Category;
 import edu.bjtu.groupone.backend.domain.entity.Work;
 import edu.bjtu.groupone.backend.mapper.WorkMybatisMapper;
+import edu.bjtu.groupone.backend.mapper.WorkMapper;
 import edu.bjtu.groupone.backend.service.CategoryService;
 import edu.bjtu.groupone.backend.service.PeerTrafficService;
 import edu.bjtu.groupone.backend.service.WorkService;
@@ -38,6 +39,9 @@
     private WorkMybatisMapper workMybatisMapper;
 
     @Mock
+    private WorkMapper workMapper;
+
+    @Mock
     private CategoryService categoryService;
 
     @InjectMocks
@@ -61,8 +65,24 @@
 
         Category category = new Category(1L, "文学艺术", null);
         works = Arrays.asList(
-                new Work(1L, "《我的世界》", "张三", 1234, category, "一部关于...", "2023-06-15"),
-                new Work(2L, "《你的世界》", "张三", 567, category, "另一部关于...", "2023-06-16")
+                Work.builder()
+                        .id(1L)
+                        .title("《我的世界》")
+                        .author("张三")
+                        .views(1234)
+                        .category(category)
+                        .description("一部关于...")
+                        .createTime("2023-06-15")
+                        .build(),
+                Work.builder()
+                        .id(2L)
+                        .title("《你的世界》")
+                        .author("张三")
+                        .views(567)
+                        .category(category)
+                        .description("另一部关于...")
+                        .createTime("2023-06-16")
+                        .build()
         );
     }
 
@@ -73,15 +93,21 @@
     }
 
     @Test
+    void createWork_shouldCallMapper() {
+        workService.createWork(testWork);
+        verify(workMapper, times(1)).insertWork(testWork);
+    }
+
+    @Test
     void deleteWork_shouldCallMapper() {
         workService.deleteWork(1L);
-        verify(workMybatisMapper, times(1)).deleteById(1L);
+        verify(workMapper, times(1)).deleteWork(1L);
     }
 
     @Test
     void updateWork_shouldCallMapper() {
         workService.updateWork(testWork);
-        verify(workMybatisMapper, times(1)).update(testWork);
+        verify(workMapper, times(1)).updateWork(testWork);
     }
 
     @Test