From 4ff1bc1101d51f7dd9165342f6a535fba03be650 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Wed, 26 Nov 2025 13:38:36 +0800 Subject: [PATCH] =?UTF-8?q?sms=E3=80=81=E9=82=AE=E4=BB=B6=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.bin/mysql/sql/initConfigData.sql | 20 +- .../org/xyzh/api/message/MessageService.java | 26 -- .../properties/EmailConfigProperties.java | 91 +++++ .../properties/SmsConfigProperties.java | 91 +++++ schoolNewsServ/common/common-util/pom.xml | 7 + .../org/xyzh/common/utils/EmailUtils.java | 8 +- .../java/org/xyzh/common/utils/SmsUtils.java | 69 ++-- .../message/controller/MessageController.java | 31 -- .../message/event/MessageCreatedEvent.java | 27 ++ .../service/impl/MessageSendService.java | 34 +- .../service/impl/MessageServiceImpl.java | 93 ++--- .../main/resources/mapper/MessageMapper.xml | 4 +- .../system/config/DynamicConfigLoader.java | 135 +++++++ .../user/service/impl/SysUserServiceImpl.java | 2 +- .../controller/UserProfileController.java | 30 +- .../profile/AccountSettingsView.vue | 365 +++++++++++++++++- 16 files changed, 847 insertions(+), 186 deletions(-) create mode 100644 schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/EmailConfigProperties.java create mode 100644 schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/SmsConfigProperties.java create mode 100644 schoolNewsServ/message/src/main/java/org/xyzh/message/event/MessageCreatedEvent.java create mode 100644 schoolNewsServ/system/src/main/java/org/xyzh/system/config/DynamicConfigLoader.java diff --git a/schoolNewsServ/.bin/mysql/sql/initConfigData.sql b/schoolNewsServ/.bin/mysql/sql/initConfigData.sql index a7d2c64..7d69b35 100644 --- a/schoolNewsServ/.bin/mysql/sql/initConfigData.sql +++ b/schoolNewsServ/.bin/mysql/sql/initConfigData.sql @@ -19,4 +19,22 @@ INSERT INTO `tb_sys_config` (`id`, `config_key`, `config_name`, `config_value`, -- Dify知识库配置 ('30', 'dify.dataset.defaultIndexingTechnique', '默认索引方式', 'high_quality', 'string', 'select', 'Dify配置', '默认索引方式', NULL, '知识库文档的默认索引方式', NULL, NULL, NULL, NULL, 'high_quality,economy', 30, 1, '1', now()), -('31', 'dify.dataset.defaultEmbeddingModel', '默认Embedding模型', 'text-embedding-ada-002', 'string', 'input', 'Dify配置', '默认Embedding模型', '请输入Embedding模型名称', '知识库使用的默认Embedding模型', NULL, NULL, NULL, NULL, NULL, 31, 1, '1', now()); +('31', 'dify.dataset.defaultEmbeddingModel', '默认Embedding模型', 'text-embedding-ada-002', 'string', 'input', 'Dify配置', '默认Embedding模型', '请输入Embedding模型名称', '知识库使用的默认Embedding模型', NULL, NULL, NULL, NULL, NULL, 31, 1, '1', now()), + +-- 邮件配置 +('40', 'email.host', 'SMTP服务器地址', 'smtp.qq.com', 'string', 'input', '邮件配置', 'SMTP服务器地址', '请输入SMTP服务器地址', '邮件发送服务器地址', NULL, NULL, NULL, NULL, NULL, 40, 1, '1', now()), +('41', 'email.port', 'SMTP端口', '587', 'integer', 'input', '邮件配置', 'SMTP服务器端口', '请输入SMTP端口', '邮件发送服务器端口', NULL, 25, 587, NULL, NULL, 41, 1, '1', now()), +('42', 'email.username', '发件人邮箱', '3223905473@qq.com', 'string', 'input', '邮件配置', '发件人邮箱地址', '请输入发件人邮箱', '用于发送邮件的邮箱账号', NULL, NULL, NULL, NULL, NULL, 42, 1, '1', now()), +('43', 'email.password', '邮箱授权码', 'xmdmxvtjumxocicc', 'string', 'password', '邮件配置', '邮箱授权码/密码', '请输入邮箱授权码', '邮箱的授权码或密码', NULL, NULL, NULL, NULL, NULL, 43, 1, '1', now()), +('44', 'email.fromName', '发件人名称', '校园新闻系统', 'string', 'input', '邮件配置', '发件人显示名称', '请输入发件人名称', '邮件中显示的发件人名称', NULL, NULL, NULL, NULL, NULL, 44, 1, '1', now()), +('45', 'email.ssl.enable', '启用SSL', 'true', 'boolean', 'switch', '邮件配置', '是否启用SSL', NULL, 'SSL加密连接', NULL, NULL, NULL, NULL, NULL, 45, 1, '1', now()), +('46', 'email.timeout', '连接超时时间', '30000', 'integer', 'input', '邮件配置', '连接超时时间(毫秒)', '请输入超时时间', 'SMTP连接超时时间', NULL, 5000, 60000, '毫秒', NULL, 46, 1, '1', now()), + +-- 短信配置 +('50', 'sms.provider', '短信服务商', 'aliyun', 'string', 'select', '短信配置', '短信服务提供商', NULL, '短信服务提供商类型', NULL, NULL, NULL, NULL, 'aliyun,tencent', 50, 1, '1', now()), +('51', 'sms.accessKeyId', 'AccessKey ID', 'LTAI5t68do3qVXx5Rufugt3X', 'string', 'input', '短信配置', '短信服务AccessKey ID', '请输入AccessKey ID', '云服务商的AccessKey ID', NULL, NULL, NULL, NULL, NULL, 51, 1, '1', now()), +('52', 'sms.accessKeySecret', 'AccessKey Secret', '2vD9ToIff49Vph4JQXsn0Cy8nXQfzA', 'string', 'password', '短信配置', '短信服务AccessKey Secret', '请输入AccessKey Secret', '云服务商的AccessKey Secret', NULL, NULL, NULL, NULL, NULL, 52, 1, '1', now()), +('53', 'sms.signName', '短信签名', '星洋智慧', 'string', 'input', '短信配置', '短信签名', '请输入短信签名', '发送短信使用的签名', NULL, NULL, NULL, NULL, NULL, 53, 1, '1', now()), +('54', 'sms.templateCode.login', '登录验证码模板', 'SMS_491985030', 'string', 'input', '短信配置', '登录验证码模板编码', '请输入模板编码', '登录验证码短信模板', NULL, NULL, NULL, NULL, NULL, 54, 1, '1', now()), +('55', 'sms.templateCode.register', '注册验证码模板', 'SMS_491985030', 'string', 'input', '短信配置', '注册验证码模板编码', '请输入模板编码', '注册验证码短信模板', NULL, NULL, NULL, NULL, NULL, 55, 1, '1', now()), +('56', 'sms.timeout', '请求超时时间', '30000', 'integer', 'input', '短信配置', '请求超时时间(毫秒)', '请输入超时时间', 'API请求超时时间', NULL, 5000, 60000, '毫秒', NULL, 56, 1, '1', now()); diff --git a/schoolNewsServ/api/api-message/src/main/java/org/xyzh/api/message/MessageService.java b/schoolNewsServ/api/api-message/src/main/java/org/xyzh/api/message/MessageService.java index 060d865..6f8a027 100644 --- a/schoolNewsServ/api/api-message/src/main/java/org/xyzh/api/message/MessageService.java +++ b/schoolNewsServ/api/api-message/src/main/java/org/xyzh/api/message/MessageService.java @@ -178,30 +178,4 @@ public interface MessageService { // ================== 辅助接口 ================== - /** - * 获取可选的部门树(当前部门及子部门) - * - * @return ResultDomain 部门树数据 - * @author Claude - * @since 2025-11-13 - */ - ResultDomain> getTargetDepts(); - - /** - * 获取可选的角色列表(当前部门及子部门的角色) - * - * @return ResultDomain 角色列表数据 - * @author Claude - * @since 2025-11-13 - */ - ResultDomain> getTargetRoles(); - - /** - * 获取可选的用户列表(当前部门及子部门的用户) - * - * @return ResultDomain 用户列表数据 - * @author Claude - * @since 2025-11-13 - */ - ResultDomain> getTargetUsers(); } diff --git a/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/EmailConfigProperties.java b/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/EmailConfigProperties.java new file mode 100644 index 0000000..23bf470 --- /dev/null +++ b/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/EmailConfigProperties.java @@ -0,0 +1,91 @@ +package org.xyzh.common.config.properties; + +import org.springframework.stereotype.Component; + +/** + * @description 邮件配置属性 + * @filename EmailConfigProperties.java + * @author yslg + * @copyright xyzh + * @since 2025-11-26 + */ +@Component +public class EmailConfigProperties { + + /** SMTP服务器地址 */ + private String host; + + /** SMTP端口 */ + private Integer port; + + /** 发件人邮箱 */ + private String username; + + /** 邮箱授权码 */ + private String password; + + /** 发件人名称 */ + private String fromName; + + /** 是否启用SSL */ + private Boolean sslEnable; + + /** 连接超时时间(毫秒) */ + private Integer timeout; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFromName() { + return fromName; + } + + public void setFromName(String fromName) { + this.fromName = fromName; + } + + public Boolean getSslEnable() { + return sslEnable; + } + + public void setSslEnable(Boolean sslEnable) { + this.sslEnable = sslEnable; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } +} diff --git a/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/SmsConfigProperties.java b/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/SmsConfigProperties.java new file mode 100644 index 0000000..4a01519 --- /dev/null +++ b/schoolNewsServ/common/common-core/src/main/java/org/xyzh/common/config/properties/SmsConfigProperties.java @@ -0,0 +1,91 @@ +package org.xyzh.common.config.properties; + +import org.springframework.stereotype.Component; + +/** + * @description 短信配置属性 + * @filename SmsConfigProperties.java + * @author yslg + * @copyright xyzh + * @since 2025-11-26 + */ +@Component +public class SmsConfigProperties { + + /** 短信服务商 */ + private String provider; + + /** AccessKey ID */ + private String accessKeyId; + + /** AccessKey Secret */ + private String accessKeySecret; + + /** 短信签名 */ + private String signName; + + /** 登录验证码模板 */ + private String templateCodeLogin; + + /** 注册验证码模板 */ + private String templateCodeRegister; + + /** 请求超时时间(毫秒) */ + private Integer timeout; + + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public String getSignName() { + return signName; + } + + public void setSignName(String signName) { + this.signName = signName; + } + + public String getTemplateCodeLogin() { + return templateCodeLogin; + } + + public void setTemplateCodeLogin(String templateCodeLogin) { + this.templateCodeLogin = templateCodeLogin; + } + + public String getTemplateCodeRegister() { + return templateCodeRegister; + } + + public void setTemplateCodeRegister(String templateCodeRegister) { + this.templateCodeRegister = templateCodeRegister; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } +} diff --git a/schoolNewsServ/common/common-util/pom.xml b/schoolNewsServ/common/common-util/pom.xml index b8d63cd..8245993 100644 --- a/schoolNewsServ/common/common-util/pom.xml +++ b/schoolNewsServ/common/common-util/pom.xml @@ -19,6 +19,13 @@ + + + org.xyzh + common-core + 1.0.0 + + org.apache.poi diff --git a/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/EmailUtils.java b/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/EmailUtils.java index 0fa6f3c..709cf54 100644 --- a/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/EmailUtils.java +++ b/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/EmailUtils.java @@ -3,11 +3,11 @@ package org.xyzh.common.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; +import org.xyzh.common.config.properties.EmailConfigProperties; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -27,8 +27,8 @@ public class EmailUtils { @Autowired private JavaMailSender mailSender; - @Value("${spring.mail.username:}") - private String from; + @Autowired + private EmailConfigProperties emailConfigProperties; /** * 发送简单文本邮件 @@ -40,6 +40,7 @@ public class EmailUtils { public boolean sendSimpleEmail(String to, String subject, String content) { try { SimpleMailMessage message = new SimpleMailMessage(); + String from = emailConfigProperties.getUsername(); message.setFrom(from); message.setTo(to); message.setSubject(subject); @@ -66,6 +67,7 @@ public class EmailUtils { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + String from = emailConfigProperties.getUsername(); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); diff --git a/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/SmsUtils.java b/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/SmsUtils.java index 9634981..985e728 100644 --- a/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/SmsUtils.java +++ b/schoolNewsServ/common/common-util/src/main/java/org/xyzh/common/utils/SmsUtils.java @@ -6,9 +6,10 @@ import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.teaopenapi.models.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import org.xyzh.common.config.properties.SmsConfigProperties; /** * @description 短信发送工具类 - 支持多种短信服务商 @@ -22,29 +23,8 @@ public class SmsUtils { private static final Logger logger = LoggerFactory.getLogger(SmsUtils.class); - @Value("${sms.enabled:false}") - private boolean enabled; - - @Value("${sms.provider:aliyun}") - private String provider; - - @Value("${sms.access-key-id:}") - private String accessKeyId; - - @Value("${sms.access-key-secret:}") - private String accessKeySecret; - - @Value("${sms.sign-name:红色思政学习平台}") - private String signName; - - @Value("${sms.template-code:}") - private String templateCode; - - @Value("${sms.region-id:cn-hangzhou}") - private String regionId; - - @Value("${sms.endpoint:dysmsapi.aliyuncs.com}") - private String endpoint; + @Autowired + private SmsConfigProperties smsConfigProperties; /** * 发送短信验证码 @@ -54,16 +34,22 @@ public class SmsUtils { */ public boolean sendVerificationCode(String phone, String code) { // 如果未启用短信服务,使用模拟模式 - if (!enabled || !StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { + String accessKeyId = smsConfigProperties.getAccessKeyId(); + String accessKeySecret = smsConfigProperties.getAccessKeySecret(); + + if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { logger.warn("短信服务未配置或未启用,使用模拟模式"); logger.info("【模拟发送】短信验证码,手机号: {}, 验证码: {}", phone, code); return true; } // 根据配置的服务商选择发送方式 + String provider = smsConfigProperties.getProvider(); + if (provider == null) provider = "aliyun"; + switch (provider.toLowerCase()) { case "aliyun": - return sendByAliyun(phone, code); + return sendByAliyun(phone, code, smsConfigProperties.getTemplateCodeLogin()); case "tencent": logger.warn("腾讯云短信服务暂未实现,使用模拟模式"); logger.info("【模拟发送】短信验证码,手机号: {}, 验证码: {}", phone, code); @@ -78,15 +64,16 @@ public class SmsUtils { * 使用阿里云发送短信验证码 * @param phone 手机号 * @param code 验证码 + * @param templateCode 短信模板CODE * @return 是否发送成功 */ - private boolean sendByAliyun(String phone, String code) { + private boolean sendByAliyun(String phone, String code, String templateCode) { try { Client client = createAliyunClient(); SendSmsRequest request = new SendSmsRequest() .setPhoneNumbers(phone) - .setSignName(signName) + .setSignName(smsConfigProperties.getSignName()) .setTemplateCode(templateCode) .setTemplateParam("{\"code\":\"" + code + "\"}"); @@ -112,9 +99,9 @@ public class SmsUtils { */ private Client createAliyunClient() throws Exception { Config config = new Config() - .setAccessKeyId(accessKeyId) - .setAccessKeySecret(accessKeySecret) - .setEndpoint(endpoint); + .setAccessKeyId(smsConfigProperties.getAccessKeyId()) + .setAccessKeySecret(smsConfigProperties.getAccessKeySecret()) + .setEndpoint("dysmsapi.aliyuncs.com"); return new Client(config); } @@ -127,13 +114,19 @@ public class SmsUtils { */ public boolean sendSms(String phone, String templateCode, String templateParam) { // 如果未启用短信服务,使用模拟模式 - if (!enabled || !StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { + String accessKeyId = smsConfigProperties.getAccessKeyId(); + String accessKeySecret = smsConfigProperties.getAccessKeySecret(); + + if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { logger.warn("短信服务未配置或未启用,使用模拟模式"); logger.info("【模拟发送】短信,手机号: {}, 模板: {}, 参数: {}", phone, templateCode, templateParam); return true; } // 根据配置的服务商选择发送方式 + String provider = smsConfigProperties.getProvider(); + if (provider == null) provider = "aliyun"; + switch (provider.toLowerCase()) { case "aliyun": return sendSmsAliyun(phone, templateCode, templateParam); @@ -156,7 +149,7 @@ public class SmsUtils { SendSmsRequest request = new SendSmsRequest() .setPhoneNumbers(phone) - .setSignName(signName) + .setSignName(smsConfigProperties.getSignName()) .setTemplateCode(templateCode) .setTemplateParam(templateParam); @@ -185,13 +178,19 @@ public class SmsUtils { */ public boolean sendBatchSms(String phones, String templateCode, String templateParam) { // 如果未启用短信服务,使用模拟模式 - if (!enabled || !StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { + String accessKeyId = smsConfigProperties.getAccessKeyId(); + String accessKeySecret = smsConfigProperties.getAccessKeySecret(); + + if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) { logger.warn("短信服务未配置或未启用,使用模拟模式"); logger.info("【模拟发送】批量短信,手机号: {}, 模板: {}, 参数: {}", phones, templateCode, templateParam); return true; } // 根据配置的服务商选择发送方式 + String provider = smsConfigProperties.getProvider(); + if (provider == null) provider = "aliyun"; + switch (provider.toLowerCase()) { case "aliyun": return sendBatchSmsAliyun(phones, templateCode, templateParam); @@ -214,7 +213,7 @@ public class SmsUtils { SendSmsRequest request = new SendSmsRequest() .setPhoneNumbers(phones) - .setSignName(signName) + .setSignName(smsConfigProperties.getSignName()) .setTemplateCode(templateCode) .setTemplateParam(templateParam); diff --git a/schoolNewsServ/message/src/main/java/org/xyzh/message/controller/MessageController.java b/schoolNewsServ/message/src/main/java/org/xyzh/message/controller/MessageController.java index b3e0c51..8ca6384 100644 --- a/schoolNewsServ/message/src/main/java/org/xyzh/message/controller/MessageController.java +++ b/schoolNewsServ/message/src/main/java/org/xyzh/message/controller/MessageController.java @@ -217,35 +217,4 @@ public class MessageController { return messageService.getUnreadCount(); } - // ================== 辅助接口 ================== - - /** - * 获取可选的部门树 - * - * @return ResultDomain - */ - @GetMapping("/targets/depts") - public ResultDomain> getTargetDepts() { - return messageService.getTargetDepts(); - } - - /** - * 获取可选的角色列表 - * - * @return ResultDomain - */ - @GetMapping("/targets/roles") - public ResultDomain> getTargetRoles() { - return messageService.getTargetRoles(); - } - - /** - * 获取可选的用户列表 - * - * @return ResultDomain - */ - @GetMapping("/targets/users") - public ResultDomain> getTargetUsers() { - return messageService.getTargetUsers(); - } } diff --git a/schoolNewsServ/message/src/main/java/org/xyzh/message/event/MessageCreatedEvent.java b/schoolNewsServ/message/src/main/java/org/xyzh/message/event/MessageCreatedEvent.java new file mode 100644 index 0000000..511f0cf --- /dev/null +++ b/schoolNewsServ/message/src/main/java/org/xyzh/message/event/MessageCreatedEvent.java @@ -0,0 +1,27 @@ +package org.xyzh.message.event; + +import org.springframework.context.ApplicationEvent; + +/** + * 消息创建事件 + * 用于在事务提交后触发异步发送 + * + * @description 消息创建完成事件 + * @filename MessageCreatedEvent.java + * @author yslg + * @copyright xyzh + * @since 2025-11-26 + */ +public class MessageCreatedEvent extends ApplicationEvent { + + private final String messageID; + + public MessageCreatedEvent(Object source, String messageID) { + super(source); + this.messageID = messageID; + } + + public String getMessageID() { + return messageID; + } +} diff --git a/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageSendService.java b/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageSendService.java index 822b09f..d5a0742 100644 --- a/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageSendService.java +++ b/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageSendService.java @@ -6,11 +6,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; import org.xyzh.common.dto.message.TbSysMessage; import org.xyzh.common.dto.message.TbSysMessageUser; import org.xyzh.common.dto.user.TbSysUser; import org.xyzh.common.utils.EmailUtils; import org.xyzh.common.utils.SmsUtils; +import org.xyzh.message.event.MessageCreatedEvent; import org.xyzh.message.mapper.MessageMapper; import org.xyzh.message.mapper.MessageUserMapper; import org.xyzh.system.mapper.UserMapper; @@ -47,6 +50,18 @@ public class MessageSendService { @Autowired private SmsUtils smsUtils; + /** + * 监听消息创建事件,在事务提交后触发异步发送 + * 这样可以确保消息已经持久化到数据库,避免异步线程查询不到数据 + * + * @param event 消息创建事件 + */ + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onMessageCreated(MessageCreatedEvent event) { + logger.info("接收到消息创建事件,准备异步发送消息:{}", event.getMessageID()); + sendMessageAsync(event.getMessageID()); + } + /** * 异步发送消息 * @@ -78,7 +93,6 @@ public class MessageSendService { updateMessageStatus(message, "sent"); return; } - // 3. 遍历发送 int successCount = 0; int failedCount = 0; @@ -280,8 +294,22 @@ public class MessageSendService { html.append(message.getContent()); html.append(""); html.append("
"); - html.append("

发送人:").append(message.getSenderName()).append(" (").append(message.getSenderDeptName()).append(")

"); - html.append("

发送时间:").append(message.getActualSendTime() != null ? message.getActualSendTime().toString() : "").append("

"); + // 发送人信息 - 安全处理null值 + String senderName = message.getSenderName() != null ? message.getSenderName() : "系统"; + String senderDept = message.getSenderDeptName() != null ? message.getSenderDeptName() : ""; + if (senderDept.isEmpty()) { + html.append("

发送人:").append(senderName).append("

"); + } else { + html.append("

发送人:").append(senderName).append(" (").append(senderDept).append(")

"); + } + // 发送时间 - 使用实际发送时间或创建时间 + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String sendTime = message.getActualSendTime() != null ? + sdf.format(message.getActualSendTime()) : + (message.getCreateTime() != null ? sdf.format(message.getCreateTime()) : ""); + if (!sendTime.isEmpty()) { + html.append("

发送时间:").append(sendTime).append("

"); + } html.append("

此邮件由系统自动发送,请勿回复。

"); html.append("
"); html.append(""); diff --git a/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageServiceImpl.java b/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageServiceImpl.java index 80a9439..b2f3e1d 100644 --- a/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageServiceImpl.java +++ b/schoolNewsServ/message/src/main/java/org/xyzh/message/service/impl/MessageServiceImpl.java @@ -1,21 +1,25 @@ package org.xyzh.message.service.impl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xyzh.api.message.MessageService; +import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageParam; import org.xyzh.common.dto.message.*; import org.xyzh.common.utils.IDUtils; +import org.xyzh.message.event.MessageCreatedEvent; import org.xyzh.message.mapper.MessageMapper; import org.xyzh.message.mapper.MessageTargetMapper; import org.xyzh.message.mapper.MessageUserMapper; import org.xyzh.system.mapper.DepartmentMapper; import org.xyzh.system.mapper.UserMapper; +import org.xyzh.system.utils.LoginUtil; import java.util.*; @@ -51,6 +55,9 @@ public class MessageServiceImpl implements MessageService { @Autowired private MessageSendService messageSendService; + @Autowired + private ApplicationEventPublisher eventPublisher; + /** * 创建消息 */ @@ -60,8 +67,9 @@ public class MessageServiceImpl implements MessageService { ResultDomain rt = new ResultDomain<>(); try { // 1. 获取当前用户信息(从Session或SecurityContext获取) - String currentUserID = getCurrentUserID(); - String currentDeptID = getCurrentUserDeptID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); + String currentDeptID = currentUser.getRoles().get(0).getDeptID(); // 2. 设置消息主体基本信息 if (message.getID() == null) { @@ -72,6 +80,7 @@ public class MessageServiceImpl implements MessageService { } message.setSenderID(currentUserID); message.setSenderDeptID(currentDeptID); + message.setSenderName(currentUser.getUser().getUsername()); // 设置状态 if (message.getStatus() == null) { @@ -104,10 +113,11 @@ public class MessageServiceImpl implements MessageService { if (targets != null && !targets.isEmpty()) { for (TbSysMessageTarget target : targets) { // 权限校验:scopeDeptID必须是当前部门或子部门 - if (!isCurrentOrSubDept(currentDeptID, target.getScopeDeptID())) { - rt.fail("无权向该部门发送消息"); - return rt; - } + // TODO: 实现部门层级检查 + // if (!isCurrentOrSubDept(currentDeptID, target.getScopeDeptID())) { + // rt.fail("无权向该部门发送消息"); + // return rt; + // } if (target.getID() == null) { target.setID(IDUtils.generateID()); @@ -130,9 +140,10 @@ public class MessageServiceImpl implements MessageService { message.setTargetUserCount(userMessages.size()); messageMapper.updateMessage(message); - // 6. 如果是立即发送,异步发送消息 + // 6. 如果是立即发送,发布事件触发异步发送 + // 使用事件机制确保在事务提交后再发送,避免异步线程读不到数据 if ("immediate".equals(message.getSendMode())) { - messageSendService.sendMessageAsync(message.getMessageID()); + eventPublisher.publishEvent(new MessageCreatedEvent(this, message.getMessageID())); } } @@ -168,7 +179,8 @@ public class MessageServiceImpl implements MessageService { } message.setUpdateTime(new Date()); - message.setUpdater(getCurrentUserID()); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + message.setUpdater(currentUser.getUser().getID()); messageMapper.updateMessage(message); rt.success("更新成功", message); return rt; @@ -259,7 +271,8 @@ public class MessageServiceImpl implements MessageService { pageParam = new PageParam(); } - String currentDeptID = getCurrentUserDeptID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentDeptID = currentUser.getRoles().get(0).getDeptID(); List list = messageMapper.selectMessagePage(filter, currentDeptID); int total = messageMapper.countMessage(filter, currentDeptID); @@ -475,7 +488,8 @@ public class MessageServiceImpl implements MessageService { pageParam = new PageParam(); } - String currentUserID = getCurrentUserID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); // 使用新的动态查询方法 List list = messageUserMapper.selectMyMessagesWithDynamicTargets(currentUserID, filter); @@ -500,7 +514,8 @@ public class MessageServiceImpl implements MessageService { public ResultDomain getMyMessageDetail(String messageID) { ResultDomain rt = new ResultDomain<>(); try { - String currentUserID = getCurrentUserID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); MessageUserVO messageUserVO = messageUserMapper.selectOrCreateUserMessage(currentUserID, messageID); if (messageUserVO == null) { @@ -546,7 +561,8 @@ public class MessageServiceImpl implements MessageService { public ResultDomain markAsRead(String messageID) { ResultDomain rt = new ResultDomain<>(); try { - String currentUserID = getCurrentUserID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); // 先尝试更新已有记录 int result = messageUserMapper.markAsRead(currentUserID, messageID); @@ -599,7 +615,8 @@ public class MessageServiceImpl implements MessageService { public ResultDomain batchMarkAsRead(List messageIDs) { ResultDomain rt = new ResultDomain<>(); try { - String currentUserID = getCurrentUserID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); int count = messageUserMapper.batchMarkAsRead(currentUserID, messageIDs); // 更新每条消息的已读数量 @@ -621,7 +638,8 @@ public class MessageServiceImpl implements MessageService { public ResultDomain getUnreadCount() { ResultDomain rt = new ResultDomain<>(); try { - String currentUserID = getCurrentUserID(); + LoginDomain currentUser = LoginUtil.getCurrentLoginDomain(); + String currentUserID = currentUser.getUser().getID(); // 使用动态计算方法,统计用户应该看到的所有未读消息 Integer count = messageUserMapper.countUnreadWithDynamicTargets(currentUserID); rt.success("查询成功", count != null ? count : 0); @@ -636,29 +654,7 @@ public class MessageServiceImpl implements MessageService { // ================== 辅助方法 ================== - @Override - public ResultDomain> getTargetDepts() { - ResultDomain> rt = new ResultDomain<>(); - // TODO: 实现获取可选部门树 - rt.success("查询成功", new HashMap<>()); - return rt; - } - @Override - public ResultDomain> getTargetRoles() { - ResultDomain> rt = new ResultDomain<>(); - // TODO: 实现获取可选角色列表 - rt.success("查询成功", new HashMap<>()); - return rt; - } - - @Override - public ResultDomain> getTargetUsers() { - ResultDomain> rt = new ResultDomain<>(); - // TODO: 实现获取可选用户列表 - rt.success("查询成功", new HashMap<>()); - return rt; - } // ================== 私有辅助方法 ================== @@ -720,27 +716,4 @@ public class MessageServiceImpl implements MessageService { return userMessages; } - /** - * 检查目标部门是否是当前部门或子部门 - */ - private boolean isCurrentOrSubDept(String currentDeptID, String targetDeptID) { - // TODO: 实现部门层级检查 - return true; - } - - /** - * 获取当前用户ID - */ - private String getCurrentUserID() { - // TODO: 从SecurityContext或Session获取 - return "1"; - } - - /** - * 获取当前用户部门ID - */ - private String getCurrentUserDeptID() { - // TODO: 从SecurityContext或Session获取 - return "root_department"; - } } diff --git a/schoolNewsServ/message/src/main/resources/mapper/MessageMapper.xml b/schoolNewsServ/message/src/main/resources/mapper/MessageMapper.xml index e8842c0..7d0dcc4 100644 --- a/schoolNewsServ/message/src/main/resources/mapper/MessageMapper.xml +++ b/schoolNewsServ/message/src/main/resources/mapper/MessageMapper.xml @@ -166,12 +166,12 @@ INSERT INTO tb_sys_message (id, message_id, title, content, message_type, priority, sender_id, sender_dept_id, - send_mode, scheduled_time, status, target_user_count, retry_count, max_retry_count, + send_mode, sender_name,scheduled_time, status, target_user_count, retry_count, max_retry_count, creator, create_time, update_time, deleted) VALUES (#{message.id}, #{message.messageID}, #{message.title}, #{message.content}, #{message.messageType}, #{message.priority}, #{message.senderID}, #{message.senderDeptID}, - #{message.sendMode}, #{message.scheduledTime}, #{message.status}, #{message.targetUserCount}, + #{message.sendMode}, #{message.senderName},#{message.scheduledTime}, #{message.status}, #{message.targetUserCount}, #{message.retryCount}, #{message.maxRetryCount}, #{message.creator}, #{message.createTime}, #{message.updateTime}, #{message.deleted}) diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/config/DynamicConfigLoader.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/config/DynamicConfigLoader.java new file mode 100644 index 0000000..e846b4e --- /dev/null +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/config/DynamicConfigLoader.java @@ -0,0 +1,135 @@ +package org.xyzh.system.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.util.StringUtils; +import org.xyzh.api.system.config.SysConfigService; +import org.xyzh.common.config.properties.EmailConfigProperties; +import org.xyzh.common.config.properties.SmsConfigProperties; + +import java.util.Properties; + +/** + * @description 动态配置加载器 - 从数据库加载配置并应用到组件 + * @filename DynamicConfigLoader.java + * @author yslg + * @copyright xyzh + * @since 2025-11-26 + */ +@Configuration +@Order(100) // 确保在其他组件初始化之后再加载配置 +public class DynamicConfigLoader implements ApplicationRunner { + + private static final Logger logger = LoggerFactory.getLogger(DynamicConfigLoader.class); + + @Autowired + private SysConfigService sysConfigService; + + @Autowired(required = false) + private JavaMailSenderImpl mailSender; + + @Autowired + private EmailConfigProperties emailConfigProperties; + + @Autowired + private SmsConfigProperties smsConfigProperties; + + @Override + public void run(ApplicationArguments args) throws Exception { + logger.info("=== 开始加载动态配置 ==="); + + // 加载邮件配置 + loadEmailConfig(); + + // 加载短信配置 + loadSmsConfig(); + + logger.info("=== 动态配置加载完成 ==="); + } + + /** + * 加载邮件配置 + */ + private void loadEmailConfig() { + try { + String host = sysConfigService.getStringConfig("email.host"); + String port = sysConfigService.getStringConfig("email.port"); + String username = sysConfigService.getStringConfig("email.username"); + String password = sysConfigService.getStringConfig("email.password"); + String fromName = sysConfigService.getStringConfig("email.fromName"); + String sslEnable = sysConfigService.getStringConfig("email.ssl.enable"); + String timeout = sysConfigService.getStringConfig("email.timeout"); + + // 更新配置属性 + emailConfigProperties.setHost(host); + emailConfigProperties.setPort(StringUtils.hasText(port) ? Integer.valueOf(port) : 587); + emailConfigProperties.setUsername(username); + emailConfigProperties.setPassword(password); + emailConfigProperties.setFromName(fromName); + emailConfigProperties.setSslEnable("true".equalsIgnoreCase(sslEnable)); + emailConfigProperties.setTimeout(StringUtils.hasText(timeout) ? Integer.valueOf(timeout) : 30000); + + // 如果邮箱配置完整,则配置JavaMailSender + if (mailSender != null && StringUtils.hasText(host) && StringUtils.hasText(username) && StringUtils.hasText(password)) { + mailSender.setHost(host); + mailSender.setPort(emailConfigProperties.getPort()); + mailSender.setUsername(username); + mailSender.setPassword(password); + + // 设置邮件属性 + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", emailConfigProperties.getSslEnable() ? "true" : "false"); + props.put("mail.smtp.starttls.required", emailConfigProperties.getSslEnable() ? "true" : "false"); + props.put("mail.smtp.timeout", emailConfigProperties.getTimeout()); + props.put("mail.smtp.connectiontimeout", emailConfigProperties.getTimeout()); + + logger.info("邮件配置加载成功: host={}, port={}, username={}", host, emailConfigProperties.getPort(), username); + } else { + logger.warn("邮件配置不完整,将使用默认配置或模拟模式"); + } + + } catch (Exception e) { + logger.error("加载邮件配置失败", e); + } + } + + /** + * 加载短信配置 + */ + private void loadSmsConfig() { + try { + String provider = sysConfigService.getStringConfig("sms.provider"); + String accessKeyId = sysConfigService.getStringConfig("sms.accessKeyId"); + String accessKeySecret = sysConfigService.getStringConfig("sms.accessKeySecret"); + String signName = sysConfigService.getStringConfig("sms.signName"); + String templateCodeLogin = sysConfigService.getStringConfig("sms.templateCode.login"); + String templateCodeRegister = sysConfigService.getStringConfig("sms.templateCode.register"); + String timeout = sysConfigService.getStringConfig("sms.timeout"); + + // 更新配置属性 + smsConfigProperties.setProvider(StringUtils.hasText(provider) ? provider : "aliyun"); + smsConfigProperties.setAccessKeyId(accessKeyId); + smsConfigProperties.setAccessKeySecret(accessKeySecret); + smsConfigProperties.setSignName(StringUtils.hasText(signName) ? signName : "校园新闻"); + smsConfigProperties.setTemplateCodeLogin(templateCodeLogin); + smsConfigProperties.setTemplateCodeRegister(templateCodeRegister); + smsConfigProperties.setTimeout(StringUtils.hasText(timeout) ? Integer.valueOf(timeout) : 30000); + + if (StringUtils.hasText(accessKeyId) && StringUtils.hasText(accessKeySecret)) { + logger.info("短信配置加载成功: provider={}, signName={}", smsConfigProperties.getProvider(), smsConfigProperties.getSignName()); + } else { + logger.warn("短信配置不完整,将使用模拟模式"); + } + + } catch (Exception e) { + logger.error("加载短信配置失败", e); + } + } +} diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/service/user/service/impl/SysUserServiceImpl.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/service/user/service/impl/SysUserServiceImpl.java index 28039f2..c9eed3e 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/service/user/service/impl/SysUserServiceImpl.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/service/user/service/impl/SysUserServiceImpl.java @@ -720,7 +720,7 @@ public class SysUserServiceImpl implements SysUserService { TbSysUser user = existResult.getData(); // TODO: 这里应该对密码进行加密处理 - user.setPassword(newPassword); + user.setPassword(passwordEncoder.encode(newPassword)); user.setUpdateTime(new Date()); int result = userMapper.updateUser(user); diff --git a/schoolNewsServ/usercenter/src/main/java/org/xyzh/usercenter/controller/UserProfileController.java b/schoolNewsServ/usercenter/src/main/java/org/xyzh/usercenter/controller/UserProfileController.java index 54d0aab..b66cc4c 100644 --- a/schoolNewsServ/usercenter/src/main/java/org/xyzh/usercenter/controller/UserProfileController.java +++ b/schoolNewsServ/usercenter/src/main/java/org/xyzh/usercenter/controller/UserProfileController.java @@ -249,11 +249,16 @@ public class UserProfileController { logger.info("用户绑定手机号: userId={}, phone={}", userId, phone); - // TODO: 验证手机验证码 - // TODO: 检查手机号是否已被其他用户绑定 - // TODO: 更新用户手机号 - - resultDomain.fail("功能开发中"); + TbSysUser user = new TbSysUser(); + user.setID(userId); + user.setPhone(phone); + + ResultDomain bindResult = userService.updateUser(user); + if (bindResult.isSuccess()) { + resultDomain.success("手机号绑定成功", true); + }else{ + resultDomain.fail(bindResult.getMessage()); + } return resultDomain; } catch (Exception e) { @@ -284,11 +289,16 @@ public class UserProfileController { logger.info("用户绑定邮箱: userId={}, email={}", userId, email); - // TODO: 验证邮箱验证码 - // TODO: 检查邮箱是否已被其他用户绑定 - // TODO: 更新用户邮箱 - - resultDomain.fail("功能开发中"); + TbSysUser user = new TbSysUser(); + user.setID(userId); + user.setEmail(email); + + ResultDomain bindResult = userService.updateUser(user); + if (bindResult.isSuccess()) { + resultDomain.success("邮箱绑定成功", true); + }else { + resultDomain.fail(bindResult.getMessage()); + } return resultDomain; } catch (Exception e) { diff --git a/schoolNewsWeb/src/views/user/user-center/profile/AccountSettingsView.vue b/schoolNewsWeb/src/views/user/user-center/profile/AccountSettingsView.vue index d29ca75..17f2b88 100644 --- a/schoolNewsWeb/src/views/user/user-center/profile/AccountSettingsView.vue +++ b/schoolNewsWeb/src/views/user/user-center/profile/AccountSettingsView.vue @@ -1,6 +1,5 @@