Merge "增加docker配置"
diff --git a/pom.xml b/pom.xml
index 222d677..5d4fd71 100644
--- a/pom.xml
+++ b/pom.xml
@@ -215,6 +215,14 @@
                 <scope>test</scope>
             </dependency>
 
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>3.5.2</version>
+            </dependency>
+
+
+
         </dependencies>
     </dependencyManagement>
 
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index aa3f738..f147ecc 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -114,6 +114,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-system</artifactId>
+            <version>3.8.8</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
index 2869856..8a5fdfe 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
@@ -1,14 +1,26 @@
 package com.ruoyi;
 
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
 
 /**
  * 启动程序
  * 
  * @author ruoyi
  */
+@ComponentScan(basePackages = {
+        "com.ruoyi",
+        "bounty"  // 新增您的包路径
+})
+// 关键添加:扩大MyBatis接口扫描范围
+@MapperScan(basePackages = {
+        "com.ruoyi.**.mapper",
+        "bounty.mapper"  // 如果bounty下有Mapper接口
+})
+
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 public class RuoYiApplication {
     public static void main(String[] args) {
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/WebConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/WebConfig.java
new file mode 100644
index 0000000..55d2e34
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/WebConfig.java
@@ -0,0 +1,14 @@
+package com.ruoyi;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/uploads/**")
+                .addResourceLocations("file:uploads/");
+    }
+}
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 3709e4e..98b03fd 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -55,16 +55,10 @@
     basename: i18n/messages
   profiles:
     active: druid
-  # 静态资源映射
-  mvc:
-    static-path-pattern: /static/**
-  web:
-    resources:
-      static-locations: classpath:/static/
   # 文件上传
   servlet:
     multipart:
-      # 单个文件大小
+      enabled: true
       max-file-size: 10MB
       # 设置总上传的文件大小
       max-request-size: 20MB
@@ -96,6 +90,10 @@
           max-active: 8
           # #连接池最大阻塞等待时间(使用负值表示没有限制)
           max-wait: -1ms
+  mvc:
+    static-path-pattern: /**
+    resources:
+      static-locations: file:uploads/
 
 # token配置
 token:
@@ -110,7 +108,7 @@
 mybatis:
   # 搜索指定包别名
   typeAliasesPackage: com.ruoyi.**.domain
-  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  # 重点修改1:扩展XML扫描路径
   mapperLocations: classpath*:mapper/**/*Mapper.xml
   # 加载全局的配置文件
   configLocation: classpath:mybatis/mybatis-config.xml
@@ -119,8 +117,8 @@
 mybatis-plus:
   # 搜索指定包别名
   typeAliasesPackage: com.ruoyi.**.domain
-  # 配置mapper的扫描,找到所有的mapper.xml映射文件
-  mapperLocations: classpath:mapper/*.xml
+  # 重点修改2:覆盖原路径,支持多模块
+  mapper-locations: classpath*:mapper/**/*.xml
   configLocation: classpath:mybatis/mybatis-config.xml
   # 全局配置
   global-config:
@@ -132,7 +130,7 @@
     db-column-underline: true
     # 刷新mapper 调试神器
     refresh-mapper: true
-    # 配置
+    # 重点修改3:明确开启驼峰映射
     configuration:
       # 驼峰式命名
       map-underscore-to-camel-case: true
@@ -145,6 +143,9 @@
       # 允许 JDBC 支持自动生成主键
       use-generated-keys: true
 
+# 新增文件上传路径配置(放在末尾)
+file:
+  upload-dir: /var/www/uploads/
 
 # PageHelper分页插件
 pagehelper:
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountyControllerTest.java b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountyControllerTest.java
new file mode 100644
index 0000000..121d59e
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountyControllerTest.java
@@ -0,0 +1,140 @@
+// BountyControllerTest.java
+package com.ruoyi.web.controller.bounty;
+
+import bounty.controller.BountyController;
+import bounty.domain.Bounty;
+import bounty.service.BountyService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@ExtendWith(MockitoExtension.class)
+class BountyControllerTest {
+
+    @Mock
+    private BountyService bountyService;
+
+    @InjectMocks
+    private BountyController bountyController;
+
+    private MockMvc mockMvc;
+    private ObjectMapper objectMapper;
+
+    @BeforeEach
+    void setUp() {
+        // 创建模拟的安全上下文
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(1L);
+        sysUser.setUserName("testUser");
+
+        LoginUser loginUser = new LoginUser(1L, 1L, sysUser, new HashSet<>());
+
+        UsernamePasswordAuthenticationToken authToken =
+                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+        SecurityContextHolder.getContext().setAuthentication(authToken);
+
+        mockMvc = MockMvcBuilders.standaloneSetup(bountyController).build();
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void testSaveBountySuccess() throws Exception {
+        Bounty bounty = new Bounty();
+        bounty.setTitle("测试悬赏");
+
+        when(bountyService.saveBounty(any(Bounty.class))).thenReturn(true);
+
+        mockMvc.perform(post("/bounties")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(bounty)))
+                .andExpect(status().isOk());
+
+        verify(bountyService, times(1)).saveBounty(any(Bounty.class));
+    }
+
+    @Test
+    void testSaveBountyFailure() throws Exception {
+        Bounty bounty = new Bounty();
+        bounty.setTitle("测试悬赏");
+
+        when(bountyService.saveBounty(any(Bounty.class))).thenReturn(false);
+
+        mockMvc.perform(post("/bounties")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(bounty)))
+                .andExpect(status().isInternalServerError());
+
+        verify(bountyService, times(1)).saveBounty(any(Bounty.class));
+    }
+
+    @Test
+    void testGetBounties() throws Exception {
+        List<Bounty> bounties = new ArrayList<>();
+        when(bountyService.getBounties()).thenReturn(bounties);
+
+        mockMvc.perform(get("/bounties"))
+                .andExpect(status().isOk());
+
+        verify(bountyService, times(1)).getBounties();
+    }
+
+    @Test
+    void testGetBountyById() throws Exception {
+        Bounty bounty = new Bounty();
+        bounty.setId(1L);
+        bounty.setTitle("测试悬赏");
+
+        when(bountyService.getBountyById(1L)).thenReturn(bounty);
+
+        mockMvc.perform(get("/bounties/1"))
+                .andExpect(status().isOk());
+
+        verify(bountyService, times(1)).getBountyById(1L);
+    }
+
+    @Test
+    void testGetNonExistentBounty() throws Exception {
+        when(bountyService.getBountyById(999L)).thenReturn(null);
+
+        mockMvc.perform(get("/bounties/999"))
+                .andExpect(status().isNotFound());
+
+        verify(bountyService, times(1)).getBountyById(999L);
+    }
+
+    @Test
+    void testPublishBountySuccess() throws Exception {
+        Bounty bounty = new Bounty();
+        bounty.setTitle("发布测试");
+
+        when(bountyService.publishBounty(any(Bounty.class))).thenReturn(true);
+
+        mockMvc.perform(post("/bounties/publish")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(bounty)))
+                .andExpect(status().isOk());
+
+        verify(bountyService, times(1)).publishBounty(any(Bounty.class));
+    }
+}
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountySubmissionControllerTest.java b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountySubmissionControllerTest.java
new file mode 100644
index 0000000..b7f7f99
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/bounty/BountySubmissionControllerTest.java
@@ -0,0 +1,165 @@
+
+// BountySubmissionControllerTest.java
+package com.ruoyi.web.controller.bounty;
+
+import bounty.controller.BountySubmissionController;
+import bounty.domain.BountySubmission;
+import bounty.service.BountySubmissionService;
+import bounty.service.FileStorageService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import org.hamcrest.Matchers;
+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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@ExtendWith(MockitoExtension.class)
+class BountySubmissionControllerTest {
+
+    @Mock
+    private BountySubmissionService bountySubmissionService;
+
+    @Mock
+    private FileStorageService fileStorageService;
+
+    @InjectMocks
+    private BountySubmissionController bountySubmissionController;
+
+    private MockMvc mockMvc;
+    private ObjectMapper objectMapper;
+
+    @BeforeEach
+    void setUp() {
+        // 创建模拟的安全上下文
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(1L);
+        sysUser.setUserName("testUser");
+
+        LoginUser loginUser = new LoginUser(1L, 1L, sysUser, new HashSet<>());
+
+        UsernamePasswordAuthenticationToken authToken =
+                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+        SecurityContextHolder.getContext().setAuthentication(authToken);
+
+        mockMvc = MockMvcBuilders.standaloneSetup(bountySubmissionController).build();
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void testSaveBountySubmissionSuccess() throws Exception {
+        BountySubmission submission = new BountySubmission();
+        submission.setBountyId(1L);
+
+        String submissionJson = objectMapper.writeValueAsString(submission);
+
+        when(fileStorageService.saveFile(any(MultipartFile.class))).thenReturn("/uploads/test.txt");
+        when(bountySubmissionService.saveBountySubmission(any(BountySubmission.class))).thenReturn(true);
+
+        mockMvc.perform(MockMvcRequestBuilders.multipart("/bounty-submissions")
+                        .file(new MockMultipartFile("submission", "", MediaType.APPLICATION_JSON_VALUE, submissionJson.getBytes()))
+                        .file("file", "test content".getBytes())
+                        .contentType(MediaType.MULTIPART_FORM_DATA))
+                .andExpect(status().isOk());
+
+        verify(fileStorageService, times(1)).saveFile(any(MultipartFile.class));
+        verify(bountySubmissionService, times(1)).saveBountySubmission(any(BountySubmission.class));
+    }
+
+    @Test
+    void testGetBountySubmissions() throws Exception {
+        // 构造分页对象
+        Page<BountySubmission> page = new Page<>(1, 10);
+        page.setTotal(0);
+        page.setRecords(new ArrayList<>());
+
+        // 模拟 service 返回空分页
+        when(bountySubmissionService.getBountySubmissionsByPage(any(Page.class), eq(1L))).thenReturn(page);
+
+        // 发起 GET 请求
+        mockMvc.perform(get("/bounty-submissions")
+                        .param("current", "1")
+                        .param("size", "10")
+                        .param("bountyId", "1"))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    void testAdoptSubmissionSuccess() throws Exception {
+        when(bountySubmissionService.adoptSubmission(1L)).thenReturn(true);
+
+        mockMvc.perform(put("/bounty-submissions/1/adopt"))
+                .andExpect(status().isOk());
+
+        verify(bountySubmissionService, times(1)).adoptSubmission(1L);
+    }
+
+    @Test
+    void testAdoptNonExistentSubmission() throws Exception {
+        when(bountySubmissionService.adoptSubmission(999L)).thenReturn(false);
+
+        mockMvc.perform(put("/bounty-submissions/999/adopt"))
+                .andExpect(status().isInternalServerError());
+
+        verify(bountySubmissionService, times(1)).adoptSubmission(999L);
+    }
+
+    @Test
+    void testUploadAttachment() throws Exception {
+        when(fileStorageService.saveFile(any(MultipartFile.class))).thenReturn("/uploads/test.jpg");
+
+        mockMvc.perform(multipart("/bounty-submissions/upload")
+                        .file("file", "test content".getBytes())
+                        .param("filename", "test.jpg"))
+                .andExpect(status().isOk());
+
+        verify(fileStorageService, times(1)).saveFile(any(MultipartFile.class));
+    }
+
+    @Test
+    void testDownloadAttachment() throws Exception {
+        Path uploadDir = Paths.get("uploads/");
+        if (!Files.exists(uploadDir)) {
+            Files.createDirectories(uploadDir);
+        }
+
+        Path testFilePath = uploadDir.resolve("test.jpg");
+        if (!Files.exists(testFilePath)) {
+            Files.write(testFilePath, "test content".getBytes());
+        }
+
+       mockMvc.perform(get("/bounty-submissions/download")
+                        .param("filename", "test.jpg"))
+                .andExpect(status().isOk())
+                .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
+                        Matchers.matchesPattern(".*filename=.*test\\.jpg.*")));
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index e0af348..c6db6f8 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -22,6 +22,18 @@
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.2</version>
+        </dependency>
+
 
     </dependencies>
 
diff --git a/ruoyi-system/src/main/java/bounty/controller/BountyController.java b/ruoyi-system/src/main/java/bounty/controller/BountyController.java
new file mode 100644
index 0000000..7c47e1b
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/controller/BountyController.java
@@ -0,0 +1,52 @@
+package bounty.controller;
+
+import bounty.domain.Bounty;
+import bounty.domain.BountySubmission;
+import bounty.service.BountyService;
+import bounty.service.BountySubmissionService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+// 悬赏 Controller 类
+@RestController
+@RequestMapping("/bounties")
+public class BountyController {
+    @Autowired
+    private BountyService bountyService;
+
+    @PostMapping
+    public ResponseEntity<?> saveBounty(@RequestBody Bounty bounty) {
+        boolean result = bountyService.saveBounty(bounty);
+        if (result) {
+            return ResponseEntity.ok().build();
+        } else {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+    @GetMapping
+    // 导入 List 类
+    public List<Bounty> getBounties() {  // 移除分页参数,返回 List 而非 IPage
+        return bountyService.getBounties();  // 调用无分页的 Service 方法
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Bounty> getBountyById(@PathVariable Long id) {
+        Bounty bounty = bountyService.getBountyById(id);
+        if (bounty == null) {
+            return ResponseEntity.notFound().build();
+        } else {
+            return ResponseEntity.ok(bounty);
+        }
+    }
+    // 新增:发布悬赏接口
+    @PostMapping("/publish")
+    public boolean publishBounty(@RequestBody Bounty bounty) {
+        return bountyService.publishBounty(bounty);
+    }
+}
+
diff --git a/ruoyi-system/src/main/java/bounty/controller/BountySubmissionController.java b/ruoyi-system/src/main/java/bounty/controller/BountySubmissionController.java
new file mode 100644
index 0000000..c8098f1
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/controller/BountySubmissionController.java
@@ -0,0 +1,142 @@
+package bounty.controller;
+
+import bounty.domain.BountySubmission;
+import bounty.service.BountySubmissionService;
+import bounty.service.FileStorageService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.http.*;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.MalformedURLException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+// 悬赏提交 Controller 类
+@RestController
+@RequestMapping("/bounty-submissions")
+public class BountySubmissionController {
+
+    @Autowired
+    private FileStorageService fileStorageService;
+
+
+
+    // BountySubmission.java
+    private String attachment; // 存储文件路径(如 /uploads/filename.ext)
+
+
+    @Autowired
+    private BountySubmissionService bountySubmissionService;
+
+
+    @PostMapping
+    public ResponseEntity<?> saveBountySubmission(
+            @RequestPart("submission") @Valid BountySubmission submission,
+            @RequestPart("file") MultipartFile file) {
+
+        try {
+            String filePath = fileStorageService.saveFile(file);
+            submission.setAttachment(filePath);
+            boolean saveResult = bountySubmissionService.saveBountySubmission(submission);
+
+            if (saveResult) {
+                return ResponseEntity.ok().body(Map.of("code", 200, "message", "提交成功", "data", submission));
+            } else {
+                return ResponseEntity.status(500).body(Map.of("code", 500, "message", "提交失败"));
+            }
+        } catch (Exception e) {
+            return ResponseEntity.status(500).body(Map.of("code", 500, "message", e.getMessage()));
+        }
+    }
+
+    @GetMapping
+    public IPage<BountySubmission> getBountySubmissions(
+            @RequestParam(defaultValue = "1") long current,
+            @RequestParam(defaultValue = "10") long size,
+            @RequestParam Long bountyId) {
+        Page<BountySubmission> page = new Page<>(current, size);
+        IPage<BountySubmission> result = bountySubmissionService.getBountySubmissionsByPage(page, bountyId);
+
+        // 👇 新增:打印分页数据
+        System.out.println("【分页数据】bountyId=" + bountyId + ", 数据量=" + result.getRecords().size());
+        result.getRecords().forEach(submission -> {
+            System.out.println("【附件路径】" + submission.getAttachment());
+        });
+
+        return result;
+    }
+
+    // ✅ 仅处理文件上传(保持原逻辑)
+    @PostMapping("/upload")
+    public String uploadAttachment(@RequestParam("file") MultipartFile file) {
+        return fileStorageService.saveFile(file);
+    }
+
+    @GetMapping("/download")
+    public ResponseEntity<Resource> downloadAttachment(@RequestParam String filename) {
+        try {
+            // ✅ 只使用文件名(避免重复拼接)
+            Path uploadDir = Paths.get("uploads/");
+            Path filePath = uploadDir.resolve(filename).normalize();
+
+            if (!filePath.startsWith(uploadDir)) {
+                return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
+            }
+
+            Resource resource = new UrlResource(filePath.toUri());
+
+            if (resource.exists() && resource.isReadable()) {
+                HttpHeaders headers = new HttpHeaders();
+                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+                headers.setContentDisposition(ContentDisposition.builder("attachment")
+                        .filename(filename, StandardCharsets.UTF_8)
+                        .build());
+                return ResponseEntity.ok()
+                        .headers(headers)
+                        .body(resource);
+            } else {
+                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
+            }
+        } catch (MalformedURLException e) {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+        }
+    }
+
+    @PutMapping("/{id}/adopt")
+    public ResponseEntity<?> adoptSubmission(@PathVariable Long id) {
+        try {
+            boolean result = bountySubmissionService.adoptSubmission(id);
+
+            // 使用 HashMap 替代 Map.of()
+            Map<String, Object> response = new HashMap<>();
+            if (result) {
+                response.put("code", 200);
+                response.put("message", "采纳成功");
+                return ResponseEntity.ok(response);
+            } else {
+                response.put("code", 500);
+                response.put("message", "采纳失败");
+                return ResponseEntity.status(500).body(response);
+            }
+        } catch (Exception e) {
+            Map<String, Object> errorResponse = new HashMap<>();
+            errorResponse.put("code", 500);
+            errorResponse.put("message", e.getMessage() != null ? e.getMessage() : "未知错误");
+
+            return ResponseEntity.status(500).body(errorResponse);
+        }
+    }
+
+
+    // BountySubmissionController.java
+
+}
diff --git a/ruoyi-system/src/main/java/bounty/domain/Bounty.java b/ruoyi-system/src/main/java/bounty/domain/Bounty.java
new file mode 100644
index 0000000..f0236f2
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/domain/Bounty.java
@@ -0,0 +1,97 @@
+package bounty.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List; // 导入List类
+
+// 悬赏实体类
+@Data
+@TableName("bounty")
+public class Bounty {
+    @TableId(type = IdType.AUTO) // 添加自增策略
+    private Long id;
+    private String title;
+    private String description;
+    private BigDecimal reward;
+    private Long creator_id;
+    private Date deadline;
+
+    //0代表发布后,还没有人回复这个悬赏   1代表这个悬赏有人回复了
+    private Integer status;
+    private Date createTime;
+
+    private List<BountySubmission> submissions;
+    public void setSubmissions(List<BountySubmission> submissions) {
+        this.submissions = submissions;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public BigDecimal getReward() {
+        return reward;
+    }
+
+    public void setReward(BigDecimal reward) {
+        this.reward = reward;
+    }
+
+    public Long getCreator_id() {
+        return creator_id;
+    }
+
+    public void setCreator_id(Long creator_id) {
+        this.creator_id = creator_id;
+    }
+
+    public Date getDeadline() {
+        return deadline;
+    }
+
+    public void setDeadline(Date deadline) {
+        this.deadline = deadline;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}
+
diff --git a/ruoyi-system/src/main/java/bounty/domain/BountySubmission.java b/ruoyi-system/src/main/java/bounty/domain/BountySubmission.java
new file mode 100644
index 0000000..508e566
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/domain/BountySubmission.java
@@ -0,0 +1,77 @@
+package bounty.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+// 悬赏提交实体类
+@Data
+@TableName("bounty_submissions")
+public class BountySubmission {
+    @TableId
+    private Long id;
+    private Long bountyId;
+    private Long userId;
+    private String content;
+    private String attachment;
+    private Integer status;
+    private Date createTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getBountyId() {
+        return bountyId;
+    }
+
+    public void setBountyId(Long bountyId) {
+        this.bountyId = bountyId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getAttachment() {
+        return attachment;
+    }
+
+    public void setAttachment(String attachment) {
+        this.attachment = attachment;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}
diff --git a/ruoyi-system/src/main/java/bounty/mapper/BountyMapper.java b/ruoyi-system/src/main/java/bounty/mapper/BountyMapper.java
new file mode 100644
index 0000000..aadf152
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/mapper/BountyMapper.java
@@ -0,0 +1,25 @@
+package bounty.mapper;
+
+import bounty.domain.Bounty;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+// 悬赏 Mapper 接口
+@Mapper
+public interface BountyMapper extends BaseMapper<Bounty> {
+// 导入 List 类
+    //IPage<Bounty> selectPage(Page<Bounty> page);
+    // 新增与 XML 中 insert 方法匹配的接口声明
+    int insert(Bounty bounty);
+
+    Bounty selectById(Long id);
+
+    // 新增:定义 selectList 方法
+    List<Bounty> selectList();  //
+
+}
+
diff --git a/ruoyi-system/src/main/java/bounty/mapper/BountySubmissionMapper.java b/ruoyi-system/src/main/java/bounty/mapper/BountySubmissionMapper.java
new file mode 100644
index 0000000..c3eca40
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/mapper/BountySubmissionMapper.java
@@ -0,0 +1,25 @@
+package bounty.mapper;
+
+import bounty.domain.BountySubmission;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+
+import java.util.List;
+import java.util.Map;
+
+// 悬赏提交 Mapper 接口
+@Mapper
+public interface BountySubmissionMapper extends BaseMapper<BountySubmission> {
+    // 与 XML 中 id="getBountySubmissionsByPage" 的 SQL 匹配
+    IPage<BountySubmission> getBountySubmissionsByPage(Page<BountySubmission> page, @Param("bountyId") Long bountyId);
+    // 导入 List 类
+    List<BountySubmission> getSubmissionsByBountyId(@Param("bountyId") Long bountyId);
+}
+
+
diff --git a/ruoyi-system/src/main/java/bounty/service/BountyService.java b/ruoyi-system/src/main/java/bounty/service/BountyService.java
new file mode 100644
index 0000000..a65e487
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/service/BountyService.java
@@ -0,0 +1,26 @@
+package bounty.service;
+
+import bounty.domain.Bounty;
+import bounty.domain.BountySubmission;
+import bounty.mapper.BountyMapper;
+import bounty.mapper.BountySubmissionMapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+
+import java.util.List;
+
+// 悬赏 Service 接口
+public interface BountyService {
+    boolean saveBounty(Bounty bounty);
+    IPage<Bounty> getBountiesByPage(Page<Bounty> page);
+    Bounty getBountyById(Long id);
+     // 新增:发布悬赏(带业务校验)
+     boolean publishBounty(Bounty bounty);
+     List<Bounty> getBounties();  //新增直接获得所有悬赏
+}
+
+
diff --git a/ruoyi-system/src/main/java/bounty/service/BountyServiceImpl.java b/ruoyi-system/src/main/java/bounty/service/BountyServiceImpl.java
new file mode 100644
index 0000000..fd6d635
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/service/BountyServiceImpl.java
@@ -0,0 +1,53 @@
+package bounty.service;
+
+import bounty.domain.Bounty;
+import bounty.mapper.BountyMapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.SecurityUtils; // 导入 Ruoyi 安全工具类
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class BountyServiceImpl extends ServiceImpl<BountyMapper, Bounty> implements BountyService {
+    @Override
+    public boolean saveBounty(Bounty bounty) {
+        return this.save(bounty);
+    }
+
+    @Override
+    public IPage<Bounty> getBountiesByPage(Page<Bounty> page) {
+        QueryWrapper<Bounty> wrapper = new QueryWrapper<>();
+        return this.page(page, wrapper);
+    }
+
+    @Override
+    public Bounty getBountyById(Long id) {
+        return this.getById(id);
+    }
+
+    @Override
+    public boolean publishBounty(Bounty bounty) {
+        // 校验标题不能为空
+        if (bounty.getTitle() == null || bounty.getTitle().trim().isEmpty()) {
+            throw new IllegalArgumentException("悬赏标题不能为空");
+        }
+
+        // 获取当前用户 ID 并设置到 Bounty 对象
+        Long currentUserId = SecurityUtils.getUserId(); // 使用 Ruoyi 工具类获取用户 ID
+        bounty.setCreator_id(currentUserId); // 设置 creator_id(需与实体类字段名一致)
+        // 设置默认状态(假设 1 为已发布)
+        bounty.setStatus(1);
+        return this.save(bounty);
+    }
+
+
+    @Override
+    public List<Bounty> getBounties() {
+    // 直接调用 MyBatis-Plus 的 list 方法查询所有数据(可根据需求添加查询条件)
+        return baseMapper.selectList();  // null 表示无查询条件,查询所有
+    }
+
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/bounty/service/BountySubmissionService.java b/ruoyi-system/src/main/java/bounty/service/BountySubmissionService.java
new file mode 100644
index 0000000..707f54c
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/service/BountySubmissionService.java
@@ -0,0 +1,14 @@
+package bounty.service;
+
+import bounty.domain.BountySubmission;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+// 悬赏提交 Service 接口
+public interface BountySubmissionService {
+    boolean saveBountySubmission(BountySubmission bountySubmission);
+    IPage<BountySubmission> getBountySubmissionsByPage(Page<BountySubmission> page, Long bountyId);
+    boolean adoptSubmission(Long submissionId);
+}
+
+
diff --git a/ruoyi-system/src/main/java/bounty/service/BountySubmissionServiceImpl.java b/ruoyi-system/src/main/java/bounty/service/BountySubmissionServiceImpl.java
new file mode 100644
index 0000000..4300911
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/service/BountySubmissionServiceImpl.java
@@ -0,0 +1,154 @@
+package bounty.service;
+
+import bounty.domain.Bounty;
+import bounty.domain.BountySubmission;
+import bounty.mapper.BountyMapper;
+import bounty.mapper.BountySubmissionMapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.SecurityUtils;
+import org.mybatis.spring.MyBatisSystemException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+@Service
+public class BountySubmissionServiceImpl extends ServiceImpl<BountySubmissionMapper, BountySubmission> implements BountySubmissionService {
+
+    @Autowired
+    private BountyMapper bountyMapper; // 注入 BountyMapper 校验悬赏存在性
+
+    @Override
+    public boolean saveBountySubmission(BountySubmission bountySubmission) {
+        // 校验 1:悬赏 ID 不能为空
+        Long bountyId = bountySubmission.getBountyId();
+        if (bountyId == null) {
+            throw new IllegalArgumentException("悬赏 ID 不能为空");
+        }
+
+        // 校验 2:目标悬赏是否存在
+        Bounty targetBounty = bountyMapper.selectById(bountyId);
+        if (targetBounty == null) {
+            throw new IllegalArgumentException("无效的悬赏 ID,未找到对应悬赏");
+        }
+
+        // 校验 3:提交内容不能为空
+        String content = bountySubmission.getContent();
+        if (!StringUtils.hasText(content)) {
+            throw new IllegalArgumentException("提交内容不能为空");
+        }
+
+        // 自动填充当前用户 ID(需用户已登录)
+        Long currentUserId = SecurityUtils.getUserId();
+        bountySubmission.setUserId(currentUserId);
+
+
+        // 调用 MyBatis-Plus 保存方法
+        boolean saveResult = this.save(bountySubmission);
+
+        // 新增:保存成功后,更新对应悬赏的状态为1(已回复)
+        if (saveResult) {
+            targetBounty.setStatus(1); // 将悬赏状态改为1
+            bountyMapper.updateById(targetBounty); // 执行更新
+        }
+
+        return saveResult;
+    }
+
+    @Override
+    public IPage<BountySubmission> getBountySubmissionsByPage(Page<BountySubmission> page, Long bountyId) {
+        QueryWrapper<BountySubmission> wrapper = new QueryWrapper<>();
+        wrapper.eq("bounty_id", bountyId);
+        return this.page(page, wrapper);
+    }
+
+    private static final Logger log = LoggerFactory.getLogger(BountySubmissionServiceImpl.class);
+
+    @Override
+    public boolean adoptSubmission(Long submissionId) {
+        log.info("【采纳流程开始】submissionId={}, 当前时间={}", submissionId, new Date());
+
+        try {
+            // 参数校验
+            if (submissionId == null || submissionId <= 0) {
+                log.warn("【参数校验失败】submissionId为空或无效");
+                throw new IllegalArgumentException("无效的回复ID");
+            }
+
+            // 获取回复详情
+            BountySubmission submission = this.getById(submissionId);
+            if (submission == null) {
+                log.warn("【回复不存在】submissionId={}", submissionId);
+                throw new IllegalArgumentException("无效的回复ID");
+            }
+
+            if (submission.getId() == null || submission.getBountyId() == null) {
+                log.warn("【无效提交数据】submissionId={}", submissionId);
+                throw new IllegalArgumentException("提交数据不完整");
+            }
+
+
+            log.debug("【原始回复数据】{}", submission);
+
+            // 获取关联悬赏
+            Bounty bounty = bountyMapper.selectById(submission.getBountyId());
+
+            if (bounty == null) {
+                log.warn("【关联悬赏不存在】bountyId={}", submission.getBountyId());
+                throw new IllegalArgumentException("关联悬赏不存在");
+            }
+
+            // 用户权限校验
+            Long currentUserId = SecurityUtils.getUserId();
+            if (currentUserId == null) {
+                log.warn("【用户未登录】");
+                throw new IllegalArgumentException("用户未登录");
+            }
+
+
+            if (!bounty.getCreator_id().equals(currentUserId)) {
+                log.warn("【权限不足】用户ID={} 试图访问悬赏ID={}", currentUserId, bounty.getId());
+                throw new IllegalArgumentException("无权操作");
+            }
+
+            // 状态校验
+            if (submission.getStatus() == 1) {
+                log.warn("【重复采纳】submissionId={}", submissionId);
+                throw new IllegalArgumentException("该回复已被采纳");
+            }
+
+            log.info("【更新前对象】{}", submission);
+
+            // 执行更新
+            submission.setStatus(1);
+            boolean submissionResult = this.updateById(submission);
+            log.info("【更新后对象】{}", this.getById(submissionId));
+
+
+            log.info("【操作结果】submissionResult={}, 新状态={}", submissionResult, submission.getStatus());
+
+            return submissionResult;
+        } catch (MyBatisSystemException e) {
+            // 显式捕获 MyBatis 异常
+            log.error("【MyBatis 异常】submissionId={}, rootCause={}", submissionId, e.getRootCause());
+            log.error("【异常详情】", e); // 打印完整堆栈
+            throw new RuntimeException("MyBatis 错误: " + e.getRootCause(), e);
+
+    } catch (IllegalArgumentException e) {
+            log.warn("【参数异常】submissionId={}, message={}", submissionId, e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            log.error("【采纳异常】submissionId={}, 错误={}", submissionId, e.getMessage(), e);
+            log.error("【未分类异常】submissionId={}, exceptionClass={}", submissionId, e.getClass().getName());
+            log.error("【异常详情】", e); // 打印完整堆栈
+            throw e;
+        }
+    }
+}
diff --git a/ruoyi-system/src/main/java/bounty/service/FileStorageService.java b/ruoyi-system/src/main/java/bounty/service/FileStorageService.java
new file mode 100644
index 0000000..9f12122
--- /dev/null
+++ b/ruoyi-system/src/main/java/bounty/service/FileStorageService.java
@@ -0,0 +1,55 @@
+package bounty.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.UUID;
+
+@Service
+public class FileStorageService {
+    public String saveFile(MultipartFile file) {
+        try {
+            // 1. 校验文件名非空
+            String originalFilename = file.getOriginalFilename();
+            if (originalFilename == null || originalFilename.isEmpty()) {
+                throw new IllegalArgumentException("上传文件名不能为空");
+            }
+
+            // 2. 生成唯一文件名
+            String fileExtension = "";
+            int dotIndex = originalFilename.lastIndexOf(".");
+            if (dotIndex > 0) {
+                fileExtension = originalFilename.substring(dotIndex);
+            }
+            String newFilename = UUID.randomUUID() + fileExtension;
+
+            // 👇 新增:打印文件名生成结果(用于调试)
+            System.out.println("【生成文件名】" + newFilename);
+
+            // 3. 指定存储路径
+            Path uploadDir = Paths.get("uploads/");
+            if (!Files.exists(uploadDir)) {
+                Files.createDirectories(uploadDir);
+            }
+
+            // 👇 新增:打印完整路径(用于调试)
+            Path filePath = uploadDir.resolve(newFilename);
+            System.out.println("【文件保存路径】" + filePath.toAbsolutePath());
+
+            // 4. 写入文件
+            Files.write(filePath, file.getBytes());
+
+            // 5. 返回访问路径
+            return "/uploads/" + newFilename;
+        } catch (IOException e) {
+            // 👇 新增:记录异常信息
+            System.err.println("【文件上传失败】" + e.getMessage());
+            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
+        }
+    }
+}
+
diff --git a/ruoyi-system/src/main/resources/mapper/bounty/BountyMapper.xml b/ruoyi-system/src/main/resources/mapper/bounty/BountyMapper.xml
new file mode 100644
index 0000000..731572a
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/bounty/BountyMapper.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="bounty.mapper.BountyMapper">
+    <!-- 确保 namespace 与 BountyMapper 接口全限定名一致 -->
+    
+    <!-- 添加 selectPage 方法的 SQL 语句 -->
+    <select id="selectPage" resultType="bounty.domain.Bounty">
+    SELECT * FROM bounty
+    <!-- MyBatis-Plus 分页插件会自动添加 LIMIT 语句,无需手动写分页 -->
+</select>
+
+    <select id="selectList" resultType="bounty.domain.Bounty">
+        SELECT * FROM bounty
+    </select>
+
+    <insert id="insert" parameterType="bounty.domain.Bounty">
+        INSERT INTO bounty (
+            title, 
+            description, 
+            reward, 
+            creator_id,  <!-- 假设 creator_id 是非空字段 -->
+            deadline, 
+            create_time,  <!-- 假设 create_time 是非空字段 -->
+            status        <!-- 假设 status 是非空字段 -->
+        )
+        VALUES (
+            #{title},
+            #{description},
+            #{reward},
+            #{creator_id},  <!-- 假设 creatorId 是 Bounty 类中的属性 -->
+            #{deadline},
+            NOW(),  <!-- create_time 默认当前时间(MySQL 函数) -->
+            '0'  <!-- status 默认值(如已发布) -->
+        )
+    </insert>
+
+
+
+     <update id="updateById" parameterType="bounty.domain.Bounty">
+        UPDATE bounty
+        SET 
+            status = #{et.status}  <!-- 使用 et 别名访问实体的 status 属性 -->
+        WHERE id = #{et.id}  <!-- 使用 et 别名访问实体的 id 属性 -->
+    </update>
+
+
+     <resultMap id="BountyWithSubmissionsResult" type="bounty.domain.Bounty">
+        <id property="id" column="id"/>
+        <result property="title" column="title"/>
+        <result property="description" column="description"/>
+        <result property="reward" column="reward"/>
+        <result property="deadline" column="deadline"/>
+        <result property="status" column="status"/>
+        <!-- 关联查询回复列表,填充到 submissions 字段 -->
+        <collection 
+            property="submissions" 
+            column="id" 
+            ofType="bounty.domain.BountySubmission"
+            select="bounty.mapper.BountySubmissionMapper.getSubmissionsByBountyId"/>
+    </resultMap>
+
+    <!-- 修改 selectById 方法,使用新的 resultMap -->
+    <select id="selectById" resultMap="BountyWithSubmissionsResult">
+        SELECT * FROM bounty WHERE id = #{id}
+    </select>
+
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/bounty/BountySubmissionMapper.xml b/ruoyi-system/src/main/resources/mapper/bounty/BountySubmissionMapper.xml
new file mode 100644
index 0000000..67d7947
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/bounty/BountySubmissionMapper.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="bounty.mapper.BountySubmissionMapper">
+    <!-- 继承 BaseMapper 的默认方法(如 insert、selectById 等),无需重复定义 -->
+    
+    <!-- 示例:自定义分页查询(若默认方法不满足需求) -->
+    <select id="getBountySubmissionsByPage" resultType="bounty.domain.BountySubmission">
+        SELECT * FROM bounty_submissions
+        WHERE bounty_id = #{bountyId}
+        LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <insert id="insert" parameterType="bounty.domain.BountySubmission">
+        INSERT INTO bounty_submissions (
+        bounty_id,
+        user_id,
+        content,
+        attachment, <!-- 新增字段 -->
+        create_time,
+        status
+        ) VALUES (
+        #{bountyId},
+        #{userId},
+        #{content},
+        #{attachment}, <!-- 绑定字段 -->
+        NOW(),
+        '0'
+        )
+    </insert>
+
+    <select id="getSubmissionsByBountyId" resultType="bounty.domain.BountySubmission">
+        SELECT id, user_id AS userId, content, attachment, status
+        FROM bounty_submissions
+        WHERE bounty_id = #{bountyId}
+    </select>
+
+    <!-- BountySubmissionMapper.xml -->
+    <update id="updateById" parameterType="bounty.domain.BountySubmission">
+        UPDATE bounty_submissions
+        SET
+            status = #{et.status}
+        WHERE id = #{et.id}
+    </update>
+
+    <select id="selectById" resultType="bounty.domain.BountySubmission">
+        SELECT id, bounty_id AS bountyId, user_id AS userId, content, attachment, status
+        FROM bounty_submissions
+        WHERE id = #{id}
+    </select>
+
+</mapper>
\ No newline at end of file