Merge remote-tracking branch 'origin/dev' into warm-flow-future
This commit is contained in:
commit
fb30169da8
2
pom.xml
2
pom.xml
@ -26,7 +26,7 @@
|
||||
<satoken.version>1.38.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<hutool.version>5.8.29</hutool.version>
|
||||
<hutool.version>5.8.31</hutool.version>
|
||||
<okhttp.version>4.10.0</okhttp.version>
|
||||
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
|
||||
<redisson.version>3.34.1</redisson.version>
|
||||
|
@ -71,7 +71,6 @@ public class UserActionListener implements SaTokenListener {
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
// 更新登录信息
|
||||
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
|
||||
|
@ -19,7 +19,10 @@ import org.dromara.common.core.enums.LoginType;
|
||||
import org.dromara.common.core.enums.TenantStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.*;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.log.event.LogininforEvent;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
@ -139,7 +142,6 @@ public class SysLoginService {
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
@ -156,9 +158,11 @@ public class SysLoginService {
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
|
||||
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
|
||||
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
||||
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
||||
if (ObjectUtil.isNotNull(user.getDeptId())) {
|
||||
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
||||
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
||||
}
|
||||
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
|
||||
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||
return loginUser;
|
||||
|
@ -11,7 +11,6 @@ import org.dromara.common.core.exception.user.CaptchaException;
|
||||
import org.dromara.common.core.exception.user.CaptchaExpireException;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.log.event.LogininforEvent;
|
||||
@ -108,7 +107,6 @@ public class SysRegisterService {
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,13 @@
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.faces</groupId>
|
||||
<artifactId>jakarta.faces-api</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.context.request;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Holder class to expose the web request in the form of a thread-bound
|
||||
* {@link RequestAttributes} object. The request will be inherited
|
||||
* by any child threads spawned by the current thread if the
|
||||
* {@code inheritable} flag is set to {@code true}.
|
||||
*
|
||||
* <p>Use {@link RequestContextListener} or
|
||||
* {@link org.springframework.web.filter.RequestContextFilter} to expose
|
||||
* the current web request. Note that
|
||||
* already exposes the current request by default.
|
||||
*
|
||||
* 修改 spring 上下文存储方式 将 ThreadLocal 替换为 TransmittableThreadLocal
|
||||
* 支持线程上下文切换变量传递 异步获取 spring 上下文
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see RequestContextListener
|
||||
* @see org.springframework.web.filter.RequestContextFilter
|
||||
*/
|
||||
public abstract class RequestContextHolder {
|
||||
|
||||
private static final boolean jsfPresent =
|
||||
ClassUtils.isPresent("jakarta.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
|
||||
|
||||
// ThreadLocal 替换为 TransmittableThreadLocal
|
||||
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
|
||||
new TransmittableThreadLocal<>();
|
||||
|
||||
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
|
||||
new TransmittableThreadLocal<>();
|
||||
|
||||
|
||||
/**
|
||||
* Reset the RequestAttributes for the current thread.
|
||||
*/
|
||||
public static void resetRequestAttributes() {
|
||||
requestAttributesHolder.remove();
|
||||
inheritableRequestAttributesHolder.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the given RequestAttributes to the current thread,
|
||||
* <i>not</i> exposing it as inheritable for child threads.
|
||||
* @param attributes the RequestAttributes to expose
|
||||
* @see #setRequestAttributes(RequestAttributes, boolean)
|
||||
*/
|
||||
public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
|
||||
setRequestAttributes(attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the given RequestAttributes to the current thread.
|
||||
* @param attributes the RequestAttributes to expose,
|
||||
* or {@code null} to reset the thread-bound context
|
||||
* @param inheritable whether to expose the RequestAttributes as inheritable
|
||||
* for child threads (using an {@link InheritableThreadLocal})
|
||||
*/
|
||||
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
|
||||
if (attributes == null) {
|
||||
resetRequestAttributes();
|
||||
}
|
||||
else {
|
||||
if (inheritable) {
|
||||
inheritableRequestAttributesHolder.set(attributes);
|
||||
requestAttributesHolder.remove();
|
||||
}
|
||||
else {
|
||||
requestAttributesHolder.set(attributes);
|
||||
inheritableRequestAttributesHolder.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RequestAttributes currently bound to the thread.
|
||||
* @return the RequestAttributes currently bound to the thread,
|
||||
* or {@code null} if none bound
|
||||
*/
|
||||
@Nullable
|
||||
public static RequestAttributes getRequestAttributes() {
|
||||
RequestAttributes attributes = requestAttributesHolder.get();
|
||||
if (attributes == null) {
|
||||
attributes = inheritableRequestAttributesHolder.get();
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RequestAttributes currently bound to the thread.
|
||||
* <p>Exposes the previously bound RequestAttributes instance, if any.
|
||||
* Falls back to the current JSF FacesContext, if any.
|
||||
* @return the RequestAttributes currently bound to the thread
|
||||
* @throws IllegalStateException if no RequestAttributes object
|
||||
* is bound to the current thread
|
||||
* @see #setRequestAttributes
|
||||
* @see ServletRequestAttributes
|
||||
* @see FacesRequestAttributes
|
||||
* @see jakarta.faces.context.FacesContext#getCurrentInstance()
|
||||
*/
|
||||
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
|
||||
RequestAttributes attributes = getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
if (jsfPresent) {
|
||||
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
|
||||
}
|
||||
if (attributes == null) {
|
||||
throw new IllegalStateException("No thread-bound request found: " +
|
||||
"Are you referring to request attributes outside of an actual web request, " +
|
||||
"or processing a request outside of the originally receiving thread? " +
|
||||
"If you are actually operating within a web request and still receive this message, " +
|
||||
"your code is probably running outside of DispatcherServlet: " +
|
||||
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded JSF dependency.
|
||||
*/
|
||||
private static class FacesRequestAttributesFactory {
|
||||
|
||||
@Nullable
|
||||
public static RequestAttributes getFacesRequestAttributes() {
|
||||
try {
|
||||
FacesContext facesContext = FacesContext.getCurrentInstance();
|
||||
return (facesContext != null ? new FacesRequestAttributes(facesContext) : null);
|
||||
}
|
||||
catch (NoClassDefFoundError err) {
|
||||
// typically for com/sun/faces/util/Util if only the JSF API jar is present
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,6 @@ import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
@ -21,7 +20,6 @@ import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessStatus;
|
||||
import org.dromara.common.log.event.OperLogEvent;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.validation.BindingResult;
|
||||
@ -88,16 +86,6 @@ public class LogAspect {
|
||||
|
||||
// *========数据库日志=========*//
|
||||
OperLogEvent operLog = new OperLogEvent();
|
||||
operLog.setTenantId(LoginHelper.getTenantId());
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = ServletUtils.getClientIP();
|
||||
operLog.setOperIp(ip);
|
||||
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
operLog.setOperName(loginUser.getUsername());
|
||||
operLog.setDeptName(loginUser.getDeptName());
|
||||
|
||||
if (e != null) {
|
||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||
@ -106,8 +94,6 @@ public class LogAspect {
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
operLog.setMethod(className + "." + methodName + "()");
|
||||
// 设置请求方式
|
||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
||||
// 处理设置注解上的参数
|
||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||
// 设置消耗时间
|
||||
|
@ -2,8 +2,6 @@ package org.dromara.common.log.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@ -39,11 +37,6 @@ public class LogininforEvent implements Serializable {
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
private HttpServletRequest request;
|
||||
|
||||
/**
|
||||
* 其他参数
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.dromara.common.mail.config;
|
||||
|
||||
import cn.hutool.extra.mail.MailAccount;
|
||||
import org.dromara.common.mail.config.properties.MailProperties;
|
||||
import org.dromara.common.mail.utils.MailAccount;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
@ -1,46 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
/**
|
||||
* 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS}
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public enum GlobalMailAccount {
|
||||
INSTANCE;
|
||||
|
||||
private final MailAccount mailAccount;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
GlobalMailAccount() {
|
||||
mailAccount = createDefaultAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得邮件帐户
|
||||
*
|
||||
* @return 邮件帐户
|
||||
*/
|
||||
public MailAccount getAccount() {
|
||||
return this.mailAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认帐户
|
||||
*
|
||||
* @return MailAccount
|
||||
*/
|
||||
private MailAccount createDefaultAccount() {
|
||||
for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) {
|
||||
try {
|
||||
return new MailAccount(mailSettingPath);
|
||||
} catch (IORuntimeException ignore) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeUtility;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 邮件内部工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public class InternalMailUtil {
|
||||
|
||||
/**
|
||||
* 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
|
||||
* 单个字符串地址可以是多个地址合并的字符串
|
||||
*
|
||||
* @param addrStrs 地址数组
|
||||
* @param charset 编码(主要用于中文用户名的编码)
|
||||
* @return 地址数组
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
|
||||
final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
|
||||
InternetAddress[] addrs;
|
||||
for (String addrStr : addrStrs) {
|
||||
addrs = parseAddress(addrStr, charset);
|
||||
if (ArrayUtil.isNotEmpty(addrs)) {
|
||||
Collections.addAll(resultList, addrs);
|
||||
}
|
||||
}
|
||||
return resultList.toArray(new InternetAddress[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析第一个地址
|
||||
*
|
||||
* @param address 地址字符串
|
||||
* @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
|
||||
* @return 地址列表
|
||||
*/
|
||||
public static InternetAddress parseFirstAddress(String address, Charset charset) {
|
||||
final InternetAddress[] internetAddresses = parseAddress(address, charset);
|
||||
if (ArrayUtil.isEmpty(internetAddresses)) {
|
||||
try {
|
||||
return new InternetAddress(address);
|
||||
} catch (AddressException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
return internetAddresses[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个地址字符串解析为多个地址<br>
|
||||
* 地址间使用" "、","、";"分隔
|
||||
*
|
||||
* @param address 地址字符串
|
||||
* @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
|
||||
* @return 地址列表
|
||||
*/
|
||||
public static InternetAddress[] parseAddress(String address, Charset charset) {
|
||||
InternetAddress[] addresses;
|
||||
try {
|
||||
addresses = InternetAddress.parse(address);
|
||||
} catch (AddressException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
//编码用户名
|
||||
if (ArrayUtil.isNotEmpty(addresses)) {
|
||||
final String charsetStr = null == charset ? null : charset.name();
|
||||
for (InternetAddress internetAddress : addresses) {
|
||||
try {
|
||||
internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码中文字符<br>
|
||||
* 编码失败返回原字符串
|
||||
*
|
||||
* @param text 被编码的文本
|
||||
* @param charset 编码
|
||||
* @return 编码后的结果
|
||||
*/
|
||||
public static String encodeText(String text, Charset charset) {
|
||||
try {
|
||||
return MimeUtility.encodeText(text, charset.name(), null);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// ignore
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
@ -1,483 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import cn.hutool.core.builder.Builder;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.activation.DataHandler;
|
||||
import jakarta.activation.DataSource;
|
||||
import jakarta.activation.FileDataSource;
|
||||
import jakarta.activation.FileTypeMap;
|
||||
import jakarta.mail.*;
|
||||
import jakarta.mail.internet.MimeBodyPart;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.MimeMultipart;
|
||||
import jakarta.mail.internet.MimeUtility;
|
||||
import jakarta.mail.util.ByteArrayDataSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 邮件发送客户端
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public class Mail implements Builder<MimeMessage> {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 邮箱帐户信息以及一些客户端配置信息
|
||||
*/
|
||||
private final MailAccount mailAccount;
|
||||
/**
|
||||
* 收件人列表
|
||||
*/
|
||||
private String[] tos;
|
||||
/**
|
||||
* 抄送人列表(carbon copy)
|
||||
*/
|
||||
private String[] ccs;
|
||||
/**
|
||||
* 密送人列表(blind carbon copy)
|
||||
*/
|
||||
private String[] bccs;
|
||||
/**
|
||||
* 回复地址(reply-to)
|
||||
*/
|
||||
private String[] reply;
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 是否为HTML
|
||||
*/
|
||||
private boolean isHtml;
|
||||
/**
|
||||
* 正文、附件和图片的混合部分
|
||||
*/
|
||||
private final Multipart multipart = new MimeMultipart();
|
||||
/**
|
||||
* 是否使用全局会话,默认为false
|
||||
*/
|
||||
private boolean useGlobalSession = false;
|
||||
|
||||
/**
|
||||
* debug输出位置,可以自定义debug日志
|
||||
*/
|
||||
private PrintStream debugOutput;
|
||||
|
||||
/**
|
||||
* 创建邮件客户端
|
||||
*
|
||||
* @param mailAccount 邮件帐号
|
||||
* @return Mail
|
||||
*/
|
||||
public static Mail create(MailAccount mailAccount) {
|
||||
return new Mail(mailAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建邮件客户端,使用全局邮件帐户
|
||||
*
|
||||
* @return Mail
|
||||
*/
|
||||
public static Mail create() {
|
||||
return new Mail();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造,使用全局邮件帐户
|
||||
*/
|
||||
public Mail() {
|
||||
this(GlobalMailAccount.INSTANCE.getAccount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置
|
||||
*/
|
||||
public Mail(MailAccount mailAccount) {
|
||||
mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
|
||||
this.mailAccount = mailAccount.defaultIfEmpty();
|
||||
}
|
||||
// --------------------------------------------------------------- Constructor end
|
||||
|
||||
// --------------------------------------------------------------- Getters and Setters start
|
||||
|
||||
/**
|
||||
* 设置收件人
|
||||
*
|
||||
* @param tos 收件人列表
|
||||
* @return this
|
||||
* @see #setTos(String...)
|
||||
*/
|
||||
public Mail to(String... tos) {
|
||||
return setTos(tos);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个收件人
|
||||
*
|
||||
* @param tos 收件人列表
|
||||
* @return this
|
||||
*/
|
||||
public Mail setTos(String... tos) {
|
||||
this.tos = tos;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个抄送人(carbon copy)
|
||||
*
|
||||
* @param ccs 抄送人列表
|
||||
* @return this
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public Mail setCcs(String... ccs) {
|
||||
this.ccs = ccs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个密送人(blind carbon copy)
|
||||
*
|
||||
* @param bccs 密送人列表
|
||||
* @return this
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public Mail setBccs(String... bccs) {
|
||||
this.bccs = bccs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个回复地址(reply-to)
|
||||
*
|
||||
* @param reply 回复地址(reply-to)列表
|
||||
* @return this
|
||||
* @since 4.6.0
|
||||
*/
|
||||
public Mail setReply(String... reply) {
|
||||
this.reply = reply;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题
|
||||
*
|
||||
* @param title 标题
|
||||
* @return this
|
||||
*/
|
||||
public Mail setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置正文<br>
|
||||
* 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
|
||||
*
|
||||
* @param content 正文
|
||||
* @return this
|
||||
*/
|
||||
public Mail setContent(String content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否是HTML
|
||||
*
|
||||
* @param isHtml 是否为HTML
|
||||
* @return this
|
||||
*/
|
||||
public Mail setHtml(boolean isHtml) {
|
||||
this.isHtml = isHtml;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置正文
|
||||
*
|
||||
* @param content 正文内容
|
||||
* @param isHtml 是否为HTML
|
||||
* @return this
|
||||
*/
|
||||
public Mail setContent(String content, boolean isHtml) {
|
||||
setContent(content);
|
||||
return setHtml(isHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件类型附件,文件可以是图片文件,此时自动设置cid(正文中引用图片),默认cid为文件名
|
||||
*
|
||||
* @param files 附件文件列表
|
||||
* @return this
|
||||
*/
|
||||
public Mail setFiles(File... files) {
|
||||
if (ArrayUtil.isEmpty(files)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
final DataSource[] attachments = new DataSource[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
attachments[i] = new FileDataSource(files[i]);
|
||||
}
|
||||
return setAttachments(attachments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
|
||||
*
|
||||
* @param attachments 附件列表
|
||||
* @return this
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public Mail setAttachments(DataSource... attachments) {
|
||||
if (ArrayUtil.isNotEmpty(attachments)) {
|
||||
final Charset charset = this.mailAccount.getCharset();
|
||||
MimeBodyPart bodyPart;
|
||||
String nameEncoded;
|
||||
try {
|
||||
for (DataSource attachment : attachments) {
|
||||
bodyPart = new MimeBodyPart();
|
||||
bodyPart.setDataHandler(new DataHandler(attachment));
|
||||
nameEncoded = attachment.getName();
|
||||
if (this.mailAccount.isEncodefilename()) {
|
||||
nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
|
||||
}
|
||||
// 普通附件文件名
|
||||
bodyPart.setFileName(nameEncoded);
|
||||
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
|
||||
// 图片附件,用于正文中引用图片
|
||||
bodyPart.setContentID(nameEncoded);
|
||||
}
|
||||
this.multipart.addBodyPart(bodyPart);
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg"
|
||||
*
|
||||
* @param cid 图片与占位符,占位符格式为cid:${cid}
|
||||
* @param imageStream 图片文件
|
||||
* @return this
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public Mail addImage(String cid, InputStream imageStream) {
|
||||
return addImage(cid, imageStream, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加图片,图片的键对应到邮件模板中的占位字符串
|
||||
*
|
||||
* @param cid 图片与占位符,占位符格式为cid:${cid}
|
||||
* @param imageStream 图片流,不关闭
|
||||
* @param contentType 图片类型,null赋值默认的"image/jpeg"
|
||||
* @return this
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public Mail addImage(String cid, InputStream imageStream, String contentType) {
|
||||
ByteArrayDataSource imgSource;
|
||||
try {
|
||||
imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
imgSource.setName(cid);
|
||||
return setAttachments(imgSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加图片,图片的键对应到邮件模板中的占位字符串
|
||||
*
|
||||
* @param cid 图片与占位符,占位符格式为cid:${cid}
|
||||
* @param imageFile 图片文件
|
||||
* @return this
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public Mail addImage(String cid, File imageFile) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = FileUtil.getInputStream(imageFile);
|
||||
return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
|
||||
} finally {
|
||||
IoUtil.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字符集编码
|
||||
*
|
||||
* @param charset 字符集编码
|
||||
* @return this
|
||||
* @see MailAccount#setCharset(Charset)
|
||||
*/
|
||||
public Mail setCharset(Charset charset) {
|
||||
this.mailAccount.setCharset(charset);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否使用全局会话,默认为true
|
||||
*
|
||||
* @param isUseGlobalSession 是否使用全局会话,默认为true
|
||||
* @return this
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public Mail setUseGlobalSession(boolean isUseGlobalSession) {
|
||||
this.useGlobalSession = isUseGlobalSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置debug输出位置,可以自定义debug日志
|
||||
*
|
||||
* @param debugOutput debug输出位置
|
||||
* @return this
|
||||
* @since 5.5.6
|
||||
*/
|
||||
public Mail setDebugOutput(PrintStream debugOutput) {
|
||||
this.debugOutput = debugOutput;
|
||||
return this;
|
||||
}
|
||||
// --------------------------------------------------------------- Getters and Setters end
|
||||
|
||||
@Override
|
||||
public MimeMessage build() {
|
||||
try {
|
||||
return buildMsg();
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送
|
||||
*
|
||||
* @return message-id
|
||||
* @throws MailException 邮件发送异常
|
||||
*/
|
||||
public String send() throws MailException {
|
||||
try {
|
||||
return doSend();
|
||||
} catch (MessagingException e) {
|
||||
if (e instanceof SendFailedException) {
|
||||
// 当地址无效时,显示更加详细的无效地址信息
|
||||
final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
|
||||
final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
|
||||
throw new MailException(msg, e);
|
||||
}
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 执行发送
|
||||
*
|
||||
* @return message-id
|
||||
* @throws MessagingException 发送异常
|
||||
*/
|
||||
private String doSend() throws MessagingException {
|
||||
final MimeMessage mimeMessage = buildMsg();
|
||||
Transport.send(mimeMessage);
|
||||
return mimeMessage.getMessageID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建消息
|
||||
*
|
||||
* @return {@link MimeMessage}消息
|
||||
* @throws MessagingException 消息异常
|
||||
*/
|
||||
private MimeMessage buildMsg() throws MessagingException {
|
||||
final Charset charset = this.mailAccount.getCharset();
|
||||
final MimeMessage msg = new MimeMessage(getSession());
|
||||
// 发件人
|
||||
final String from = this.mailAccount.getFrom();
|
||||
if (StrUtil.isEmpty(from)) {
|
||||
// 用户未提供发送方,则从Session中自动获取
|
||||
msg.setFrom();
|
||||
} else {
|
||||
msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
|
||||
}
|
||||
// 标题
|
||||
msg.setSubject(this.title, (null == charset) ? null : charset.name());
|
||||
// 发送时间
|
||||
msg.setSentDate(new Date());
|
||||
// 内容和附件
|
||||
msg.setContent(buildContent(charset));
|
||||
// 收件人
|
||||
msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset));
|
||||
// 抄送人
|
||||
if (ArrayUtil.isNotEmpty(this.ccs)) {
|
||||
msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset));
|
||||
}
|
||||
// 密送人
|
||||
if (ArrayUtil.isNotEmpty(this.bccs)) {
|
||||
msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset));
|
||||
}
|
||||
// 回复地址(reply-to)
|
||||
if (ArrayUtil.isNotEmpty(this.reply)) {
|
||||
msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset));
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建邮件信息主体
|
||||
*
|
||||
* @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
|
||||
* @return 邮件信息主体
|
||||
* @throws MessagingException 消息异常
|
||||
*/
|
||||
private Multipart buildContent(Charset charset) throws MessagingException {
|
||||
final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
|
||||
// 正文
|
||||
final MimeBodyPart body = new MimeBodyPart();
|
||||
body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
|
||||
this.multipart.addBodyPart(body);
|
||||
|
||||
return this.multipart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认邮件会话<br>
|
||||
* 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
|
||||
*
|
||||
* @return 邮件会话 {@link Session}
|
||||
*/
|
||||
private Session getSession() {
|
||||
final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
|
||||
|
||||
if (null != this.debugOutput) {
|
||||
session.setDebugOut(debugOutput);
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
// --------------------------------------------------------------- Private method end
|
||||
}
|
@ -1,659 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.setting.Setting;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 邮件账户对象
|
||||
*
|
||||
* @author Luxiaolei
|
||||
*/
|
||||
public class MailAccount implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -6937313421815719204L;
|
||||
|
||||
private static final String MAIL_PROTOCOL = "mail.transport.protocol";
|
||||
private static final String SMTP_HOST = "mail.smtp.host";
|
||||
private static final String SMTP_PORT = "mail.smtp.port";
|
||||
private static final String SMTP_AUTH = "mail.smtp.auth";
|
||||
private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
|
||||
private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
|
||||
private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout";
|
||||
|
||||
// SSL
|
||||
private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
|
||||
private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
|
||||
private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
|
||||
private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
|
||||
private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
|
||||
private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
|
||||
|
||||
// System Properties
|
||||
private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
|
||||
//private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
|
||||
//private static final String CHARSET = "mail.mime.charset";
|
||||
|
||||
// 其他
|
||||
private static final String MAIL_DEBUG = "mail.debug";
|
||||
|
||||
public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
|
||||
|
||||
/**
|
||||
* SMTP服务器域名
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* SMTP服务端口
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* 是否需要用户名密码验证
|
||||
*/
|
||||
private Boolean auth;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String user;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String pass;
|
||||
/**
|
||||
* 发送方,遵循RFC-822标准
|
||||
*/
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
|
||||
*/
|
||||
private boolean debug;
|
||||
/**
|
||||
* 编码用于编码邮件正文和发送人、收件人等中文
|
||||
*/
|
||||
private Charset charset = CharsetUtil.CHARSET_UTF_8;
|
||||
/**
|
||||
* 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
|
||||
*/
|
||||
private boolean splitlongparameters = false;
|
||||
/**
|
||||
* 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
|
||||
*/
|
||||
private boolean encodefilename = true;
|
||||
|
||||
/**
|
||||
* 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
|
||||
*/
|
||||
private boolean starttlsEnable = false;
|
||||
/**
|
||||
* 使用 SSL安全连接
|
||||
*/
|
||||
private Boolean sslEnable;
|
||||
|
||||
/**
|
||||
* SSL协议,多个协议用空格分隔
|
||||
*/
|
||||
private String sslProtocols;
|
||||
|
||||
/**
|
||||
* 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
*/
|
||||
private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory";
|
||||
/**
|
||||
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
|
||||
*/
|
||||
private boolean socketFactoryFallback;
|
||||
/**
|
||||
* 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
|
||||
*/
|
||||
private int socketFactoryPort = 465;
|
||||
|
||||
/**
|
||||
* SMTP超时时长,单位毫秒,缺省值不超时
|
||||
*/
|
||||
private long timeout;
|
||||
/**
|
||||
* Socket连接超时值,单位毫秒,缺省值不超时
|
||||
*/
|
||||
private long connectionTimeout;
|
||||
/**
|
||||
* Socket写出超时值,单位毫秒,缺省值不超时
|
||||
*/
|
||||
private long writeTimeout;
|
||||
|
||||
/**
|
||||
* 自定义的其他属性,此自定义属性会覆盖默认属性
|
||||
*/
|
||||
private final Map<String, Object> customProperty = new HashMap<>();
|
||||
|
||||
// -------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造,所有参数需自行定义或保持默认值
|
||||
*/
|
||||
public MailAccount() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param settingPath 配置文件路径
|
||||
*/
|
||||
public MailAccount(String settingPath) {
|
||||
this(new Setting(settingPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param setting 配置文件
|
||||
*/
|
||||
public MailAccount(Setting setting) {
|
||||
setting.toBean(this);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 获得SMTP服务器域名
|
||||
*
|
||||
* @return SMTP服务器域名
|
||||
*/
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SMTP服务器域名
|
||||
*
|
||||
* @param host SMTP服务器域名
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setHost(String host) {
|
||||
this.host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得SMTP服务端口
|
||||
*
|
||||
* @return SMTP服务端口
|
||||
*/
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SMTP服务端口
|
||||
*
|
||||
* @param port SMTP服务端口
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setPort(Integer port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要用户名密码验证
|
||||
*
|
||||
* @return 是否需要用户名密码验证
|
||||
*/
|
||||
public Boolean isAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否需要用户名密码验证
|
||||
*
|
||||
* @param isAuth 是否需要用户名密码验证
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setAuth(boolean isAuth) {
|
||||
this.auth = isAuth;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名
|
||||
*
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户名
|
||||
*
|
||||
* @param user 用户名
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setUser(String user) {
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密码
|
||||
*
|
||||
* @return 密码
|
||||
*/
|
||||
public String getPass() {
|
||||
return pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置密码
|
||||
*
|
||||
* @param pass 密码
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setPass(String pass) {
|
||||
this.pass = pass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发送方,遵循RFC-822标准
|
||||
*
|
||||
* @return 发送方,遵循RFC-822标准
|
||||
*/
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置发送方,遵循RFC-822标准<br>
|
||||
* 发件人可以是以下形式:
|
||||
*
|
||||
* <pre>
|
||||
* 1. user@xxx.xx
|
||||
* 2. name <user@xxx.xx>
|
||||
* </pre>
|
||||
*
|
||||
* @param from 发送方,遵循RFC-822标准
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setFrom(String from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
|
||||
*
|
||||
* @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
|
||||
*
|
||||
* @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
|
||||
* @return this
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public MailAccount setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符集编码
|
||||
*
|
||||
* @return 编码,可能为{@code null}
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置:
|
||||
* <pre>
|
||||
* System.setProperty("mail.mime.charset", charset);
|
||||
* </pre>
|
||||
*
|
||||
* @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
|
||||
*
|
||||
* @return 对于超长参数是否切分为多份
|
||||
*/
|
||||
public boolean isSplitlongparameters() {
|
||||
return splitlongparameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)<br>
|
||||
* 注意此项为全局设置,此项会调用
|
||||
* <pre>
|
||||
* System.setProperty("mail.mime.splitlongparameters", true)
|
||||
* </pre>
|
||||
*
|
||||
* @param splitlongparameters 对于超长参数是否切分为多份
|
||||
*/
|
||||
public void setSplitlongparameters(boolean splitlongparameters) {
|
||||
this.splitlongparameters = splitlongparameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
|
||||
*
|
||||
* @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public boolean isEncodefilename() {
|
||||
|
||||
return encodefilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置<br>
|
||||
* 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
|
||||
* <ul>
|
||||
* <li>mail.mime.encodefilename 是否编码附件文件名</li>
|
||||
* <li>mail.mime.charset 编码文件名的编码</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param encodefilename 对于文件名是否使用{@link #charset}编码
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public void setEncodefilename(boolean encodefilename) {
|
||||
this.encodefilename = encodefilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
|
||||
*
|
||||
* @return 是否使用 STARTTLS安全连接
|
||||
*/
|
||||
public boolean isStarttlsEnable() {
|
||||
return this.starttlsEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否使用STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
|
||||
*
|
||||
* @param startttlsEnable 是否使用STARTTLS安全连接
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setStarttlsEnable(boolean startttlsEnable) {
|
||||
this.starttlsEnable = startttlsEnable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用 SSL安全连接
|
||||
*
|
||||
* @return 是否使用 SSL安全连接
|
||||
*/
|
||||
public Boolean isSslEnable() {
|
||||
return this.sslEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否使用SSL安全连接
|
||||
*
|
||||
* @param sslEnable 是否使用SSL安全连接
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setSslEnable(Boolean sslEnable) {
|
||||
this.sslEnable = sslEnable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SSL协议,多个协议用空格分隔
|
||||
*
|
||||
* @return SSL协议,多个协议用空格分隔
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public String getSslProtocols() {
|
||||
return sslProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SSL协议,多个协议用空格分隔
|
||||
*
|
||||
* @param sslProtocols SSL协议,多个协议用空格分隔
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public void setSslProtocols(String sslProtocols) {
|
||||
this.sslProtocols = sslProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
*
|
||||
* @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字
|
||||
*/
|
||||
public String getSocketFactoryClass() {
|
||||
return socketFactoryClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
*
|
||||
* @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setSocketFactoryClass(String socketFactoryClass) {
|
||||
this.socketFactoryClass = socketFactoryClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
|
||||
*
|
||||
* @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
|
||||
*/
|
||||
public boolean isSocketFactoryFallback() {
|
||||
return socketFactoryFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
|
||||
*
|
||||
* @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) {
|
||||
this.socketFactoryFallback = socketFactoryFallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
|
||||
*
|
||||
* @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
|
||||
*/
|
||||
public int getSocketFactoryPort() {
|
||||
return socketFactoryPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
|
||||
*
|
||||
* @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount setSocketFactoryPort(int socketFactoryPort) {
|
||||
this.socketFactoryPort = socketFactoryPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SMTP超时时长,单位毫秒,缺省值不超时
|
||||
*
|
||||
* @param timeout SMTP超时时长,单位毫秒,缺省值不超时
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public MailAccount setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Socket连接超时值,单位毫秒,缺省值不超时
|
||||
*
|
||||
* @param connectionTimeout Socket连接超时值,单位毫秒,缺省值不超时
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public MailAccount setConnectionTimeout(long connectionTimeout) {
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Socket写出超时值,单位毫秒,缺省值不超时
|
||||
*
|
||||
* @param writeTimeout Socket写出超时值,单位毫秒,缺省值不超时
|
||||
* @return this
|
||||
* @since 5.8.3
|
||||
*/
|
||||
public MailAccount setWriteTimeout(long writeTimeout) {
|
||||
this.writeTimeout = writeTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义属性列表
|
||||
*
|
||||
* @return 自定义参数列表
|
||||
* @since 5.6.4
|
||||
*/
|
||||
public Map<String, Object> getCustomProperty() {
|
||||
return customProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义属性,如mail.smtp.ssl.socketFactory
|
||||
*
|
||||
* @param key 属性名,空白被忽略
|
||||
* @param value 属性值, null被忽略
|
||||
* @return this
|
||||
* @since 5.6.4
|
||||
*/
|
||||
public MailAccount setCustomProperty(String key, Object value) {
|
||||
if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) {
|
||||
this.customProperty.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得SMTP相关信息
|
||||
*
|
||||
* @return {@link Properties}
|
||||
*/
|
||||
public Properties getSmtpProps() {
|
||||
//全局系统参数
|
||||
System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters));
|
||||
|
||||
final Properties p = new Properties();
|
||||
p.put(MAIL_PROTOCOL, "smtp");
|
||||
p.put(SMTP_HOST, this.host);
|
||||
p.put(SMTP_PORT, String.valueOf(this.port));
|
||||
p.put(SMTP_AUTH, String.valueOf(this.auth));
|
||||
if (this.timeout > 0) {
|
||||
p.put(SMTP_TIMEOUT, String.valueOf(this.timeout));
|
||||
}
|
||||
if (this.connectionTimeout > 0) {
|
||||
p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout));
|
||||
}
|
||||
// issue#2355
|
||||
if (this.writeTimeout > 0) {
|
||||
p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout));
|
||||
}
|
||||
|
||||
p.put(MAIL_DEBUG, String.valueOf(this.debug));
|
||||
|
||||
if (this.starttlsEnable) {
|
||||
//STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
|
||||
p.put(STARTTLS_ENABLE, "true");
|
||||
|
||||
if (null == this.sslEnable) {
|
||||
//为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待
|
||||
this.sslEnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
// SSL
|
||||
if (null != this.sslEnable && this.sslEnable) {
|
||||
p.put(SSL_ENABLE, "true");
|
||||
p.put(SOCKET_FACTORY, socketFactoryClass);
|
||||
p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
|
||||
p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
|
||||
// issue#IZN95@Gitee,在Linux下需自定义SSL协议版本
|
||||
if (StrUtil.isNotBlank(this.sslProtocols)) {
|
||||
p.put(SSL_PROTOCOLS, this.sslProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
// 补充自定义属性,允许自定属性覆盖已经设置的值
|
||||
p.putAll(this.customProperty);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果某些值为null,使用默认值
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public MailAccount defaultIfEmpty() {
|
||||
// 去掉发件人的姓名部分
|
||||
final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
|
||||
|
||||
if (StrUtil.isBlank(this.host)) {
|
||||
// 如果SMTP地址为空,默认使用smtp.<发件人邮箱后缀>
|
||||
this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1));
|
||||
}
|
||||
if (StrUtil.isBlank(user)) {
|
||||
// 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee)
|
||||
//this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@'));
|
||||
this.user = fromAddress;
|
||||
}
|
||||
if (null == this.auth) {
|
||||
// 如果密码非空白,则使用认证模式
|
||||
this.auth = (false == StrUtil.isBlank(this.pass));
|
||||
}
|
||||
if (null == this.port) {
|
||||
// 端口在SSL状态下默认与socketFactoryPort一致,非SSL状态下默认为25
|
||||
this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25;
|
||||
}
|
||||
if (null == this.charset) {
|
||||
// 默认UTF-8编码
|
||||
this.charset = CharsetUtil.CHARSET_UTF_8;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable="
|
||||
+ starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]";
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 邮件异常
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class MailException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 8247610319171014183L;
|
||||
|
||||
public MailException(Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public MailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MailException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public MailException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public MailException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.mail.JakartaMail;
|
||||
import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
|
||||
import cn.hutool.extra.mail.MailAccount;
|
||||
import jakarta.mail.Authenticator;
|
||||
import jakarta.mail.Session;
|
||||
import lombok.AccessLevel;
|
||||
@ -17,7 +20,7 @@ import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* 邮件工具类
|
||||
@ -385,7 +388,7 @@ public class MailUtils {
|
||||
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
|
||||
Authenticator authenticator = null;
|
||||
if (mailAccount.isAuth()) {
|
||||
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
||||
authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
||||
}
|
||||
|
||||
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
|
||||
@ -412,7 +415,7 @@ public class MailUtils {
|
||||
*/
|
||||
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
|
||||
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
|
||||
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
|
||||
final JakartaMail mail = JakartaMail.create(mailAccount).setUseGlobalSession(useGlobalSession);
|
||||
|
||||
// 可选抄送人
|
||||
if (CollUtil.isNotEmpty(ccs)) {
|
||||
@ -431,7 +434,7 @@ public class MailUtils {
|
||||
|
||||
// 图片
|
||||
if (MapUtil.isNotEmpty(imageMap)) {
|
||||
for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
|
||||
for (Entry<String, InputStream> entry : imageMap.entrySet()) {
|
||||
mail.addImage(entry.getKey(), entry.getValue());
|
||||
// 关闭流
|
||||
IoUtil.close(entry.getValue());
|
||||
@ -463,5 +466,4 @@ public class MailUtils {
|
||||
return result;
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------------------------ Private method end
|
||||
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
package org.dromara.common.mail.utils;
|
||||
|
||||
import jakarta.mail.Authenticator;
|
||||
import jakarta.mail.PasswordAuthentication;
|
||||
|
||||
/**
|
||||
* 用户名密码验证器
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class UserPassAuthenticator extends Authenticator {
|
||||
|
||||
private final String user;
|
||||
private final String pass;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param user 用户名
|
||||
* @param pass 密码
|
||||
*/
|
||||
public UserPassAuthenticator(String user, String pass) {
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(this.user, this.pass);
|
||||
}
|
||||
|
||||
}
|
@ -13,9 +13,9 @@ import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
* 内置数据:
|
||||
* - {@code user}: 当前登录用户信息,参考 {@link LoginUser}
|
||||
* 内置服务:
|
||||
* - {@code sdss}: 系统数据权限服务,参考 {@link ISysDataScopeService}
|
||||
* - {@code sdss}: 系统数据权限服务,参考 ISysDataScopeService
|
||||
* 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
|
||||
* 如需扩展服务,可以通过 {@link ISysDataScopeService} 自行编写
|
||||
* 如需扩展服务,可以通过 ISysDataScopeService 自行编写
|
||||
* </p>
|
||||
*
|
||||
* @author Lion Li
|
||||
@ -32,29 +32,21 @@ public enum DataScopeType {
|
||||
|
||||
/**
|
||||
* 自定数据权限
|
||||
* 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )`
|
||||
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
|
||||
*/
|
||||
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 部门数据权限
|
||||
* 使用 SpEL 表达式:`#{#deptName} = #{#user.deptId}`
|
||||
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
|
||||
*/
|
||||
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 部门及以下数据权限
|
||||
* 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )}`
|
||||
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
|
||||
*/
|
||||
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
* 使用 SpEL 表达式:`#{#userName} = #{#user.userId}`
|
||||
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
|
||||
*/
|
||||
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
|
||||
|
||||
|
@ -215,6 +215,9 @@ public class VelocityUtils {
|
||||
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
|
||||
} else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
|
||||
importList.add("java.math.BigDecimal");
|
||||
} else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
|
||||
importList.add("org.dromara.common.translation.annotation.Translation");
|
||||
importList.add("org.dromara.common.translation.constant.TransConstant");
|
||||
}
|
||||
}
|
||||
return importList;
|
||||
|
@ -53,6 +53,13 @@ public class ${ClassName}Vo implements Serializable {
|
||||
#end
|
||||
private $column.javaType $column.javaField;
|
||||
|
||||
#if($column.htmlType == "imageUpload")
|
||||
/**
|
||||
* ${column.columnComment}Url
|
||||
*/
|
||||
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
|
||||
private String ${column.javaField}Url";
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
|
||||
|
@ -9,6 +9,12 @@ export interface ${BusinessName}VO {
|
||||
#elseif($column.javaType == 'Boolean') boolean;
|
||||
#else string;
|
||||
#end
|
||||
#if($column.htmlType == "imageUpload")
|
||||
/**
|
||||
* ${column.columnComment}Url
|
||||
*/
|
||||
${column.javaField}Url: string;
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($table.tree)
|
||||
|
@ -99,9 +99,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
#elseif($column.list && $column.htmlType == "imageUpload")
|
||||
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
|
||||
<el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
|
||||
<template #default="scope">
|
||||
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
|
||||
<image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
#elseif($column.list && $column.dictType && "" != $column.dictType)
|
||||
|
@ -101,9 +101,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
#elseif($column.list && $column.htmlType == "imageUpload")
|
||||
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
|
||||
<el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
|
||||
<template #default="scope">
|
||||
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
|
||||
<image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
#elseif($column.list && $column.dictType && "" != $column.dictType)
|
||||
|
@ -12,6 +12,7 @@ import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.system.domain.bo.SysDeptBo;
|
||||
import org.dromara.system.domain.vo.SysDeptVo;
|
||||
import org.dromara.system.service.ISysDeptService;
|
||||
import org.dromara.system.service.ISysPostService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -29,6 +30,7 @@ import java.util.List;
|
||||
public class SysDeptController extends BaseController {
|
||||
|
||||
private final ISysDeptService deptService;
|
||||
private final ISysPostService postService;
|
||||
|
||||
/**
|
||||
* 获取部门列表
|
||||
@ -117,6 +119,9 @@ public class SysDeptController extends BaseController {
|
||||
if (deptService.checkDeptExistUser(deptId)) {
|
||||
return R.warn("部门存在用户,不允许删除");
|
||||
}
|
||||
if (postService.countPostByDeptId(deptId) > 0) {
|
||||
return R.warn("部门存在岗位,不允许删除");
|
||||
}
|
||||
deptService.checkDeptDataScope(deptId);
|
||||
return toAjax(deptService.deleteDeptById(deptId));
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.system.domain.bo.SysUserBo;
|
||||
@ -72,7 +73,8 @@ public class SysProfileController extends BaseController {
|
||||
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
|
||||
return R.fail("修改用户'" + username + "'失败,邮箱账号已存在");
|
||||
}
|
||||
if (userService.updateUserProfile(user) > 0) {
|
||||
int rows = DataPermissionHelper.ignore(() -> userService.updateUserProfile(user));
|
||||
if (rows > 0) {
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("修改个人信息异常,请联系管理员");
|
||||
|
@ -92,6 +92,9 @@ public class SysTenantPackageController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
|
||||
if (!tenantPackageService.checkPackageNameUnique(bo)) {
|
||||
return R.fail("新增套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
|
||||
}
|
||||
return toAjax(tenantPackageService.insertByBo(bo));
|
||||
}
|
||||
|
||||
@ -104,6 +107,9 @@ public class SysTenantPackageController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
|
||||
if (!tenantPackageService.checkPackageNameUnique(bo)) {
|
||||
return R.fail("修改套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
|
||||
}
|
||||
return toAjax(tenantPackageService.updateByBo(bo));
|
||||
}
|
||||
|
||||
|
@ -61,13 +61,13 @@ public class SysUserVo implements Serializable {
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.EMAIL)
|
||||
@Sensitive(strategy = SensitiveStrategy.EMAIL, perms = "system:user:edit")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE, perms = "system:user:edit")
|
||||
private String phonenumber;
|
||||
|
||||
/**
|
||||
|
@ -80,6 +80,14 @@ public interface ISysPostService {
|
||||
*/
|
||||
long countUserPostById(Long postId);
|
||||
|
||||
/**
|
||||
* 通过部门ID查询岗位使用数量
|
||||
*
|
||||
* @param deptId 部门id
|
||||
* @return 结果
|
||||
*/
|
||||
long countPostByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 删除岗位信息
|
||||
*
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.dromara.system.service;
|
||||
|
||||
import org.dromara.system.domain.vo.SysTenantPackageVo;
|
||||
import org.dromara.system.domain.bo.SysTenantPackageBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.system.domain.bo.SysTenantPackageBo;
|
||||
import org.dromara.system.domain.vo.SysTenantPackageVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -45,6 +45,11 @@ public interface ISysTenantPackageService {
|
||||
*/
|
||||
Boolean updateByBo(SysTenantPackageBo bo);
|
||||
|
||||
/**
|
||||
* 校验套餐名称是否唯一
|
||||
*/
|
||||
boolean checkPackageNameUnique(SysTenantPackageBo bo);
|
||||
|
||||
/**
|
||||
* 修改套餐状态
|
||||
*/
|
||||
|
@ -2,6 +2,7 @@ package org.dromara.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
@ -38,6 +39,9 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
||||
*/
|
||||
@Override
|
||||
public String getRoleCustom(Long roleId) {
|
||||
if (ObjectUtil.isNull(roleId)) {
|
||||
return "-1";
|
||||
}
|
||||
List<SysRoleDept> list = roleDeptMapper.selectList(
|
||||
new LambdaQueryWrapper<SysRoleDept>()
|
||||
.select(SysRoleDept::getDeptId)
|
||||
@ -45,7 +49,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId()));
|
||||
}
|
||||
return null;
|
||||
return "-1";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,6 +60,9 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
||||
*/
|
||||
@Override
|
||||
public String getDeptAndChild(Long deptId) {
|
||||
if (ObjectUtil.isNull(deptId)) {
|
||||
return "-1";
|
||||
}
|
||||
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
|
||||
.select(SysDept::getDeptId)
|
||||
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
|
||||
@ -64,7 +71,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
||||
if (CollUtil.isNotEmpty(ids)) {
|
||||
return StreamUtils.join(ids, Convert::toStr);
|
||||
}
|
||||
return null;
|
||||
return "-1";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class SysLogininforServiceImpl implements ISysLogininforService {
|
||||
@Async
|
||||
@EventListener
|
||||
public void recordLogininfor(LogininforEvent logininforEvent) {
|
||||
HttpServletRequest request = logininforEvent.getRequest();
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
||||
final String ip = ServletUtils.getClientIP(request);
|
||||
// 客户端信息
|
||||
|
@ -3,18 +3,23 @@ package org.dromara.system.service.impl;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ip.AddressUtils;
|
||||
import org.dromara.common.log.enums.BusinessStatus;
|
||||
import org.dromara.common.log.event.OperLogEvent;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.domain.SysOperLog;
|
||||
import org.dromara.system.domain.bo.SysOperLogBo;
|
||||
import org.dromara.system.domain.vo.SysOperLogVo;
|
||||
import org.dromara.system.mapper.SysOperLogMapper;
|
||||
import org.dromara.system.service.ISysOperLogService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -44,6 +49,18 @@ public class SysOperLogServiceImpl implements ISysOperLogService {
|
||||
@EventListener
|
||||
public void recordOper(OperLogEvent operLogEvent) {
|
||||
SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class);
|
||||
operLog.setTenantId(LoginHelper.getTenantId());
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = ServletUtils.getClientIP();
|
||||
operLog.setOperIp(ip);
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
operLog.setOperUrl(StringUtils.substring(request.getRequestURI(), 0, 255));
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
operLog.setOperName(loginUser.getUsername());
|
||||
operLog.setDeptName(loginUser.getDeptName());
|
||||
// 设置请求方式
|
||||
operLog.setRequestMethod(request.getMethod());
|
||||
// 远程查询操作地点
|
||||
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
|
||||
insertOperlog(operLog);
|
||||
|
@ -177,6 +177,17 @@ public class SysPostServiceImpl implements ISysPostService {
|
||||
return userPostMapper.selectCount(new LambdaQueryWrapper<SysUserPost>().eq(SysUserPost::getPostId, postId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过部门ID查询岗位使用数量
|
||||
*
|
||||
* @param deptId 部门id
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public long countPostByDeptId(Long deptId) {
|
||||
return baseMapper.selectCount(new LambdaQueryWrapper<SysPost>().eq(SysPost::getDeptId, deptId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除岗位信息
|
||||
*
|
||||
|
@ -293,6 +293,10 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateRole(SysRoleBo bo) {
|
||||
SysRole role = MapstructUtils.convert(bo, SysRole.class);
|
||||
|
||||
if (UserConstants.ROLE_DISABLE.equals(role.getStatus()) && this.countUserRoleByRoleId(role.getRoleId()) > 0) {
|
||||
throw new ServiceException("角色已分配,不能禁用!");
|
||||
}
|
||||
// 修改角色信息
|
||||
baseMapper.updateById(role);
|
||||
// 删除角色与菜单关联
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dromara.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@ -116,6 +117,17 @@ public class SysTenantPackageServiceImpl implements ISysTenantPackageService {
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验套餐名称是否唯一
|
||||
*/
|
||||
@Override
|
||||
public boolean checkPackageNameUnique(SysTenantPackageBo bo) {
|
||||
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysTenantPackage>()
|
||||
.eq(SysTenantPackage::getPackageName, bo.getPackageName())
|
||||
.ne(ObjectUtil.isNotNull(bo.getPackageId()), SysTenantPackage::getPackageId, bo.getPackageId()));
|
||||
return !exist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改套餐状态
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user