mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-19 09:55:16 +08:00
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>
|
<satoken.version>1.38.0</satoken.version>
|
||||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||||
<p6spy.version>3.9.1</p6spy.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>
|
<okhttp.version>4.10.0</okhttp.version>
|
||||||
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
|
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
|
||||||
<redisson.version>3.34.1</redisson.version>
|
<redisson.version>3.34.1</redisson.version>
|
||||||
|
|||||||
@ -71,7 +71,6 @@ public class UserActionListener implements SaTokenListener {
|
|||||||
logininforEvent.setUsername(username);
|
logininforEvent.setUsername(username);
|
||||||
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
|
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||||
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
|
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
|
||||||
SpringUtils.context().publishEvent(logininforEvent);
|
SpringUtils.context().publishEvent(logininforEvent);
|
||||||
// 更新登录信息
|
// 更新登录信息
|
||||||
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
|
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.enums.TenantStatus;
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.exception.user.UserException;
|
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.log.event.LogininforEvent;
|
||||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||||
import org.dromara.common.redis.utils.RedisUtils;
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
@ -139,7 +142,6 @@ public class SysLoginService {
|
|||||||
logininforEvent.setUsername(username);
|
logininforEvent.setUsername(username);
|
||||||
logininforEvent.setStatus(status);
|
logininforEvent.setStatus(status);
|
||||||
logininforEvent.setMessage(message);
|
logininforEvent.setMessage(message);
|
||||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
|
||||||
SpringUtils.context().publishEvent(logininforEvent);
|
SpringUtils.context().publishEvent(logininforEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +158,11 @@ public class SysLoginService {
|
|||||||
loginUser.setUserType(user.getUserType());
|
loginUser.setUserType(user.getUserType());
|
||||||
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
|
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
|
||||||
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
|
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
|
||||||
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
if (ObjectUtil.isNotNull(user.getDeptId())) {
|
||||||
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||||
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
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());
|
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
|
||||||
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||||
return loginUser;
|
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.CaptchaExpireException;
|
||||||
import org.dromara.common.core.exception.user.UserException;
|
import org.dromara.common.core.exception.user.UserException;
|
||||||
import org.dromara.common.core.utils.MessageUtils;
|
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.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.log.event.LogininforEvent;
|
import org.dromara.common.log.event.LogininforEvent;
|
||||||
@ -108,7 +107,6 @@ public class SysRegisterService {
|
|||||||
logininforEvent.setUsername(username);
|
logininforEvent.setUsername(username);
|
||||||
logininforEvent.setStatus(status);
|
logininforEvent.setStatus(status);
|
||||||
logininforEvent.setMessage(message);
|
logininforEvent.setMessage(message);
|
||||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
|
||||||
SpringUtils.context().publishEvent(logininforEvent);
|
SpringUtils.context().publishEvent(logininforEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -99,6 +99,13 @@
|
|||||||
<artifactId>transmittable-thread-local</artifactId>
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.faces</groupId>
|
||||||
|
<artifactId>jakarta.faces-api</artifactId>
|
||||||
|
<version>4.1.0</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</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.AfterThrowing;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Before;
|
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.ServletUtils;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessStatus;
|
import org.dromara.common.log.enums.BusinessStatus;
|
||||||
import org.dromara.common.log.event.OperLogEvent;
|
import org.dromara.common.log.event.OperLogEvent;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
@ -88,16 +86,6 @@ public class LogAspect {
|
|||||||
|
|
||||||
// *========数据库日志=========*//
|
// *========数据库日志=========*//
|
||||||
OperLogEvent operLog = new OperLogEvent();
|
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) {
|
if (e != null) {
|
||||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||||
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||||
@ -106,8 +94,6 @@ public class LogAspect {
|
|||||||
String className = joinPoint.getTarget().getClass().getName();
|
String className = joinPoint.getTarget().getClass().getName();
|
||||||
String methodName = joinPoint.getSignature().getName();
|
String methodName = joinPoint.getSignature().getName();
|
||||||
operLog.setMethod(className + "." + methodName + "()");
|
operLog.setMethod(className + "." + methodName + "()");
|
||||||
// 设置请求方式
|
|
||||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
|
||||||
// 处理设置注解上的参数
|
// 处理设置注解上的参数
|
||||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||||
// 设置消耗时间
|
// 设置消耗时间
|
||||||
|
|||||||
@ -2,8 +2,6 @@ package org.dromara.common.log.event;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@ -39,11 +37,6 @@ public class LogininforEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求体
|
|
||||||
*/
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 其他参数
|
* 其他参数
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.common.mail.config;
|
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.config.properties.MailProperties;
|
||||||
import org.dromara.common.mail.utils.MailAccount;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
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.map.MapUtil;
|
||||||
import cn.hutool.core.util.CharUtil;
|
import cn.hutool.core.util.CharUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.Authenticator;
|
||||||
import jakarta.mail.Session;
|
import jakarta.mail.Session;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@ -17,7 +20,7 @@ import java.io.InputStream;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮件工具类
|
* 邮件工具类
|
||||||
@ -385,7 +388,7 @@ public class MailUtils {
|
|||||||
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
|
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
|
||||||
Authenticator authenticator = null;
|
Authenticator authenticator = null;
|
||||||
if (mailAccount.isAuth()) {
|
if (mailAccount.isAuth()) {
|
||||||
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
|
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,
|
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) {
|
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)) {
|
if (CollUtil.isNotEmpty(ccs)) {
|
||||||
@ -431,7 +434,7 @@ public class MailUtils {
|
|||||||
|
|
||||||
// 图片
|
// 图片
|
||||||
if (MapUtil.isNotEmpty(imageMap)) {
|
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());
|
mail.addImage(entry.getKey(), entry.getValue());
|
||||||
// 关闭流
|
// 关闭流
|
||||||
IoUtil.close(entry.getValue());
|
IoUtil.close(entry.getValue());
|
||||||
@ -463,5 +466,4 @@ public class MailUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------------------------------------------------------ Private method end
|
// ------------------------------------------------------------------------------------------------------------------------ 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 user}: 当前登录用户信息,参考 {@link LoginUser}
|
||||||
* 内置服务:
|
* 内置服务:
|
||||||
* - {@code sdss}: 系统数据权限服务,参考 {@link ISysDataScopeService}
|
* - {@code sdss}: 系统数据权限服务,参考 ISysDataScopeService
|
||||||
* 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
|
* 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
|
||||||
* 如需扩展服务,可以通过 {@link ISysDataScopeService} 自行编写
|
* 如需扩展服务,可以通过 ISysDataScopeService 自行编写
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @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 "),
|
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门数据权限
|
* 部门数据权限
|
||||||
* 使用 SpEL 表达式:`#{#deptName} = #{#user.deptId}`
|
|
||||||
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
|
|
||||||
*/
|
*/
|
||||||
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 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 "),
|
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 ");
|
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
|
||||||
|
|
||||||
|
|||||||
@ -215,6 +215,9 @@ public class VelocityUtils {
|
|||||||
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
|
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
|
||||||
} else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
|
} else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
|
||||||
importList.add("java.math.BigDecimal");
|
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;
|
return importList;
|
||||||
|
|||||||
@ -53,6 +53,13 @@ public class ${ClassName}Vo implements Serializable {
|
|||||||
#end
|
#end
|
||||||
private $column.javaType $column.javaField;
|
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
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,12 @@ export interface ${BusinessName}VO {
|
|||||||
#elseif($column.javaType == 'Boolean') boolean;
|
#elseif($column.javaType == 'Boolean') boolean;
|
||||||
#else string;
|
#else string;
|
||||||
#end
|
#end
|
||||||
|
#if($column.htmlType == "imageUpload")
|
||||||
|
/**
|
||||||
|
* ${column.columnComment}Url
|
||||||
|
*/
|
||||||
|
${column.javaField}Url: string;
|
||||||
|
#end
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
#if ($table.tree)
|
#if ($table.tree)
|
||||||
|
|||||||
@ -99,9 +99,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
#elseif($column.list && $column.htmlType == "imageUpload")
|
#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">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
#elseif($column.list && $column.dictType && "" != $column.dictType)
|
#elseif($column.list && $column.dictType && "" != $column.dictType)
|
||||||
|
|||||||
@ -101,9 +101,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
#elseif($column.list && $column.htmlType == "imageUpload")
|
#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">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
#elseif($column.list && $column.dictType && "" != $column.dictType)
|
#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.bo.SysDeptBo;
|
||||||
import org.dromara.system.domain.vo.SysDeptVo;
|
import org.dromara.system.domain.vo.SysDeptVo;
|
||||||
import org.dromara.system.service.ISysDeptService;
|
import org.dromara.system.service.ISysDeptService;
|
||||||
|
import org.dromara.system.service.ISysPostService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ import java.util.List;
|
|||||||
public class SysDeptController extends BaseController {
|
public class SysDeptController extends BaseController {
|
||||||
|
|
||||||
private final ISysDeptService deptService;
|
private final ISysDeptService deptService;
|
||||||
|
private final ISysPostService postService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取部门列表
|
* 获取部门列表
|
||||||
@ -117,6 +119,9 @@ public class SysDeptController extends BaseController {
|
|||||||
if (deptService.checkDeptExistUser(deptId)) {
|
if (deptService.checkDeptExistUser(deptId)) {
|
||||||
return R.warn("部门存在用户,不允许删除");
|
return R.warn("部门存在用户,不允许删除");
|
||||||
}
|
}
|
||||||
|
if (postService.countPostByDeptId(deptId) > 0) {
|
||||||
|
return R.warn("部门存在岗位,不允许删除");
|
||||||
|
}
|
||||||
deptService.checkDeptDataScope(deptId);
|
deptService.checkDeptDataScope(deptId);
|
||||||
return toAjax(deptService.deleteDeptById(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.idempotent.annotation.RepeatSubmit;
|
||||||
import org.dromara.common.log.annotation.Log;
|
import org.dromara.common.log.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
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.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.web.core.BaseController;
|
import org.dromara.common.web.core.BaseController;
|
||||||
import org.dromara.system.domain.bo.SysUserBo;
|
import org.dromara.system.domain.bo.SysUserBo;
|
||||||
@ -72,7 +73,8 @@ public class SysProfileController extends BaseController {
|
|||||||
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
|
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
|
||||||
return R.fail("修改用户'" + username + "'失败,邮箱账号已存在");
|
return R.fail("修改用户'" + username + "'失败,邮箱账号已存在");
|
||||||
}
|
}
|
||||||
if (userService.updateUserProfile(user) > 0) {
|
int rows = DataPermissionHelper.ignore(() -> userService.updateUserProfile(user));
|
||||||
|
if (rows > 0) {
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
return R.fail("修改个人信息异常,请联系管理员");
|
return R.fail("修改个人信息异常,请联系管理员");
|
||||||
|
|||||||
@ -92,6 +92,9 @@ public class SysTenantPackageController extends BaseController {
|
|||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@PostMapping()
|
@PostMapping()
|
||||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
|
||||||
|
if (!tenantPackageService.checkPackageNameUnique(bo)) {
|
||||||
|
return R.fail("新增套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
|
||||||
|
}
|
||||||
return toAjax(tenantPackageService.insertByBo(bo));
|
return toAjax(tenantPackageService.insertByBo(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +107,9 @@ public class SysTenantPackageController extends BaseController {
|
|||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@PutMapping()
|
@PutMapping()
|
||||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
|
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
|
||||||
|
if (!tenantPackageService.checkPackageNameUnique(bo)) {
|
||||||
|
return R.fail("修改套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
|
||||||
|
}
|
||||||
return toAjax(tenantPackageService.updateByBo(bo));
|
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;
|
private String email;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手机号码
|
* 手机号码
|
||||||
*/
|
*/
|
||||||
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
@Sensitive(strategy = SensitiveStrategy.PHONE, perms = "system:user:edit")
|
||||||
private String phonenumber;
|
private String phonenumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -80,6 +80,14 @@ public interface ISysPostService {
|
|||||||
*/
|
*/
|
||||||
long countUserPostById(Long postId);
|
long countUserPostById(Long postId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过部门ID查询岗位使用数量
|
||||||
|
*
|
||||||
|
* @param deptId 部门id
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
long countPostByDeptId(Long deptId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除岗位信息
|
* 删除岗位信息
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package org.dromara.system.service;
|
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.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.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -45,6 +45,11 @@ public interface ISysTenantPackageService {
|
|||||||
*/
|
*/
|
||||||
Boolean updateByBo(SysTenantPackageBo bo);
|
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.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
@ -38,6 +39,9 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getRoleCustom(Long roleId) {
|
public String getRoleCustom(Long roleId) {
|
||||||
|
if (ObjectUtil.isNull(roleId)) {
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
List<SysRoleDept> list = roleDeptMapper.selectList(
|
List<SysRoleDept> list = roleDeptMapper.selectList(
|
||||||
new LambdaQueryWrapper<SysRoleDept>()
|
new LambdaQueryWrapper<SysRoleDept>()
|
||||||
.select(SysRoleDept::getDeptId)
|
.select(SysRoleDept::getDeptId)
|
||||||
@ -45,7 +49,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
|||||||
if (CollUtil.isNotEmpty(list)) {
|
if (CollUtil.isNotEmpty(list)) {
|
||||||
return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId()));
|
return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId()));
|
||||||
}
|
}
|
||||||
return null;
|
return "-1";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +60,9 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getDeptAndChild(Long deptId) {
|
public String getDeptAndChild(Long deptId) {
|
||||||
|
if (ObjectUtil.isNull(deptId)) {
|
||||||
|
return "-1";
|
||||||
|
}
|
||||||
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
|
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
|
||||||
.select(SysDept::getDeptId)
|
.select(SysDept::getDeptId)
|
||||||
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
|
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
|
||||||
@ -64,7 +71,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
|||||||
if (CollUtil.isNotEmpty(ids)) {
|
if (CollUtil.isNotEmpty(ids)) {
|
||||||
return StreamUtils.join(ids, Convert::toStr);
|
return StreamUtils.join(ids, Convert::toStr);
|
||||||
}
|
}
|
||||||
return null;
|
return "-1";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public class SysLogininforServiceImpl implements ISysLogininforService {
|
|||||||
@Async
|
@Async
|
||||||
@EventListener
|
@EventListener
|
||||||
public void recordLogininfor(LogininforEvent logininforEvent) {
|
public void recordLogininfor(LogininforEvent logininforEvent) {
|
||||||
HttpServletRequest request = logininforEvent.getRequest();
|
HttpServletRequest request = ServletUtils.getRequest();
|
||||||
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
||||||
final String ip = ServletUtils.getClientIP(request);
|
final String ip = ServletUtils.getClientIP(request);
|
||||||
// 客户端信息
|
// 客户端信息
|
||||||
|
|||||||
@ -3,18 +3,23 @@ package org.dromara.system.service.impl;
|
|||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
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.core.utils.MapstructUtils;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.core.utils.ServletUtils;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.core.utils.ip.AddressUtils;
|
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.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.SysOperLog;
|
||||||
import org.dromara.system.domain.bo.SysOperLogBo;
|
import org.dromara.system.domain.bo.SysOperLogBo;
|
||||||
import org.dromara.system.domain.vo.SysOperLogVo;
|
import org.dromara.system.domain.vo.SysOperLogVo;
|
||||||
import org.dromara.system.mapper.SysOperLogMapper;
|
import org.dromara.system.mapper.SysOperLogMapper;
|
||||||
import org.dromara.system.service.ISysOperLogService;
|
import org.dromara.system.service.ISysOperLogService;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -44,6 +49,18 @@ public class SysOperLogServiceImpl implements ISysOperLogService {
|
|||||||
@EventListener
|
@EventListener
|
||||||
public void recordOper(OperLogEvent operLogEvent) {
|
public void recordOper(OperLogEvent operLogEvent) {
|
||||||
SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class);
|
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()));
|
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
|
||||||
insertOperlog(operLog);
|
insertOperlog(operLog);
|
||||||
|
|||||||
@ -177,6 +177,17 @@ public class SysPostServiceImpl implements ISysPostService {
|
|||||||
return userPostMapper.selectCount(new LambdaQueryWrapper<SysUserPost>().eq(SysUserPost::getPostId, postId));
|
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)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int updateRole(SysRoleBo bo) {
|
public int updateRole(SysRoleBo bo) {
|
||||||
SysRole role = MapstructUtils.convert(bo, SysRole.class);
|
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);
|
baseMapper.updateById(role);
|
||||||
// 删除角色与菜单关联
|
// 删除角色与菜单关联
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.dromara.system.service.impl;
|
package org.dromara.system.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
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.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@ -116,6 +117,17 @@ public class SysTenantPackageServiceImpl implements ISysTenantPackageService {
|
|||||||
return baseMapper.updateById(update) > 0;
|
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…
Reference in New Issue
Block a user