diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/TodoItemController.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/TodoItemController.java new file mode 100644 index 00000000..da343bd1 --- /dev/null +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/TodoItemController.java @@ -0,0 +1,43 @@ +package com.nflg.qms.admin.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.nflg.qms.admin.service.TodoItemControllerService; +import com.nflg.wms.common.pojo.ApiResult; +import com.nflg.wms.common.pojo.PageData; +import com.nflg.wms.common.pojo.qo.QmsTodoItemSearchQO; +import com.nflg.wms.common.pojo.vo.QmsTodoItemVO; +import com.nflg.wms.starter.BaseController; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 待办事项 + */ +@RestController +@RequestMapping("/todo-item") +public class TodoItemController extends BaseController { + + @Resource + private TodoItemControllerService todoItemControllerService; + + /** + * 分页查询待办事项列表 + */ + @PostMapping("search") + public ApiResult> search(@Valid @RequestBody QmsTodoItemSearchQO request) { + IPage page = todoItemControllerService.search(request); + return ApiResult.success(page); + } + + /** + * 批量标记已读 + */ + @PostMapping("mark-as-read") + public ApiResult markAsRead(@RequestBody List ids) { + todoItemControllerService.markAsRead(ids); + return ApiResult.success(); + } +} diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/TodoItemControllerService.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/TodoItemControllerService.java new file mode 100644 index 00000000..d8b684b7 --- /dev/null +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/TodoItemControllerService.java @@ -0,0 +1,34 @@ +package com.nflg.qms.admin.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.nflg.wms.common.pojo.qo.QmsTodoItemSearchQO; +import com.nflg.wms.common.pojo.vo.QmsTodoItemVO; +import com.nflg.wms.repository.service.IQmsTodoItemService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 待办事项业务逻辑 + */ +@Component +public class TodoItemControllerService { + + @Resource + private IQmsTodoItemService todoItemService; + + /** + * 分页查询待办事项列表 + */ + public IPage search(QmsTodoItemSearchQO request) { + return todoItemService.search(request); + } + + /** + * 批量标记已读 + */ + public void markAsRead(List ids) { + todoItemService.markAsRead(ids); + } +} diff --git a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java b/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java index cbb7b133..57d6c679 100644 --- a/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java +++ b/nflg-wms-admin/src/main/java/com/nflg/wms/admin/service/EmailService.java @@ -11,11 +11,14 @@ import jakarta.mail.*; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component @@ -24,45 +27,180 @@ public class EmailService { @Resource private IParamConfigService paramConfigService; - public void sendEmail(String to, String subject, String content) throws MessagingException { - log.info("准备发送邮件,to:{},subject:{},content:{}", to, subject, content); - List configs = paramConfigService.lambdaQuery().eq(ParamConfig::getGroupName, "EmailSet").list(); - VUtil.trueThrowBusinessError(CollectionUtil.isNotEmpty(configs)).throwMessage("未配置邮件参数"); -// EmailConfigDTO emailConfig = JSONUtil.toBean(config.getValue(), EmailConfigDTO.class); - EmailConfigDTO emailConfig = new EmailConfigDTO(); - ParamConfig cfg = configs.stream().filter(c -> StrUtil.equals("host", c.getCode())).findFirst().orElse(null); - VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("主机名未配置"); - emailConfig.setHost(cfg.getValue()); - cfg = configs.stream().filter(c -> StrUtil.equals("port", c.getCode())).findFirst().orElse(null); - VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("端口号未配置"); - emailConfig.setPort(Integer.valueOf(cfg.getValue())); - cfg = configs.stream().filter(c -> StrUtil.equals("username", c.getCode())).findFirst().orElse(null); - VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("用户名未配置"); - emailConfig.setUsername(cfg.getValue()); - cfg = configs.stream().filter(c -> StrUtil.equals("password", c.getCode())).findFirst().orElse(null); - VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("密码未配置"); - emailConfig.setPassword(cfg.getValue()); + /** + * 邮件配置缓存 + */ + private volatile EmailConfigDTO cachedEmailConfig; + + /** + * Session缓存 + */ + private final ConcurrentHashMap sessionCache = new ConcurrentHashMap<>(); + + /** + * 发送邮件(单个收件人) + * + * @param to 收件人邮箱 + * @param subject 邮件主题 + * @param content 邮件内容(HTML格式) + */ + public void sendEmail(String to, String subject, String content) { + sendEmail(to, null, null, subject, content); + } + + /** + * 发送邮件(支持多收件人、抄送、密送) + * + * @param to 收件人邮箱,多个用逗号分隔 + * @param cc 抄送人邮箱,多个用逗号分隔,可为null + * @param bcc 密送人邮箱,多个用逗号分隔,可为null + * @param subject 邮件主题 + * @param content 邮件内容(HTML格式) + */ + public void sendEmail(String to, String cc, String bcc, String subject, String content) { + try { + log.info("准备发送邮件, to:{}, cc:{}, bcc:{}, subject:{}", to, cc, bcc, subject); + EmailConfigDTO emailConfig = getEmailConfig(); + Session session = getSession(emailConfig); + MimeMessage message = createMessage(session, emailConfig, to, cc, bcc, subject, content); + Transport.send(message); + log.info("发送邮件完成, to:{}, subject:{}", to, subject); + } catch (Exception e) { + log.error("发送邮件失败, to:{}, subject:{}, error:{}", to, subject, e.getMessage(), e); + throw new RuntimeException("发送邮件失败: " + e.getMessage(), e); + } + } + + /** + * 异步发送邮件 + * + * @param to 收件人邮箱 + * @param subject 邮件主题 + * @param content 邮件内容(HTML格式) + */ + @Async + public void sendEmailAsync(String to, String subject, String content) { + sendEmail(to, subject, content); + } + + /** + * 异步发送邮件(支持多收件人、抄送、密送) + * + * @param to 收件人邮箱,多个用逗号分隔 + * @param cc 抄送人邮箱,多个用逗号分隔,可为null + * @param bcc 密送人邮箱,多个用逗号分隔,可为null + * @param subject 邮件主题 + * @param content 邮件内容(HTML格式) + */ + @Async + public void sendEmailAsync(String to, String cc, String bcc, String subject, String content) { + sendEmail(to, cc, bcc, subject, content); + } + + /** + * 刷新邮件配置缓存 + */ + public void refreshConfig() { + cachedEmailConfig = null; + sessionCache.clear(); + log.info("邮件配置缓存已刷新"); + } + + /** + * 获取邮件配置(带缓存) + */ + private EmailConfigDTO getEmailConfig() { + if (cachedEmailConfig != null) { + return cachedEmailConfig; + } + synchronized (this) { + if (cachedEmailConfig != null) { + return cachedEmailConfig; + } + List configs = paramConfigService.lambdaQuery() + .eq(ParamConfig::getGroupName, "EmailSet") + .list(); + VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(configs)).throwMessage("未配置邮件参数"); + + EmailConfigDTO emailConfig = new EmailConfigDTO(); + ParamConfig cfg = configs.stream().filter(c -> StrUtil.equals("host", c.getCode())).findFirst().orElse(null); + VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("主机名未配置"); + emailConfig.setHost(cfg.getValue()); + + cfg = configs.stream().filter(c -> StrUtil.equals("port", c.getCode())).findFirst().orElse(null); + VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("端口号未配置"); + emailConfig.setPort(Integer.valueOf(cfg.getValue())); + + cfg = configs.stream().filter(c -> StrUtil.equals("username", c.getCode())).findFirst().orElse(null); + VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("用户名未配置"); + emailConfig.setUsername(cfg.getValue()); + + cfg = configs.stream().filter(c -> StrUtil.equals("password", c.getCode())).findFirst().orElse(null); + VUtil.trueThrowBusinessError(Objects.isNull(cfg)).throwMessage("密码未配置"); + emailConfig.setPassword(cfg.getValue()); + + cachedEmailConfig = emailConfig; + return emailConfig; + } + } + + /** + * 获取Session(带缓存) + */ + private Session getSession(EmailConfigDTO emailConfig) { + String cacheKey = emailConfig.getHost() + ":" + emailConfig.getPort(); + return sessionCache.computeIfAbsent(cacheKey, k -> createSession(emailConfig)); + } + + /** + * 创建Session + */ + private Session createSession(EmailConfigDTO emailConfig) { Properties properties = new Properties(); properties.put("mail.smtp.host", emailConfig.getHost()); properties.put("mail.smtp.port", emailConfig.getPort().toString()); properties.put("mail.smtp.auth", "true"); properties.put("mail.smtp.ssl.enable", "true"); - // 设置超时时间(单位:毫秒) - properties.put("mail.smtp.connectiontimeout", "5000"); // 连接超时 - properties.put("mail.smtp.timeout", "5000"); // 读取超时 - properties.put("mail.smtp.writetimeout", "5000"); // 写入超时 - Session session = Session.getInstance(properties, new Authenticator() { + properties.put("mail.smtp.connectiontimeout", "5000"); + properties.put("mail.smtp.timeout", "5000"); + properties.put("mail.smtp.writetimeout", "5000"); + return Session.getInstance(properties, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(emailConfig.getUsername(), emailConfig.getPassword()); } }); + } + + /** + * 创建邮件消息 + */ + private MimeMessage createMessage(Session session, EmailConfigDTO emailConfig, + String to, String cc, String bcc, + String subject, String content) throws MessagingException, UnsupportedEncodingException { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(emailConfig.getUsername())); - message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); - message.setSubject(subject); + + // 设置收件人 + if (StrUtil.isNotBlank(to)) { + InternetAddress[] toAddresses = InternetAddress.parse(to); + message.setRecipients(Message.RecipientType.TO, toAddresses); + } + + // 设置抄送 + if (StrUtil.isNotBlank(cc)) { + InternetAddress[] ccAddresses = InternetAddress.parse(cc); + message.setRecipients(Message.RecipientType.CC, ccAddresses); + } + + // 设置密送 + if (StrUtil.isNotBlank(bcc)) { + InternetAddress[] bccAddresses = InternetAddress.parse(bcc); + message.setRecipients(Message.RecipientType.BCC, bccAddresses); + } + + message.setSubject(subject, "UTF-8"); message.setContent(content, "text/html; charset=UTF-8"); - Transport.send(message); - log.info("发送邮件完成,to:{},subject:{},content:{}", to, subject, content); + return message; } } diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsTodoItemSearchQO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsTodoItemSearchQO.java new file mode 100644 index 00000000..3328efae --- /dev/null +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsTodoItemSearchQO.java @@ -0,0 +1,32 @@ +package com.nflg.wms.common.pojo.qo; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 待办事项 列表查询参数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class QmsTodoItemSearchQO extends SearchBaseQO { + + /** + * 编号 + */ + private String code; + + /** + * 标题 + */ + private String title; + + /** + * 来源类型ID + */ + private Long sourceTypeId; + + /** + * 是否已读 + */ + private Boolean isRead; +} diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsTodoItemVO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsTodoItemVO.java new file mode 100644 index 00000000..00cc7859 --- /dev/null +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsTodoItemVO.java @@ -0,0 +1,74 @@ +package com.nflg.wms.common.pojo.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 待办事项 信息VO + */ +@Data +public class QmsTodoItemVO { + + private Long id; + + /** + * 编号 + */ + private String code; + + /** + * 标题 + */ + private String title; + + /** + * 来源类型ID + */ + private Long sourceTypeId; + + /** + * 来源类型名称 + */ + private String sourceTypeName; + + /** + * 来源ID + */ + private Long sourceId; + + /** + * 是否已读 + */ + private Boolean isRead; + + /** + * 创建人ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsTodoItem.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsTodoItem.java new file mode 100644 index 00000000..642d877b --- /dev/null +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsTodoItem.java @@ -0,0 +1,83 @@ +package com.nflg.wms.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 待办事项表 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@TableName("qms_todo_item") +public class QmsTodoItem implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 编号 + */ + private String code; + + /** + * 标题 + */ + private String title; + + /** + * 来源类型ID,关联dictionary_item表(字典编码MessageType) + */ + private Long sourceTypeId; + + /** + * 来源ID + */ + private Long sourceId; + + /** + * 是否已读 + */ + private Boolean isRead; + + /** + * 创建人ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/QmsTodoItemMapper.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/QmsTodoItemMapper.java new file mode 100644 index 00000000..ca830cb3 --- /dev/null +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/QmsTodoItemMapper.java @@ -0,0 +1,16 @@ +package com.nflg.wms.repository.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nflg.wms.common.pojo.qo.QmsTodoItemSearchQO; +import com.nflg.wms.common.pojo.vo.QmsTodoItemVO; +import com.nflg.wms.repository.entity.QmsTodoItem; + +/** + * 待办事项 Mapper 接口 + */ +public interface QmsTodoItemMapper extends BaseMapper { + + IPage search(QmsTodoItemSearchQO request, Page page); +} diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsTodoItemService.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsTodoItemService.java new file mode 100644 index 00000000..11eebaed --- /dev/null +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsTodoItemService.java @@ -0,0 +1,26 @@ +package com.nflg.wms.repository.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.nflg.wms.common.pojo.qo.QmsTodoItemSearchQO; +import com.nflg.wms.common.pojo.vo.QmsTodoItemVO; +import com.nflg.wms.repository.entity.QmsTodoItem; + +import java.util.List; + +/** + * 待办事项 服务类 + */ +public interface IQmsTodoItemService extends IService { + + /** + * 分页查询待办事项列表 + */ + IPage search(QmsTodoItemSearchQO request); + + /** + * 批量标记已读 + * @param ids 待办事项ID列表 + */ + void markAsRead(List ids); +} diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/impl/QmsTodoItemServiceImpl.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/impl/QmsTodoItemServiceImpl.java new file mode 100644 index 00000000..ada0ce9d --- /dev/null +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/impl/QmsTodoItemServiceImpl.java @@ -0,0 +1,45 @@ +package com.nflg.wms.repository.service.impl; + +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.nflg.wms.common.pojo.qo.QmsTodoItemSearchQO; +import com.nflg.wms.common.pojo.vo.QmsTodoItemVO; +import com.nflg.wms.common.util.UserUtil; +import com.nflg.wms.repository.entity.QmsTodoItem; +import com.nflg.wms.repository.mapper.QmsTodoItemMapper; +import com.nflg.wms.repository.service.IQmsTodoItemService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 待办事项 服务实现类 + */ +@Service +public class QmsTodoItemServiceImpl extends ServiceImpl implements IQmsTodoItemService { + + @Override + public IPage search(QmsTodoItemSearchQO request) { + return baseMapper.search(request, new Page<>(request.getPage(), request.getPageSize())); + } + + @Override + public void markAsRead(List ids) {】 + if (ids == null || ids.isEmpty()) { + return; + } + Long userId = UserUtil.getUserId(); + String userName = UserUtil.getUserName(); + LocalDateTime now = LocalDateTime.now(); + lambdaUpdate() + .eq(QmsTodoItem::getIsRead, false) + .in(QmsTodoItem::getId, ids) + .set(QmsTodoItem::getIsRead, true) + .set(QmsTodoItem::getUpdateUserId, userId) + .set(QmsTodoItem::getUpdateUserName, userName) + .set(QmsTodoItem::getUpdateTime, now) + .update(); + } +} diff --git a/nflg-wms-repository/src/main/resources/mapper/QmsTodoItemMapper.xml b/nflg-wms-repository/src/main/resources/mapper/QmsTodoItemMapper.xml new file mode 100644 index 00000000..ec9872e1 --- /dev/null +++ b/nflg-wms-repository/src/main/resources/mapper/QmsTodoItemMapper.xml @@ -0,0 +1,44 @@ + + + + + + +