# Conflicts:
#	yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
This commit is contained in:
YunaiV
2025-12-25 22:28:27 +08:00
29 changed files with 1410 additions and 46 deletions

View File

@@ -85,7 +85,7 @@ public class SocialClientApiImpl implements SocialClientApi {
// 2. 获得社交用户
SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(),
SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
if (StrUtil.isBlankIfStr(socialUser.getOpenid())) {
if (ObjUtil.isNull(socialUser) || StrUtil.isBlankIfStr(socialUser.getOpenid())) {
log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO);
return;
}

View File

@@ -36,6 +36,14 @@ public class LoginLogController {
@Resource
private LoginLogService loginLogService;
@GetMapping("/get")
@Operation(summary = "获得登录日志")
@PreAuthorize("@ss.hasPermission('system:login-log:query')")
public CommonResult<LoginLogRespVO> getLoginLog(Long id) {
LoginLogDO loginLog = loginLogService.getLoginLog(id);
return success(BeanUtils.toBean(loginLog, LoginLogRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得登录日志分页列表")
@PreAuthorize("@ss.hasPermission('system:login-log:query')")

View File

@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.fhs.core.trans.vo.VO;
@@ -31,6 +33,11 @@ public class OperateLogRespVO implements VO {
@ExcelProperty("操作人")
private String userName;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", implementation = Integer.class)
@ExcelProperty("用户类型")
@DictFormat(DictTypeConstants.USER_TYPE)
private Integer userType;
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@ExcelProperty("操作模块类型")
private String type;

View File

@@ -11,10 +11,12 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.module.system.service.sms.SmsLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -44,6 +46,15 @@ public class SmsLogController {
return success(BeanUtils.toBean(pageResult, SmsLogRespVO.class));
}
@GetMapping("/get")
@Operation(summary = "获得短信日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:sms-log:query')")
public CommonResult<SmsLogRespVO> getSmsLog(@RequestParam("id") Long id) {
SmsLogDO smsLog = smsLogService.getSmsLog(id);
return success(BeanUtils.toBean(smsLog, SmsLogRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出短信日志 Excel")
@PreAuthorize("@ss.hasPermission('system:sms-log:export')")

View File

@@ -12,6 +12,14 @@ import javax.validation.Valid;
*/
public interface LoginLogService {
/**
* 获得登录日志
*
* @param id 编号
* @return 登录日志
*/
LoginLogDO getLoginLog(Long id);
/**
* 获得登录日志分页
*

View File

@@ -21,6 +21,11 @@ public class LoginLogServiceImpl implements LoginLogService {
@Resource
private LoginLogMapper loginLogMapper;
@Override
public LoginLogDO getLoginLog(Long id) {
return loginLogMapper.selectById(id);
}
@Override
public PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO) {
return loginLogMapper.selectPage(pageReqVO);

View File

@@ -11,16 +11,18 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -53,7 +55,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
// 插入
MailTemplateDO template = BeanUtils.toBean(createReqVO, MailTemplateDO.class)
.setParams(parseTemplateContentParams(createReqVO.getContent()));
.setParams(parseTemplateTitleAndContentParams(createReqVO.getTitle(), createReqVO.getContent()));
mailTemplateMapper.insert(template);
return template.getId();
}
@@ -69,7 +71,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
// 更新
MailTemplateDO updateObj = BeanUtils.toBean(updateReqVO, MailTemplateDO.class)
.setParams(parseTemplateContentParams(updateReqVO.getContent()));
.setParams(parseTemplateTitleAndContentParams(updateReqVO.getTitle(), updateReqVO.getContent()));
mailTemplateMapper.updateById(updateObj);
}
@@ -129,7 +131,77 @@ public class MailTemplateServiceImpl implements MailTemplateService {
@Override
public String formatMailTemplateContent(String content, Map<String, Object> params) {
return StrUtil.format(content, params);
// 1. 先替换模板变量
String formattedContent = StrUtil.format(content, params);
// 关联 Pull Requesthttps://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1461 讨论
// 2.1 反转义HTML特殊字符
formattedContent = unescapeHtml(formattedContent);
// 2.2 处理代码块(确保<pre><code>标签格式正确)
formattedContent = formatHtmlCodeBlocks(formattedContent);
// 2.3 将最外层的 pre 标签替换为 div 标签
formattedContent = replaceOuterPreWithDiv(formattedContent);
return formattedContent;
}
private String replaceOuterPreWithDiv(String content) {
if (StrUtil.isEmpty(content)) {
return content;
}
// 使用正则表达式匹配所有的 <pre> 标签,包括嵌套的 <code> 标签
String regex = "(?s)<pre[^>]*>(.*?)</pre>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
// 提取 <pre> 标签内的内容
String innerContent = matcher.group(1);
// 返回 div 标签包裹的内容
matcher.appendReplacement(sb, "<div>" + innerContent + "</div>");
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 反转义 HTML 特殊字符
*
* @param input 输入字符串
* @return 反转义后的字符串
*/
private String unescapeHtml(String input) {
if (StrUtil.isEmpty(input)) {
return input;
}
return input
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"")
.replace("&#39;", "'")
.replace("&nbsp;", " ");
}
/**
* 格式化 HTML 中的代码块
*
* @param content 邮件内容
* @return 格式化后的邮件内容
*/
private String formatHtmlCodeBlocks(String content) {
// 匹配 <pre><code> 标签的代码块
Pattern codeBlockPattern = Pattern.compile("<pre\\s*.*?><code\\s*.*?>(.*?)</code></pre>", Pattern.DOTALL);
Matcher matcher = codeBlockPattern.matcher(content);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
// 获取代码块内容
String codeBlock = matcher.group(1);
// 为代码块添加样式
String replacement = "<pre style=\"background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;\"><code>" + codeBlock + "</code></pre>";
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}
@Override
@@ -137,14 +209,31 @@ public class MailTemplateServiceImpl implements MailTemplateService {
return mailTemplateMapper.selectCountByAccountId(accountId);
}
/**
* 解析标题和内容中的参数
*/
@VisibleForTesting
public List<String> parseTemplateTitleAndContentParams(String title, String content) {
List<String> titleParams = ReUtil.findAllGroup1(PATTERN_PARAMS, title);
List<String> contentParams = ReUtil.findAllGroup1(PATTERN_PARAMS, content);
// 合并参数并去重
List<String> allParams = new ArrayList<>(titleParams);
for (String param : contentParams) {
if (!allParams.contains(param)) {
allParams.add(param);
}
}
return allParams;
}
/**
* 获得邮件模板中的参数,形如 {key}
*
* @param content 内容
* @return 参数列表
*/
private List<String> parseTemplateContentParams(String content) {
List<String> parseTemplateContentParams(String content) {
return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
}
}
}

View File

@@ -255,6 +255,9 @@ public class MenuServiceImpl implements MenuService {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
if (id == null) {
throw exception(MENU_NAME_DUPLICATE);
}
if (!menu.getId().equals(id)) {
throw exception(MENU_NAME_DUPLICATE);
}
@@ -277,7 +280,7 @@ public class MenuServiceImpl implements MenuService {
}
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
if (id == null) {
return;
throw exception(MENU_COMPONENT_NAME_DUPLICATE);
}
if (!menu.getId().equals(id)) {
throw exception(MENU_COMPONENT_NAME_DUPLICATE);

View File

@@ -58,6 +58,14 @@ public interface SmsLogService {
void updateSmsReceiveResult(Long id, String apiSerialNo, Boolean success,
LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg);
/**
* 获得短信日志
*
* @param id 日志编号
* @return 短信日志
*/
SmsLogDO getSmsLog(Long id);
/**
* 获得短信日志分页
*

View File

@@ -78,6 +78,11 @@ public class SmsLogServiceImpl implements SmsLogService {
.receiveTime(receiveTime).apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build());
}
@Override
public SmsLogDO getSmsLog(Long id) {
return smsLogMapper.selectById(id);
}
@Override
public PageResult<SmsLogDO> getSmsLogPage(SmsLogPageReqVO pageReqVO) {
return smsLogMapper.selectPage(pageReqVO);