import cn.hutool.core.util.BooleanUtil;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author mac
* @date 2022/1/14 03:22
*/
public class AuthUser implements UserDetails {
private final UserDto userDto;
private final List<String> perms;
public AuthUser(UserDto userDto, List<String> perms) {
this.userDto = userDto;
this.perms = perms;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//返回当前用户的权限
return this.getPerms()
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return userDto.getPassword();
}
@Override
public String getUsername() {
return userDto.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return BooleanUtil.isTrue(userDto.getEnable());
}
public UserDto getUserDto() {
return userDto;
}
public List<String> getPerms() {
return perms;
}
}
/**
* 总消息类 用来存放消息
*
* @date 2020-09-22 17:07
*/
public interface BaseMsg {
/**
* 获取消息的状态码
*/
Integer getCode();
/**
* 获取消息提示信息
*/
String getMessage();
}
import cn.hutool.core.util.StrUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
/**
* @author iqiao
* @date 2018/8/18.
*/
public class ContextHolder {
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getRequest();
}
public static HttpServletResponse getResponse() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getResponse();
}
public static String getRequestIp() {
return IPUtil.getClientId(getRequest());
}
public static String getToken() {
return getRequest().getHeader(HeaderConstants.ACCESS_TOKEN);
}
public static Long getCurrUserId() {
return getCurrUser().getId();
}
public static UserDto getCurrUser() {
UserDto userDto = getCurrUserWithNull();
if (userDto == null) {
throw new ServiceException(TokenMsg.EXCEPTION_USER_NULL);
}
return userDto;
}
public static Long getCurrUserIdWithNull() {
return Optional.ofNullable(getCurrUserWithNull()).map(UserDto::getId).orElse(null);
}
public static String getCurrUserNameWithNull() {
return Optional.ofNullable(getCurrUserWithNull())
.map(userDto -> StrUtil.blankToDefault(userDto.getUsername(), userDto.getRealName()))
.orElse(null);
}
public static UserDto getCurrUserWithNull() {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
if (auth == null) {
return null;
}
Object principal = auth.getPrincipal();
if ("anonymousUser" == principal) {
return null;
}
AuthUser authUser = (AuthUser) principal;
return authUser.getUserDto();
}
}
/**
* 核心类 - 消息
*
* @date 2020-09-13 19:36
*/
public enum CoreMsg implements BaseMsg {
/**
* SQL
*/
SQL_EXCEPTION_UPDATE(10100, "更新数据失败,是否刷新页面重试?"),
SQL_EXCEPTION_INSERT(10100, "新增数据失败,是否刷新页面重试?"),
SQL_EXCEPTION_DELETE(10100, "删除数据失败,是否刷新页面重试?"),
SQL_EXCEPTION_INTEGRITY_CONSTRAINT_VIOLATION(10105, "数据主键冲突或者已有该数据!"),
SQL_EXCEPTION_NOT_HAVE_DEFAULT_VALUE(10106, "数据异常:{} 字段没有默认值!"),
SQL_EXCEPTION_UNKNOWN(10106, "数据异常:未知异常,请联系系统管理员 {}"),
SQL_EXCEPTION_ERROR(10107, "数据异常:未知异常,请联系系统管理员"),
/**
* Redis
*/
REDIS_EXCEPTION_PUSH_SUB(10200, "Redis 订阅通道失败!"),
REDIS_EXCEPTION_LOCK(10201, "无法申领分布式锁"),
/**
* Excel
*/
EXCEL_EXPORT_SUCCESS(200, "Excel 导出成功! - 数据行数:{} - 耗时:{}"),
EXCEL_EXPORT_ERROR(10301, "Excel 导出失败! - 耗时:{} - 失败信息:{}"),
EXCEL_IMPORT_SUCCESS(200, "EXCEL 导入成功! - 耗时:{}"),
EXCEL_IMPORT_ERROR(10303, "Excel导入失败! - 耗时:{} - 失败信息:{}"),
EXCEL_IMPORT_NO(10304, "导入对象为空"),
EXCEL_FILE_NULL(10305, "请选择文件"),
EXCEL_HANDLE_MAX(10700, "超出最大操作数量, 当前数据[{}]条,允许最大阈值[{}]条"),
/**
* 缓存
*/
CACHE_PUNCTURE_EXCEPTION(10405, "当前服务繁忙,客官请稍微再次尝试!"),
CACHE_DEL_EXCEPTION(10406, "无法清除缓存,请稍后再试"),
/**
* dubbo
*/
RPC_INVOKE_ERROR(10600, "服务调用失败"),
/**
* 其他
*/
OTHER_EXCEPTION_LIMITER(10700, "当前系统繁忙,请稍后再试"),
OTHER_EXCEPTION_CRYPTO_EN(10702, "加密失败"),
OTHER_EXCEPTION_CRYPTO_DE(10703, "解密失败"),
OTHER_EXCEPTION_CRYPTO_REFLEX(10704, "解密反射失败"),
OTHER_EXCEPTION_UTILS_INIT(10705, "系统工具类暂未初始化"),
OTHER_EXCEPTION_SERVER_ERROR(10706, "当前系统繁忙,请稍后再试"),
/**
* 远程调用失败
*/
REMOTE_INVOKE_ERROR(10800, "远程服务调用失败"),
DUBBO_INVOKE_ERROR(10801, "消费服务调用失败"),
/**
* Excel 异常
*/
EXCEPTION_FILE_FORMAT(10900, "文件格式错误!"),
EXCEPTION_CREATE_ERROR(10901, "创建文件失败!"),
;
private final int code;
private final String message;
CoreMsg(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
/**
* 自定义操作日志注解
*
* @date 2020-09-12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableLog {
/**
* 标题
*/
String title() default "";
/**
* 忽略
*/
boolean ignore() default false;
}
/**
* @author mac
* @date 2022/1/13 09:47
*/
public interface HeaderConstants {
/**
* ACCESS_TOKEN
*/
String ACCESS_TOKEN = "X-Token";
/**
* USER_ID
*/
String USER_ID = "X-USER-ID";
/**
* USER_ID
*/
String USER_NAME = "X-USER-NAME";
}
import cn.hutool.core.lang.Validator;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import javax.servlet.http.HttpServletRequest;
/**
* IP 工具类
*
* @date 2020-09-19 23:21
*/
public final class IPUtil {
/**
* 排除结果
*/
private static final String UNKNOWN = "unknown";
/**
* 尝试字段
*/
private static final String[] HEADERS_TO_TRY = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-Real-IP"
};
private IPUtil() {
}
/***
* 获取客户端地址
*
* @param request request
* @return String
*/
public static String getClientAddress(HttpServletRequest request) {
for (String header : HEADERS_TO_TRY) {
String address = request.getHeader(header);
if (StrUtil.isNotBlank(address)
&& !UNKNOWN.equalsIgnoreCase(address)) {
return address;
}
}
return request.getRemoteAddr();
}
/***
* 获取客户端地址 (多重代理只获得第一个)
*
* @param request request
* @return String
*/
public static String getClientAddressBySingle(HttpServletRequest request) {
String clientAddress = getClientAddress(request);
return NetUtil.getMultistageReverseProxyIp(clientAddress);
}
/***
* 获取客户端IP
*
* @param request request
* @return String
*/
public static String getClientId(HttpServletRequest request) {
for (String header : HEADERS_TO_TRY) {
String ip = request.getHeader(header);
if (StrUtil.isNotBlank(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
String reverseProxyIp = NetUtil.getMultistageReverseProxyIp(ip);
if (Validator.isIpv4(reverseProxyIp) || Validator.isIpv6(reverseProxyIp)) {
// 判断是否为IP 返回原始IP
return ip;
}
}
}
// 否则返回 空地址
return "";
}
// ===============
/***
* 获取客户端IP (多重代理只获得第一个)
*
* @param request request
* @return String
*/
public static String getClientIdBySingle(HttpServletRequest request) {
String clientIp = getClientId(request);
return NetUtil.getMultistageReverseProxyIp(clientIp);
}
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 参数校验 拦截处理
*
* @date 2020-09-16
*/
@Slf4j
@Order(150)
@Aspect
@Component
public class LogAop {
@Pointcut("execution(public * com.traffic.admin..*.*Controller*.*(..))")
public void requestMapping() {
}
/**
* 切如 post 请求
*
* @param point point
*/
@Around("requestMapping()")
public Object logAop(ProceedingJoinPoint point) throws Throwable {
// 计时器
TimeInterval timer = DateUtil.timer();
// 执行
Exception exception = null;
// 防止线程抛异常 线程变量不回收 导致oom
Object returnValue;
try {
// 执行正常操作
returnValue = point.proceed();
} catch (Exception e) {
exception = e;
throw e;
} finally {
// 花费毫秒数
long timerCount = timer.interval();
//保存日志
LogUtil.saveLog(point, exception, timerCount);
}
return returnValue;
}
}
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 日志
*
* @author mac
* @date 2022/1/13 11:15
*/
@Data
public class LogsDto implements Serializable {
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Long id;
/**
* 日志类型(1:接入日志;2:错误日志)
*/
@ApiModelProperty(value = "日志类型")
private Integer type;
/**
* 日志标题
*/
@ApiModelProperty(value = "日志标题")
private String title;
/**
* 操作用户的IP地址
*/
@ApiModelProperty(value = "操作用户的IP地址")
private String remoteAddr;
/**
* 操作用户代理信息
*/
@ApiModelProperty(value = "操作用户代理信息")
private String userAgent;
/**
* 执行时间
*/
@ApiModelProperty(value = "执行时间")
private Long timeout;
/**
* 操作的URI
*/
@ApiModelProperty(value = "操作的URI")
private String requestUri;
/**
* 操作的方式
*/
@ApiModelProperty(value = "操作的方式")
private String method;
/**
* 操作提交的数据
*/
@ApiModelProperty(value = "操作提交的数据")
private String params;
/**
* 异常信息
*/
@ApiModelProperty(value = "异常信息")
private String exception;
/**
* 创建人
*/
@ApiModelProperty(value = "创建人")
private Long createBy;
/**
* 创建时间
*/
@ApiModelProperty(value = "创建时间")
private Date createTime;
/**
* 更新人
*/
@ApiModelProperty(value = "修改人")
private Long updateBy;
/**
* 更新时间
*/
@ApiModelProperty(value = "修改时间")
private Date updateTime;
}
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* @author mac
* @date 2022/1/13 11:18
*/
public enum LogsType implements IEnum<Integer> {
ACCESS(1, "接入日志"),
EXCEPTION(2, "错误日志"),
;
@JsonValue
private final int value;
private final String label;
LogsType(final int value, final String label) {
this.value = value;
this.label = label;
}
@Override
public Integer getValue() {
return this.value;
}
public String getLabel() {
return label;
}
}
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.traffic.admin.core.securities.SpringSecurity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* 日志工具类
*
* @date 2020-09-22 11:17
*/
@Slf4j
public final class LogUtil {
private LogUtil() {
}
/**
* 保存日志
*
* @param point point
* @param e 异常
* @param timerCount 花费毫秒数
*/
public static void saveLog(ProceedingJoinPoint point, Exception e, long timerCount) {
try {
Object[] args = point.getArgs();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
if (sra == null) {
return;
}
HttpServletRequest request = sra.getRequest();
// EnableLog 忽略 则直接退出
EnableLog enableLog = method.getAnnotation(EnableLog.class);
if (enableLog != null && BooleanUtil.isTrue(enableLog.ignore())) {
return;
}
Long userId;
try {
userId = ContextHolder.getCurrUserId();
// userId = Long.valueOf(SpringSecurity.getUserId());
} catch (Exception ex) {
return;
}
String argsStr = null;
try {
argsStr = JSONUtil.toJsonStr(args);
} catch (Exception ignored) {
}
LogsDto logsDto = new LogsDto();
// 操作方法
String methodName = request.getMethod();
// 获得IP
String clientIpAddress = IPUtil.getClientIdBySingle(request);
// 设置标题
setTitle(point, method, logsDto);
// 设置类型
logsDto.setType(e == null ? LogsType.ACCESS.getValue() : LogsType.EXCEPTION.getValue());
// 设置客户端代理
logsDto.setUserAgent(request.getHeader("user-agent"));
// 设置URI
logsDto.setRequestUri(request.getRequestURI());
// 设置IP
logsDto.setRemoteAddr(clientIpAddress);
// 设置参数
logsDto.setParams(argsStr);
// 设置方法
logsDto.setMethod(methodName);
// 设置执行时长
logsDto.setTimeout(timerCount);
// 设置异常信息
if (e != null) {
logsDto.setException(e.getMessage());
}
logsDto.setCreateBy(userId);
logsDto.setUpdateBy(userId);
// 保存日志
log.info(logsDto.toString());
LogsThreadPool.process(logsDto);
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
}
/**
* 设置日志标题
*
* @param point point
* @param method 方法
* @param logsDto 日志模型
*/
private static void setTitle(ProceedingJoinPoint point, Method method, LogsDto logsDto) {
// 设置 title
EnableLog enableLog = method.getAnnotation(EnableLog.class);
if (enableLog != null) {
//注解上的描述,操作日志内容
String title = enableLog.title();
if (StringUtils.isNotEmpty(title)) {
logsDto.setTitle(title);
}
}
// 如果title 还是为空 则系统自动赋class
if (StringUtils.isEmpty(logsDto.getTitle())) {
// 获取请求的类名
String className = point.getTarget().getClass().getName();
String methodName = method.getName();
logsDto.setTitle(className + "." + methodName);
}
}
}
/**
* 框架服务异常
*
* @date 2020-09-13 19:41
*/
public class ServiceException extends RuntimeException {
private Integer code;
private String errorMessage;
public ServiceException() {
this(CoreMsg.OTHER_EXCEPTION_SERVER_ERROR.getCode(), CoreMsg.OTHER_EXCEPTION_SERVER_ERROR.getMessage());
}
public ServiceException(Integer code, String errorMessage) {
super(errorMessage);
this.code = code;
this.errorMessage = errorMessage;
}
public ServiceException(Integer code, String errorMessage, Throwable e) {
super(errorMessage, e);
this.code = code;
this.errorMessage = errorMessage;
}
public ServiceException(BaseMsg msg) {
super(msg.getMessage());
this.code = msg.getCode();
this.errorMessage = msg.getMessage();
}
public ServiceException(BaseMsg baseMsg, String message) {
super(message);
this.code = baseMsg.getCode();
this.errorMessage = message;
}
public ServiceException(BaseMsg msg, Throwable e) {
super(msg.getMessage(), e);
this.code = msg.getCode();
this.errorMessage = msg.getMessage();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
/**
* Token - 消息
*
* @date 2020-09-13 19:36
*/
public enum TokenMsg implements BaseMsg {
/**
* Token
*/
EXCEPTION_TOKEN_CREATE_ERROR(12000, "生成Token失败"),
EXCEPTION_TOKEN_CREATE_LIMIT_ERROR(12001, "您的账号已在其他设备登录"),
EXCEPTION_TOKEN_LOSE_EFFICACY(401, "Token失效,请重新登录"),
/**
* 登陆
*/
EXCEPTION_CAPTCHA_ERROR(12100, "验证码不正确!"),
EXCEPTION_CAPTCHA_NULL(12201, "验证码已失效"),
EXCEPTION_CAPTCHA_UUID_NULL(12202, "验证码UUID为空"),
EXCEPTION_CAPTCHA_CODE_NULL(12203, "验证码为空"),
EXCEPTION_LOGIN_ACCOUNT_NO(12101, "账号或密码不正确!"),
EXCEPTION_LOGIN_ACCOUNT_LOCKED(12102, "账号已被锁定,请联系管理员!"),
EXCEPTION_LOGOUT_ERROR(12103, "登出失败,没有授权Token!"),
EXCEPTION_LOGOUT_SUCCESS(12104, "登出成功!"),
EXCEPTION_LOGIN_ACCOUNT_LOCK(12104, "账号已锁定,请{}后,再次尝试"),
EXCEPTION_LOGIN_TENANT_NOT_USABLE(12105, "租户未启用,请联系管理员"),
EXCEPTION_LOGIN_NULL(12106, "请输入账号密码"),
EXCEPTION_LOGIN_DECRYPT(12107, "登录账号密码解析失败"),
EXCEPTION_USER_ROLE_NOT_NULL(12108, "用户暂无角色,请设置后登录"),
EXCEPTION_USER_MENU_NOT_NULL(12109, "用户暂无角色菜单,请设置后登录"),
EXCEPTION_USER_PERMS_NOT_NULL(12110, "用户暂无权限,请设置后登录"),
/**
* 其他
*/
EXCEPTION_USER_NULL(12200, "用户为空"),
EXCEPTION_NOT_AUTH(403, "无权访问该方法"),
EXCEPTION_NOT_REALM(12202, "找不到认证授权器"),
;
private final int code;
private final String message;
TokenMsg(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author mac
* @date 2022/1/11 01:42
*/
@Data
public class UserDto implements Serializable {
/**
* 主键
*/
private Long id;
/**
* 身份(1普通用户 2管理员 3商家 4店员)
*
*/
private Integer identity;
/**
* 登录账户
*/
@ApiModelProperty(value = "登录账户")
private String username;
/**
* 登录密码
*/
@ApiModelProperty(value = "登录密码")
private String password;
/**
* 登录密码强度
*/
@ApiModelProperty(value = "登录密码强度")
private Integer passwordLevel;
/**
* 是否启用
*/
@ApiModelProperty(value = "是否启用")
private Boolean enable;
/**
* 真实姓名
*/
@ApiModelProperty(value = "真实姓名")
private String realName;
/**
* 手机
*/
@ApiModelProperty(value = "手机")
private String mobile;
/**
* 邮箱
*/
@ApiModelProperty(value = "邮箱")
private String email;
// /**
// * 工号
// */
// @ApiModelProperty(value = "工号")
// private String no;
/**
* 头像
*/
@ApiModelProperty(value = "头像")
private String avatar;
/**
* 最后登陆IP
*/
@ApiModelProperty(value = "最后登陆IP")
private String loginIp;
/**
* 备注
*/
@ApiModelProperty(value = "备注")
private String remark;
/**
* 签名
*/
@ApiModelProperty(value = "签名")
private String sign;
@ApiModelProperty(value = "小程序openId")
private String openId;
/**
* 角色名称
*/
@ApiModelProperty(value = "角色名称")
private String roleNames;
/**
* 商家id
*/
@ApiModelProperty(value = "商家id")
private Long shopId;
/**
* 门店id
*/
@ApiModelProperty(value = "门店id")
private Long storeId;
/**
* 员工id
*/
@ApiModelProperty(value = "员工id")
private Long employeeId;
/**
* 是否商家所有者
*/
@ApiModelProperty(value = "是否商家所有者")
private Boolean isShopOwner;
/**
* 门店
*/
// private StoreDto store;
/**
* 员工信息
*/
// private EmployeeDto employeeDto;
/**
* 是否管理员
*
* @return
*/
// public boolean isAdmin() {
// return UserIdentity.ADMIN.getValue().equals(this.identity);
// }
}
import com.ichurun.saas.common.thread.AsyncProcessExecutor;
import com.ichurun.saas.common.thread.AsyncProcessExecutorFactory;
import com.ichurun.saas.open.system.dto.LogsDto;
import com.ichurun.saas.open.system.rpc.LogsService;
import lombok.extern.slf4j.Slf4j;
/**
* 日志保存线程
*
* @date 2020-09-16
*/
@Slf4j
public class LogsThreadPool {
/**
* 日志API
*/
private static LogsService logsService;
/**
* 执行
*
* @param logsDto 日志模型
*/
public static void process(LogsDto logsDto) {
if (logsDto == null) {
return;
}
AsyncProcessExecutor normalExecutor = AsyncProcessExecutorFactory.createNormalExecutor();
normalExecutor.put(() -> {
// 存储临时 token
try {
logsService.insert(logsDto);
} catch (Exception e) {
log.error(e.getMessage());
}
});
normalExecutor.execute();
}
// ========================
public void setLogsService(LogsService logsService) {
LogsThreadPool.logsService = logsService;
}
}
/**
* 异步进程 执行器
*
* @date 2021年7月15日13:43:37
*/
public interface AsyncProcessExecutor {
/**
* 存放任务
*
* @param task 任务
* @return AsyncProcessExecutor
*/
AsyncProcessExecutor put(final Runnable task);
/**
* 执行
*
* @return boolean
*/
boolean execute();
}
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 多线程锁执行器 正常处理
*
* @date 2020-12-10 10:36
*/
@Slf4j
public class AsyncProcessExecutorByNormal implements AsyncProcessExecutor {
/**
* 线程池字典
*/
private static final Map<String, AsyncProcessor> EXECUTOR_MAP = Maps.newConcurrentMap();
/**
* 线程Key
*/
private final String key;
/**
* 任务队列
*/
private final List<Runnable> taskList;
/**
* 执行器
*/
private final AsyncProcessor processor;
/**
* 构造函数
*/
public AsyncProcessExecutorByNormal() {
this.key = "def";
taskList = new ArrayList<>();
processor = AsyncProcessExecutorByNormal.getProcessor(this.key);
}
/**
* 构造函数
*
* @param key 线程池唯一Key
*/
public AsyncProcessExecutorByNormal(String key) {
this.key = key;
taskList = new ArrayList<>();
processor = AsyncProcessExecutorByNormal.getProcessor(this.key);
}
/**
* 获得执行器
*
* @param key Key
* @return AsyncProcessor
*/
private synchronized static AsyncProcessor getProcessor(String key) {
AsyncProcessor asyncProcessor = EXECUTOR_MAP.get(key);
if (null == asyncProcessor) {
asyncProcessor = new AsyncProcessor();
asyncProcessor.init(key);
EXECUTOR_MAP.put(key, asyncProcessor);
}
return asyncProcessor;
}
/**
* 执行
*
* @param task 任务
*/
@Override
public AsyncProcessExecutorByNormal put(final Runnable task) {
taskList.add(task);
return this;
}
// ====================================
/**
* 执行 线程锁 等待查询结果 结果完成后继续执行
*
* @return boolean 最终直接结果
*/
@Override
public boolean execute() {
if (CollUtil.isEmpty(this.taskList)) {
return true;
}
for (Runnable task : this.taskList) {
// 多线程执行任务
this.execute(task);
}
// 返回执行结果
return true;
}
/**
* 执行指定的任务
*
* @param task 任务
* @return boolean
*/
private boolean execute(final Runnable task) {
return processor.executeTask(task);
}
}
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多线程锁执行器
* 用于当前方法中复杂业务多线程处理,等待线程执行完毕后 获得统一结果
* 2021年11月2日14:07:54 重构 多线程异步等待执行器
*
* @author 周鹏程
* @date 2020-12-10 10:36
*/
@Slf4j
public class AsyncProcessExecutorByWait implements AsyncProcessExecutor {
/**
* 线程池字典
*/
private static final Map<String, AsyncProcessor> EXECUTOR_MAP = Maps.newConcurrentMap();
/**
* 线程Key
*/
private final String key;
/**
* 任务队列
*/
private final List<Callable<Object>> taskList;
/**
* 执行器
*/
private final AsyncProcessor processor;
/**
* 任务执行计数器
*/
private AtomicInteger count;
/**
* 构造函数
*/
public AsyncProcessExecutorByWait() {
this.key = "def";
taskList = new ArrayList<>();
processor = getProcessor(this.key);
}
/**
* 构造函数
*
* @param key 线程池唯一Key
*/
public AsyncProcessExecutorByWait(String key) {
this.key = key;
taskList = new ArrayList<>();
processor = getProcessor(this.key);
}
/**
* 获得执行器
*
* @param key Key
* @return AsyncProcessor
*/
private synchronized static AsyncProcessor getProcessor(String key) {
AsyncProcessor asyncProcessor = EXECUTOR_MAP.get(key);
if (null == asyncProcessor) {
asyncProcessor = new AsyncProcessor();
asyncProcessor.init(key);
EXECUTOR_MAP.put(key, asyncProcessor);
}
return asyncProcessor;
}
/**
* 放入执行任务
* 特殊处理 Runnable 转换为 Callable
*
* @param task 任务
*/
@Override
public AsyncProcessExecutor put(final Runnable task) {
taskList.add(Executors.callable(task));
return this;
}
/**
* 执行 线程锁 等待查询结果 结果完成后继续执行
*/
@Override
public boolean execute() {
if (CollUtil.isEmpty(this.taskList)) {
return true;
}
// 初始化锁参数
count = new AtomicInteger(this.taskList.size());
// 门闩 线程锁
CountDownLatch latch = new CountDownLatch(this.taskList.size());
for (Callable<Object> task : this.taskList) {
// 回调减 门闩
processor.executeTaskAndCallback(task, (result) -> {
if (result.getSuccess()) {
count.decrementAndGet();
}
latch.countDown();
return null;
});
}
// 线程锁 等待查询结果 结果完成后继续执行
try {
latch.await();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
this.taskList.clear();
}
// 返回执行结果
return count.get() == 0;
}
}
/**
* 异步进程 执行器 工厂
*
* @date 2021年7月15日13:43:37
*/
public final class AsyncProcessExecutorFactory {
private AsyncProcessExecutorFactory() {
}
/**
* 创建等待执行器
*
* @return AsyncProcessExecutor
*/
public static AsyncProcessExecutor createWaitExecutor() {
return new AsyncProcessExecutorByWait();
}
/**
* 创建等待执行器
*
* @param key KEY
* @return AsyncProcessExecutor
*/
public static AsyncProcessExecutor createWaitExecutor(String key) {
return new AsyncProcessExecutorByWait(key);
}
/**
* 创建正常执行器
*
* @return AsyncProcessExecutor
*/
public static AsyncProcessExecutor createNormalExecutor() {
return new AsyncProcessExecutorByNormal();
}
// =====================
/**
* 创建正常执行器
*
* @param key KEY
* @return AsyncProcessExecutor
*/
public static AsyncProcessExecutor createNormalExecutor(String key) {
return new AsyncProcessExecutorByNormal(key);
}
}
import cn.hutool.core.util.StrUtil;
import com.google.common.util.concurrent.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.Callable;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* 自定义线程执行器 - 等待线程执行完毕不拒绝
*
* @author 周鹏程
* @date 2020-10-08 10:24
*/
@Slf4j
public class AsyncProcessor {
/**
* 线程池名称格式
*/
private static final String THREAD_POOL_NAME = "AsyncProcessorWaitPool-{}-%d";
/**
* 默认线程池关闭等待时间 秒
*/
private static final int DEFAULT_WAIT_TIME = 10;
/**
* 线程池监听执行器
*/
private ListeningExecutorService execute;
/**
* 初始化
*
* @param key 线程池标识
*/
public void init(String key) {
if (StringUtils.isBlank(key)) {
return;
}
// 线程工厂名称
String formatThreadPoolName = StrUtil.format(THREAD_POOL_NAME, key);
// 创建 Executor
// 此处默认最大值改为处理器数量的 4 倍
try {
// 监听执行器
execute = MoreExecutors.listeningDecorator(
ThreadPoolFactory.createDefThreadPool(formatThreadPoolName));
// 这里不会自动关闭线程, 当线程超过阈值时 抛异常
// 关闭事件的挂钩
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("ProcessorWait 异步处理器关闭");
execute.shutdown();
try {
// 等待1秒执行关闭
if (!execute.awaitTermination(DEFAULT_WAIT_TIME, TimeUnit.SECONDS)) {
log.error("ProcessorWait 由于等待超时,异步处理器立即关闭");
execute.shutdownNow();
}
} catch (InterruptedException e) {
log.error("ProcessorWait 异步处理器关闭中断");
execute.shutdownNow();
}
log.info("ProcessorWait 异步处理器关闭完成");
}));
} catch (Exception e) {
log.error("ProcessorWait 异步处理器初始化错误", e);
throw new ExceptionInInitializerError(e);
}
}
/**
* 执行任务,不管是否成功<br>
* 其实也就是包装以后的 {@link } 方法
*
* @param task 任务
* @return boolean
*/
public boolean executeTask(Runnable task) {
try {
execute.execute(task);
} catch (RejectedExecutionException e) {
log.error("AsyncProcessorWait 执行任务被拒绝", e);
return false;
}
return true;
}
/**
* 提交任务,并可以在稍后获取其执行情况<br>
* 当提交失败时,会抛出 {@link }
*
* @param task 任务
*/
public <T> void executeTaskAndCallback(Callable<T> task, Function<CallbackResult<T>, Void> callback) {
ListenableFuture<T> future = execute.submit(task);
Futures.addCallback(future, new FutureCallback<T>() {
@Override
public void onSuccess(T result) {
CallbackResult<T> callbackResult = new CallbackResult<>();
callbackResult.setSuccess(true);
callbackResult.setResult(result);
// 线程池失败后 返回该 Runnable
callback.apply(callbackResult);
}
@Override
public void onFailure(Throwable t) {
log.error("线程名称:{} - 执行异常信息:{}", Thread.currentThread().getName(), t.getMessage());
CallbackResult<T> callbackResult = new CallbackResult<>();
callbackResult.setSuccess(false);
callback.apply(callbackResult);
}
}, execute);
}
// =================
/**
* 回调结果
*
* @param <T>
*/
@Data
public static class CallbackResult<T> {
/**
* 状态
*/
private Boolean success;
/**
* 结果
*/
private T result;
}
}
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ExecutorService;
/**
* 单线程池
*
* @author 周鹏程
* @date 2021/8/27 17:00
*/
@Slf4j
public final class SyncProcessSingleExecutor {
private static final Map<String, ExecutorService> EXECUTOR_MAP = Maps.newConcurrentMap();
private static final String KEY = "def";
private SyncProcessSingleExecutor() {
}
/**
* 执行器
*
* @param r 任务
*/
public static synchronized void execute(Runnable r) {
execute(KEY, r);
}
/**
* 执行器
*
* @param key 唯一Key
* @param r 任务
*/
public static synchronized void execute(String key, Runnable r) {
if (null == r) {
return;
}
ExecutorService executorService = EXECUTOR_MAP.get(key);
if (null == executorService) {
executorService = ThreadUtil.newSingleExecutor();
EXECUTOR_MAP.put(key, executorService);
}
executorService.execute(new TaskWrapper(r));
}
/**
* Task 包装类<br>
* 此类型的意义是记录可能会被 Executor 吃掉的异常<br>
*/
private static class TaskWrapper implements Runnable {
private final Runnable gift;
public TaskWrapper(final Runnable target) {
this.gift = target;
}
@Override
public void run() {
// 捕获异常,避免在 Executor 里面被吞掉了
if (gift != null) {
try {
gift.run();
} catch (Exception e) {
String errMsg = StrUtil.format("线程池-包装的目标执行异常: {}", e.getMessage());
log.error(errMsg, e);
}
}
}
}
}
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池工厂
*
* @date 2021/11/2 10:48
*/
public final class ThreadPoolFactory {
/**
* 默认最大并发数<br>
*/
private static final int DEFAULT_MAX_CONCURRENT = Runtime.getRuntime().availableProcessors() * 2;
/**
* 默认线程存活时间
*/
private static final long DEFAULT_KEEP_ALIVE = 60L;
/**
* 默认队列大小
*/
private static final int DEFAULT_SIZE = 1024;
/**
* 线程池名称格式
*/
private static final String DEFAULT_THREAD_POOL_NAME = "ProcessPool-{}-%d";
private ThreadPoolFactory() {
}
/**
* 创建默认的线程池
*
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool() {
return createInitThreadPool(DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_CONCURRENT * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, DEFAULT_THREAD_POOL_NAME, new ThreadPoolExecutor.CallerRunsPolicy());
}
/**
* 创建默认的线程池
*
* @param poolName 线程池名称
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool(String poolName) {
return createInitThreadPool(DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_CONCURRENT * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, poolName, new ThreadPoolExecutor.CallerRunsPolicy());
}
/**
* 创建默认的线程池
*
* @param maxConcurrent 最大线程数
* @param poolName 线程池名称
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool(int maxConcurrent, String poolName) {
return createInitThreadPool(maxConcurrent, maxConcurrent * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, poolName, new ThreadPoolExecutor.CallerRunsPolicy());
}
/**
* 创建线程池
*
* @param coreConcurrent 核心线程数
* @param maxConcurrent 最大线程数
* @param keepAlive 线程存活时效
* @param timeUnit 线程存活单位
* @param queueSize 队列大小
* @param poolName 线程池名称
* @param handler 拒绝处理策略
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createInitThreadPool(final int coreConcurrent,
final int maxConcurrent,
final long keepAlive,
final TimeUnit timeUnit,
final int queueSize,
final String poolName,
final RejectedExecutionHandler handler
) {
return new ThreadPoolExecutor(coreConcurrent, maxConcurrent, keepAlive, timeUnit,
new LinkedBlockingDeque<>(queueSize),
new ThreadFactoryBuilder().setNameFormat(poolName).build(),
handler
);
}
}
结果
LogsDto(id=null, type=1, title=com.traffic.admin.api.controller.screen.ScreenController.getSocietyPerson, remoteAddr=, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36, timeout=43926, requestUri=/screen/getSocietyPerson, method=GET, params=[], exception=null, createBy=1, createTime=null, updateBy=1, updateTime=null)
数据库脚本
CREATE TABLE `sys_logs` (
`id` bigint(19) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
`type` char(1) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT '1' COMMENT '日志类型',
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '日志标题',
`remote_addr` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '操作IP地址',
`user_agent` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT '用户代理',
`request_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '请求URI',
`method` varchar(5) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '操作方式',
`timeout` bigint(20) DEFAULT NULL COMMENT '执行时间',
`params` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT '操作提交的数据',
`exception` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT '异常信息',
`create_by` bigint(19) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(19) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_by_name` varchar(80) DEFAULT NULL COMMENT '修改人',
`create_by_name` varchar(80) DEFAULT NULL COMMENT '创建人',
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳',
PRIMARY KEY (`id`) USING BTREE,
KEY `sys_log_create_by` (`create_by`) USING BTREE,
KEY `sys_log_request_uri` (`request_uri`) USING BTREE,
KEY `sys_log_type` (`type`) USING BTREE,
KEY `sys_log_create_date` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=147392 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='日志表';