Initial commit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>actionLog</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:行为日志</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>shiro</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.linln.component.actionLog.action;
|
||||
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.component.actionLog.action.model.BusinessMethod;
|
||||
import com.linln.modules.system.domain.Role;
|
||||
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* 角色日志行为
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
public class RoleAction extends BaseActionMap {
|
||||
|
||||
public static final String ROLE_SAVE = "role_save";
|
||||
public static final String ROLE_AUTH = "role_auth";
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 保存日志行为
|
||||
putMethod(ROLE_SAVE, new BusinessMethod("日志管理","roleSave"));
|
||||
// 角色授权行为
|
||||
putMethod(ROLE_AUTH, new BusinessMethod("角色授权","roleAuth"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户行为方法
|
||||
*/
|
||||
public void roleSave(ResetLog resetLog){
|
||||
resetLog.getActionLog().setMessage("日志成功:${title}");
|
||||
SaveAction.defaultMethod(resetLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色授权行为方法
|
||||
*/
|
||||
public void roleAuth(ResetLog resetLog){
|
||||
Role role = (Role) resetLog.getParam("role");
|
||||
Table table = Role.class.getAnnotation(Table.class);
|
||||
resetLog.getActionLog().setModel(table.name());
|
||||
resetLog.getActionLog().setRecordId(role.getId());
|
||||
if (resetLog.isSuccess()){
|
||||
resetLog.getActionLog().setMessage("角色授权成功:"+role.getTitle());
|
||||
}else {
|
||||
resetLog.getActionLog().setMessage("角色授权失败:"+role.getTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.linln.component.actionLog.action;
|
||||
|
||||
import com.linln.common.utils.EntityBeanUtil;
|
||||
import com.linln.common.utils.HttpServletUtil;
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* 通用:记录保存数据的行为
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
@Slf4j
|
||||
public class SaveAction extends BaseActionMap {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 记录数据保存日志
|
||||
putMethod("default", "defaultMethod");
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新包装保存的数据行为方法
|
||||
*
|
||||
* @param resetLog ResetLog对象数据
|
||||
*/
|
||||
public static void defaultMethod(ResetLog resetLog) {
|
||||
ActionLog actionLog = resetLog.getActionLog();
|
||||
Object entity = resetLog.getEntityParam();
|
||||
Assert.notNull(entity, "日志记录失败:未发现@EntityParam注解参数,将不做数据日志记录");
|
||||
|
||||
// 获取实体类的@Table表名
|
||||
Table table = entity.getClass().getAnnotation(Table.class);
|
||||
Assert.notNull(table, "该对象不存在" + Table.class.getName() + "注解!请检查!");
|
||||
actionLog.setModel(table.name());
|
||||
|
||||
// 获取实体对象数据ID
|
||||
Object[] idInfo = EntityBeanUtil.getId(entity);
|
||||
Assert.notNull(idInfo, "无法获取该实体对象的主键ID字段!");
|
||||
actionLog.setRecordId(Long.valueOf(String.valueOf(idInfo[1])));
|
||||
|
||||
// 日志消息
|
||||
String idParam = HttpServletUtil.getParameter(String.valueOf(idInfo[0]));
|
||||
String message = "数据成功";
|
||||
if (!actionLog.getMessage().isEmpty()) {
|
||||
message = resetLog.fillRule(entity, actionLog.getMessage());
|
||||
}
|
||||
|
||||
if (idParam == null) {
|
||||
actionLog.setMessage("添加" + message);
|
||||
} else {
|
||||
actionLog.setMessage("更新" + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.linln.component.actionLog.action;
|
||||
|
||||
import com.linln.common.utils.StatusUtil;
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.common.enums.StatusEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用:记录数据状态的行为
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
public class StatusAction extends BaseActionMap {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 记录数据状态改变日志
|
||||
putMethod("default", "defaultMethod");
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新包装保存的数据行为方法
|
||||
*
|
||||
* @param resetLog ResetLog对象数据
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void defaultMethod(ResetLog resetLog) {
|
||||
if(resetLog.isSuccessRecord()){
|
||||
String param = (String) resetLog.getParam("param");
|
||||
StatusEnum statusEnum = StatusUtil.getStatusEnum(param);
|
||||
List<Long> ids = (List<Long>) resetLog.getParam("ids");
|
||||
resetLog.getActionLog().setMessage(statusEnum.getMessage() + "ID:" + ids.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.linln.component.actionLog.action;
|
||||
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.component.actionLog.action.model.SystemMethod;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/19
|
||||
*/
|
||||
public class SystemAction extends BaseActionMap {
|
||||
|
||||
public static final String RUNTIME_EXCEPTION = "runtime_exception";
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 系统异常行为
|
||||
putMethod(RUNTIME_EXCEPTION, new SystemMethod("系统异常","runtimeException"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统异常行为方法
|
||||
*/
|
||||
public void runtimeException(ResetLog resetLog){
|
||||
RuntimeException runtime = (RuntimeException) resetLog.getParam("e");
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(runtime.toString());
|
||||
StackTraceElement[] stackTrace = runtime.getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
message.append("\n\t").append(stack.toString());
|
||||
}
|
||||
ActionLog actionLog = resetLog.getActionLog();
|
||||
actionLog.setOperName("系统");
|
||||
actionLog.setMessage(String.valueOf(message));
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.linln.component.actionLog.action;
|
||||
|
||||
import com.linln.common.vo.ResultVo;
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.component.actionLog.action.model.BusinessMethod;
|
||||
import com.linln.component.actionLog.action.model.LoginMethod;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.ActionLogService;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import javax.persistence.Table;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户日志行为
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
public class UserAction extends BaseActionMap {
|
||||
|
||||
public static final String USER_LOGIN = "user_login";
|
||||
public static final String USER_SAVE = "user_save";
|
||||
public static final String EDIT_PWD = "edit_pwd";
|
||||
public static final String EDIT_ROLE = "edit_role";
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 用户登录行为
|
||||
putMethod(USER_LOGIN, new LoginMethod("用户登录","userLogin"));
|
||||
// 保存用户行为
|
||||
putMethod(USER_SAVE, new BusinessMethod("用户管理","userSave"));
|
||||
// 修改用户密码行为
|
||||
putMethod(EDIT_PWD, new BusinessMethod("用户密码","editPwd"));
|
||||
// 角色分配行为
|
||||
putMethod(EDIT_ROLE, new BusinessMethod("角色分配","editRole"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录行为方法
|
||||
*/
|
||||
public void userLogin(ResetLog resetLog){
|
||||
ActionLog actionLog = resetLog.getActionLog();
|
||||
if (resetLog.isSuccess()){
|
||||
actionLog.setMessage("后台登录成功");
|
||||
}else {
|
||||
String username = (String) resetLog.getParam("username");
|
||||
ResultVo resultVo = (ResultVo) resetLog.getRetValue();
|
||||
actionLog.setOperName(username);
|
||||
actionLog.setMessage(String.format("后台登录失败:[%s]%s", username, resultVo.getMsg()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户行为方法
|
||||
*/
|
||||
public void userSave(ResetLog resetLog){
|
||||
resetLog.getActionLog().setMessage("用户成功:${username}");
|
||||
SaveAction.defaultMethod(resetLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户密码行为方法
|
||||
*/
|
||||
public void editPwd(ResetLog resetLog){
|
||||
@SuppressWarnings("unchecked") List<User> users = (List<User>) resetLog.getParam("users");
|
||||
Table table = User.class.getAnnotation(Table.class);
|
||||
String message = "修改用户密码成功";
|
||||
if(!resetLog.isSuccess()){
|
||||
message = "修改用户密码失败";
|
||||
}
|
||||
ActionLogService actionLogService = SpringContextUtil.getBean(ActionLogService.class);
|
||||
|
||||
String finalMessage = message;
|
||||
users.forEach(user -> {
|
||||
ActionLog actionLog = new ActionLog();
|
||||
BeanUtils.copyProperties(resetLog.getActionLog(), actionLog);
|
||||
actionLog.setModel(table.name());
|
||||
actionLog.setRecordId(user.getId());
|
||||
actionLog.setMessage(finalMessage + user.getUsername());
|
||||
|
||||
// 保存日志
|
||||
actionLogService.save(actionLog);
|
||||
});
|
||||
resetLog.setRecord(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色分配行为方法
|
||||
*/
|
||||
public void editRole(ResetLog resetLog){
|
||||
User user = (User) resetLog.getParam("user");
|
||||
Table table = User.class.getAnnotation(Table.class);
|
||||
resetLog.getActionLog().setModel(table.name());
|
||||
resetLog.getActionLog().setRecordId(user.getId());
|
||||
if (resetLog.isSuccess()){
|
||||
resetLog.getActionLog().setMessage("角色分配成功:"+user.getUsername());
|
||||
}else {
|
||||
resetLog.getActionLog().setMessage("角色分配失败:"+user.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.linln.component.actionLog.action.base;
|
||||
|
||||
import com.linln.component.actionLog.action.model.BusinessMethod;
|
||||
import com.linln.component.actionLog.action.model.BusinessType;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
public abstract class BaseActionMap {
|
||||
|
||||
protected HashMap<String, Object> dictory = new HashMap<>();
|
||||
|
||||
public BaseActionMap(){
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化行为列表
|
||||
*/
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* 获取指定的行为
|
||||
* @param key 行为key
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return this.dictory.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为
|
||||
* @param key 行为key
|
||||
* @param modelType 模型类型对象
|
||||
*/
|
||||
public void put(String key, Object modelType) {
|
||||
this.dictory.put(key, modelType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为-默认类型(业务)
|
||||
* @param key 行为key
|
||||
* @param message 日志消息
|
||||
*/
|
||||
public void put(String key, String message) {
|
||||
this.dictory.put(key, new BusinessType(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为-默认类型(业务)
|
||||
* @param key 行为key
|
||||
* @param name 日志名称
|
||||
* @param message 日志消息
|
||||
*/
|
||||
public void put(String key, String name, String message) {
|
||||
this.dictory.put(key, new BusinessType(name, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为方法名
|
||||
* @param key 行为key
|
||||
* @param modelMethod 模型方法名对象
|
||||
*/
|
||||
public void putMethod(String key, Object modelMethod) {
|
||||
this.dictory.put(key, modelMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为方法名-默认类型(业务)
|
||||
* @param key 行为key
|
||||
* @param method 方法名
|
||||
*/
|
||||
public void putMethod(String key, String method) {
|
||||
this.dictory.put(key, new BusinessMethod(method));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加行为方法名-默认类型(业务)
|
||||
* @param key 行为key
|
||||
* @param name 日志名称
|
||||
* @param method 方法名
|
||||
*/
|
||||
public void putMethod(String key, String name, String method) {
|
||||
this.dictory.put(key, new BusinessMethod(name, method));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.linln.component.actionLog.action.base;
|
||||
|
||||
import com.linln.common.enums.ResultEnum;
|
||||
import com.linln.common.utils.EntityBeanUtil;
|
||||
import com.linln.common.vo.ResultVo;
|
||||
import com.linln.component.actionLog.annotation.EntityParam;
|
||||
import com.linln.component.shiro.ShiroUtil;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
import lombok.Data;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 自定义日志数据
|
||||
* @author gion
|
||||
* @date 2018/10/14
|
||||
*/
|
||||
@Data
|
||||
public class ResetLog {
|
||||
|
||||
/* 封装操作对象 */
|
||||
/** 注解日志的方法返回值 */
|
||||
private Object retValue;
|
||||
/** 获取日志实体对象 */
|
||||
private ActionLog actionLog;
|
||||
/** Aop连接点信息对象 */
|
||||
private JoinPoint joinPoint;
|
||||
/** 是否记录日志(默认记录) */
|
||||
private Boolean record = true;
|
||||
|
||||
/* 辅助方法 */
|
||||
/**
|
||||
* 判断返回值是否为ResultVo对象
|
||||
*/
|
||||
public boolean isResultVo(){
|
||||
return retValue instanceof ResultVo;
|
||||
}
|
||||
/**
|
||||
* 判断ResultVo状态码是否为成功
|
||||
*/
|
||||
public boolean isSuccess(){
|
||||
return retValue instanceof ResultVo &&
|
||||
((ResultVo) retValue).getCode().equals(ResultEnum.SUCCESS.getCode());
|
||||
}
|
||||
/**
|
||||
* 判断ResultVo状态码是否为成功,且设置是否记录日志
|
||||
*/
|
||||
public boolean isSuccessRecord(){
|
||||
return record = retValue instanceof ResultVo &&
|
||||
((ResultVo) retValue).getCode().equals(ResultEnum.SUCCESS.getCode());
|
||||
}
|
||||
/**
|
||||
* 获取切入点方法指定名称的参数值
|
||||
*/
|
||||
public Object getParam(String name){
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if(args.length > 0){
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
String[] parameterNames = methodSignature.getParameterNames();
|
||||
for (int i = 0; i < parameterNames.length; i++) {
|
||||
if(parameterNames[i].equals(name)){
|
||||
return args[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 获取切入点参数注解@EntityParam的对象
|
||||
*/
|
||||
public Object getEntityParam(){
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if(args.length > 0){
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||
for (int j = 0; j < parameterAnnotations[i].length; j++) {
|
||||
if(parameterAnnotations[i][j] instanceof EntityParam){
|
||||
return args[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static final Pattern FILL_PATTERN = Pattern.compile("\\$\\{[a-zA-Z0-9]+\\}");
|
||||
/**
|
||||
* 内容填充规则
|
||||
*/
|
||||
public String fillRule(Object beanObject, String content){
|
||||
Matcher matcher = FILL_PATTERN.matcher(content);
|
||||
while(matcher.find()){
|
||||
String matchWord = matcher.group(0);
|
||||
String property = matchWord.substring(2, matchWord.length()-1);
|
||||
String fill = null;
|
||||
try {
|
||||
fill = String.valueOf(EntityBeanUtil.getField(beanObject, property));
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
} finally {
|
||||
content = content.replace(matchWord, fill);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/* 快捷数据 */
|
||||
/**
|
||||
* 获取用户名
|
||||
*/
|
||||
public String getUsername(){
|
||||
return ShiroUtil.getSubject().getUsername();
|
||||
}
|
||||
/**
|
||||
* 获取用户昵称
|
||||
*/
|
||||
public String getNickname(){
|
||||
return ShiroUtil.getSubject().getNickname();
|
||||
}
|
||||
/**
|
||||
* 获取当前时间
|
||||
*/
|
||||
public String getDatetime(){
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return df.format(new Date());
|
||||
}
|
||||
/**
|
||||
* 获取当前时间(自定义时间格式)
|
||||
*/
|
||||
public String getDatetime(String pattern){
|
||||
SimpleDateFormat df = new SimpleDateFormat(pattern);
|
||||
return df.format(new Date());
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class ActionModel {
|
||||
/** 日志名称 */
|
||||
protected String name;
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessMethod extends ActionModel{
|
||||
/** 日志名称 */
|
||||
protected String name;
|
||||
|
||||
/** 行为方法名 */
|
||||
protected String method;
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.BUSINESS.getCode();
|
||||
|
||||
/**
|
||||
* 只构建行为方法名,日志名称由日志注解name定义
|
||||
* @param method 行为方法名
|
||||
*/
|
||||
public BusinessMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建日志名称和行为方法名
|
||||
* @param name 日志名称
|
||||
* @param method 行为方法名
|
||||
*/
|
||||
public BusinessMethod(String name, String method) {
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessType extends ActionModel{
|
||||
|
||||
/** 日志名称 */
|
||||
protected String name;
|
||||
|
||||
/** 日志消息 */
|
||||
protected String message;
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.BUSINESS.getCode();
|
||||
|
||||
/**
|
||||
* 只构建日志消息,日志名称有日志注解name定义
|
||||
* @param message 日志消息
|
||||
*/
|
||||
public BusinessType(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建日志名称和日志消息
|
||||
* @param name 日志名称
|
||||
* @param message 日志消息
|
||||
*/
|
||||
public BusinessType(String name, String message) {
|
||||
this.name = name;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class LoginMethod extends BusinessMethod{
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.LOGIN.getCode();
|
||||
|
||||
public LoginMethod(String method) {
|
||||
super(method);
|
||||
}
|
||||
|
||||
public LoginMethod(String name, String method) {
|
||||
super(name, method);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class LoginType extends BusinessType{
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.LOGIN.getCode();
|
||||
|
||||
public LoginType(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public LoginType(String name, String message) {
|
||||
super(name, message);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class SystemMethod extends BusinessMethod{
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.SYSTEM.getCode();
|
||||
|
||||
public SystemMethod(String method) {
|
||||
super(method);
|
||||
}
|
||||
|
||||
public SystemMethod(String name, String method) {
|
||||
super(name, method);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.linln.component.actionLog.action.model;
|
||||
|
||||
import com.linln.modules.system.enums.ActionLogEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/15
|
||||
*/
|
||||
@Getter
|
||||
public class SystemType extends BusinessType{
|
||||
|
||||
/** 日志类型 */
|
||||
protected Byte type = ActionLogEnum.SYSTEM.getCode();
|
||||
|
||||
public SystemType(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SystemType(String name, String message) {
|
||||
super(name, message);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.linln.component.actionLog.annotation;
|
||||
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 行为日志注解
|
||||
* @author gion
|
||||
* @date 2018/11/12
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface ActionLog {
|
||||
// 日志名称
|
||||
String name() default "";
|
||||
// 日志消息
|
||||
String message() default "";
|
||||
// 行为key
|
||||
String key() default "";
|
||||
// 行为类
|
||||
Class<? extends BaseActionMap> action() default BaseActionMap.class;
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.linln.component.actionLog.annotation;
|
||||
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.component.actionLog.action.base.BaseActionMap;
|
||||
import com.linln.component.actionLog.action.base.ResetLog;
|
||||
import com.linln.component.actionLog.action.model.ActionModel;
|
||||
import com.linln.component.actionLog.action.model.BusinessMethod;
|
||||
import com.linln.component.actionLog.action.model.BusinessType;
|
||||
import com.linln.component.shiro.ShiroUtil;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
import com.linln.modules.system.service.ActionLogService;
|
||||
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.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 行为日志注解AOP
|
||||
* @author gion
|
||||
* @date 2018/11/12
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ActionLogAop {
|
||||
private final static String DEFAULT_ACTION_NAME = "default";
|
||||
|
||||
@Pointcut("@annotation(com.linln.component.actionLog.annotation.ActionLog)")
|
||||
public void actionLog() {};
|
||||
|
||||
@Around("actionLog()")
|
||||
public Object recordLog(ProceedingJoinPoint point) throws Throwable {
|
||||
// 先执行切入点,获取返回值
|
||||
Object proceed = point.proceed();
|
||||
|
||||
|
||||
/* 读取ActionLog注解消息 */
|
||||
Method targetMethod = ((MethodSignature)(point.getSignature())).getMethod();
|
||||
com.linln.component.actionLog.annotation.ActionLog anno =
|
||||
targetMethod.getAnnotation(com.linln.component.actionLog.annotation.ActionLog.class);
|
||||
// 获取name值
|
||||
String name = anno.name();
|
||||
// 获取message值
|
||||
String message = anno.message();
|
||||
// 获取key值
|
||||
String key = anno.key();
|
||||
// 获取行为模型
|
||||
Class<? extends BaseActionMap> action = anno.action();
|
||||
BaseActionMap instance = action.newInstance();
|
||||
Object actionModel = instance.get(!key.isEmpty() ? key : DEFAULT_ACTION_NAME);
|
||||
Assert.notNull(actionModel, "无法获取日志的行为方法,请检查:"+point.getSignature());
|
||||
|
||||
|
||||
// 封装日志实例对象
|
||||
ActionLog actionLog = new ActionLog();
|
||||
actionLog.setIpaddr(ShiroUtil.getIp());
|
||||
actionLog.setClazz(point.getTarget().getClass().getName());
|
||||
actionLog.setMethod(targetMethod.getName());
|
||||
actionLog.setType(((ActionModel) actionModel).getType());
|
||||
actionLog.setName(!name.isEmpty() ? name : ((ActionModel) actionModel).getName());
|
||||
actionLog.setMessage(message);
|
||||
actionLog.setOperBy(ShiroUtil.getSubject());
|
||||
if(ShiroUtil.getSubject() != null){
|
||||
actionLog.setOperName(ShiroUtil.getSubject().getNickname());
|
||||
}
|
||||
|
||||
//判断是否为普通实例对象
|
||||
if(actionModel instanceof BusinessType){
|
||||
actionLog.setMessage(((BusinessType) actionModel).getMessage());
|
||||
}else {
|
||||
// 重置日志-自定义日志数据
|
||||
ResetLog resetLog = new ResetLog();
|
||||
resetLog.setActionLog(actionLog);
|
||||
resetLog.setRetValue(proceed);
|
||||
resetLog.setJoinPoint(point);
|
||||
try {
|
||||
Method method = action.getDeclaredMethod(((BusinessMethod)actionModel).getMethod(), ResetLog.class);
|
||||
method.invoke(instance, resetLog);
|
||||
if(!resetLog.getRecord()) {
|
||||
return proceed;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
log.error("获取行为对象方法错误!请检查方法名称是否正确!", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 保存日志
|
||||
ActionLogService actionLogService = SpringContextUtil.getBean(ActionLogService.class);
|
||||
actionLogService.save(actionLog);
|
||||
|
||||
return proceed;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.linln.component.actionLog.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 控制器实体参数注解
|
||||
* @author gion
|
||||
* @date 2019/2/25
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
@Documented
|
||||
public @interface EntityParam {
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.actionLog.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.actionLog")
|
||||
public class ActionLogAutoConfig {
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.linln.component.actionLog.exception;
|
||||
|
||||
import com.linln.common.exception.advice.ExceptionAdvice;
|
||||
import com.linln.component.actionLog.action.SystemAction;
|
||||
import com.linln.component.actionLog.annotation.ActionLog;
|
||||
|
||||
/**
|
||||
* 运行时抛出的异常进行日志记录
|
||||
* @author gion
|
||||
* @date 2019/4/6
|
||||
*/
|
||||
public class ActionLogProceedAdvice implements ExceptionAdvice {
|
||||
|
||||
@Override
|
||||
@ActionLog(key = SystemAction.RUNTIME_EXCEPTION, action = SystemAction.class)
|
||||
public void run(RuntimeException e) {}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.linln.component.actionLog.exception;
|
||||
|
||||
import com.linln.common.exception.advice.ResultExceptionAdvice;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 将异常切入程序添加到异常通知器中
|
||||
* @author gion
|
||||
* @date 2019/4/6
|
||||
*/
|
||||
@Configuration
|
||||
public class ActionLogProceedAdviceConfig {
|
||||
|
||||
@Bean
|
||||
public ActionLogProceedAdvice actionLogProceedAdvice(ResultExceptionAdvice advice) {
|
||||
ActionLogProceedAdvice authorization = new ActionLogProceedAdvice();
|
||||
advice.putProceed(authorization);
|
||||
return authorization;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.actionLog.config.ActionLogAutoConfig
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>excel</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:Excel</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- excel工具框架 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,403 @@
|
||||
package com.linln.component.excel;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.linln.component.excel.enums.ExcelType;
|
||||
import com.linln.component.excel.annotation.Excel;
|
||||
import com.linln.common.utils.EhCacheUtil;
|
||||
import com.linln.common.utils.HttpServletUtil;
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.Element;
|
||||
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
|
||||
import org.apache.poi.openxml4j.util.ZipSecureFile;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.*;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/11/8
|
||||
*/
|
||||
public class ExcelUtil {
|
||||
|
||||
private static int dataRow = 2;
|
||||
private static Cache dictCache = EhCacheUtil.getDictCache();
|
||||
|
||||
/**
|
||||
* 获取通用样式
|
||||
*/
|
||||
private static XSSFCellStyle getCellStyle(XSSFWorkbook workbook) {
|
||||
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||
cellStyle.setBorderTop(BorderStyle.THIN);
|
||||
cellStyle.setBorderLeft(BorderStyle.THIN);
|
||||
cellStyle.setBorderRight(BorderStyle.THIN);
|
||||
cellStyle.setBorderBottom(BorderStyle.THIN);
|
||||
cellStyle.setAlignment(HorizontalAlignment.LEFT);
|
||||
XSSFFont font = workbook.createFont();
|
||||
font.setFontName("Microsoft YaHei UI");
|
||||
cellStyle.setFont(font);
|
||||
return cellStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能模板(标题及表头)
|
||||
*/
|
||||
private static XSSFWorkbook getCommon(String sheetTitle, List<Field> fields) {
|
||||
XSSFWorkbook workbook = new XSSFWorkbook();
|
||||
XSSFSheet sheet = workbook.createSheet(sheetTitle);
|
||||
|
||||
// 设置列宽度
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
sheet.setColumnWidth(i, 16 * 256);
|
||||
}
|
||||
|
||||
// 通用样式
|
||||
XSSFCellStyle cellStyle = getCellStyle(workbook);
|
||||
|
||||
// 标题样式
|
||||
XSSFCellStyle titleStyle = workbook.createCellStyle();
|
||||
titleStyle.cloneStyleFrom(cellStyle);
|
||||
titleStyle.setAlignment(HorizontalAlignment.CENTER);
|
||||
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
XSSFFont titleFont = workbook.createFont();
|
||||
titleFont.setFontName(cellStyle.getFont().getFontName());
|
||||
titleFont.setBold(true);
|
||||
titleFont.setFontHeight(14);
|
||||
titleStyle.setFont(titleFont);
|
||||
|
||||
// 表头样式
|
||||
XSSFCellStyle thStyle = workbook.createCellStyle();
|
||||
thStyle.cloneStyleFrom(titleStyle);
|
||||
thStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
thStyle .setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||
XSSFFont thFont = workbook.createFont();
|
||||
thFont.setFontName(cellStyle.getFont().getFontName());
|
||||
thFont.setBold(titleFont.getBold());
|
||||
thFont.setColor(IndexedColors.WHITE.getIndex());
|
||||
thStyle.setFont(thFont);
|
||||
|
||||
// 创建标题样式、表格表头
|
||||
XSSFRow titleRow = sheet.createRow(0);
|
||||
XSSFRow thsRow = sheet.createRow(1);
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
Excel excel = fields.get(i).getAnnotation(Excel.class);
|
||||
XSSFCell title = titleRow.createCell(i);
|
||||
title.setCellStyle(titleStyle);
|
||||
XSSFCell th = thsRow.createCell(i);
|
||||
th.setCellValue(excel.value());
|
||||
th.setCellStyle(thStyle);
|
||||
}
|
||||
|
||||
// 绘制标题
|
||||
titleRow.setHeight((short) (26 * 20));
|
||||
XSSFCell titleCell = titleRow.createCell(0);
|
||||
titleCell.setCellValue(sheetTitle);
|
||||
titleCell.setCellStyle(titleStyle);
|
||||
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, fields.size() - 1));
|
||||
return workbook;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体类带有@Excel的属性
|
||||
*/
|
||||
private static List<Field> getExcelList(Class<?> entity, ExcelType type) {
|
||||
List<Field> list = new ArrayList<>();
|
||||
Field[] fields = entity.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.isAnnotationPresent(Excel.class)) {
|
||||
ExcelType fieldType = field.getAnnotation(Excel.class).type();
|
||||
if(fieldType.equals(ExcelType.ALL) || fieldType.equals(type)){
|
||||
list.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体类带有@Excel字段名
|
||||
*/
|
||||
private static List<String> getFieldName(List<Field> fields) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Field field : fields) {
|
||||
list.add(field.getName());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载操作
|
||||
*/
|
||||
private static void download(XSSFWorkbook workbook, String fileName) {
|
||||
try {
|
||||
fileName = URLEncoder.encode(fileName + ".xlsx", "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
HttpServletResponse response = HttpServletUtil.getResponse();
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.setContentType("multipart/form-data");
|
||||
response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
|
||||
OutputStream ros = null;
|
||||
try {
|
||||
ros = response.getOutputStream();
|
||||
workbook.write(ros);
|
||||
ros.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if(ros != null){
|
||||
try {
|
||||
ros.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if(workbook != null){
|
||||
try {
|
||||
workbook.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Excel模板
|
||||
* @param entity 实体类Class
|
||||
*/
|
||||
public static void genTemplate(Class<?> entity) {
|
||||
Excel excel = entity.getAnnotation(Excel.class);
|
||||
if (excel != null) {
|
||||
genTemplate(entity, excel.value());
|
||||
} else {
|
||||
genTemplate(entity, entity.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Excel模板
|
||||
* @param entity 实体类Class
|
||||
* @param sheetTitle 工作组标题(文件名称)
|
||||
*/
|
||||
public static void genTemplate(Class<?> entity, String sheetTitle) {
|
||||
XSSFWorkbook workbook = getCommon(sheetTitle, getExcelList(entity, ExcelType.IMPORT));
|
||||
download(workbook, sheetTitle + "模板");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出Excel数据
|
||||
* @param entity 实体类Class
|
||||
* @param list 导出的数据列表
|
||||
*/
|
||||
public static <T> void exportExcel(Class<?> entity, List<T> list) {
|
||||
Excel excel = entity.getAnnotation(Excel.class);
|
||||
if (excel != null) {
|
||||
exportExcel(entity, list, excel.value());
|
||||
} else {
|
||||
exportExcel(entity, list, entity.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出Excel数据
|
||||
* @param entity 实体类Class
|
||||
* @param list 导出的数据列表
|
||||
* @param sheetTitle 工作组标题(文件名称)
|
||||
*/
|
||||
public static <T> void exportExcel(Class<?> entity, List<T> list, String sheetTitle) {
|
||||
List<Field> fields = getExcelList(entity, ExcelType.EXPORT);
|
||||
List<String> fns = getFieldName(fields);
|
||||
|
||||
XSSFWorkbook workbook = getCommon(sheetTitle, fields);
|
||||
XSSFSheet sheet = workbook.getSheet(sheetTitle);
|
||||
XSSFCellStyle cellStyle = getCellStyle(workbook);
|
||||
|
||||
// 时间样式
|
||||
XSSFCellStyle dateStyle = workbook.createCellStyle();
|
||||
dateStyle.cloneStyleFrom(cellStyle);
|
||||
XSSFDataFormat format = workbook.createDataFormat();
|
||||
dateStyle.setDataFormat(format.getFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
XSSFRow row = sheet.createRow(i + dataRow);
|
||||
T item = list.get(i);
|
||||
|
||||
// 通过反射机制获取实体对象的状态
|
||||
try {
|
||||
final BeanInfo bi = Introspector.getBeanInfo(item.getClass());
|
||||
for (final PropertyDescriptor pd : bi.getPropertyDescriptors()) {
|
||||
if (fns.contains(pd.getName())) {
|
||||
Object value = pd.getReadMethod().invoke(item, (Object[]) null);
|
||||
int index = fns.indexOf(pd.getName());
|
||||
XSSFCell cell = row.createCell(index);
|
||||
if (value != null) {
|
||||
Excel excel = fields.get(index).getAnnotation(Excel.class);
|
||||
// 字典值转换
|
||||
String dict = excel.dict();
|
||||
if (!dict.isEmpty()){
|
||||
Element dictEle = dictCache.get(dict);
|
||||
if(dictEle != null){
|
||||
@SuppressWarnings("unchecked") Map<String, String> dictValue = (Map<String, String>) dictEle.getObjectValue();
|
||||
value = dictValue.get(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取关联对象指定的值
|
||||
String joinField = excel.joinField();
|
||||
if (!joinField.isEmpty()){
|
||||
PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(value.getClass(), joinField);
|
||||
value = sourcePd.getReadMethod().invoke(value, (Object[]) null);
|
||||
}
|
||||
|
||||
// 给单元格赋值
|
||||
if (value instanceof Number) {
|
||||
cell.setCellValue((Double.valueOf(String.valueOf(value))));
|
||||
} else if (value instanceof Date) {
|
||||
cell.setCellValue((Date) value);
|
||||
cell.setCellStyle(dateStyle);
|
||||
continue;
|
||||
} else {
|
||||
cell.setCellValue(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
cell.setCellStyle(cellStyle);
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException | IntrospectionException | IllegalAccessException e) {
|
||||
String message = "导入失败:字段名称匹配失败!";
|
||||
throw new IllegalArgumentException(message, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
|
||||
download(workbook, dateFormat.format(new Date()) + "_" + IdUtil.getSnowflakeNextId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取Excel文件数据
|
||||
* @param entity 实体类
|
||||
* @param inputStream Excel文件输入流
|
||||
* @return 返回数据集合
|
||||
*/
|
||||
public static <T> List<T> importExcel(Class<T> entity, InputStream inputStream){
|
||||
List<T> list = new ArrayList<>();
|
||||
List<String> fns = getFieldName(getExcelList(entity, ExcelType.IMPORT));
|
||||
|
||||
// 读取Excel文件
|
||||
XSSFWorkbook workbook = null;
|
||||
try {
|
||||
ZipSecureFile.setMinInflateRatio(-1.0d);
|
||||
workbook = new XSSFWorkbook(inputStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Assert.notNull(workbook, "该取Excel文件失败,请检查文件!");
|
||||
XSSFSheet sheet = workbook.getSheetAt(0);
|
||||
Assert.notNull(sheet, "该Excel文件没有工作区,无法读取数据!");
|
||||
int count = 0;
|
||||
for (Row row : sheet) {
|
||||
|
||||
// 通过非数据行
|
||||
if(count < dataRow ) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 读取当前行数据
|
||||
int end = row.getLastCellNum();
|
||||
String[] rowData = new String[end];
|
||||
for (int i = 0; i < end; i++) {
|
||||
Cell cell = row.getCell(i);
|
||||
if(cell != null){
|
||||
if(cell.getCellType() == CellType.NUMERIC && HSSFDateUtil.isCellDateFormatted(cell)){
|
||||
Date date = cell.getDateCellValue();
|
||||
rowData[i] = String.valueOf(date.getTime());
|
||||
}else {
|
||||
// 强制其他类型为STRING字符串类型
|
||||
cell.setCellType(CellType.STRING);
|
||||
rowData[i] = cell.getStringCellValue();
|
||||
}
|
||||
}else {
|
||||
rowData[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 将数据添加到数据列表中
|
||||
try {
|
||||
T newInstance = entity.newInstance();
|
||||
final BeanInfo bi = Introspector.getBeanInfo(entity);
|
||||
for (final PropertyDescriptor pd : bi.getPropertyDescriptors()) {
|
||||
if (fns.contains(pd.getName())) {
|
||||
Method writeMethod = pd.getWriteMethod();
|
||||
if(writeMethod != null){
|
||||
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
|
||||
writeMethod.setAccessible(true);
|
||||
}
|
||||
String value = rowData[fns.indexOf(pd.getName())];
|
||||
if(!StringUtils.isEmpty(value)){
|
||||
Class<?> propertyType = pd.getPropertyType();
|
||||
if (String.class == propertyType){
|
||||
writeMethod.invoke(newInstance, value);
|
||||
}else if(Integer.class == propertyType){
|
||||
writeMethod.invoke(newInstance, Integer.valueOf(value));
|
||||
}else if(Long.class == propertyType){
|
||||
writeMethod.invoke(newInstance, Double.valueOf(value).longValue());
|
||||
}else if(Float.class == propertyType){
|
||||
writeMethod.invoke(newInstance, Float.valueOf(value));
|
||||
}else if(Short.class == propertyType){
|
||||
writeMethod.invoke(newInstance, Short.valueOf(value));
|
||||
}else if(Double.class == propertyType){
|
||||
writeMethod.invoke(newInstance, Double.valueOf(value));
|
||||
}else if(Character.class == propertyType){
|
||||
if ((value != null) && (value.length() > 0)){
|
||||
writeMethod.invoke(newInstance, Character.valueOf(value.charAt(0)));
|
||||
}
|
||||
}else if(Date.class == propertyType){
|
||||
writeMethod.invoke(newInstance, new Date(Long.parseLong(value)));
|
||||
}else if(BigDecimal.class == propertyType){
|
||||
writeMethod.invoke(newInstance, new BigDecimal(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.add(newInstance);
|
||||
} catch (InvocationTargetException | IntrospectionException | IllegalAccessException e) {
|
||||
String message = "导入失败:字段名称匹配失败!";
|
||||
throw new IllegalArgumentException(message, e);
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.linln.component.excel.annotation;
|
||||
|
||||
import com.linln.component.excel.enums.ExcelType;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/12/14
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||
public @interface Excel {
|
||||
// 字段标题名称或文件名称
|
||||
public String value();
|
||||
// excel操作类型ExcelType
|
||||
public ExcelType type() default ExcelType.ALL;
|
||||
// 字段字典标识,用于导入导出时进行字典转换(只支持导出操作)
|
||||
public String dict() default "";
|
||||
// 关联操作实体对象字段名称,用于获取关联数据(只支持导出操作)
|
||||
public String joinField() default "";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.excel.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.excel")
|
||||
public class ExcelAutoConfig {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.excel.enums;
|
||||
|
||||
/**
|
||||
* Excel操作类型
|
||||
* @author gion
|
||||
* @date 2018/12/18
|
||||
*/
|
||||
public enum ExcelType {
|
||||
// ALL表示全部操作,IMPORT只导入操作,EXPORT只导出操作
|
||||
ALL, IMPORT, EXPORT;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.excel.config.ExcelAutoConfig
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>fileUpload</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:文件上传</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.linln.component.fileUpload;
|
||||
|
||||
import com.linln.common.exception.ResultException;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.common.utils.ToolUtil;
|
||||
import com.linln.component.fileUpload.config.properties.UploadProjectProperties;
|
||||
import com.linln.component.fileUpload.enums.UploadResultEnum;
|
||||
import com.linln.modules.system.domain.Upload;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 文件上传处理工具
|
||||
* @author gion
|
||||
* @date 2018/11/4
|
||||
*/
|
||||
public class FileUpload {
|
||||
|
||||
/**
|
||||
* 创建一个Upload实体对象
|
||||
* @param multipartFile MultipartFile对象
|
||||
* @param modulePath 文件模块路径
|
||||
*/
|
||||
public static Upload getFile(MultipartFile multipartFile, String modulePath){
|
||||
if (multipartFile.getSize() == 0){
|
||||
throw new ResultException(UploadResultEnum.NO_FILE_NULL);
|
||||
}
|
||||
Upload upload = new Upload();
|
||||
upload.setMime(multipartFile.getContentType());
|
||||
upload.setSize(multipartFile.getSize());
|
||||
upload.setName(FileUpload.genFileName(multipartFile.getOriginalFilename()));
|
||||
upload.setPath(getPathPattern() + modulePath + FileUpload.genDateMkdir("yyyyMMdd") + upload.getName());
|
||||
return upload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为支持的格式
|
||||
* @param multipartFile MultipartFile对象
|
||||
* @param types 支持的文件类型数组
|
||||
*/
|
||||
public static boolean isContentType(MultipartFile multipartFile, String[] types){
|
||||
List<String> typeList = Arrays.asList(types);
|
||||
return typeList.contains(multipartFile.getContentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件上传保存路径
|
||||
*/
|
||||
public static String getUploadPath(){
|
||||
UploadProjectProperties properties = SpringContextUtil.getBean(UploadProjectProperties.class);
|
||||
return properties.getFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件上传目录的静态资源路径
|
||||
*/
|
||||
public static String getPathPattern(){
|
||||
UploadProjectProperties properties = SpringContextUtil.getBean(UploadProjectProperties.class);
|
||||
return properties.getStaticPath().replace("/**", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机且唯一的文件名
|
||||
*/
|
||||
public static String genFileName(String originalFilename){
|
||||
String fileSuffix = ToolUtil.getFileSuffix(originalFilename);
|
||||
return UUID.randomUUID().toString().replace("-", "") + fileSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定格式的目录名称(日期格式)
|
||||
*/
|
||||
public static String genDateMkdir(String format){
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
return "/" + sdf.format(new Date()) + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目标文件对象
|
||||
* @param upload 上传实体类
|
||||
*/
|
||||
public static File getDestFile(Upload upload) throws IOException {
|
||||
|
||||
// 创建保存文件对象
|
||||
String path = upload.getPath().replace(getPathPattern(), "");
|
||||
String filePath = getUploadPath() + path;
|
||||
File dest = new File(filePath.replace("//", "/"));
|
||||
if(!dest.exists()){
|
||||
dest.getParentFile().mkdirs();
|
||||
dest.createNewFile();
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件及获取文件MD5值和SHA1值
|
||||
* @param multipartFile MultipartFile对象
|
||||
* @param upload Upload
|
||||
*/
|
||||
public static void transferTo(MultipartFile multipartFile, Upload upload) throws IOException, NoSuchAlgorithmException {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
|
||||
try (OutputStream fos = Files.newOutputStream(getDestFile(upload).toPath()); InputStream fis = multipartFile.getInputStream()) {
|
||||
int len = 0;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
md5.update(buffer, 0, len);
|
||||
sha1.update(buffer, 0, len);
|
||||
}
|
||||
fos.flush();
|
||||
}
|
||||
BigInteger md5Bi = new BigInteger(1, md5.digest());
|
||||
BigInteger sha1Bi = new BigInteger(1, sha1.digest());
|
||||
upload.setMd5(md5Bi.toString(16));
|
||||
upload.setSha1(sha1Bi.toString(16));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的SHA1值
|
||||
*/
|
||||
public static String getFileSha1(MultipartFile multipartFile) {
|
||||
if (multipartFile.getSize() == 0){
|
||||
throw new ResultException(UploadResultEnum.NO_FILE_NULL);
|
||||
}
|
||||
byte[] buffer = new byte[4096];
|
||||
try (InputStream fis = multipartFile.getInputStream()) {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
|
||||
int len = 0;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
sha1.update(buffer, 0, len);
|
||||
}
|
||||
BigInteger sha1Bi = new BigInteger(1, sha1.digest());
|
||||
return sha1Bi.toString(16);
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.fileUpload.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.fileUpload")
|
||||
public class FileUploadAutoConfig {
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.linln.component.fileUpload.config;
|
||||
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.component.fileUpload.FileUpload;
|
||||
import com.linln.component.fileUpload.config.properties.UploadProjectProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/11/3
|
||||
*/
|
||||
@Configuration
|
||||
public class UploadFileConfigurer implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
UploadProjectProperties properties = SpringContextUtil.getBean(UploadProjectProperties.class);
|
||||
registry.addResourceHandler(properties.getStaticPath()).addResourceLocations("file:" + FileUpload.getUploadPath());
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.linln.component.fileUpload.config.properties;
|
||||
|
||||
import com.linln.common.utils.ToolUtil;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 项目-文件上传配置项
|
||||
* @author gion
|
||||
* @date 2018/11/6
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "project.upload")
|
||||
public class UploadProjectProperties {
|
||||
|
||||
/** 上传文件路径 */
|
||||
private String filePath;
|
||||
|
||||
/** 上传文件静态访问路径 */
|
||||
private String staticPath = "/upload/**";
|
||||
|
||||
/** 获取文件路径 */
|
||||
public String getFilePath() {
|
||||
if (filePath == null) {
|
||||
return ToolUtil.getProjectPath() + "/upload/";
|
||||
}
|
||||
if (!filePath.endsWith("/")) {
|
||||
return filePath + "/";
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.linln.component.fileUpload.enums;
|
||||
|
||||
import com.linln.common.exception.interfaces.ResultInterface;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 后台返回结果集枚举
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Getter
|
||||
public enum UploadResultEnum implements ResultInterface {
|
||||
|
||||
/**
|
||||
* 文件操作
|
||||
*/
|
||||
NO_FILE_NULL(401, "文件不能为空"),
|
||||
NO_FILE_TYPE(402, "不支持该文件类型"),
|
||||
|
||||
;
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String message;
|
||||
|
||||
UploadResultEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.fileUpload.config.FileUploadAutoConfig
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>jwt</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:jwt</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- jwt处理框架-->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.linln.component.jwt.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 忽略jwt权限验证注解(只在拦截的地址内有效)
|
||||
* @author gion
|
||||
* @date 2019/4/14
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface IgnorePermissions {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.linln.component.jwt.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* jwt权限注解(需要权限)
|
||||
* @author gion
|
||||
* @date 2019/4/13
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface JwtPermissions {
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.linln.component.jwt.annotation;
|
||||
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.exceptions.TokenExpiredException;
|
||||
import com.linln.common.exception.ResultException;
|
||||
import com.linln.component.jwt.config.properties.JwtProjectProperties;
|
||||
import com.linln.component.jwt.enums.JwtResultEnums;
|
||||
import com.linln.component.jwt.utlis.JwtUtil;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Jwt权限注解AOP
|
||||
* @author gion
|
||||
* @date 2019/4/13
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "project.jwt.pattern-anno", havingValue = "true", matchIfMissing = true)
|
||||
public class JwtPermissionsAop {
|
||||
|
||||
@Autowired
|
||||
private JwtProjectProperties jwtProperties;
|
||||
|
||||
@Autowired
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Pointcut("@annotation(com.linln.component.jwt.annotation.JwtPermissions)")
|
||||
public void jwtPermissions() {};
|
||||
|
||||
@Around("jwtPermissions()")
|
||||
public Object doPermission(ProceedingJoinPoint point) throws Throwable {
|
||||
|
||||
// 获取请求对象头部token数据
|
||||
String token = JwtUtil.getRequestToken(request);
|
||||
|
||||
// 验证token数据是否正确
|
||||
try {
|
||||
JwtUtil.verifyToken(token, jwtProperties.getSecret());
|
||||
} catch (TokenExpiredException e) {
|
||||
throw new ResultException(JwtResultEnums.TOKEN_EXPIRED);
|
||||
} catch (JWTVerificationException e) {
|
||||
throw new ResultException(JwtResultEnums.TOKEN_ERROR);
|
||||
}
|
||||
|
||||
return point.proceed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.jwt.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.jwt")
|
||||
public class JwtAutoConfig {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.linln.component.jwt.config;
|
||||
|
||||
import com.linln.component.jwt.interceptor.AuthenticationInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* jwt权限配置拦截器
|
||||
* @author gion
|
||||
* @date 2019/4/12
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "project.jwt.pattern-path", havingValue = "true")
|
||||
public class JwtInterceptorConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationInterceptor authenticationInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authenticationInterceptor).addPathPatterns("/api/**");
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.linln.component.jwt.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* jwt配置项
|
||||
* @author gion
|
||||
* @date 2019/4/13
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "project.jwt")
|
||||
public class JwtProjectProperties {
|
||||
|
||||
/** jwt秘钥 */
|
||||
private String secret = "mySecret";
|
||||
|
||||
/** 过期时间(天),默认3天 */
|
||||
private Integer expired = 3;
|
||||
|
||||
/** 权限模式-路径拦截 */
|
||||
private boolean patternPath = false;
|
||||
|
||||
/** 权限模式-注解拦截 */
|
||||
private boolean patternAnno = true;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.linln.component.jwt.controller;
|
||||
|
||||
import com.linln.common.enums.StatusEnum;
|
||||
import com.linln.common.exception.ResultException;
|
||||
import com.linln.common.utils.EncryptUtil;
|
||||
import com.linln.common.utils.ResultVoUtil;
|
||||
import com.linln.common.vo.ResultVo;
|
||||
import com.linln.component.jwt.annotation.IgnorePermissions;
|
||||
import com.linln.component.jwt.config.properties.JwtProjectProperties;
|
||||
import com.linln.component.jwt.enums.JwtResultEnums;
|
||||
import com.linln.component.jwt.utlis.JwtUtil;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.UserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 默认登录验证控制器
|
||||
* 说明:默认采用系统用户进行登录验证
|
||||
*
|
||||
* @author gion
|
||||
* @date 2019/4/9
|
||||
*/
|
||||
@RestController
|
||||
@Api(tags = "登录接口")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private JwtProjectProperties properties;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@IgnorePermissions
|
||||
@PostMapping("/api/auth")
|
||||
@ApiOperation(value = "jwt登录")
|
||||
public ResultVo auth(
|
||||
@ApiParam(value = "用户名", required = true) String username,
|
||||
@ApiParam(value = "密码", required = true) String password) {
|
||||
// 根据用户名获取系统用户数据
|
||||
User user = userService.getByName(username);
|
||||
if (user == null) {
|
||||
throw new ResultException(JwtResultEnums.AUTH_REQUEST_ERROR);
|
||||
} else if (user.getStatus().equals(StatusEnum.FREEZED.getCode())) {
|
||||
throw new ResultException(JwtResultEnums.AUTH_REQUEST_LOCKED);
|
||||
} else {
|
||||
// 对明文密码加密处理
|
||||
String encrypt = EncryptUtil.encrypt(password, user.getSalt());
|
||||
// 判断密码是否正确
|
||||
if (encrypt.equals(user.getPassword())) {
|
||||
String token = JwtUtil.getToken(username, properties.getSecret(), properties.getExpired());
|
||||
return ResultVoUtil.success("登录成功", token);
|
||||
} else {
|
||||
throw new ResultException(JwtResultEnums.AUTH_REQUEST_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.linln.component.jwt.enums;
|
||||
|
||||
import com.linln.common.exception.interfaces.ResultInterface;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* jwt结果集枚举
|
||||
* @author gion
|
||||
* @date 2019/4/13
|
||||
*/
|
||||
@Getter
|
||||
public enum JwtResultEnums implements ResultInterface {
|
||||
|
||||
/**
|
||||
* token问题
|
||||
*/
|
||||
TOKEN_ERROR(301, "token无效"),
|
||||
TOKEN_EXPIRED(302, "token已过期"),
|
||||
|
||||
/**
|
||||
* 账号问题
|
||||
*/
|
||||
AUTH_REQUEST_ERROR(401, "用户名或密码错误"),
|
||||
AUTH_REQUEST_LOCKED(402, "该账号已被冻结"),
|
||||
;
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String message;
|
||||
|
||||
JwtResultEnums(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.linln.component.jwt.interceptor;
|
||||
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.exceptions.TokenExpiredException;
|
||||
import com.linln.common.exception.ResultException;
|
||||
import com.linln.component.jwt.annotation.IgnorePermissions;
|
||||
import com.linln.component.jwt.config.properties.JwtProjectProperties;
|
||||
import com.linln.component.jwt.enums.JwtResultEnums;
|
||||
import com.linln.component.jwt.utlis.JwtUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* jwt权限拦截器
|
||||
* @author gion
|
||||
* @date 2019/4/12
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Autowired
|
||||
private JwtProjectProperties jwtProperties;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 如果不是映射到方法直接通过
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断请求映射的方式是否忽略权限验证
|
||||
HandlerMethod handlerMethod=(HandlerMethod) handler;
|
||||
Method method=handlerMethod.getMethod();
|
||||
if (method.isAnnotationPresent(IgnorePermissions.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取请求对象头部token数据
|
||||
String token = JwtUtil.getRequestToken(request);
|
||||
|
||||
// 验证token数据是否正确
|
||||
try {
|
||||
JwtUtil.verifyToken(token, jwtProperties.getSecret());
|
||||
} catch (TokenExpiredException e) {
|
||||
throw new ResultException(JwtResultEnums.TOKEN_EXPIRED);
|
||||
} catch (JWTVerificationException e) {
|
||||
throw new ResultException(JwtResultEnums.TOKEN_ERROR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.linln.component.jwt.utlis;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.exceptions.TokenExpiredException;
|
||||
import com.linln.common.exception.ResultException;
|
||||
import com.linln.common.utils.HttpServletUtil;
|
||||
import com.linln.common.utils.ToolUtil;
|
||||
import com.linln.component.jwt.enums.JwtResultEnums;
|
||||
import com.linln.modules.system.domain.User;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2019/4/9
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
/**
|
||||
* 生成JwtToken
|
||||
* @param username 用户名
|
||||
* @param secret 秘钥
|
||||
* @param amount 过期天数
|
||||
*/
|
||||
public static String getToken(String username, String secret, int amount){
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
return getToken(user, secret, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JwtToken
|
||||
* @param user 用户对象
|
||||
* @param secret 秘钥
|
||||
* @param amount 过期天数
|
||||
*/
|
||||
public static String getToken(User user, String secret, int amount){
|
||||
// 过期时间
|
||||
Calendar ca = Calendar.getInstance();
|
||||
ca.add(Calendar.DATE, amount);
|
||||
|
||||
// 随机Claim
|
||||
String random = ToolUtil.getRandomString(6);
|
||||
|
||||
// 创建JwtToken对象
|
||||
String token="";
|
||||
token= JWT.create()
|
||||
// 用户名
|
||||
.withSubject(user.getUsername())
|
||||
// 发布时间
|
||||
.withIssuedAt(new Date())
|
||||
// 过期时间
|
||||
.withExpiresAt(ca.getTime())
|
||||
// 自定义随机Claim
|
||||
.withClaim("ran", random)
|
||||
.sign(getSecret(secret, random));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求对象中的token数据
|
||||
*/
|
||||
public static String getRequestToken(HttpServletRequest request){
|
||||
// 获取JwtTokens失败
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (authorization == null || !authorization.startsWith("Bearer ")) {
|
||||
throw new ResultException(JwtResultEnums.TOKEN_ERROR);
|
||||
}
|
||||
return authorization.substring(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前token中的用户名
|
||||
*/
|
||||
public static String getSubject(){
|
||||
HttpServletRequest request = HttpServletUtil.getRequest();
|
||||
String token = getRequestToken(request);
|
||||
return JWT.decode(token).getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JwtToken
|
||||
* @param token JwtToken数据
|
||||
* @return true 验证通过
|
||||
* @exception TokenExpiredException Token过期
|
||||
* @exception JWTVerificationException 令牌无效(验证不通过)
|
||||
*/
|
||||
public static void verifyToken(String token, String secret) throws JWTVerificationException {
|
||||
String ran = JWT.decode(token).getClaim("ran").asString();
|
||||
JWTVerifier jwtVerifier = JWT.require(getSecret(secret, ran)).build();
|
||||
jwtVerifier.verify(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Secret混淆数据
|
||||
*/
|
||||
private static Algorithm getSecret(String secret, String random){
|
||||
String salt = "君不见黄河之水天上来,奔流到海不复回。君不见高堂明镜悲白发,朝如青丝暮成雪。";
|
||||
//String salt = "元嘉草草,封狼居胥,赢得仓皇北顾。四十三年,望中犹记,烽火扬州路。可堪回首,佛狸祠下,一片神鸦社鼓。凭谁问、廉颇老矣,尚能饭否?";
|
||||
//String salt = "安能摧眉折腰事权贵,使我不得开心颜。";
|
||||
//String salt = "大江东去,浪淘尽,千古风流人物。故垒西边,人道是,三国周郎赤壁。乱石穿空,惊涛拍岸,卷起千堆雪。江山如画,一时多少豪杰。";
|
||||
return Algorithm.HMAC256(secret + salt + "(ノ ̄▽ ̄)ノ 皮一下" + random);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.jwt.config.JwtAutoConfig
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>component</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>组件模块</name>
|
||||
<modules>
|
||||
<module>excel</module>
|
||||
<module>actionLog</module>
|
||||
<module>shiro</module>
|
||||
<module>thymeleaf</module>
|
||||
<module>fileUpload</module>
|
||||
<module>jwt</module>
|
||||
</modules>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>pet-admin</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>shiro</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:Shiro权限</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--shiro权限管理框架-->
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-ehcache</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import com.linln.common.constant.AdminConst;
|
||||
import com.linln.common.enums.StatusEnum;
|
||||
import com.linln.modules.system.domain.Role;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.UserService;
|
||||
import org.apache.shiro.authc.*;
|
||||
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.codec.CodecSupport;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.util.ByteSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class AuthRealm extends AuthorizingRealm {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 授权逻辑
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
// 获取用户Principal对象
|
||||
User user = (User) principal.getPrimaryPrincipal();
|
||||
|
||||
// 管理员拥有所有权限
|
||||
if (user.getId().equals(AdminConst.ADMIN_ID)) {
|
||||
info.addRole(AdminConst.ADMIN_ROLE_NAME);
|
||||
info.addStringPermission("*:*:*");
|
||||
return info;
|
||||
}
|
||||
|
||||
// 赋予角色和资源授权
|
||||
Set<Role> roles = ShiroUtil.getSubjectRoles();
|
||||
roles.forEach(role -> {
|
||||
info.addRole(role.getName());
|
||||
role.getMenus().forEach(menu -> {
|
||||
String perms = menu.getPerms();
|
||||
if (menu.getStatus().equals(StatusEnum.OK.getCode())
|
||||
&& !StringUtils.isEmpty(perms) && !perms.contains("*")) {
|
||||
info.addStringPermission(perms);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证逻辑
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
|
||||
// 获取数据库中的用户名密码
|
||||
User user = userService.getByName(token.getUsername());
|
||||
|
||||
// 判断用户名是否存在
|
||||
if (user == null) {
|
||||
throw new UnknownAccountException();
|
||||
} else if (user.getStatus().equals(StatusEnum.FREEZED.getCode())) {
|
||||
throw new LockedAccountException();
|
||||
}
|
||||
|
||||
// 对盐进行加密处理
|
||||
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
|
||||
|
||||
/* 传入密码自动判断是否正确
|
||||
* 参数1:传入对象给Principal
|
||||
* 参数2:正确的用户密码
|
||||
* 参数3:加盐处理
|
||||
* 参数4:固定写法
|
||||
*/
|
||||
return new SimpleAuthenticationInfo(user, user.getPassword(), salt, getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义密码验证匹配器
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initCredentialsMatcher() {
|
||||
setCredentialsMatcher(new SimpleCredentialsMatcher() {
|
||||
@Override
|
||||
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
|
||||
SimpleAuthenticationInfo info = (SimpleAuthenticationInfo) authenticationInfo;
|
||||
// 获取明文密码及密码盐
|
||||
String password = String.valueOf(token.getPassword());
|
||||
String salt = CodecSupport.toString(info.getCredentialsSalt().getBytes());
|
||||
|
||||
return equals(ShiroUtil.encrypt(password, salt), info.getCredentials());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import com.linln.common.utils.EncryptUtil;
|
||||
import com.linln.common.utils.HttpServletUtil;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.modules.system.domain.Role;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.RoleService;
|
||||
import com.linln.modules.system.service.UserService;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.LazyInitializationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shiro工具类
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class ShiroUtil {
|
||||
|
||||
/**
|
||||
* 多个IP的分隔符
|
||||
*/
|
||||
private static final char IP_SPLIT = ',';
|
||||
|
||||
/**
|
||||
* IP验证正则对象
|
||||
*/
|
||||
private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
|
||||
|
||||
/**
|
||||
* 加密算法
|
||||
*/
|
||||
public final static String HASH_ALGORITHM_NAME = EncryptUtil.HASH_ALGORITHM_NAME;
|
||||
|
||||
/**
|
||||
* 循环次数
|
||||
*/
|
||||
public final static int HASH_ITERATIONS = EncryptUtil.HASH_ITERATIONS;
|
||||
|
||||
/**
|
||||
* 加密处理(64位字符)
|
||||
* 备注:采用自定义的密码加密方式,其原理与SimpleHash一致,
|
||||
* 为的是在多个模块间可以使用同一套加密方式,方便共用系统用户。
|
||||
*
|
||||
* @param password 密码
|
||||
* @param salt 密码盐
|
||||
*/
|
||||
public static String encrypt(String password, String salt) {
|
||||
return EncryptUtil.encrypt(password, salt, HASH_ALGORITHM_NAME, HASH_ITERATIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机盐值
|
||||
*/
|
||||
public static String getRandomSalt() {
|
||||
return EncryptUtil.getRandomSalt();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户对象
|
||||
*/
|
||||
public static User getSubject() {
|
||||
User user = (User) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
// 初始化延迟加载的部门信息
|
||||
if (user != null && !Hibernate.isInitialized(user.getDept())) {
|
||||
try {
|
||||
Hibernate.initialize(user.getDept());
|
||||
} catch (LazyInitializationException e) {
|
||||
// 部门数据延迟加载超时,重新查询用户数据(用于更新“记住我”状态登录的数据)
|
||||
UserService userService = SpringContextUtil.getBean(UserService.class);
|
||||
User reload = userService.getById(user.getId());
|
||||
Hibernate.initialize(reload.getDept());
|
||||
// 将重载用户数据拷贝到登录用户中
|
||||
BeanUtils.copyProperties(reload, user, "roles");
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户角色列表
|
||||
*/
|
||||
public static Set<Role> getSubjectRoles() {
|
||||
User user = (User) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
// 如果用户为空,则返回空列表
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
}
|
||||
|
||||
// 判断角色列表是否已缓存
|
||||
if (!Hibernate.isInitialized(user.getRoles())) {
|
||||
try {
|
||||
Hibernate.initialize(user.getRoles());
|
||||
} catch (LazyInitializationException e) {
|
||||
// 延迟加载超时,重新查询角色列表数据
|
||||
RoleService roleService = SpringContextUtil.getBean(RoleService.class);
|
||||
user.setRoles(roleService.getUserOkRoleList(user.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
return user.getRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户IP地址
|
||||
*/
|
||||
public static String getIp() {
|
||||
HttpServletRequest request = HttpServletUtil.getRequest();
|
||||
// 反向代理时获取真实ip
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
// 校验IP地址,防止恶意伪造请求头信息,暂时只允许ipv4的地址
|
||||
if (StringUtils.hasText(ip)) {
|
||||
ip = ip.substring(ip.lastIndexOf(IP_SPLIT) + 1).trim();
|
||||
if (!IP_PATTERN.matcher(ip).matches()) {
|
||||
ip = "ipv4 unknown";
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.AccessControlFilter;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 处理session超时问题拦截器
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class UserAuthFilter extends AccessControlFilter {
|
||||
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
if (isLoginRequest(request, response)) {
|
||||
return true;
|
||||
} else {
|
||||
Subject subject = getSubject(request, response);
|
||||
return subject.getPrincipal() != null && (subject.isRemembered() || subject.isAuthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
||||
HttpServletRequest httpRequest = WebUtils.toHttp(request);
|
||||
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||
|
||||
if (httpRequest.getHeader("X-Requested-With") != null
|
||||
&& "XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
|
||||
httpResponse.sendError(HttpStatus.UNAUTHORIZED.value());
|
||||
} else {
|
||||
redirectToLogin(request, response);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import com.linln.modules.system.domain.User;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.UnavailableSecurityManagerException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 审核员自动赋值配置
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Configuration
|
||||
public class AuditorConfig implements AuditorAware<User> {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Optional<User> getCurrentAuditor() {
|
||||
try {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
User user = (User) subject.getPrincipal();
|
||||
return Optional.ofNullable(user);
|
||||
} catch (UnavailableSecurityManagerException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.shiro")
|
||||
public class ShiroAutoConfig {
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import com.linln.component.shiro.AuthRealm;
|
||||
import com.linln.component.shiro.UserAuthFilter;
|
||||
import com.linln.component.shiro.config.properties.ShiroProjectProperties;
|
||||
import com.linln.component.shiro.remember.RememberMeManager;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import org.apache.shiro.cache.ehcache.EhCacheManager;
|
||||
import org.apache.shiro.codec.Base64;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.CookieRememberMeManager;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.shiro.web.servlet.SimpleCookie;
|
||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
|
||||
@Bean
|
||||
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, ShiroProjectProperties properties) {
|
||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||
|
||||
/**
|
||||
* 添加自定义拦截器,重写user认证方式,处理session超时问题
|
||||
*/
|
||||
HashMap<String, Filter> myFilters = new HashMap<>(16);
|
||||
myFilters.put("userAuth", new UserAuthFilter());
|
||||
shiroFilterFactoryBean.setFilters(myFilters);
|
||||
|
||||
/**
|
||||
* 过滤规则(注意优先级)
|
||||
* —anon 无需认证(登录)可访问
|
||||
* —authc 必须认证才可访问
|
||||
* —perms[标识] 拥有资源权限才可访问
|
||||
* —role 拥有角色权限才可访问
|
||||
* —user 认证和自动登录可访问
|
||||
*/
|
||||
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
|
||||
filterMap.put("/login", "anon");
|
||||
filterMap.put("/logout", "anon");
|
||||
filterMap.put("/captcha", "anon");
|
||||
filterMap.put("/noAuth", "anon");
|
||||
filterMap.put("/css/**", "anon");
|
||||
filterMap.put("/js/**", "anon");
|
||||
filterMap.put("/images/**", "anon");
|
||||
filterMap.put("/lib/**", "anon");
|
||||
filterMap.put("/favicon.ico", "anon");
|
||||
// 通过yml配置文件方式配置的[anon]忽略规则
|
||||
String[] excludes = properties.getExcludes().split(",");
|
||||
for (String exclude : excludes) {
|
||||
if (!StringUtils.isEmpty(exclude.trim())) {
|
||||
filterMap.put(exclude, "anon");
|
||||
}
|
||||
}
|
||||
// 拦截根目录下所有路径,需要放行的路径必须在之前添加
|
||||
filterMap.put("/**", "userAuth");
|
||||
|
||||
// 设置过滤规则
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
|
||||
// 设置登录页面
|
||||
shiroFilterFactoryBean.setLoginUrl("/login");
|
||||
// 未授权错误页面
|
||||
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
|
||||
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm,
|
||||
EhCacheManager cacheManager,
|
||||
DefaultWebSessionManager sessionManager,
|
||||
CookieRememberMeManager rememberMeManager) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(authRealm);
|
||||
securityManager.setCacheManager(cacheManager);
|
||||
securityManager.setSessionManager(sessionManager);
|
||||
securityManager.setRememberMeManager(rememberMeManager);
|
||||
return securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的Realm
|
||||
*/
|
||||
@Bean
|
||||
public AuthRealm getRealm(EhCacheManager ehCacheManager) {
|
||||
AuthRealm authRealm = new AuthRealm();
|
||||
authRealm.setCacheManager(ehCacheManager);
|
||||
return authRealm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存管理器-使用Ehcache实现缓存
|
||||
*/
|
||||
@Bean
|
||||
public EhCacheManager ehCacheManager(CacheManager cacheManager) {
|
||||
EhCacheManager ehCacheManager = new EhCacheManager();
|
||||
ehCacheManager.setCacheManager(cacheManager);
|
||||
return ehCacheManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* session管理器
|
||||
*/
|
||||
@Bean
|
||||
public DefaultWebSessionManager getDefaultWebSessionManager(EhCacheManager cacheManager, ShiroProjectProperties properties) {
|
||||
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
|
||||
sessionManager.setCacheManager(cacheManager);
|
||||
sessionManager.setGlobalSessionTimeout(properties.getGlobalSessionTimeout() * 1000);
|
||||
sessionManager.setSessionValidationInterval(properties.getSessionValidationInterval() * 1000);
|
||||
sessionManager.setDeleteInvalidSessions(true);
|
||||
sessionManager.validateSessions();
|
||||
// 去掉登录页面地址栏jsessionid
|
||||
sessionManager.setSessionIdUrlRewritingEnabled(false);
|
||||
return sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* rememberMe管理器
|
||||
*/
|
||||
@Bean
|
||||
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
|
||||
RememberMeManager manager = new RememberMeManager();
|
||||
manager.setCipherKey(Base64.decode("WcfHGU25gNnTxTlmJMeSpw=="));
|
||||
manager.setCookie(rememberMeCookie);
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个简单的Cookie对象
|
||||
*/
|
||||
@Bean
|
||||
public SimpleCookie rememberMeCookie(ShiroProjectProperties properties) {
|
||||
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
|
||||
simpleCookie.setHttpOnly(true);
|
||||
// cookie记住登录信息时间,默认7天
|
||||
simpleCookie.setMaxAge(properties.getRememberMeTimeout() * 24 * 60 * 60);
|
||||
return simpleCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
|
||||
*/
|
||||
@Bean
|
||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
|
||||
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
|
||||
new AuthorizationAttributeSourceAdvisor();
|
||||
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
|
||||
return authorizationAttributeSourceAdvisor;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.linln.component.shiro.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 项目-shiro会话配置项
|
||||
* @author gion
|
||||
* @date 2018/11/6
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "project.shiro")
|
||||
public class ShiroProjectProperties {
|
||||
|
||||
/** cookie记住登录信息时间,默认7天 */
|
||||
private Integer rememberMeTimeout = 7;
|
||||
|
||||
/** Session会话超时时间,默认30分钟 */
|
||||
private Integer globalSessionTimeout = 1800;
|
||||
|
||||
/** Session会话检测间隔时间,默认15分钟 */
|
||||
private Integer sessionValidationInterval = 900;
|
||||
|
||||
/** 忽略的路径规则,多个规则使用","逗号隔开 */
|
||||
private String excludes = "";
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.linln.component.shiro.exception;
|
||||
|
||||
import com.linln.common.enums.ResultEnum;
|
||||
import com.linln.common.utils.ResultVoUtil;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.common.vo.ResultVo;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 拦截访问权限异常处理
|
||||
* @author gion
|
||||
* @date 2019/4/26
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@Order(-1)
|
||||
public class AuthorizationExceptionHandler {
|
||||
|
||||
/**
|
||||
* 拦截访问权限异常
|
||||
*/
|
||||
@ExceptionHandler(AuthorizationException.class)
|
||||
@ResponseBody
|
||||
public ResultVo authorizationException(AuthorizationException e, HttpServletRequest request,
|
||||
HttpServletResponse response){
|
||||
Integer code = ResultEnum.NO_PERMISSIONS.getCode();
|
||||
String msg = ResultEnum.NO_PERMISSIONS.getMessage();
|
||||
|
||||
// 获取异常信息
|
||||
Throwable cause = e.getCause();
|
||||
String message = cause.getMessage();
|
||||
Class<ResultVo> resultVoClass = ResultVo.class;
|
||||
|
||||
// 判断无权限访问的方法返回对象是否为ResultVo
|
||||
if(!message.contains(resultVoClass.getName())){
|
||||
try {
|
||||
// 重定向到无权限页面
|
||||
String contextPath = request.getContextPath();
|
||||
ShiroFilterFactoryBean shiroFilter = SpringContextUtil.getBean(ShiroFilterFactoryBean.class);
|
||||
response.sendRedirect(contextPath+shiroFilter.getUnauthorizedUrl());
|
||||
} catch (IOException e1) {
|
||||
return ResultVoUtil.error(code, msg);
|
||||
}
|
||||
}
|
||||
return ResultVoUtil.error(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.linln.component.shiro.remember;
|
||||
|
||||
import com.linln.modules.system.domain.Dept;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LazyInitializationException;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 记住我部门适配器(用于判断延迟加载超时)
|
||||
*
|
||||
* @author gion
|
||||
* @date 2019/10/30
|
||||
*/
|
||||
public class RememberMeDept extends Dept implements HibernateProxy {
|
||||
|
||||
@Override
|
||||
public Object writeReplace() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyInitializer getHibernateLazyInitializer() {
|
||||
|
||||
return new LazyInitializer() {
|
||||
@Override
|
||||
public void initialize() throws HibernateException {
|
||||
throw new LazyInitializationException(RememberMeDept.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable getIdentifier() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifier(Serializable id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getPersistentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUninitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getImplementation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getImplementation(SharedSessionContractImplementor session) throws HibernateException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImplementation(Object target) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnlySettingAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedSessionContractImplementor getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSession(SharedSessionContractImplementor session) throws HibernateException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetSession() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnwrap(boolean unwrap) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnwrap() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.linln.component.shiro.remember;
|
||||
|
||||
import com.linln.common.utils.EntityBeanUtil;
|
||||
import com.linln.component.shiro.AuthRealm;
|
||||
import com.linln.component.shiro.ShiroUtil;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.web.mgt.CookieRememberMeManager;
|
||||
import org.hibernate.collection.internal.PersistentSet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 记住我管理器
|
||||
*
|
||||
* @author gion
|
||||
* @date 2019/10/28
|
||||
*/
|
||||
public class RememberMeManager extends CookieRememberMeManager {
|
||||
|
||||
/**
|
||||
* “记住我”二次加密索引,无需修改
|
||||
*/
|
||||
private final String CIPHER_KEY = "(~ ̄▽ ̄)~";
|
||||
/**
|
||||
* 二次加密密码盐长度
|
||||
*/
|
||||
private final int ENCRYPT_LENGTH = 64;
|
||||
|
||||
/**
|
||||
* 重写“记住我”实体类【系列化】数据
|
||||
*
|
||||
* @param principals the principals to remember for retrieval later.
|
||||
*/
|
||||
@Override
|
||||
protected byte[] serialize(PrincipalCollection principals) {
|
||||
|
||||
// 获取用户信息
|
||||
User user = (User) principals.getPrimaryPrincipal();
|
||||
|
||||
// 克隆一个Principal对象,隐藏用户密码及密码盐,消除部门及角色数据
|
||||
String[] ignores = {"password", "salt", "dept", "roles"};
|
||||
User principal = (User) EntityBeanUtil.cloneBean(user, ignores);
|
||||
|
||||
// 二次加密用户密码
|
||||
String password = ShiroUtil.encrypt(user.getPassword(), user.getSalt());
|
||||
principal.setPassword(password);
|
||||
|
||||
// 定义简单的SimplePrincipal对象
|
||||
SimplePrincipalCollection collection = new SimplePrincipalCollection(principal, AuthRealm.class.getName());
|
||||
return confusion(super.serialize(collection), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写“记住我”实体类【反系列化】数据
|
||||
*
|
||||
* @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected PrincipalCollection deserialize(byte[] serializedIdentity) {
|
||||
|
||||
// 获取“记住我”缓存中的用户对象
|
||||
PrincipalCollection collection = super.deserialize(extSerializeData(serializedIdentity));
|
||||
User principal = (User) collection.getPrimaryPrincipal();
|
||||
|
||||
// 提取二次加密密码盐数据
|
||||
byte[] encrypt = new byte[ENCRYPT_LENGTH];
|
||||
System.arraycopy(serializedIdentity, 0, encrypt, 0, encrypt.length);
|
||||
|
||||
// 判断二次加密密码盐是否正确
|
||||
String password = principal.getPassword();
|
||||
byte[] verifyEncrypt = ShiroUtil.encrypt(password, CIPHER_KEY).getBytes();
|
||||
if (!Arrays.equals(encrypt, verifyEncrypt)) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
// 更新“记住我”用户数据,使部门及角色超时
|
||||
principal.setDept(new RememberMeDept());
|
||||
principal.setRoles(new PersistentSet());
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 混淆系列化数据
|
||||
* {格式:二次加密密码盐(64位)+系列化数据}
|
||||
*/
|
||||
private byte[] confusion(byte[] serializeData, String password) {
|
||||
byte[] encrypt = ShiroUtil.encrypt(password, CIPHER_KEY).getBytes();
|
||||
// 合并两个数组
|
||||
byte[] confusionData = new byte[ENCRYPT_LENGTH + serializeData.length];
|
||||
System.arraycopy(encrypt, 0, confusionData, 0, ENCRYPT_LENGTH);
|
||||
System.arraycopy(serializeData, 0, confusionData, ENCRYPT_LENGTH, serializeData.length);
|
||||
|
||||
return confusionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取系列化数据
|
||||
* {格式:二次加密密码盐(64位)+系列化数据}
|
||||
*/
|
||||
private byte[] extSerializeData(byte[] serializedIdentity) {
|
||||
if (serializedIdentity.length > ENCRYPT_LENGTH) {
|
||||
// 提取系列化数据
|
||||
byte[] serializeData = new byte[serializedIdentity.length - ENCRYPT_LENGTH];
|
||||
System.arraycopy(serializedIdentity, ENCRYPT_LENGTH, serializeData, 0, serializeData.length);
|
||||
|
||||
return serializeData;
|
||||
}
|
||||
return serializedIdentity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.shiro.config.ShiroAutoConfig
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:thymeleaf</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--thymeleaf视图模板框架-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--shiro模板视图权限标签扩展-->
|
||||
<dependency>
|
||||
<groupId>com.github.theborakompanioni</groupId>
|
||||
<artifactId>thymeleaf-extras-shiro</artifactId>
|
||||
<version>${thymeleaf-shiro.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.linln.component.thymeleaf;
|
||||
|
||||
import com.linln.component.thymeleaf.attribute.SelectDictAttrProcessor;
|
||||
import com.linln.component.thymeleaf.attribute.SelectListAttrProcessor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.thymeleaf.dialect.AbstractProcessorDialect;
|
||||
import org.thymeleaf.dialect.IExpressionObjectDialect;
|
||||
import org.thymeleaf.expression.IExpressionObjectFactory;
|
||||
import org.thymeleaf.processor.IProcessor;
|
||||
import org.thymeleaf.standard.StandardDialect;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class TimoDialect extends AbstractProcessorDialect implements IExpressionObjectDialect {
|
||||
|
||||
private static final String NAME = "TimoDialect";
|
||||
private static final String PREFIX = "mo";
|
||||
private IExpressionObjectFactory expressionObjectFactory = null;
|
||||
|
||||
public TimoDialect() {
|
||||
super(NAME, PREFIX, StandardDialect.PROCESSOR_PRECEDENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<IProcessor> getProcessors(String dialectPrefix) {
|
||||
Set<IProcessor> processors = new LinkedHashSet<IProcessor>();
|
||||
processors.add(new SelectDictAttrProcessor(TemplateMode.HTML, dialectPrefix));
|
||||
processors.add(new SelectListAttrProcessor(TemplateMode.HTML, dialectPrefix));
|
||||
return processors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExpressionObjectFactory getExpressionObjectFactory() {
|
||||
if (this.expressionObjectFactory == null) {
|
||||
this.expressionObjectFactory = new TimoExpressionObjectFactory();
|
||||
}
|
||||
return this.expressionObjectFactory;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.linln.component.thymeleaf;
|
||||
|
||||
import com.linln.component.thymeleaf.utility.DictUtil;
|
||||
import com.linln.component.thymeleaf.utility.LogUtil;
|
||||
import com.linln.component.thymeleaf.utility.PageUtil;
|
||||
import org.thymeleaf.context.IExpressionContext;
|
||||
import org.thymeleaf.expression.IExpressionObjectFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class TimoExpressionObjectFactory implements IExpressionObjectFactory {
|
||||
|
||||
public static final String PAGE_UTIL_NAME = "pageUtil";
|
||||
public static final PageUtil PAGE_UTIL_OBJECT = new PageUtil();
|
||||
public static final String DICT_UTIL_NAME = "dicts";
|
||||
public static final DictUtil DICT_UTIL_OBJECT = new DictUtil();
|
||||
public static final String LOG_UTIL_NAME = "logs";
|
||||
public static final LogUtil LOG_UTIL_OBJECT = new LogUtil();
|
||||
|
||||
@Override
|
||||
public Set<String> getAllExpressionObjectNames() {
|
||||
Set<String> names = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
|
||||
PAGE_UTIL_NAME,
|
||||
DICT_UTIL_NAME,
|
||||
LOG_UTIL_NAME
|
||||
)));
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object buildObject(IExpressionContext context, String expressionObjectName) {
|
||||
if(PAGE_UTIL_NAME.equals(expressionObjectName)){
|
||||
return PAGE_UTIL_OBJECT;
|
||||
}
|
||||
if(DICT_UTIL_NAME.equals(expressionObjectName)){
|
||||
return DICT_UTIL_OBJECT;
|
||||
}
|
||||
if(LOG_UTIL_NAME.equals(expressionObjectName)){
|
||||
return LOG_UTIL_OBJECT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCacheable(String expressionObjectName) {
|
||||
return expressionObjectName != null;
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.linln.component.thymeleaf.attribute;
|
||||
|
||||
import com.linln.component.thymeleaf.utility.DictUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.thymeleaf.IEngineConfiguration;
|
||||
import org.thymeleaf.context.ITemplateContext;
|
||||
import org.thymeleaf.engine.AttributeName;
|
||||
import org.thymeleaf.model.IProcessableElementTag;
|
||||
import org.thymeleaf.postprocessor.IPostProcessor;
|
||||
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
|
||||
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
||||
import org.thymeleaf.standard.expression.*;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 根据字典标识生成下拉列表
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class SelectDictAttrProcessor extends AbstractAttributeTagProcessor {
|
||||
|
||||
public static final int PRECEDENCE = 1400;
|
||||
public static final String ATTR_NAME = "dict";
|
||||
public static final String SELECTED_ATTR_NAME= "mo-selected";
|
||||
public static final String EMPTY_ATTR_NAME= "mo-empty";
|
||||
|
||||
public SelectDictAttrProcessor(final TemplateMode templateMode, final String dialectPrefix) {
|
||||
super(templateMode, dialectPrefix, null, false, ATTR_NAME, true, PRECEDENCE, true);
|
||||
}
|
||||
|
||||
public SelectDictAttrProcessor(final TemplateMode templateMode, final String dialectPrefix, String attrName, int precedence) {
|
||||
super(templateMode, dialectPrefix, null, false, attrName, true, precedence, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doProcess(
|
||||
final ITemplateContext context,
|
||||
final IProcessableElementTag tag,
|
||||
final AttributeName attributeName,
|
||||
final String attributeValue,
|
||||
final IElementTagStructureHandler structureHandler) {
|
||||
|
||||
// 如果属性值为空或者标签不为select,则不处理
|
||||
String elementName = tag.getElementCompleteName();
|
||||
if(attributeValue.isEmpty() || !"select".equals(elementName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取列表对象,空则不处理
|
||||
Map<String, String> valueList = DictUtil.value(attributeValue);
|
||||
if(valueList != null && valueList.size() > 0) {
|
||||
doProcess(context, tag, attributeName, attributeValue, structureHandler, valueList);
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void doProcess(
|
||||
final ITemplateContext context,
|
||||
final IProcessableElementTag tag,
|
||||
final AttributeName attributeName,
|
||||
final String attributeValue,
|
||||
final IElementTagStructureHandler structureHandler,
|
||||
Map<String, String> valueList) {
|
||||
|
||||
// 获取默认选中值
|
||||
String attributeSelectedValue = tag.getAttributeValue(SELECTED_ATTR_NAME);
|
||||
Object selectedValue = null;
|
||||
final IEngineConfiguration configuration = context.getConfiguration();
|
||||
if (attributeSelectedValue != null && !attributeSelectedValue.isEmpty()){
|
||||
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
|
||||
final IStandardExpression expression = expressionParser.parseExpression(context, attributeSelectedValue);
|
||||
final Object expressionResult;
|
||||
if (expression != null && expression instanceof FragmentExpression) {
|
||||
final FragmentExpression.ExecutedFragmentExpression executedFragmentExpression =
|
||||
FragmentExpression.createExecutedFragmentExpression(context, (FragmentExpression) expression);
|
||||
expressionResult =
|
||||
FragmentExpression.resolveExecutedFragmentExpression(context, executedFragmentExpression, true);
|
||||
} else {
|
||||
expressionResult = expression.execute(context, StandardExpressionExecutionContext.NORMAL);
|
||||
}
|
||||
if (expressionResult == NoOpToken.VALUE) {
|
||||
return;
|
||||
}
|
||||
selectedValue = (expressionResult == null ? "" : expressionResult);
|
||||
}
|
||||
|
||||
// 拼装下拉列表选项
|
||||
List<String> keyList = new ArrayList<>();
|
||||
if (selectedValue instanceof List){
|
||||
((List) selectedValue).forEach(item -> keyList.add(String.valueOf(item)));
|
||||
}else {
|
||||
keyList.add(selectedValue == null ? "" : selectedValue.toString());
|
||||
}
|
||||
StringBuilder optionContent = new StringBuilder();
|
||||
valueList.forEach((key, val) -> {
|
||||
optionContent.append("<option value = '").append(key);
|
||||
if(keyList.contains(String.valueOf(key))){
|
||||
optionContent.append("' ").append("selected='selected");
|
||||
}
|
||||
optionContent.append("'>").append(val).append("</option>");
|
||||
});
|
||||
|
||||
// 判断是添加空值选项
|
||||
String attributeEmptyValue = tag.getAttributeValue(EMPTY_ATTR_NAME);
|
||||
if(attributeEmptyValue != null){
|
||||
String emptyStr = "<option value=''>" + attributeEmptyValue + "</option>";
|
||||
optionContent.insert(0, emptyStr);
|
||||
}
|
||||
|
||||
// 返回新的HTML节点
|
||||
final Set<IPostProcessor> postProcessors = configuration.getPostProcessors(getTemplateMode());
|
||||
if (postProcessors.isEmpty()) {
|
||||
structureHandler.removeAttribute(SELECTED_ATTR_NAME);
|
||||
structureHandler.removeAttribute(EMPTY_ATTR_NAME);
|
||||
structureHandler.setBody(optionContent, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.linln.component.thymeleaf.attribute;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.thymeleaf.IEngineConfiguration;
|
||||
import org.thymeleaf.context.ITemplateContext;
|
||||
import org.thymeleaf.engine.AttributeName;
|
||||
import org.thymeleaf.model.IProcessableElementTag;
|
||||
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
||||
import org.thymeleaf.standard.expression.*;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义下拉列表生成标签
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class SelectListAttrProcessor extends SelectDictAttrProcessor {
|
||||
|
||||
public static final int PRECEDENCE = 1400;
|
||||
public static final String ATTR_NAME = "list";
|
||||
|
||||
public SelectListAttrProcessor(final TemplateMode templateMode, final String dialectPrefix) {
|
||||
super(templateMode, dialectPrefix, ATTR_NAME, PRECEDENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void doProcess(
|
||||
final ITemplateContext context,
|
||||
final IProcessableElementTag tag,
|
||||
final AttributeName attributeName,
|
||||
final String attributeValue,
|
||||
final IElementTagStructureHandler structureHandler) {
|
||||
|
||||
// 如果属性值为空或者标签不为select。则不处理
|
||||
String elementName = tag.getElementCompleteName();
|
||||
if (attributeValue.isEmpty() || !"select".equals(elementName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取列表对象
|
||||
final IEngineConfiguration configuration = context.getConfiguration();
|
||||
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
|
||||
final IStandardExpression expression = expressionParser.parseExpression(context, attributeValue);
|
||||
Object expressionResult;
|
||||
if (expression != null && expression instanceof FragmentExpression) {
|
||||
final FragmentExpression.ExecutedFragmentExpression executedFragmentExpression =
|
||||
FragmentExpression.createExecutedFragmentExpression(context, (FragmentExpression) expression);
|
||||
expressionResult =
|
||||
FragmentExpression.resolveExecutedFragmentExpression(context, executedFragmentExpression, true);
|
||||
} else {
|
||||
expressionResult = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED);
|
||||
if (expressionResult == NoOpToken.VALUE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 转换列表对象
|
||||
Map<String, String> valueList = new LinkedHashMap<>();
|
||||
if(expressionResult.getClass().isArray()){
|
||||
// 转换数组
|
||||
int length = Array.getLength(expressionResult);
|
||||
for (int i = 0; i < length; i++) {
|
||||
String value = String.valueOf(Array.get(expressionResult, i));
|
||||
valueList.put(value, value);
|
||||
}
|
||||
}else if(expressionResult instanceof Collection){
|
||||
// 装换Collection集合
|
||||
Collection list = (Collection) expressionResult;
|
||||
list.forEach(item -> {
|
||||
valueList.put(String.valueOf(item), String.valueOf(item));
|
||||
});
|
||||
}else if(expressionResult instanceof Map){
|
||||
// 装换Map集合
|
||||
Map list = (Map) expressionResult;
|
||||
list.forEach((key, item) -> {
|
||||
valueList.put(String.valueOf(key), String.valueOf(item));
|
||||
});
|
||||
}
|
||||
|
||||
if (valueList.size() > 0) {
|
||||
doProcess(context, tag, attributeName, attributeValue, structureHandler, valueList);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.thymeleaf.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.thymeleaf")
|
||||
public class ThymeleafAutoConfig {
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.linln.component.thymeleaf.config;
|
||||
|
||||
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
|
||||
import com.linln.component.thymeleaf.TimoDialect;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Configuration
|
||||
public class ThymeleafConfig {
|
||||
|
||||
/**
|
||||
* 配置自定义的CusDialect,用于整合thymeleaf模板
|
||||
*/
|
||||
@Bean
|
||||
public TimoDialect getTimoDialect() {
|
||||
return new TimoDialect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置shiro扩展标签,用于控制权限按钮的显示
|
||||
*/
|
||||
@Bean
|
||||
public ShiroDialect shiroDialect(){
|
||||
return new ShiroDialect();
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package com.linln.component.thymeleaf.utility;
|
||||
|
||||
import com.linln.common.utils.EhCacheUtil;
|
||||
import com.linln.modules.system.domain.Dict;
|
||||
import com.linln.modules.system.service.DictService;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.Element;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字典提取工具对象
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class DictUtil {
|
||||
|
||||
private static Cache dictCache = EhCacheUtil.getDictCache();
|
||||
|
||||
/**
|
||||
* 获取字典值集合
|
||||
* @param label 字典标识
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, String> value(String label){
|
||||
Map<String, String> value = null;
|
||||
Element dictEle = dictCache.get(label);
|
||||
if(dictEle != null){
|
||||
value = (Map<String, String>) dictEle.getObjectValue();
|
||||
}else {
|
||||
DictService dictService = SpringContextUtil.getBean(DictService.class);
|
||||
Dict dict = dictService.getByNameOk(label);
|
||||
if(dict != null){
|
||||
String dictValue = dict.getValue();
|
||||
String[] outerSplit = dictValue.split(",");
|
||||
value = new LinkedHashMap<>();
|
||||
for (String osp : outerSplit) {
|
||||
String[] split = osp.split(":");
|
||||
if(split.length > 1){
|
||||
value.put(split[0], split[1]);
|
||||
}
|
||||
}
|
||||
dictCache.put(new Element(dict.getName(), value));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据选项编码获取选项值
|
||||
* @param label 字典标识
|
||||
* @param code 选项编码
|
||||
*/
|
||||
public static String keyValue(String label, String code){
|
||||
Map<String, String> list = DictUtil.value(label);
|
||||
if(list != null){
|
||||
return list.get(code);
|
||||
}else{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String dataStatus(Byte status){
|
||||
String label = "DATA_STATUS";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String orderStatus(Byte status){
|
||||
String label = "ORDER_STATUS";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String payStatus(Byte status){
|
||||
String label = "PAY_STATUS";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String roleType(Byte status){
|
||||
String label = "ROLE_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String dispatchStatus(Byte status){
|
||||
String label = "DISPATCH_STATUS";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String payType(Byte status){
|
||||
String label = "PAY_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String userAmountRecordType(Byte status){
|
||||
String label = "USER_AMOUNT_RECORD_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petType(Byte status){
|
||||
String label = "PET_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petSize(Byte status){
|
||||
String label = "PET_SIZE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petHair(Byte status){
|
||||
String label = "PET_HAIR";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petVaccine(Byte status){
|
||||
String label = "PET_VACCINE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petEunuch(Byte status){
|
||||
String label = "PET_EUNUCH";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petGender(Byte status){
|
||||
String label = "PET_GENDER";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petHeadType(Byte status){
|
||||
String label = "PET_HEAD_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petCatWeight(Byte status){
|
||||
String label = "PET_CAT_WEIGHT";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String petDogWeight(Byte status){
|
||||
String label = "PET_DOG_WEIGHT";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String goodsType(Byte status){
|
||||
String label = "GOODS_TYPE";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
/**
|
||||
* 封装数据状态字典
|
||||
* @param status 状态
|
||||
*/
|
||||
public static String goodsBuyMany(Byte status){
|
||||
String label = "GOODS_BUY_MANY";
|
||||
return DictUtil.keyValue(label, String.valueOf(status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存中指定的数据
|
||||
* @param label 字典标识
|
||||
*/
|
||||
public static void clearCache(String label){
|
||||
Element dictEle = dictCache.get(label);
|
||||
if (dictEle != null){
|
||||
dictCache.remove(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.linln.component.thymeleaf.utility;
|
||||
|
||||
import com.linln.common.utils.EntityBeanUtil;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.modules.system.domain.ActionLog;
|
||||
import com.linln.modules.system.service.ActionLogService;
|
||||
|
||||
import javax.persistence.Table;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/10/16
|
||||
*/
|
||||
public class LogUtil {
|
||||
|
||||
/**
|
||||
* 获取实体对象的日志
|
||||
* @param entity 实体对象
|
||||
*/
|
||||
public List<ActionLog> entityList(Object entity){
|
||||
ActionLogService actionLogService = SpringContextUtil.getBean(ActionLogService.class);
|
||||
Table table = entity.getClass().getAnnotation(Table.class);
|
||||
String tableName = table.name();
|
||||
try {
|
||||
Object object = EntityBeanUtil.getField(entity, "id");
|
||||
Long entityId = Long.valueOf(String.valueOf(object));
|
||||
return actionLogService.getDataLogList(tableName, entityId);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.linln.component.thymeleaf.utility;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分类显示辅助工具对象
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class PageUtil {
|
||||
|
||||
private String paramHref;
|
||||
|
||||
public boolean pageInit(Page page, HttpServletRequest request){
|
||||
if (page.getTotalElements() > page.getSize() && page.getNumberOfElements() != 0){
|
||||
|
||||
// 获取分页参数地址
|
||||
String servletPath = request.getServletPath();
|
||||
StringBuffer param = new StringBuffer(servletPath + "?");
|
||||
Enumeration em = request.getParameterNames();
|
||||
while (em.hasMoreElements()) {
|
||||
String name = (String) em.nextElement();
|
||||
if(!"page".equals(name)){
|
||||
String value = request.getParameter(name);
|
||||
param.append(name + "=" + value + "&");
|
||||
}
|
||||
}
|
||||
this.paramHref = param.toString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<String> pageCode(Page page){
|
||||
int number = page.getNumber()+1;
|
||||
int totalPages = page.getTotalPages();
|
||||
int start = 0;
|
||||
int length = 7;
|
||||
int half = length % 2 == 0 ? length / 2 : length / 2 + 1;
|
||||
List<String> codeList = new ArrayList<>();
|
||||
|
||||
if(totalPages > length && number > half){
|
||||
start = number - half;
|
||||
}
|
||||
if(totalPages > length && number > totalPages - half){
|
||||
start = totalPages - length;
|
||||
}
|
||||
for (int i=1; i <= (totalPages > length ? length : totalPages); i++){
|
||||
codeList.add(String.valueOf( i + start));
|
||||
}
|
||||
if(totalPages > length && number > half){
|
||||
codeList.set(0, "1");
|
||||
codeList.set(1, "…");
|
||||
}
|
||||
if(totalPages > length && number < totalPages - (half-1)){
|
||||
codeList.set(length-2, "…");
|
||||
codeList.set(length-1, String.valueOf(totalPages));
|
||||
}
|
||||
return codeList;
|
||||
}
|
||||
|
||||
public String pageActive(Page page, String pageCode, String className){
|
||||
int number = page.getNumber();
|
||||
if(!"…".equals(pageCode)){
|
||||
if(number == Integer.valueOf(pageCode) - 1){
|
||||
return " "+className;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean isPrevious(Page page){
|
||||
return page.getNumber() != 0;
|
||||
}
|
||||
|
||||
public boolean isNext(Page page){
|
||||
return page.getNumber() != page.getTotalPages() - 1;
|
||||
}
|
||||
|
||||
public boolean isCode(String pageCode){
|
||||
return "…".equals(pageCode);
|
||||
}
|
||||
|
||||
public String pageHref(String pageCode){
|
||||
return paramHref + "page=" + pageCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one
|
||||
~ or more contributor license agreements. See the NOTICE file
|
||||
~ distributed with this work for additional information
|
||||
~ regarding copyright ownership. The ASF licenses this file
|
||||
~ to you 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<dialect
|
||||
xmlns="http://www.thymeleaf.org/extras/dialect"
|
||||
prefix="mo"
|
||||
namespace-uri="https://gitee.com/aun/Timo"
|
||||
class="com.linln.component.thymeleaf.TimoDialect">
|
||||
|
||||
<!-- 自定义属性标签 -->
|
||||
<attribute-processor name="list"
|
||||
class="com.linln.component.thymeleaf.attribute.SelectListAttrProcessor">
|
||||
<documentation><![CDATA[
|
||||
自定义下拉列表生成标签,值可以为数组和集合!
|
||||
mo-selected属性:默认选择的值
|
||||
mo-empty属性:添加无值下拉选项,值为显示内容
|
||||
]]></documentation>
|
||||
</attribute-processor>
|
||||
<attribute-processor name="dict"
|
||||
class="com.linln.component.thymeleaf.attribute.SelectDictAttrProcessor">
|
||||
<documentation><![CDATA[
|
||||
根据字典标识生成下拉列表,值可以为数组和集合!
|
||||
mo-selected属性:默认选择的值
|
||||
mo-empty属性:添加无值下拉选项,值为显示内容
|
||||
]]></documentation>
|
||||
</attribute-processor>
|
||||
|
||||
<!-- 自定义对象工具 -->
|
||||
<expression-object name="pageUtil" class="com.linln.component.thymeleaf.utility.PageUtil"/>
|
||||
<expression-object name="dicts" class="com.linln.component.thymeleaf.utility.DictUtil"/>
|
||||
|
||||
|
||||
</dialect>
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.thymeleaf.config.ThymeleafAutoConfig
|
||||
Reference in New Issue
Block a user