From 223230e9bc269d40a3d853cac73f166ba720cab2 Mon Sep 17 00:00:00 2001 From: mengjc Date: Mon, 8 Jun 2026 09:24:41 +0800 Subject: [PATCH] first commit --- pom.xml | 96 ++++++++ .../report/DisciplineReportApplication.java | 18 ++ .../report/config/CustomConfigProperties.java | 22 ++ .../report/config/FileUploadProperties.java | 29 +++ .../report/config/WebMvcConfig.java | 21 ++ .../report/controller/QrCodeController.java | 35 +++ .../report/controller/ReportController.java | 33 +++ .../report/dto/ReportSubmitDTO.java | 29 +++ .../report/entity/ReportEmailReceiver.java | 50 +++++ .../discipline/report/entity/ReportFile.java | 18 ++ .../report/entity/ReportRecord.java | 19 ++ .../report/exception/BusinessException.java | 16 ++ .../exception/GlobalExceptionHandler.java | 38 ++++ .../mapper/ReportEmailReceiverMapper.java | 23 ++ .../report/mapper/ReportFileMapper.java | 16 ++ .../report/mapper/ReportRecordMapper.java | 14 ++ .../com/discipline/report/result/Result.java | 13 ++ .../discipline/report/result/ResultUtil.java | 37 ++++ .../report/service/FileUploadService.java | 17 ++ .../service/ReportEmailReceiverService.java | 27 +++ .../report/service/ReportService.java | 11 + .../service/impl/FileUploadServiceImpl.java | 73 ++++++ .../impl/ReportEmailReceiverServiceImpl.java | 42 ++++ .../service/impl/ReportServiceImpl.java | 209 ++++++++++++++++++ .../discipline/report/util/EmailSendUtil.java | 62 ++++++ .../discipline/report/util/QrCodeUtil.java | 67 ++++++ src/main/resources/application-dev.yml | 52 +++++ src/main/resources/application-prod.yml | 49 ++++ src/main/resources/application.yml | 3 + .../mapper/ReportEmailReceiverMapper.xml | 32 +++ .../resources/mapper/ReportFileMapper.xml | 19 ++ .../resources/mapper/ReportRecordMapper.xml | 14 ++ src/main/resources/static/index.html | 6 + .../DisciplineReportApplicationTests.java | 13 ++ 34 files changed, 1223 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/discipline/report/DisciplineReportApplication.java create mode 100644 src/main/java/com/discipline/report/config/CustomConfigProperties.java create mode 100644 src/main/java/com/discipline/report/config/FileUploadProperties.java create mode 100644 src/main/java/com/discipline/report/config/WebMvcConfig.java create mode 100644 src/main/java/com/discipline/report/controller/QrCodeController.java create mode 100644 src/main/java/com/discipline/report/controller/ReportController.java create mode 100644 src/main/java/com/discipline/report/dto/ReportSubmitDTO.java create mode 100644 src/main/java/com/discipline/report/entity/ReportEmailReceiver.java create mode 100644 src/main/java/com/discipline/report/entity/ReportFile.java create mode 100644 src/main/java/com/discipline/report/entity/ReportRecord.java create mode 100644 src/main/java/com/discipline/report/exception/BusinessException.java create mode 100644 src/main/java/com/discipline/report/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/discipline/report/mapper/ReportEmailReceiverMapper.java create mode 100644 src/main/java/com/discipline/report/mapper/ReportFileMapper.java create mode 100644 src/main/java/com/discipline/report/mapper/ReportRecordMapper.java create mode 100644 src/main/java/com/discipline/report/result/Result.java create mode 100644 src/main/java/com/discipline/report/result/ResultUtil.java create mode 100644 src/main/java/com/discipline/report/service/FileUploadService.java create mode 100644 src/main/java/com/discipline/report/service/ReportEmailReceiverService.java create mode 100644 src/main/java/com/discipline/report/service/ReportService.java create mode 100644 src/main/java/com/discipline/report/service/impl/FileUploadServiceImpl.java create mode 100644 src/main/java/com/discipline/report/service/impl/ReportEmailReceiverServiceImpl.java create mode 100644 src/main/java/com/discipline/report/service/impl/ReportServiceImpl.java create mode 100644 src/main/java/com/discipline/report/util/EmailSendUtil.java create mode 100644 src/main/java/com/discipline/report/util/QrCodeUtil.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mapper/ReportEmailReceiverMapper.xml create mode 100644 src/main/resources/mapper/ReportFileMapper.xml create mode 100644 src/main/resources/mapper/ReportRecordMapper.xml create mode 100644 src/main/resources/static/index.html create mode 100644 src/test/java/com/discipline/report/DisciplineReportApplicationTests.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..02053f9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.15 + + + + com.discipline + report + 1.0.0 + discipline-report + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.3.1 + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.google.zxing + core + 3.5.1 + + + com.google.zxing + javase + 3.5.1 + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/discipline/report/DisciplineReportApplication.java b/src/main/java/com/discipline/report/DisciplineReportApplication.java new file mode 100644 index 0000000..b03f949 --- /dev/null +++ b/src/main/java/com/discipline/report/DisciplineReportApplication.java @@ -0,0 +1,18 @@ +package com.discipline.report; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +import com.discipline.report.config.CustomConfigProperties; +import com.discipline.report.config.FileUploadProperties; + +@SpringBootApplication +@MapperScan("com.discipline.report.mapper") +@EnableConfigurationProperties({CustomConfigProperties.class, FileUploadProperties.class}) +public class DisciplineReportApplication { + public static void main(String[] args) { + SpringApplication.run(DisciplineReportApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/config/CustomConfigProperties.java b/src/main/java/com/discipline/report/config/CustomConfigProperties.java new file mode 100644 index 0000000..40c96ab --- /dev/null +++ b/src/main/java/com/discipline/report/config/CustomConfigProperties.java @@ -0,0 +1,22 @@ +package com.discipline.report.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 自定义配置属性 + */ +@Data +@ConfigurationProperties(prefix = "custom") +public class CustomConfigProperties { + private String disciplineEmail; // 纪委接收邮箱 + private String h5IndexUrl; // H5首页URL + private String tipContent; // 填报提示文案 + private String qrCodeFixedUrl; // 二维码固定URL + private String emailFrom; // 发件人邮箱 + + private String emailSubject; + private String emailContent; + +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/config/FileUploadProperties.java b/src/main/java/com/discipline/report/config/FileUploadProperties.java new file mode 100644 index 0000000..6171841 --- /dev/null +++ b/src/main/java/com/discipline/report/config/FileUploadProperties.java @@ -0,0 +1,29 @@ +package com.discipline.report.config; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * 文件上传配置属性 + */ +@Slf4j +@Data +@ConfigurationProperties(prefix = "discipline.file.upload") +public class FileUploadProperties { + private String path; // 文件上传路径 + + @PostConstruct + public void init() { + try { + Files.createDirectories(Paths.get(path)); + } catch (Exception e) { + log.error("文件目录创建失败:{}", path, e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/config/WebMvcConfig.java b/src/main/java/com/discipline/report/config/WebMvcConfig.java new file mode 100644 index 0000000..b5a1a39 --- /dev/null +++ b/src/main/java/com/discipline/report/config/WebMvcConfig.java @@ -0,0 +1,21 @@ +package com.discipline.report.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Web MVC配置 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/controller/QrCodeController.java b/src/main/java/com/discipline/report/controller/QrCodeController.java new file mode 100644 index 0000000..8f11197 --- /dev/null +++ b/src/main/java/com/discipline/report/controller/QrCodeController.java @@ -0,0 +1,35 @@ +package com.discipline.report.controller; + +import com.discipline.report.config.CustomConfigProperties; +import com.discipline.report.result.Result; +import com.discipline.report.result.ResultUtil; +import com.discipline.report.util.QrCodeUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 二维码生成控制器 + */ +@RestController +@RequestMapping("/api/discipline/qrcode") +public class QrCodeController { + @Resource + private CustomConfigProperties config; + + @GetMapping + public Result getQrCode() { + try { + String fixedUrl = config.getQrCodeFixedUrl(); + if (fixedUrl == null || fixedUrl.trim().isEmpty()) { + return ResultUtil.error(500, "配置文件未配置二维码固定链接"); + } + String qrCode = QrCodeUtil.createQrCode(fixedUrl); + return ResultUtil.success(qrCode); + } catch (Exception e) { + return ResultUtil.error(500, "生成二维码失败:" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/controller/ReportController.java b/src/main/java/com/discipline/report/controller/ReportController.java new file mode 100644 index 0000000..a88e3bf --- /dev/null +++ b/src/main/java/com/discipline/report/controller/ReportController.java @@ -0,0 +1,33 @@ +package com.discipline.report.controller; +import com.discipline.report.dto.ReportSubmitDTO; +import com.discipline.report.result.Result; +import com.discipline.report.result.ResultUtil; +import com.discipline.report.service.ReportService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import javax.annotation.Resource; +import javax.validation.Valid; +/** + * 举报提交控制器 + */ +@RestController +@RequestMapping("/api/discipline/report") +public class ReportController { + @Resource + private ReportService reportService; + /** + * 提交举报表单+附件 + */ + @PostMapping("/submit") + public Result submit(@Valid ReportSubmitDTO dto) { + try { + reportService.saveReportAndSendEmail(dto); + return ResultUtil.success("举报提交成功"); + } catch (Exception e) { + // 移除e.printStackTrace(),全局异常统一打印日志 + return ResultUtil.error(500, "系统异常,提交失败"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/dto/ReportSubmitDTO.java b/src/main/java/com/discipline/report/dto/ReportSubmitDTO.java new file mode 100644 index 0000000..676ca14 --- /dev/null +++ b/src/main/java/com/discipline/report/dto/ReportSubmitDTO.java @@ -0,0 +1,29 @@ +package com.discipline.report.dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 举报提交数据传输对象 + */ +@Data +public class ReportSubmitDTO { + @NotNull(message = "是否匿名不能为空") + private Boolean isAnonymous; // 是否匿名 + private String reporterName; // 举报人姓名 + @NotBlank(message = "举报内容不能为空") + private String reportContent; // 举报内容 + private String contactInfo; // 联系方式 + private MultipartFile[] file; // 上传文件数组 + + public MultipartFile[] getFile() { + return file; + } + + public void setFile(MultipartFile[] file) { + this.file = file; + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/entity/ReportEmailReceiver.java b/src/main/java/com/discipline/report/entity/ReportEmailReceiver.java new file mode 100644 index 0000000..f68e837 --- /dev/null +++ b/src/main/java/com/discipline/report/entity/ReportEmailReceiver.java @@ -0,0 +1,50 @@ +package com.discipline.report.entity; + +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 举报邮件接收人配置实体 + */ +@Data +public class ReportEmailReceiver { + /** + * 主键ID + */ + private Long id; + + /** + * 接收人姓名 + */ + private String receiverName; + + /** + * 接收人邮箱地址 + */ + private String receiverEmail; + + /** + * 接收类型:1-主送(TO) 2-抄送(CC) + */ + private Integer receiveType; + + /** + * 排序号 + */ + private Integer sortNum; + + /** + * 状态:0-禁用 1-启用 + */ + private Integer status; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/entity/ReportFile.java b/src/main/java/com/discipline/report/entity/ReportFile.java new file mode 100644 index 0000000..81d13b4 --- /dev/null +++ b/src/main/java/com/discipline/report/entity/ReportFile.java @@ -0,0 +1,18 @@ +package com.discipline.report.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 举报文件实体 + */ +@Data +public class ReportFile { + private Long id; // 主键ID + private Long reportId; // 举报记录ID + private String fileName; // 文件名 + private String filePath; // 文件路径 + private Long fileSize; // 文件大小 + private LocalDateTime createTime;// 创建时间 +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/entity/ReportRecord.java b/src/main/java/com/discipline/report/entity/ReportRecord.java new file mode 100644 index 0000000..bee7cf5 --- /dev/null +++ b/src/main/java/com/discipline/report/entity/ReportRecord.java @@ -0,0 +1,19 @@ +package com.discipline.report.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 举报记录实体 + */ +@Data +public class ReportRecord { + private Long id; // 主键ID + private Boolean isAnonymous; // 是否匿名 + private String reporterName; // 举报人姓名 + private String reportContent; // 举报内容 + private String contactInfo; // 联系方式 + private String emailStatus; // 邮件发送状态 + private LocalDateTime createTime;// 创建时间 +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/exception/BusinessException.java b/src/main/java/com/discipline/report/exception/BusinessException.java new file mode 100644 index 0000000..6b6693a --- /dev/null +++ b/src/main/java/com/discipline/report/exception/BusinessException.java @@ -0,0 +1,16 @@ +package com.discipline.report.exception; + +import lombok.Getter; + +/** + * 业务异常类 + */ +@Getter +public class BusinessException extends RuntimeException { + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/exception/GlobalExceptionHandler.java b/src/main/java/com/discipline/report/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..9e3d007 --- /dev/null +++ b/src/main/java/com/discipline/report/exception/GlobalExceptionHandler.java @@ -0,0 +1,38 @@ +package com.discipline.report.exception; + +import com.discipline.report.result.Result; +import com.discipline.report.result.ResultUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(BusinessException.class) + @ResponseBody + public Result businessException(BusinessException e) { + log.error("业务异常:{}", e.getMessage()); + return ResultUtil.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseBody + public Result validException(MethodArgumentNotValidException e) { + String msg = e.getBindingResult().getFieldError().getDefaultMessage(); + log.error("参数校验异常:{}", msg); + return ResultUtil.error(400, msg); + } + + @ExceptionHandler(Exception.class) + @ResponseBody + public Result exception(Exception e) { + log.error("系统异常", e); + return ResultUtil.error(500, "系统异常,请联系管理员"); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/mapper/ReportEmailReceiverMapper.java b/src/main/java/com/discipline/report/mapper/ReportEmailReceiverMapper.java new file mode 100644 index 0000000..3f56387 --- /dev/null +++ b/src/main/java/com/discipline/report/mapper/ReportEmailReceiverMapper.java @@ -0,0 +1,23 @@ +package com.discipline.report.mapper; + +import com.discipline.report.entity.ReportEmailReceiver; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 举报邮件接收人Mapper + */ +public interface ReportEmailReceiverMapper { + /** + * 根据接收类型查询启用的接收人列表 + * @param receiveType 接收类型 1-主送 2-抄送 + * @return 接收人列表 + */ + List selectByReceiveType(@Param("receiveType") Integer receiveType); + + /** + * 查询所有启用的接收人 + * @return 接收人列表 + */ + List selectAllEnabled(); +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/mapper/ReportFileMapper.java b/src/main/java/com/discipline/report/mapper/ReportFileMapper.java new file mode 100644 index 0000000..1bb8fbe --- /dev/null +++ b/src/main/java/com/discipline/report/mapper/ReportFileMapper.java @@ -0,0 +1,16 @@ +package com.discipline.report.mapper; + +import com.discipline.report.entity.ReportFile; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 举报文件数据访问接口 + */ +@Mapper +public interface ReportFileMapper { + void insertBatch(@Param("files") List files); + List selectByReportId(@Param("reportId") Long reportId); +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/mapper/ReportRecordMapper.java b/src/main/java/com/discipline/report/mapper/ReportRecordMapper.java new file mode 100644 index 0000000..94cbfc1 --- /dev/null +++ b/src/main/java/com/discipline/report/mapper/ReportRecordMapper.java @@ -0,0 +1,14 @@ +package com.discipline.report.mapper; + +import com.discipline.report.entity.ReportRecord; +import org.apache.ibatis.annotations.Mapper; + +/** + * 举报记录数据访问接口 + */ +@Mapper +public interface ReportRecordMapper { + void insert(ReportRecord record); + // 新增:根据主键更新邮件状态 + int updateById(ReportRecord record); +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/result/Result.java b/src/main/java/com/discipline/report/result/Result.java new file mode 100644 index 0000000..e96597d --- /dev/null +++ b/src/main/java/com/discipline/report/result/Result.java @@ -0,0 +1,13 @@ +package com.discipline.report.result; + +import lombok.Data; + +/** + * 统一返回结果类 + */ +@Data +public class Result { + private int code; // 响应码 + private String msg; // 响应消息 + private T data; // 响应数据 +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/result/ResultUtil.java b/src/main/java/com/discipline/report/result/ResultUtil.java new file mode 100644 index 0000000..3fe461d --- /dev/null +++ b/src/main/java/com/discipline/report/result/ResultUtil.java @@ -0,0 +1,37 @@ +package com.discipline.report.result; + +/** + * 统一返回结果工具类 + */ +public class ResultUtil { + /** + * 成功返回(带数据) + */ + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMsg("成功"); + result.setData(data); + return result; + } + + /** + * 成功返回(无数据) + */ + public static Result success() { + Result result = new Result<>(); + result.setCode(200); + result.setMsg("成功"); + return result; + } + + /** + * 失败返回 + */ + public static Result error(int code, String msg) { + Result result = new Result<>(); + result.setCode(code); + result.setMsg(msg); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/FileUploadService.java b/src/main/java/com/discipline/report/service/FileUploadService.java new file mode 100644 index 0000000..cfe88b4 --- /dev/null +++ b/src/main/java/com/discipline/report/service/FileUploadService.java @@ -0,0 +1,17 @@ +package com.discipline.report.service; + +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + +/** + * 文件上传服务接口 + */ +public interface FileUploadService { + /** + * 上传单个文件,返回本地全路径 + * @param file 前端上传文件 + * @return 本地磁盘全路径 + * @throws IOException IO异常 + */ + String uploadFile(MultipartFile file) throws IOException; +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/ReportEmailReceiverService.java b/src/main/java/com/discipline/report/service/ReportEmailReceiverService.java new file mode 100644 index 0000000..0fb9db5 --- /dev/null +++ b/src/main/java/com/discipline/report/service/ReportEmailReceiverService.java @@ -0,0 +1,27 @@ +package com.discipline.report.service; + +import com.discipline.report.entity.ReportEmailReceiver; +import java.util.List; + +/** + * 举报邮件接收人服务接口 + */ +public interface ReportEmailReceiverService { + /** + * 获取主送接收人邮箱列表 + * @return 邮箱数组 + */ + String[] getToEmailArray(); + + /** + * 获取抄送接收人邮箱列表 + * @return 邮箱数组 + */ + String[] getCcEmailArray(); + + /** + * 查询所有启用的接收人 + * @return 接收人列表 + */ + List getAllEnabledReceivers(); +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/ReportService.java b/src/main/java/com/discipline/report/service/ReportService.java new file mode 100644 index 0000000..c4c5393 --- /dev/null +++ b/src/main/java/com/discipline/report/service/ReportService.java @@ -0,0 +1,11 @@ +package com.discipline.report.service; + +import com.discipline.report.dto.ReportSubmitDTO; +import java.util.List; + +/** + * 举报服务接口 + */ +public interface ReportService { + void saveReportAndSendEmail(ReportSubmitDTO dto) throws Exception; +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/impl/FileUploadServiceImpl.java b/src/main/java/com/discipline/report/service/impl/FileUploadServiceImpl.java new file mode 100644 index 0000000..8102167 --- /dev/null +++ b/src/main/java/com/discipline/report/service/impl/FileUploadServiceImpl.java @@ -0,0 +1,73 @@ +package com.discipline.report.service.impl; + +import com.discipline.report.config.FileUploadProperties; +import com.discipline.report.exception.BusinessException; +import com.discipline.report.service.FileUploadService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +/** + * 文件上传服务实现 + */ +@Slf4j +@Service +public class FileUploadServiceImpl implements FileUploadService { + // 修正注释:原注释写的10MB但值是50MB,保持值不变仅修正注释(可选) + private static final long MAX_SIZE = 50*1024*1024; // 50MB + + @Resource + private FileUploadProperties uploadProperties; + + @Override + public String uploadFile(MultipartFile file) throws IOException { + // 1. 校验文件是否为空 + if (file.isEmpty()) { + throw new BusinessException(400, "上传文件不能为空"); + } + // 2. 校验文件大小 + if (file.getSize() > MAX_SIZE) { + throw new BusinessException(400, "文件大小不能超过50MB"); + } + + // 3. 校验文件名是否为空(仅校验名称,不再校验后缀) + String originalName = file.getOriginalFilename(); + if (originalName == null || originalName.trim().isEmpty()) { + throw new BusinessException(400, "上传文件名称不能为空"); + } + + // 4. 生成新文件名(兼容无后缀文件) + String newFileName; + int dotIndex = originalName.lastIndexOf("."); + if (dotIndex == -1) { + // 无后缀文件,直接用UUID作为文件名 + newFileName = UUID.randomUUID().toString(); + } else { + // 有后缀文件,UUID + 原后缀 + String suffix = originalName.substring(dotIndex).toLowerCase(); + newFileName = UUID.randomUUID().toString() + suffix; + } + + // 5. 保存文件 + String uploadPath = uploadProperties.getPath(); + File dest = new File(uploadPath + File.separator + newFileName); + // 确保上传目录存在(新增:防止目录不存在导致报错) + if (!dest.getParentFile().exists()) { + boolean mkdirs = dest.getParentFile().mkdirs(); + if (!mkdirs) { + log.error("创建上传目录失败:{}", dest.getParentFile().getAbsolutePath()); + throw new BusinessException(500, "文件上传失败,创建目录失败"); + } + } + + file.transferTo(dest); + log.info("文件上传成功:{}", dest.getAbsolutePath()); + + return dest.getAbsolutePath(); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/impl/ReportEmailReceiverServiceImpl.java b/src/main/java/com/discipline/report/service/impl/ReportEmailReceiverServiceImpl.java new file mode 100644 index 0000000..5f4bd3b --- /dev/null +++ b/src/main/java/com/discipline/report/service/impl/ReportEmailReceiverServiceImpl.java @@ -0,0 +1,42 @@ +package com.discipline.report.service.impl; + +import com.discipline.report.entity.ReportEmailReceiver; +import com.discipline.report.mapper.ReportEmailReceiverMapper; +import com.discipline.report.service.ReportEmailReceiverService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 举报邮件接收人服务实现 + */ +@Slf4j +@Service +public class ReportEmailReceiverServiceImpl implements ReportEmailReceiverService { + @Resource + private ReportEmailReceiverMapper receiverMapper; + + @Override + public String[] getToEmailArray() { + List toList = receiverMapper.selectByReceiveType(1); + return toList.stream() + .map(ReportEmailReceiver::getReceiverEmail) + .toArray(String[]::new); + } + + @Override + public String[] getCcEmailArray() { + List ccList = receiverMapper.selectByReceiveType(2); + return ccList.stream() + .map(ReportEmailReceiver::getReceiverEmail) + .toArray(String[]::new); + } + + @Override + public List getAllEnabledReceivers() { + return receiverMapper.selectAllEnabled(); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/service/impl/ReportServiceImpl.java b/src/main/java/com/discipline/report/service/impl/ReportServiceImpl.java new file mode 100644 index 0000000..2f5bda3 --- /dev/null +++ b/src/main/java/com/discipline/report/service/impl/ReportServiceImpl.java @@ -0,0 +1,209 @@ +package com.discipline.report.service.impl; +import com.discipline.report.config.CustomConfigProperties; +import com.discipline.report.dto.ReportSubmitDTO; +import com.discipline.report.entity.ReportFile; +import com.discipline.report.entity.ReportRecord; +import com.discipline.report.mapper.ReportFileMapper; +import com.discipline.report.mapper.ReportRecordMapper; +import com.discipline.report.service.FileUploadService; +import com.discipline.report.service.ReportEmailReceiverService; +import com.discipline.report.service.ReportService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import javax.annotation.Resource; +import javax.mail.internet.MimeMessage; +import java.io.File; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 举报服务实现类 + * 负责举报记录保存、文件上传、举报通知邮件发送 + */ +@Slf4j +@Service +public class ReportServiceImpl implements ReportService { + /** + * 邮件发送状态枚举 + */ + private enum EmailStatus { + PENDING("PENDING"), // 待发送 + SUCCESS("SUCCESS"), // 发送成功 + FAIL("FAIL"); // 发送失败 + private final String value; + EmailStatus(String value) { + this.value = value; + } + public String getValue() { + return value; + } + } + + @Resource + private ReportRecordMapper reportRecordMapper; + @Resource + private ReportFileMapper reportFileMapper; + @Resource + private FileUploadService fileUploadService; + @Resource + private JavaMailSender mailSender; + @Resource + private ReportEmailReceiverService receiverService; + @Resource + private CustomConfigProperties config; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveReportAndSendEmail(ReportSubmitDTO dto) throws Exception { + // 1. 保存举报主记录 + ReportRecord reportRecord = saveReportRecord(dto); + // 2. 处理举报附件并保存 + handleReportFiles(reportRecord.getId(), dto.getFile()); + // 3. 发送举报通知邮件 + sendReportNotificationEmail(reportRecord); + } + + /** + * 保存举报主记录 + */ + private ReportRecord saveReportRecord(ReportSubmitDTO dto) { + ReportRecord record = new ReportRecord(); + record.setIsAnonymous(dto.getIsAnonymous()); + // 优化:匿名自动清空姓名和联系方式 + if(Boolean.TRUE.equals(dto.getIsAnonymous())){ + record.setReporterName(null); + record.setContactInfo(null); + }else{ + record.setReporterName(dto.getReporterName()); + record.setContactInfo(dto.getContactInfo()); + } + record.setReportContent(dto.getReportContent()); + record.setEmailStatus(EmailStatus.PENDING.getValue()); + record.setCreateTime(LocalDateTime.now()); + reportRecordMapper.insert(record); + log.info("举报记录保存成功,举报ID:{}", record.getId()); + return record; + } + + /** + * 处理举报附件上传并保存 + */ + private void handleReportFiles(Long reportId, MultipartFile[] files) { + if (files == null || files.length == 0) { + log.info("举报ID:{} 无附件需要上传", reportId); + return; + } + List reportFiles = new ArrayList<>(); + for (MultipartFile file : files) { + if (file.isEmpty()) { + log.warn("举报ID:{} 存在空文件,跳过处理", reportId); + continue; + } + try { + String filePath = fileUploadService.uploadFile(file); + ReportFile reportFile = new ReportFile(); + reportFile.setReportId(reportId); + reportFile.setFileName(file.getOriginalFilename()); + reportFile.setFilePath(filePath); + reportFile.setFileSize(file.getSize()); + reportFile.setCreateTime(LocalDateTime.now()); + reportFiles.add(reportFile); + } catch (Exception e) { + log.error("举报ID:{} 附件上传失败,文件名:{}", reportId, file.getOriginalFilename(), e); + } + } + if (!reportFiles.isEmpty()) { + reportFileMapper.insertBatch(reportFiles); + log.info("举报ID:{} 成功保存{}个附件记录", reportId, reportFiles.size()); + } + } + + /** + * 发送举报通知邮件(优化:添加附件 + 拼接举报详情正文) + */ + private void sendReportNotificationEmail(ReportRecord reportRecord) { + Long reportId = reportRecord.getId(); + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + // 设置邮件基础信息 + helper.setFrom(config.getEmailFrom()); + String subject = config.getEmailSubject() != null ? config.getEmailSubject() : "新的举报通知"; + helper.setSubject(subject); + + // ========== 核心:拼接举报详情到邮件正文 ========== + String baseContent = config.getEmailContent() != null ? config.getEmailContent() : "

收到一条新的举报信息,请及时处理!

"; + StringBuilder sb = new StringBuilder(baseContent); + sb.append("
"); + sb.append("

是否匿名:").append(reportRecord.getIsAnonymous() ? "是" : "否").append("

"); + if (!reportRecord.getIsAnonymous()) { + sb.append("

举报人:").append(reportRecord.getReporterName()).append("

"); + sb.append("

联系方式:").append(reportRecord.getContactInfo()).append("

"); + } + sb.append("

举报内容:").append(reportRecord.getReportContent()).append("

"); + helper.setText(sb.toString(), true); + // ================================================ + + // 获取主送、抄送邮箱 + String[] toEmails = receiverService.getToEmailArray(); + String[] ccEmails = receiverService.getCcEmailArray(); + + // 主送为空直接失败 + if (toEmails == null || toEmails.length == 0) { + log.warn("举报ID:{} 邮件主送人为空,无法发送邮件", reportId); + updateEmailStatus(reportRecord, EmailStatus.FAIL); + return; + } + helper.setTo(toEmails); + if (ccEmails != null && ccEmails.length > 0) { + helper.setCc(ccEmails); + } + + // 查询附件并添加到邮件 + List fileList = reportFileMapper.selectByReportId(reportId); + for(ReportFile rf : fileList){ + File attachFile = new File(rf.getFilePath()); + if(attachFile.exists()){ + helper.addAttachment(rf.getFileName(), new FileSystemResource(attachFile)); + }else{ + log.warn("附件文件不存在,路径:{}",rf.getFilePath()); + } + } + + // 发送邮件 + mailSender.send(mimeMessage); + updateEmailStatus(reportRecord, EmailStatus.SUCCESS); + log.info("举报邮件发送成功,举报ID:{},主送:{},抄送:{}", + reportId, + arrayToString(toEmails), + arrayToString(ccEmails)); + } catch (Exception e) { + log.error("举报邮件发送失败,举报ID:{}", reportId, e); + updateEmailStatus(reportRecord, EmailStatus.FAIL); + } + } + + /** + * 更新邮件状态 + */ + private void updateEmailStatus(ReportRecord reportRecord, EmailStatus status) { + reportRecord.setEmailStatus(status.getValue()); + reportRecordMapper.updateById(reportRecord); + } + + /** + * 数组转字符串(空安全) + */ + private String arrayToString(String[] array) { + if (array == null || array.length == 0) { + return ""; + } + return String.join(",", array); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/util/EmailSendUtil.java b/src/main/java/com/discipline/report/util/EmailSendUtil.java new file mode 100644 index 0000000..4302fb7 --- /dev/null +++ b/src/main/java/com/discipline/report/util/EmailSendUtil.java @@ -0,0 +1,62 @@ +package com.discipline.report.util; + +import com.discipline.report.config.CustomConfigProperties; +import com.discipline.report.entity.ReportFile; +import com.discipline.report.entity.ReportRecord; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.File; +import java.util.List; + +/** + * 邮件发送工具类 + */ +@Slf4j +@Component +public class EmailSendUtil { + @Resource + private JavaMailSender mailSender; + @Resource + private CustomConfigProperties config; + + public void sendReportEmail(ReportRecord record, List fileList) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setFrom(config.getEmailFrom()); + helper.setTo(config.getDisciplineEmail()); + helper.setSubject("【纪律举报】新举报信息提交"); + + // 构建邮件内容 + StringBuilder content = new StringBuilder(); + content.append("

新举报信息

"); + content.append("

举报时间:").append(record.getCreateTime()).append("

"); + content.append("

是否匿名:").append(record.getIsAnonymous() ? "是" : "否").append("

"); + if (!record.getIsAnonymous() && record.getReporterName() != null) { + content.append("

举报人:").append(record.getReporterName()).append("

"); + } + if (record.getContactInfo() != null) { + content.append("

联系方式:").append(record.getContactInfo()).append("

"); + } + content.append("

举报内容:
").append(record.getReportContent().replace("\n", "
")).append("

"); + + // 添加附件 + if (fileList != null && !fileList.isEmpty()) { + content.append("

附件列表:

"); + for (ReportFile file : fileList) { + content.append("

").append(file.getFileName()).append(" (").append(file.getFileSize()/1024).append("KB)").append("

"); + helper.addAttachment(file.getFileName(), new File(file.getFilePath())); + } + } + + helper.setText(content.toString(), true); + mailSender.send(message); + log.info("举报邮件发送成功,收件人:{}", config.getDisciplineEmail()); + } +} \ No newline at end of file diff --git a/src/main/java/com/discipline/report/util/QrCodeUtil.java b/src/main/java/com/discipline/report/util/QrCodeUtil.java new file mode 100644 index 0000000..76ee8bf --- /dev/null +++ b/src/main/java/com/discipline/report/util/QrCodeUtil.java @@ -0,0 +1,67 @@ +package com.discipline.report.util; + +import com.google.zxing.*; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import org.springframework.util.Base64Utils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * 二维码生成工具类(基于ZXing实现) + * 用于生成固定尺寸、固定纠错级别的Base64格式二维码 + */ +public class QrCodeUtil { + // 固定配置:确保每次生成的二维码完全一致 + private static final int QR_CODE_SIZE = 300; // 二维码尺寸(300x300像素) + private static final ErrorCorrectionLevel ERROR_CORRECTION = ErrorCorrectionLevel.H; // 高纠错级别(30%容错) + private static final String IMAGE_FORMAT = "png"; // 图片格式 + private static final String CHARSET = "UTF-8"; // 字符编码 + private static final int MARGIN = 1; // 二维码边距(最小) + + /** + * 生成Base64格式的二维码图片 + * @param content 二维码内容(前端提供的固定URL) + * @return Base64编码的图片字符串(带data:image/png;base64前缀) + * @throws Exception 生成过程中的异常 + */ + public static String createQrCode(String content) throws Exception { + // 校验输入内容 + if (content == null || content.trim().isEmpty()) { + throw new IllegalArgumentException("二维码链接不能为空"); + } + + QRCodeWriter writer = new QRCodeWriter(); + + // 配置二维码参数(固定配置,确保结果一致) + Map hints = new HashMap<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, ERROR_CORRECTION); + hints.put(EncodeHintType.CHARACTER_SET, CHARSET); + hints.put(EncodeHintType.MARGIN, MARGIN); + + // 生成固定尺寸的二维码矩阵 + BitMatrix matrix = writer.encode( + content, + BarcodeFormat.QR_CODE, + QR_CODE_SIZE, + QR_CODE_SIZE, + hints + ); + + // 转换为BufferedImage + BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix); + + // 写入字节流并转换为Base64 + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageIO.write(image, IMAGE_FORMAT, bos); + + // 返回带前缀的Base64字符串(前端可直接显示) + return "data:image/png;base64," + Base64Utils.encodeToString(bos.toByteArray()); + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..7d22c88 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,52 @@ +server: + port: 8090 +spring: + application: + name: discipline-report + # 数据库 + datasource: + url: jdbc:mysql://10.73.199.164:1444/finedb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: bigdata + password: bigdata@2020 + driver-class-name: com.mysql.cj.jdbc.Driver + # 邮箱配置 + mail: + host: smtp.163.com + username: 18817503105@163.com + password: NScs2Ct8YTniyBNP + port: 465 + protocol: smtps + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +# Mybatis +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.discipline.report.entity + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +# 自定义配置【和CustomConfigProperties字段完全对齐】 +custom: + h5-index-url: http://localhost + tip-content: 请如实填写举报信息,我们将严格保密 + qr-code-fixed-url: http://10.15.33.107:3301/#/home + email-from: 18817503105@163.com + email-subject: 【系统提醒】新举报工单提交 + email-content:

系统收到一条新举报,请及时处理!

+ +# 文件上传(Windows开发路径) +discipline: + file: + upload: + path: D:/home/upload/report \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..a9bf6de --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,49 @@ +server: + port: 8090 +spring: + application: + name: discipline-report + datasource: + url: jdbc:mysql://10.73.199.144:3506/finedb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: bigdata + password: bigdata@2020 + driver-class-name: com.mysql.cj.jdbc.Driver + mail: + host: smtp.163.com + username: 18817503105@163.com + password: NScs2Ct8YTniyBNP + port: 465 + protocol: smtps + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.discipline.report.entity + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +# 自定义配置 +custom: + h5-index-url: https://isee.ahtrq.com + tip-content: 请如实填写举报信息,我们将严格保密 + qr-code-fixed-url: https://isee.ahtrq.com:9081/gmo + email-from: 18817503105@163.com + email-subject: 【系统提醒】新举报工单提交 + email-content:

系统收到一条新举报,请及时处理!

+ +# Linux生产路径 +discipline: + file: + upload: + path: /opt/ahtrq/discipline-report/upload/report \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..90385b2 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: prod \ No newline at end of file diff --git a/src/main/resources/mapper/ReportEmailReceiverMapper.xml b/src/main/resources/mapper/ReportEmailReceiverMapper.xml new file mode 100644 index 0000000..7aa311a --- /dev/null +++ b/src/main/resources/mapper/ReportEmailReceiverMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + id, receiver_name, receiver_email, receive_type, sort_num, status, create_time, update_time + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/ReportFileMapper.xml b/src/main/resources/mapper/ReportFileMapper.xml new file mode 100644 index 0000000..b40552e --- /dev/null +++ b/src/main/resources/mapper/ReportFileMapper.xml @@ -0,0 +1,19 @@ + + + + + INSERT INTO report_file (report_id, file_name, file_path, file_size, create_time) + VALUES + + (#{file.reportId}, #{file.fileName}, #{file.filePath}, #{file.fileSize}, #{file.createTime}) + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/ReportRecordMapper.xml b/src/main/resources/mapper/ReportRecordMapper.xml new file mode 100644 index 0000000..7880312 --- /dev/null +++ b/src/main/resources/mapper/ReportRecordMapper.xml @@ -0,0 +1,14 @@ + + + + + INSERT INTO report_record (is_anonymous, reporter_name, report_content, contact_info, email_status, create_time) + VALUES (#{isAnonymous}, #{reporterName}, #{reportContent}, #{contactInfo}, #{emailStatus}, #{createTime}) + + + UPDATE report_record + SET email_status = #{emailStatus} + WHERE id = #{id} + + \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..89bb8ba --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,6 @@ + + +

hello word!!!

+

this is a html page

+ + \ No newline at end of file diff --git a/src/test/java/com/discipline/report/DisciplineReportApplicationTests.java b/src/test/java/com/discipline/report/DisciplineReportApplicationTests.java new file mode 100644 index 0000000..b8b3d57 --- /dev/null +++ b/src/test/java/com/discipline/report/DisciplineReportApplicationTests.java @@ -0,0 +1,13 @@ +package com.discipline.report; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DisciplineReportApplicationTests { + + @Test + void contextLoads() { + } + +}