我是人,我提交了悬赏功能哦!悬赏功能是:1.人可以发布悬赏 2.人可以回复悬赏 3.只有回复人和悬赏发布者可以下载回复的附件
Change-Id: I269fb69c6ee4dd695a38fa0c91fa8fbe72fc5322
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