mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-09-18 03:16:40 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
63ef235b95 | ||
|
3ceea05ba1 | ||
|
9fda0a7bd6 | ||
|
9361097097 | ||
|
942c628cc6 | ||
|
521b98746f | ||
|
4229ec80b0 | ||
|
4582656e27 | ||
|
fff0120058 | ||
|
b8f2200af6 | ||
|
0a56497b51 | ||
|
2eb3742063 | ||
|
74aa2da89b | ||
|
3dcad0b78a | ||
|
6ba6b18849 | ||
|
d2c55e35ff | ||
|
8135e0ec10 | ||
|
2a545117fa | ||
|
d8baf9dba7 |
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
**<font color="#DC143C">国内首个满足《网络安全-三级等保》、《数据安全》</font>** 功能要求,支持登录限制、接口国产加解密、数据脱敏等一系列安全要求。
|
**<font color="#DC143C">国内首个满足《网络安全-三级等保》、《数据安全》</font>** 功能要求,支持登录限制、接口国产加解密、数据脱敏等一系列安全要求。
|
||||||
|
|
||||||
**<font color="#DC143C">支持国产数据库:达梦、金仓、南大通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB。 </font>**
|
**<font color="#DC143C">支持国产数据库:【达梦、金仓、南大通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB】等,主流数据库:【Mysql, PostgreSQL】等</font>**
|
||||||
|
|
||||||
前端提供 **<font color="#DC143C">JavaScript和TypeScript双版本</font>**,后端提供 **<font color="#DC143C">Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本</font>**。
|
**<font color="#DC143C">前端提供JavaScript和TypeScript双版本,后端提供Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本</font>**。
|
||||||
|
|
||||||
同时 **<font color="#DC143C">重磅开源</font>** 开源六年来 **<font color="#DC143C">千余家企业验证过且正在使用</font>** 的代码规范: **<font color="#DC143C">《高质量代码思想》、《Vue3规范》、《Java规范》</font>** ,让大家在这浮躁的世界里感受到一股把代码写好的清流!同时又能节省大量时间,减少加班,快乐工作,保持谦逊,保持学习,**<font color="#DC143C">热爱代码,更热爱生活</font>** !
|
同时 **<font color="#DC143C">重磅开源</font>** 开源六年来 **<font color="#DC143C">千余家企业验证过且正在使用</font>** 的代码规范: **<font color="#DC143C">《高质量代码思想》、《Vue3规范》、《Java规范》</font>** ,让大家在这浮躁的世界里感受到一股把代码写好的清流!同时又能节省大量时间,减少加班,快乐工作,保持谦逊,保持学习,**<font color="#DC143C">热爱代码,更热爱生活</font>** !
|
||||||
### **技术体系**
|
### **技术体系**
|
||||||
|
@ -67,7 +67,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
Method method = ((HandlerMethod) handler).getMethod();
|
Method method = ((HandlerMethod) handler).getMethod();
|
||||||
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
||||||
if (noNeedLogin != null) {
|
if (noNeedLogin != null) {
|
||||||
checkActiveTimeout(requestEmployee);
|
updateActiveTimeout(requestEmployee);
|
||||||
SmartRequestUtil.setRequestUser(requestEmployee);
|
SmartRequestUtil.setRequestUser(requestEmployee);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -77,8 +77,8 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测token 活跃频率
|
// 更新活跃
|
||||||
checkActiveTimeout(requestEmployee);
|
updateActiveTimeout(requestEmployee);
|
||||||
|
|
||||||
|
|
||||||
// --------------- 第三步: 校验 权限 ---------------
|
// --------------- 第三步: 校验 权限 ---------------
|
||||||
@ -123,15 +123,12 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测:token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结
|
* 更新活跃时间
|
||||||
*/
|
*/
|
||||||
private void checkActiveTimeout(RequestEmployee requestEmployee) {
|
private void updateActiveTimeout(RequestEmployee requestEmployee) {
|
||||||
// 用户不在线,也不用检测
|
|
||||||
if (requestEmployee == null) {
|
if (requestEmployee == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StpUtil.checkActiveTimeout();
|
|
||||||
StpUtil.updateLastActiveToNow();
|
StpUtil.updateLastActiveToNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +153,19 @@ public class LoginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@CacheEvict(value = {AdminCacheConst.Login.USER_PERMISSION, AdminCacheConst.Login.REQUEST_EMPLOYEE}, allEntries = true)
|
/**
|
||||||
public void clear(){
|
* 清除用户权限
|
||||||
|
*/
|
||||||
|
@CacheEvict(value = AdminCacheConst.Login.USER_PERMISSION)
|
||||||
|
public void clearUserPermission(Long employeeId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除用户登录信息
|
||||||
|
*/
|
||||||
|
@CacheEvict(value = AdminCacheConst.Login.REQUEST_EMPLOYEE)
|
||||||
|
public void clearUserLoginInfo(Long employeeId) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,8 +317,8 @@ public class LoginService implements StpInterface {
|
|||||||
// sa token 登出
|
// sa token 登出
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
|
|
||||||
// 清空登录信息缓存
|
// 清除用户登录信息缓存和权限信息
|
||||||
loginManager.clear();
|
this.clearLoginEmployeeCache(requestUser.getUserId());
|
||||||
|
|
||||||
//保存登出日志
|
//保存登出日志
|
||||||
LoginLogEntity loginEntity = LoginLogEntity.builder()
|
LoginLogEntity loginEntity = LoginLogEntity.builder()
|
||||||
@ -474,6 +474,7 @@ public class LoginService implements StpInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clearLoginEmployeeCache(Long employeeId) {
|
public void clearLoginEmployeeCache(Long employeeId) {
|
||||||
loginManager.clear();
|
loginManager.clearUserPermission(employeeId);
|
||||||
|
loginManager.clearUserLoginInfo(employeeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,25 @@ public class LongJsonSerializer extends JsonSerializer<Long> {
|
|||||||
|
|
||||||
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS 安全整数范围
|
||||||
|
* 根据 JS Number.MIN_SAFE_INTEGER 与 Number.MAX_SAFE_INTEGER 得来
|
||||||
|
*/
|
||||||
|
private static final long JS_MIN_SAFE_INTEGER = -9007199254740991L;
|
||||||
|
private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||||
if (null == value) {
|
if (null == value) {
|
||||||
gen.writeNull();
|
gen.writeNull();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// js中最大安全整数16位 Number.MAX_SAFE_INTEGER
|
// 如果超出了 JavaScript 安全整数范围,则序列化为字符串
|
||||||
String longStr = String.valueOf(value);
|
if (value < JS_MIN_SAFE_INTEGER || value > JS_MAX_SAFE_INTEGER) {
|
||||||
if (longStr.length() > 16) {
|
gen.writeString(Long.toString(value));
|
||||||
gen.writeString(longStr);
|
|
||||||
} else {
|
} else {
|
||||||
|
// 否则,序列化为数字
|
||||||
gen.writeNumber(value);
|
gen.writeNumber(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,19 @@ import jakarta.annotation.Resource;
|
|||||||
import net.lab1024.sa.base.module.support.cache.CacheService;
|
import net.lab1024.sa.base.module.support.cache.CacheService;
|
||||||
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
|
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
|
||||||
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
|
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
|
||||||
|
import net.lab1024.sa.base.module.support.redis.CustomRedisCacheManager;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存配置
|
* 缓存配置
|
||||||
*
|
*
|
||||||
* @author zhoumingfa
|
|
||||||
* @date 2025/03/28
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CacheConfig {
|
public class CacheConfig {
|
||||||
@ -26,27 +27,44 @@ public class CacheConfig {
|
|||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisConnectionFactory factory;
|
private RedisConnectionFactory redisConnectionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义Redis缓存管理器Bean 整合spring-cache
|
||||||
|
* Redis连接工厂,用于建立与Redis服务器的连接
|
||||||
|
*
|
||||||
|
* @return CacheManager Redis缓存管理器实例
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||||
public RedisCacheConfiguration redisCacheConfiguration() {
|
public CacheManager cacheManager() {
|
||||||
return RedisCacheConfiguration.defaultCacheConfig()
|
// 使用非阻塞模式的缓存写入器,适用于大多数高并发场景
|
||||||
|
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
|
||||||
|
|
||||||
|
// 构建默认缓存配置
|
||||||
|
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
// 禁止缓存 null 值,避免缓存穿透
|
||||||
.disableCachingNullValues()
|
.disableCachingNullValues()
|
||||||
.computePrefixWith(name -> "cache:" + name + ":")
|
.computePrefixWith(name -> "cache:" + name + ":")
|
||||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
|
// 使用 FastJSON 序列化缓存值,支持复杂对象
|
||||||
|
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||||
|
.fromSerializer(new GenericFastJsonRedisSerializer()));
|
||||||
|
|
||||||
|
// 返回自定义缓存管理器,支持 cacheName#ttl 格式与永久缓存(#-1)
|
||||||
|
return new CustomRedisCacheManager(redisCacheWriter, defaultCacheConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
|
||||||
public CacheService redisCacheService() {
|
|
||||||
return new RedisCacheServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||||
public CacheService caffeineCacheService() {
|
public CacheService redisCacheService() {
|
||||||
return new CaffeineCacheServiceImpl();
|
return new RedisCacheServiceImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
||||||
|
public CacheService caffeineCacheService() {
|
||||||
|
return new CaffeineCacheServiceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -81,7 +81,7 @@ public class FileConfig implements WebMvcConfigurer {
|
|||||||
StaticCredentialsProvider.create(
|
StaticCredentialsProvider.create(
|
||||||
AwsBasicCredentials.create(accessKey, secretKey)))
|
AwsBasicCredentials.create(accessKey, secretKey)))
|
||||||
.serviceConfiguration(S3Configuration.builder()
|
.serviceConfiguration(S3Configuration.builder()
|
||||||
.pathStyleAccessEnabled(false)
|
.pathStyleAccessEnabled(true)
|
||||||
.chunkedEncodingEnabled(false)
|
.chunkedEncodingEnabled(false)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
@ -2,6 +2,7 @@ package net.lab1024.sa.base.config;
|
|||||||
|
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
@ -13,6 +14,8 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
@ -37,6 +40,9 @@ public class JsonConfig {
|
|||||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
||||||
|
builder.serializerByType(Long.TYPE, LongJsonSerializer.INSTANCE);
|
||||||
|
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
|
||||||
|
builder.serializerByType(BigDecimal.class, ToStringSerializer.instance);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ public class DictDataVO implements Serializable {
|
|||||||
private String dictName;
|
private String dictName;
|
||||||
|
|
||||||
@Schema(description = "字典禁用状态")
|
@Schema(description = "字典禁用状态")
|
||||||
private Integer dictDisabledFlag;
|
private Boolean dictDisabledFlag;
|
||||||
|
|
||||||
@Schema(description = "字典项值")
|
@Schema(description = "字典项值")
|
||||||
private String dataValue;
|
private String dataValue;
|
||||||
|
@ -30,7 +30,7 @@ public class DictVO {
|
|||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "禁用状态")
|
@Schema(description = "禁用状态")
|
||||||
private Integer disabledFlag;
|
private Boolean disabledFlag;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
@ -101,7 +101,18 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
|||||||
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
|
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
|
||||||
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
|
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
|
||||||
|
|
||||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).metadata(userMetadata).contentLength(file.getSize()).contentType(this.getContentType(fileType)).contentEncoding(StandardCharsets.UTF_8.name()).contentDisposition("attachment;filename=" + urlEncoderFilename).build();
|
// 根据文件路径获取并设置访问权限
|
||||||
|
ObjectCannedACL acl = this.getACL(path);
|
||||||
|
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||||
|
.bucket(cloudConfig.getBucketName())
|
||||||
|
.key(fileKey)
|
||||||
|
.metadata(userMetadata)
|
||||||
|
.contentLength(file.getSize())
|
||||||
|
.contentType(this.getContentType(fileType))
|
||||||
|
.contentEncoding(StandardCharsets.UTF_8.name())
|
||||||
|
.contentDisposition("attachment;filename=" + urlEncoderFilename)
|
||||||
|
.acl(acl)
|
||||||
|
.build();
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
inputStream = file.getInputStream();
|
inputStream = file.getInputStream();
|
||||||
@ -112,10 +123,6 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
|||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(inputStream);
|
IOUtils.closeQuietly(inputStream);
|
||||||
}
|
}
|
||||||
// 根据文件路径获取并设置访问权限
|
|
||||||
ObjectCannedACL acl = this.getACL(path);
|
|
||||||
PutObjectAclRequest aclRequest = PutObjectAclRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).acl(this.getACL(path)).build();
|
|
||||||
s3Client.putObjectAcl(aclRequest);
|
|
||||||
// 返回上传结果
|
// 返回上传结果
|
||||||
FileUploadVO uploadVO = new FileUploadVO();
|
FileUploadVO uploadVO = new FileUploadVO();
|
||||||
uploadVO.setFileName(originalFileName);
|
uploadVO.setFileName(originalFileName);
|
||||||
|
@ -11,6 +11,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.lab1024.sa.base.common.constant.StringConst;
|
import net.lab1024.sa.base.common.constant.StringConst;
|
||||||
import net.lab1024.sa.base.common.domain.RequestUser;
|
import net.lab1024.sa.base.common.domain.RequestUser;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
||||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
||||||
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
|
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
|
||||||
@ -46,7 +47,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
* @Date 2021-12-08 20:48:52
|
* @Date 2021-12-08 20:48:52
|
||||||
* @Wechat zhuoda1024
|
* @Wechat zhuoda1024
|
||||||
* @Email lab1024@163.com
|
* @Email lab1024@163.com
|
||||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Aspect
|
@Aspect
|
||||||
@ -71,14 +72,14 @@ public abstract class OperateLogAspect {
|
|||||||
public void logPointCut() {
|
public void logPointCut() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterReturning(pointcut = "logPointCut()")
|
@AfterReturning(pointcut = "logPointCut()", returning = "responseDTO")
|
||||||
public void doAfterReturning(JoinPoint joinPoint) {
|
public void doAfterReturning(JoinPoint joinPoint, Object responseDTO) {
|
||||||
handleLog(joinPoint, null);
|
handleLog(joinPoint, null, responseDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
||||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
||||||
handleLog(joinPoint, e);
|
handleLog(joinPoint, e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,16 +110,15 @@ public abstract class OperateLogAspect {
|
|||||||
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
|
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleLog(final JoinPoint joinPoint, final Exception e) {
|
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object responseDTO) {
|
||||||
try {
|
try {
|
||||||
OperateLog operateLog = this.getAnnotationLog(joinPoint);
|
OperateLog operateLog = this.getAnnotationLog(joinPoint);
|
||||||
if (operateLog == null) {
|
if (operateLog == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.submitLog(joinPoint, e);
|
this.submitLog(joinPoint, e, responseDTO);
|
||||||
} catch (Exception exp) {
|
} catch (Exception exp) {
|
||||||
log.error("保存操作日志异常:{}", exp.getMessage());
|
log.error("保存操作日志异常:{}", exp.getMessage());
|
||||||
exp.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,11 +173,8 @@ public abstract class OperateLogAspect {
|
|||||||
/**
|
/**
|
||||||
* 提交存储操作日志
|
* 提交存储操作日志
|
||||||
*
|
*
|
||||||
* @param joinPoint
|
|
||||||
* @param e
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
*/
|
||||||
private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception {
|
private void submitLog(final JoinPoint joinPoint, final Throwable e, Object responseDTO) {
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||||
//设置用户信息
|
//设置用户信息
|
||||||
RequestUser user = SmartRequestUtil.getRequestUser();
|
RequestUser user = SmartRequestUtil.getRequestUser();
|
||||||
@ -191,7 +188,7 @@ public abstract class OperateLogAspect {
|
|||||||
String methodName = joinPoint.getSignature().getName();
|
String methodName = joinPoint.getSignature().getName();
|
||||||
String operateMethod = className + "." + methodName;
|
String operateMethod = className + "." + methodName;
|
||||||
String failReason = null;
|
String failReason = null;
|
||||||
Boolean successFlag = true;
|
boolean successFlag = true;
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
successFlag = false;
|
successFlag = false;
|
||||||
failReason = getExceptionString(e);
|
failReason = getExceptionString(e);
|
||||||
@ -210,15 +207,32 @@ public abstract class OperateLogAspect {
|
|||||||
.userAgent(user.getUserAgent())
|
.userAgent(user.getUserAgent())
|
||||||
.failReason(failReason)
|
.failReason(failReason)
|
||||||
.successFlag(successFlag).build();
|
.successFlag(successFlag).build();
|
||||||
|
|
||||||
Operation apiOperation = this.getApiOperation(joinPoint);
|
Operation apiOperation = this.getApiOperation(joinPoint);
|
||||||
if (apiOperation != null) {
|
if (apiOperation != null) {
|
||||||
operateLogEntity.setContent(apiOperation.summary());
|
operateLogEntity.setContent(apiOperation.summary());
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag api = this.getApi(joinPoint);
|
Tag api = this.getApi(joinPoint);
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
String name = api.name();
|
String name = api.name();
|
||||||
operateLogEntity.setModule(StrUtil.join(",", name));
|
operateLogEntity.setModule(StrUtil.join(",", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理返回值 ResponseDTO
|
||||||
|
if(responseDTO instanceof ResponseDTO) {
|
||||||
|
ResponseDTO response = (ResponseDTO) responseDTO;
|
||||||
|
ResponseDTO logResponseDTO = new ResponseDTO(
|
||||||
|
response.getCode(),
|
||||||
|
response.getLevel(),
|
||||||
|
response.getOk(),
|
||||||
|
response.getMsg(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
logResponseDTO.setDataType(response.getDataType());
|
||||||
|
operateLogEntity.setResponse(JSON.toJSONString(logResponseDTO));
|
||||||
|
}
|
||||||
|
|
||||||
taskExecutor.execute(() -> {
|
taskExecutor.execute(() -> {
|
||||||
this.saveLog(operateLogEntity);
|
this.saveLog(operateLogEntity);
|
||||||
});
|
});
|
||||||
|
@ -71,6 +71,11 @@ public class OperateLogEntity {
|
|||||||
*/
|
*/
|
||||||
private String param;
|
private String param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回值
|
||||||
|
*/
|
||||||
|
private String response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户ip
|
* 客户ip
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +47,9 @@ public class OperateLogVO {
|
|||||||
@Schema(description = "请求参数")
|
@Schema(description = "请求参数")
|
||||||
private String param;
|
private String param;
|
||||||
|
|
||||||
|
@Schema(description = "返回值")
|
||||||
|
private String response;
|
||||||
|
|
||||||
@Schema(description = "客户ip")
|
@Schema(description = "客户ip")
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.redis;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.convert.DurationStyle;
|
||||||
|
import org.springframework.data.redis.cache.*;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static net.lab1024.sa.base.common.constant.StringConst.COLON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 RedisCacheManager,支持在 cacheName 中通过 '#' 指定 TTL(过期时间)。
|
||||||
|
*
|
||||||
|
* @Author CoderKK
|
||||||
|
* @Date 2025-08-15 13:01:01
|
||||||
|
* <p>
|
||||||
|
* 支持格式:{@code cacheName#ttl},其中 ttl 支持 Spring 的 Duration 格式。
|
||||||
|
* 特殊值:{@code -1} 表示永久缓存(永不过期)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <h3>使用示例:</h3>
|
||||||
|
* <pre>
|
||||||
|
* // 10 秒后过期
|
||||||
|
* @Cacheable(value = "user#10s", key = "#id")
|
||||||
|
* // 2 小时后过期
|
||||||
|
* @Cacheable(value = "report#2h", key = "#date")
|
||||||
|
* // 30 分钟后过期
|
||||||
|
* @Cacheable(value = "session#30m", key = "#token")
|
||||||
|
* // 永不过期(永久缓存),适用于极少变化的配置数据
|
||||||
|
* @Cacheable(value = "appConfig#-1", key = "'globalSettings'")
|
||||||
|
* // 无 TTL,使用全局默认过期时间(如 7 天)
|
||||||
|
* @Cacheable(value = "product", key = "#productId")
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h3>生成的 Redis Key 格式:</h3>
|
||||||
|
* <pre>
|
||||||
|
* cache:cacheName:key
|
||||||
|
* 例如:cache:user:123
|
||||||
|
* cache:appConfig:globalSettings
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h3>支持的 TTL 单位:</h3>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code ms} / {@code millis} / {@code milliseconds} - 毫秒</li>
|
||||||
|
* <li>{@code s} / {@code secs} / {@code seconds} - 秒</li>
|
||||||
|
* <li>{@code m} / {@code mins} / {@code minutes} - 分钟</li>
|
||||||
|
* <li>{@code h} / {@code hrs} / {@code hours} - 小时</li>
|
||||||
|
* <li>{@code d} / {@code days} - 天</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h3>注意事项:</h3>
|
||||||
|
* <ul>
|
||||||
|
* <li>不写单位默认为毫秒</li>
|
||||||
|
* <li>永久缓存(#-1)不会自动过期,请配合 @CacheEvict 手动清理。</li>
|
||||||
|
* <li>避免对频繁更新的数据使用永久缓存,防止数据陈旧。</li>
|
||||||
|
* <li>cacheName 中的 '#' 只解析第一个,后续字符将作为 TTL 处理。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CustomRedisCacheManager extends RedisCacheManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存全局前缀
|
||||||
|
*/
|
||||||
|
private static final String CACHE_PREFIX = "cache";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 TTL 分隔符,用于在 cacheName 后附加过期时间
|
||||||
|
*/
|
||||||
|
private static final String CUSTOM_TTL_SEPARATOR = "#";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认缓存过期时间:7 天
|
||||||
|
*/
|
||||||
|
private static final Duration DEFAULT_TTL = Duration.ofDays(7);
|
||||||
|
|
||||||
|
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
|
||||||
|
super(cacheWriter, defaultCacheConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 RedisCache 实例,支持从 cacheName 解析 TTL
|
||||||
|
*
|
||||||
|
* @param name 缓存名称(支持 name#ttl 格式)
|
||||||
|
* @param cacheConfig 默认缓存配置
|
||||||
|
* @return RedisCache
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
|
||||||
|
Duration ttl = parseTtlFromCacheName(name);
|
||||||
|
if (ttl == null) {
|
||||||
|
ttl = DEFAULT_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheKeyPrefix keyPrefix = cacheName -> {
|
||||||
|
if (StrUtil.isBlank(cacheName)) {
|
||||||
|
return CACHE_PREFIX + COLON;
|
||||||
|
}
|
||||||
|
String[] parts = cacheName.split(CUSTOM_TTL_SEPARATOR, 2);
|
||||||
|
String cleanName = StrUtil.trim(parts[0]);
|
||||||
|
return CACHE_PREFIX + COLON + cleanName + COLON;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建最终缓存配置:设置 key 前缀 + TTL
|
||||||
|
RedisCacheConfiguration config = cacheConfig.computePrefixWith(keyPrefix).entryTtl(ttl);
|
||||||
|
|
||||||
|
return super.createRedisCache(name, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 cacheName 中解析 TTL
|
||||||
|
*
|
||||||
|
* @param name 缓存名称,格式如:users#10m, products#2h, config#-1(永久)
|
||||||
|
* @return 解析出的 Duration若无效则返回 null;若为 -1,则返回 Duration.ofMillis(-1) 表示永久缓存
|
||||||
|
*/
|
||||||
|
private Duration parseTtlFromCacheName(String name) {
|
||||||
|
if (StrUtil.isBlank(name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = name.split(CUSTOM_TTL_SEPARATOR, 2);
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return null; // 无 TTL 部分
|
||||||
|
}
|
||||||
|
|
||||||
|
String ttlStr = StrUtil.trim(parts[1]);
|
||||||
|
if (StrUtil.isBlank(ttlStr)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊处理:-1 表示永久缓存
|
||||||
|
if ("-1".equals(ttlStr)) {
|
||||||
|
return Duration.ofMillis(-1); // Spring Redis 中负数 Duration 表示永不过期
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Duration ttl = DurationStyle.detectAndParse(ttlStr);
|
||||||
|
return ttl.getSeconds() > 0 ? ttl : null;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("解析缓存 TTL 失败,cacheName='{}', ttl='{}', 错误: {}", name, ttlStr, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
@ -106,7 +106,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Dict")
|
#if($field.queryTypeEnum == "Dict")
|
||||||
<a-form-item label="${field.label}" class="smart-query-form-item">
|
<a-form-item label="${field.label}" class="smart-query-form-item">
|
||||||
<DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Enum")
|
#if($field.queryTypeEnum == "Enum")
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
@ -106,7 +106,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Dict")
|
#if($field.queryTypeEnum == "Dict")
|
||||||
<a-form-item label="${field.label}" class="smart-query-form-item">
|
<a-form-item label="${field.label}" class="smart-query-form-item">
|
||||||
<DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Enum")
|
#if($field.queryTypeEnum == "Enum")
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
|
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
|
||||||
</if>
|
</if>
|
||||||
<if test="query.requestKeywords != null and query.requestKeywords != ''">
|
<if test="query.requestKeywords != null and query.requestKeywords != ''">
|
||||||
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}))
|
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}) OR INSTR(response,#{query.requestKeywords}))
|
||||||
</if>
|
</if>
|
||||||
<if test="query.successFlag != null">
|
<if test="query.successFlag != null">
|
||||||
AND success_flag = #{query.successFlag}
|
AND success_flag = #{query.successFlag}
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
<bcprov.version>1.80</bcprov.version>
|
<bcprov.version>1.80</bcprov.version>
|
||||||
<smartdb.version>1.2.0</smartdb.version>
|
<smartdb.version>1.2.0</smartdb.version>
|
||||||
<redisson.version>3.50.0</redisson.version>
|
<redisson.version>3.25.0</redisson.version>
|
||||||
<snakeyaml.version>2.4</snakeyaml.version>
|
<snakeyaml.version>2.4</snakeyaml.version>
|
||||||
<freemarker.version>2.3.34</freemarker.version>
|
<freemarker.version>2.3.34</freemarker.version>
|
||||||
<jsoup.version>1.21.1</jsoup.version>
|
<jsoup.version>1.21.1</jsoup.version>
|
||||||
|
@ -67,7 +67,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
Method method = ((HandlerMethod) handler).getMethod();
|
Method method = ((HandlerMethod) handler).getMethod();
|
||||||
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
||||||
if (noNeedLogin != null) {
|
if (noNeedLogin != null) {
|
||||||
checkActiveTimeout(requestEmployee);
|
updateActiveTimeout(requestEmployee);
|
||||||
SmartRequestUtil.setRequestUser(requestEmployee);
|
SmartRequestUtil.setRequestUser(requestEmployee);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -77,8 +77,8 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测token 活跃频率
|
// 更新活跃
|
||||||
checkActiveTimeout(requestEmployee);
|
updateActiveTimeout(requestEmployee);
|
||||||
|
|
||||||
|
|
||||||
// --------------- 第三步: 校验 权限 ---------------
|
// --------------- 第三步: 校验 权限 ---------------
|
||||||
@ -123,15 +123,12 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测:token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结
|
* 更新活跃时间
|
||||||
*/
|
*/
|
||||||
private void checkActiveTimeout(RequestEmployee requestEmployee) {
|
private void updateActiveTimeout(RequestEmployee requestEmployee) {
|
||||||
// 用户不在线,也不用检测
|
|
||||||
if (requestEmployee == null) {
|
if (requestEmployee == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StpUtil.checkActiveTimeout();
|
|
||||||
StpUtil.updateLastActiveToNow();
|
StpUtil.updateLastActiveToNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +153,19 @@ public class LoginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@CacheEvict(value = {AdminCacheConst.Login.USER_PERMISSION, AdminCacheConst.Login.REQUEST_EMPLOYEE}, allEntries = true)
|
/**
|
||||||
public void clear(){
|
* 清除用户权限
|
||||||
|
*/
|
||||||
|
@CacheEvict(value = AdminCacheConst.Login.USER_PERMISSION)
|
||||||
|
public void clearUserPermission(Long employeeId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除用户登录信息
|
||||||
|
*/
|
||||||
|
@CacheEvict(value = AdminCacheConst.Login.REQUEST_EMPLOYEE)
|
||||||
|
public void clearUserLoginInfo(Long employeeId) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,8 +319,8 @@ public class LoginService implements StpInterface {
|
|||||||
// sa token 登出
|
// sa token 登出
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
|
|
||||||
// 清空登录信息缓存
|
// 清除用户登录信息缓存和权限信息
|
||||||
loginManager.clear();
|
this.clearLoginEmployeeCache(requestUser.getUserId());
|
||||||
|
|
||||||
//保存登出日志
|
//保存登出日志
|
||||||
LoginLogEntity loginEntity = LoginLogEntity.builder()
|
LoginLogEntity loginEntity = LoginLogEntity.builder()
|
||||||
@ -476,6 +476,7 @@ public class LoginService implements StpInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clearLoginEmployeeCache(Long employeeId) {
|
public void clearLoginEmployeeCache(Long employeeId) {
|
||||||
loginManager.clear();
|
loginManager.clearUserPermission(employeeId);
|
||||||
|
loginManager.clearUserLoginInfo(employeeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,25 @@ public class LongJsonSerializer extends JsonSerializer<Long> {
|
|||||||
|
|
||||||
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS 安全整数范围
|
||||||
|
* 根据 JS Number.MIN_SAFE_INTEGER 与 Number.MAX_SAFE_INTEGER 得来
|
||||||
|
*/
|
||||||
|
private static final long JS_MIN_SAFE_INTEGER = -9007199254740991L;
|
||||||
|
private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||||
if (null == value) {
|
if (null == value) {
|
||||||
gen.writeNull();
|
gen.writeNull();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// js中最大安全整数16位 Number.MAX_SAFE_INTEGER
|
// 如果超出了 JavaScript 安全整数范围,则序列化为字符串
|
||||||
String longStr = String.valueOf(value);
|
if (value < JS_MIN_SAFE_INTEGER || value > JS_MAX_SAFE_INTEGER) {
|
||||||
if (longStr.length() > 16) {
|
gen.writeString(Long.toString(value));
|
||||||
gen.writeString(longStr);
|
|
||||||
} else {
|
} else {
|
||||||
|
// 否则,序列化为数字
|
||||||
gen.writeNumber(value);
|
gen.writeNumber(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,13 @@ import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
|
|||||||
import net.lab1024.sa.base.module.support.cache.CacheService;
|
import net.lab1024.sa.base.module.support.cache.CacheService;
|
||||||
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
|
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
|
||||||
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
|
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
|
||||||
|
import net.lab1024.sa.base.module.support.redis.CustomRedisCacheManager;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||||
|
|
||||||
@ -16,8 +19,6 @@ import javax.annotation.Resource;
|
|||||||
/**
|
/**
|
||||||
* 缓存配置
|
* 缓存配置
|
||||||
*
|
*
|
||||||
* @author zhoumingfa
|
|
||||||
* @date 2025/03/28
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CacheConfig {
|
public class CacheConfig {
|
||||||
@ -27,27 +28,44 @@ public class CacheConfig {
|
|||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisConnectionFactory factory;
|
private RedisConnectionFactory redisConnectionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义Redis缓存管理器Bean 整合spring-cache
|
||||||
|
* Redis连接工厂,用于建立与Redis服务器的连接
|
||||||
|
*
|
||||||
|
* @return CacheManager Redis缓存管理器实例
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||||
public RedisCacheConfiguration redisCacheConfiguration() {
|
public CacheManager cacheManager() {
|
||||||
return RedisCacheConfiguration.defaultCacheConfig()
|
// 使用非阻塞模式的缓存写入器,适用于大多数高并发场景
|
||||||
|
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
|
||||||
|
|
||||||
|
// 构建默认缓存配置
|
||||||
|
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
// 禁止缓存 null 值,避免缓存穿透
|
||||||
.disableCachingNullValues()
|
.disableCachingNullValues()
|
||||||
.computePrefixWith(name -> "cache:" + name + ":")
|
.computePrefixWith(name -> "cache:" + name + ":")
|
||||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
|
// 使用 FastJSON 序列化缓存值,支持复杂对象
|
||||||
|
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||||
|
.fromSerializer(new GenericFastJsonRedisSerializer()));
|
||||||
|
|
||||||
|
// 返回自定义缓存管理器,支持 cacheName#ttl 格式与永久缓存(#-1)
|
||||||
|
return new CustomRedisCacheManager(redisCacheWriter, defaultCacheConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
|
||||||
public CacheService redisCacheService() {
|
|
||||||
return new RedisCacheServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||||
public CacheService caffeineCacheService() {
|
public CacheService redisCacheService() {
|
||||||
return new CaffeineCacheServiceImpl();
|
return new RedisCacheServiceImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
||||||
|
public CacheService caffeineCacheService() {
|
||||||
|
return new CaffeineCacheServiceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -81,7 +81,7 @@ public class FileConfig implements WebMvcConfigurer {
|
|||||||
StaticCredentialsProvider.create(
|
StaticCredentialsProvider.create(
|
||||||
AwsBasicCredentials.create(accessKey, secretKey)))
|
AwsBasicCredentials.create(accessKey, secretKey)))
|
||||||
.serviceConfiguration(S3Configuration.builder()
|
.serviceConfiguration(S3Configuration.builder()
|
||||||
.pathStyleAccessEnabled(false)
|
.pathStyleAccessEnabled(true)
|
||||||
.chunkedEncodingEnabled(false)
|
.chunkedEncodingEnabled(false)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
@ -2,6 +2,7 @@ package net.lab1024.sa.base.config;
|
|||||||
|
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
@ -13,6 +14,8 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
@ -37,6 +40,9 @@ public class JsonConfig {
|
|||||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
||||||
|
builder.serializerByType(Long.TYPE, LongJsonSerializer.INSTANCE);
|
||||||
|
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
|
||||||
|
builder.serializerByType(BigDecimal.class, ToStringSerializer.instance);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ public class DictDataVO implements Serializable {
|
|||||||
private String dictName;
|
private String dictName;
|
||||||
|
|
||||||
@Schema(description = "字典禁用状态")
|
@Schema(description = "字典禁用状态")
|
||||||
private Integer dictDisabledFlag;
|
private Boolean dictDisabledFlag;
|
||||||
|
|
||||||
@Schema(description = "字典项值")
|
@Schema(description = "字典项值")
|
||||||
private String dataValue;
|
private String dataValue;
|
||||||
|
@ -30,7 +30,7 @@ public class DictVO {
|
|||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "禁用状态")
|
@Schema(description = "禁用状态")
|
||||||
private Integer disabledFlag;
|
private Boolean disabledFlag;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
@ -106,7 +106,18 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
|||||||
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
|
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
|
||||||
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
|
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
|
||||||
|
|
||||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).metadata(userMetadata).contentLength(file.getSize()).contentType(this.getContentType(fileType)).contentEncoding(StandardCharsets.UTF_8.name()).contentDisposition("attachment;filename=" + urlEncoderFilename).build();
|
// 根据文件路径获取并设置访问权限
|
||||||
|
ObjectCannedACL acl = this.getACL(path);
|
||||||
|
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||||
|
.bucket(cloudConfig.getBucketName())
|
||||||
|
.key(fileKey)
|
||||||
|
.metadata(userMetadata)
|
||||||
|
.contentLength(file.getSize())
|
||||||
|
.contentType(this.getContentType(fileType))
|
||||||
|
.contentEncoding(StandardCharsets.UTF_8.name())
|
||||||
|
.contentDisposition("attachment;filename=" + urlEncoderFilename)
|
||||||
|
.acl(acl)
|
||||||
|
.build();
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
inputStream = file.getInputStream();
|
inputStream = file.getInputStream();
|
||||||
@ -117,10 +128,6 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
|||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(inputStream);
|
IOUtils.closeQuietly(inputStream);
|
||||||
}
|
}
|
||||||
// 根据文件路径获取并设置访问权限
|
|
||||||
ObjectCannedACL acl = this.getACL(path);
|
|
||||||
PutObjectAclRequest aclRequest = PutObjectAclRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).acl(this.getACL(path)).build();
|
|
||||||
s3Client.putObjectAcl(aclRequest);
|
|
||||||
// 返回上传结果
|
// 返回上传结果
|
||||||
FileUploadVO uploadVO = new FileUploadVO();
|
FileUploadVO uploadVO = new FileUploadVO();
|
||||||
uploadVO.setFileName(originalFileName);
|
uploadVO.setFileName(originalFileName);
|
||||||
|
@ -3,11 +3,12 @@ package net.lab1024.sa.base.module.support.operatelog.core;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.lab1024.sa.base.common.constant.StringConst;
|
import net.lab1024.sa.base.common.constant.StringConst;
|
||||||
import net.lab1024.sa.base.common.domain.RequestUser;
|
import net.lab1024.sa.base.common.domain.RequestUser;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
||||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
||||||
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
|
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
|
||||||
@ -20,7 +21,6 @@ import org.aspectj.lang.annotation.AfterThrowing;
|
|||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.context.properties.bind.BindResult;
|
import org.springframework.boot.context.properties.bind.BindResult;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
@ -36,7 +36,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.BindException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@ -48,7 +47,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
* @Date 2021-12-08 20:48:52
|
* @Date 2021-12-08 20:48:52
|
||||||
* @Wechat zhuoda1024
|
* @Wechat zhuoda1024
|
||||||
* @Email lab1024@163.com
|
* @Email lab1024@163.com
|
||||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Aspect
|
@Aspect
|
||||||
@ -73,14 +72,14 @@ public abstract class OperateLogAspect {
|
|||||||
public void logPointCut() {
|
public void logPointCut() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterReturning(pointcut = "logPointCut()")
|
@AfterReturning(pointcut = "logPointCut()", returning = "responseDTO")
|
||||||
public void doAfterReturning(JoinPoint joinPoint) {
|
public void doAfterReturning(JoinPoint joinPoint, Object responseDTO) {
|
||||||
handleLog(joinPoint, null);
|
handleLog(joinPoint, null, responseDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
||||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
||||||
handleLog(joinPoint, e);
|
handleLog(joinPoint, e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,16 +110,15 @@ public abstract class OperateLogAspect {
|
|||||||
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
|
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleLog(final JoinPoint joinPoint, final Exception e) {
|
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object responseDTO) {
|
||||||
try {
|
try {
|
||||||
OperateLog operateLog = this.getAnnotationLog(joinPoint);
|
OperateLog operateLog = this.getAnnotationLog(joinPoint);
|
||||||
if (operateLog == null) {
|
if (operateLog == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.submitLog(joinPoint, e);
|
this.submitLog(joinPoint, e, responseDTO);
|
||||||
} catch (Exception exp) {
|
} catch (Exception exp) {
|
||||||
log.error("保存操作日志异常:{}", exp.getMessage());
|
log.error("保存操作日志异常:{}", exp.getMessage());
|
||||||
exp.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,11 +173,8 @@ public abstract class OperateLogAspect {
|
|||||||
/**
|
/**
|
||||||
* 提交存储操作日志
|
* 提交存储操作日志
|
||||||
*
|
*
|
||||||
* @param joinPoint
|
|
||||||
* @param e
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
*/
|
||||||
private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception {
|
private void submitLog(final JoinPoint joinPoint, final Throwable e, Object responseDTO) {
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||||
//设置用户信息
|
//设置用户信息
|
||||||
RequestUser user = SmartRequestUtil.getRequestUser();
|
RequestUser user = SmartRequestUtil.getRequestUser();
|
||||||
@ -193,7 +188,7 @@ public abstract class OperateLogAspect {
|
|||||||
String methodName = joinPoint.getSignature().getName();
|
String methodName = joinPoint.getSignature().getName();
|
||||||
String operateMethod = className + "." + methodName;
|
String operateMethod = className + "." + methodName;
|
||||||
String failReason = null;
|
String failReason = null;
|
||||||
Boolean successFlag = true;
|
boolean successFlag = true;
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
successFlag = false;
|
successFlag = false;
|
||||||
failReason = getExceptionString(e);
|
failReason = getExceptionString(e);
|
||||||
@ -212,15 +207,32 @@ public abstract class OperateLogAspect {
|
|||||||
.userAgent(user.getUserAgent())
|
.userAgent(user.getUserAgent())
|
||||||
.failReason(failReason)
|
.failReason(failReason)
|
||||||
.successFlag(successFlag).build();
|
.successFlag(successFlag).build();
|
||||||
|
|
||||||
Operation apiOperation = this.getApiOperation(joinPoint);
|
Operation apiOperation = this.getApiOperation(joinPoint);
|
||||||
if (apiOperation != null) {
|
if (apiOperation != null) {
|
||||||
operateLogEntity.setContent(apiOperation.summary());
|
operateLogEntity.setContent(apiOperation.summary());
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag api = this.getApi(joinPoint);
|
Tag api = this.getApi(joinPoint);
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
String name = api.name();
|
String name = api.name();
|
||||||
operateLogEntity.setModule(StrUtil.join(",", name));
|
operateLogEntity.setModule(StrUtil.join(",", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理返回值 ResponseDTO
|
||||||
|
if(responseDTO instanceof ResponseDTO) {
|
||||||
|
ResponseDTO response = (ResponseDTO) responseDTO;
|
||||||
|
ResponseDTO logResponseDTO = new ResponseDTO(
|
||||||
|
response.getCode(),
|
||||||
|
response.getLevel(),
|
||||||
|
response.getOk(),
|
||||||
|
response.getMsg(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
logResponseDTO.setDataType(response.getDataType());
|
||||||
|
operateLogEntity.setResponse(JSON.toJSONString(logResponseDTO));
|
||||||
|
}
|
||||||
|
|
||||||
taskExecutor.execute(() -> {
|
taskExecutor.execute(() -> {
|
||||||
this.saveLog(operateLogEntity);
|
this.saveLog(operateLogEntity);
|
||||||
});
|
});
|
||||||
|
@ -71,6 +71,11 @@ public class OperateLogEntity {
|
|||||||
*/
|
*/
|
||||||
private String param;
|
private String param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回值
|
||||||
|
*/
|
||||||
|
private String response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户ip
|
* 客户ip
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +47,9 @@ public class OperateLogVO {
|
|||||||
@Schema(description = "请求参数")
|
@Schema(description = "请求参数")
|
||||||
private String param;
|
private String param;
|
||||||
|
|
||||||
|
@Schema(description = "返回值")
|
||||||
|
private String response;
|
||||||
|
|
||||||
@Schema(description = "客户ip")
|
@Schema(description = "客户ip")
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.redis;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.convert.DurationStyle;
|
||||||
|
import org.springframework.data.redis.cache.*;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static net.lab1024.sa.base.common.constant.StringConst.COLON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 RedisCacheManager,支持在 cacheName 中通过 '#' 指定 TTL(过期时间)。
|
||||||
|
*
|
||||||
|
* @Author CoderKK
|
||||||
|
* @Date 2025-08-15 13:01:01
|
||||||
|
* <p>
|
||||||
|
* 支持格式:{@code cacheName#ttl},其中 ttl 支持 Spring 的 Duration 格式。
|
||||||
|
* 特殊值:{@code -1} 表示永久缓存(永不过期)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <h3>使用示例:</h3>
|
||||||
|
* <pre>
|
||||||
|
* // 10 秒后过期
|
||||||
|
* @Cacheable(value = "user#10s", key = "#id")
|
||||||
|
* // 2 小时后过期
|
||||||
|
* @Cacheable(value = "report#2h", key = "#date")
|
||||||
|
* // 30 分钟后过期
|
||||||
|
* @Cacheable(value = "session#30m", key = "#token")
|
||||||
|
* // 永不过期(永久缓存),适用于极少变化的配置数据
|
||||||
|
* @Cacheable(value = "appConfig#-1", key = "'globalSettings'")
|
||||||
|
* // 无 TTL,使用全局默认过期时间(如 7 天)
|
||||||
|
* @Cacheable(value = "product", key = "#productId")
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h3>生成的 Redis Key 格式:</h3>
|
||||||
|
* <pre>
|
||||||
|
* cache:cacheName:key
|
||||||
|
* 例如:cache:user:123
|
||||||
|
* cache:appConfig:globalSettings
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h3>支持的 TTL 单位:</h3>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code ms} / {@code millis} / {@code milliseconds} - 毫秒</li>
|
||||||
|
* <li>{@code s} / {@code secs} / {@code seconds} - 秒</li>
|
||||||
|
* <li>{@code m} / {@code mins} / {@code minutes} - 分钟</li>
|
||||||
|
* <li>{@code h} / {@code hrs} / {@code hours} - 小时</li>
|
||||||
|
* <li>{@code d} / {@code days} - 天</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h3>注意事项:</h3>
|
||||||
|
* <ul>
|
||||||
|
* <li>不写单位默认为毫秒</li>
|
||||||
|
* <li>永久缓存(#-1)不会自动过期,请配合 @CacheEvict 手动清理。</li>
|
||||||
|
* <li>避免对频繁更新的数据使用永久缓存,防止数据陈旧。</li>
|
||||||
|
* <li>cacheName 中的 '#' 只解析第一个,后续字符将作为 TTL 处理。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CustomRedisCacheManager extends RedisCacheManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存全局前缀
|
||||||
|
*/
|
||||||
|
private static final String CACHE_PREFIX = "cache";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 TTL 分隔符,用于在 cacheName 后附加过期时间
|
||||||
|
*/
|
||||||
|
private static final String CUSTOM_TTL_SEPARATOR = "#";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认缓存过期时间:7 天
|
||||||
|
*/
|
||||||
|
private static final Duration DEFAULT_TTL = Duration.ofDays(7);
|
||||||
|
|
||||||
|
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
|
||||||
|
super(cacheWriter, defaultCacheConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 RedisCache 实例,支持从 cacheName 解析 TTL
|
||||||
|
*
|
||||||
|
* @param name 缓存名称(支持 name#ttl 格式)
|
||||||
|
* @param cacheConfig 默认缓存配置
|
||||||
|
* @return RedisCache
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
|
||||||
|
Duration ttl = parseTtlFromCacheName(name);
|
||||||
|
if (ttl == null) {
|
||||||
|
ttl = DEFAULT_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheKeyPrefix keyPrefix = cacheName -> {
|
||||||
|
if (StrUtil.isBlank(cacheName)) {
|
||||||
|
return CACHE_PREFIX + COLON;
|
||||||
|
}
|
||||||
|
String[] parts = cacheName.split(CUSTOM_TTL_SEPARATOR, 2);
|
||||||
|
String cleanName = StrUtil.trim(parts[0]);
|
||||||
|
return CACHE_PREFIX + COLON + cleanName + COLON;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建最终缓存配置:设置 key 前缀 + TTL
|
||||||
|
RedisCacheConfiguration config = cacheConfig.computePrefixWith(keyPrefix).entryTtl(ttl);
|
||||||
|
|
||||||
|
return super.createRedisCache(name, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 cacheName 中解析 TTL
|
||||||
|
*
|
||||||
|
* @param name 缓存名称,格式如:users#10m, products#2h, config#-1(永久)
|
||||||
|
* @return 解析出的 Duration若无效则返回 null;若为 -1,则返回 Duration.ofMillis(-1) 表示永久缓存
|
||||||
|
*/
|
||||||
|
private Duration parseTtlFromCacheName(String name) {
|
||||||
|
if (StrUtil.isBlank(name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = name.split(CUSTOM_TTL_SEPARATOR, 2);
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return null; // 无 TTL 部分
|
||||||
|
}
|
||||||
|
|
||||||
|
String ttlStr = StrUtil.trim(parts[1]);
|
||||||
|
if (StrUtil.isBlank(ttlStr)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊处理:-1 表示永久缓存
|
||||||
|
if ("-1".equals(ttlStr)) {
|
||||||
|
return Duration.ofMillis(-1); // Spring Redis 中负数 Duration 表示永不过期
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Duration ttl = DurationStyle.detectAndParse(ttlStr);
|
||||||
|
return ttl.getSeconds() > 0 ? ttl : null;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("解析缓存 TTL 失败,cacheName='{}', ttl='{}', 错误: {}", name, ttlStr, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
@ -106,7 +106,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Dict")
|
#if($field.queryTypeEnum == "Dict")
|
||||||
<a-form-item label="${field.label}" class="smart-query-form-item">
|
<a-form-item label="${field.label}" class="smart-query-form-item">
|
||||||
<DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Enum")
|
#if($field.queryTypeEnum == "Enum")
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
@ -106,7 +106,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "DictSelect")
|
#if($field.frontComponent == "DictSelect")
|
||||||
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
|
||||||
<DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.frontComponent == "Date")
|
#if($field.frontComponent == "Date")
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Dict")
|
#if($field.queryTypeEnum == "Dict")
|
||||||
<a-form-item label="${field.label}" class="smart-query-form-item">
|
<a-form-item label="${field.label}" class="smart-query-form-item">
|
||||||
<DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
#end
|
#end
|
||||||
#if($field.queryTypeEnum == "Enum")
|
#if($field.queryTypeEnum == "Enum")
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
|
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
|
||||||
</if>
|
</if>
|
||||||
<if test="query.requestKeywords != null and query.requestKeywords != ''">
|
<if test="query.requestKeywords != null and query.requestKeywords != ''">
|
||||||
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}))
|
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}) OR INSTR(response,#{query.requestKeywords}))
|
||||||
</if>
|
</if>
|
||||||
<if test="query.successFlag != null">
|
<if test="query.successFlag != null">
|
||||||
AND success_flag = #{query.successFlag}
|
AND success_flag = #{query.successFlag}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"axios": "1.6.8",
|
"axios": "1.6.8",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"crypto-js": "4.1.1",
|
"crypto-js": "4.1.1",
|
||||||
"dayjs": "1.10.5",
|
"dayjs": "1.11.13",
|
||||||
"decimal.js": "10.3.1",
|
"decimal.js": "10.3.1",
|
||||||
"diff": "5.2.0",
|
"diff": "5.2.0",
|
||||||
"diff2html": "3.4.47",
|
"diff2html": "3.4.47",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"vue": "3.4.27",
|
"vue": "3.4.27",
|
||||||
"vue-i18n": "9.13.1",
|
"vue-i18n": "9.13.1",
|
||||||
"vue-router": "4.3.2",
|
"vue-router": "4.3.2",
|
||||||
"vue3-json-viewer": "2.2.2"
|
"vue3-json-viewer": "2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">
|
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">
|
||||||
|
@ -71,7 +71,6 @@
|
|||||||
v-model:pageSize="params.pageSize"
|
v-model:pageSize="params.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryEmployee"
|
@change="queryEmployee"
|
||||||
@showSizeChange="queryEmployee"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,7 @@ export const appDefaultConfig = {
|
|||||||
// 圆角
|
// 圆角
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
// 菜单展开模式
|
// 菜单展开模式
|
||||||
flatPattern: false,
|
menuSingleExpandFlag: true,
|
||||||
// 标签页
|
// 标签页
|
||||||
pageTagFlag: true,
|
pageTagFlag: true,
|
||||||
// 标签页样式: default、 antd、chrome
|
// 标签页样式: default、 antd、chrome
|
||||||
|
@ -21,11 +21,11 @@ export default {
|
|||||||
'setting.menu.layout': 'Menu Layout',
|
'setting.menu.layout': 'Menu Layout',
|
||||||
'setting.menu.width': 'Menu Width',
|
'setting.menu.width': 'Menu Width',
|
||||||
'setting.menu.theme': 'Menu Theme',
|
'setting.menu.theme': 'Menu Theme',
|
||||||
|
'setting.menu.expand': 'Menu Expand',
|
||||||
'setting.page.width': 'Page Width',
|
'setting.page.width': 'Page Width',
|
||||||
'setting.border.radius': 'Border Radius',
|
'setting.border.radius': 'Border Radius',
|
||||||
'setting.compact': 'Page Compact',
|
'setting.compact': 'Page Compact',
|
||||||
'setting.bread': 'Show Bread',
|
'setting.bread': 'Show Bread',
|
||||||
'setting.flatPattern': 'Flat Pattern',
|
|
||||||
'setting.pagetag': 'Show PageTag',
|
'setting.pagetag': 'Show PageTag',
|
||||||
'setting.pagetag.style': 'PageTag Style',
|
'setting.pagetag.style': 'PageTag Style',
|
||||||
'setting.footer': 'Show Footer',
|
'setting.footer': 'Show Footer',
|
||||||
|
@ -21,11 +21,11 @@ export default {
|
|||||||
'setting.menu.layout': '菜单布局',
|
'setting.menu.layout': '菜单布局',
|
||||||
'setting.menu.width': '菜单宽度',
|
'setting.menu.width': '菜单宽度',
|
||||||
'setting.menu.theme': '菜单主题',
|
'setting.menu.theme': '菜单主题',
|
||||||
|
'setting.menu.expand': '菜单展开',
|
||||||
'setting.compact': '页面紧凑',
|
'setting.compact': '页面紧凑',
|
||||||
'setting.border.radius': '页面圆角',
|
'setting.border.radius': '页面圆角',
|
||||||
'setting.page.width': '页面宽度',
|
'setting.page.width': '页面宽度',
|
||||||
'setting.bread': '面包屑',
|
'setting.bread': '面包屑',
|
||||||
'setting.flatPattern': '菜单展开模式',
|
|
||||||
'setting.pagetag': '标签页',
|
'setting.pagetag': '标签页',
|
||||||
'setting.pagetag.style': '标签页样式',
|
'setting.pagetag.style': '标签页样式',
|
||||||
'setting.footer': '页脚',
|
'setting.footer': '页脚',
|
||||||
|
@ -83,8 +83,8 @@
|
|||||||
<a-radio-button value="chrome">Chrome</a-radio-button>
|
<a-radio-button value="chrome">Chrome</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('setting.flatPattern')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
<a-form-item :label="$t('setting.menu.expand')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
||||||
<a-switch @change="changeFlatPattern" v-model:checked="formState.flatPattern" checked-children="多个" un-checked-children="单个" />
|
<a-switch @change="changeMenuExpandFlag" v-model:checked="formState.menuSingleExpandFlag" checked-children="单个" un-checked-children="多个" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('setting.pagetag')">
|
<a-form-item :label="$t('setting.pagetag')">
|
||||||
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
|
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||||
@ -213,8 +213,8 @@
|
|||||||
borderRadius: appConfigStore.borderRadius,
|
borderRadius: appConfigStore.borderRadius,
|
||||||
// 标签页
|
// 标签页
|
||||||
pageTagFlag: appConfigStore.pageTagFlag,
|
pageTagFlag: appConfigStore.pageTagFlag,
|
||||||
// 标签页
|
// 菜单展开方式
|
||||||
flatPattern: appConfigStore.flatPattern,
|
menuSingleExpandFlag: appConfigStore.menuSingleExpandFlag,
|
||||||
// 标签页 样式
|
// 标签页 样式
|
||||||
pageTagStyle: appConfigStore.pageTagStyle,
|
pageTagStyle: appConfigStore.pageTagStyle,
|
||||||
// 面包屑
|
// 面包屑
|
||||||
@ -303,9 +303,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeFlatPattern(e) {
|
function changeMenuExpandFlag(e) {
|
||||||
appConfigStore.$patch({
|
appConfigStore.$patch({
|
||||||
flatPattern: e,
|
menuSingleExpandFlag: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import { useUserStore } from '/@/store/modules/system/user';
|
||||||
|
|
||||||
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
|
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
|
||||||
const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
|
const menuSingleExpandFlag = computed(() => useAppConfigStore().$state.menuSingleExpandFlag);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
collapsed: {
|
collapsed: {
|
||||||
@ -46,8 +46,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const menuTree = computed(() => useUserStore().getMenuTree || []);
|
const menuTree = computed(() => useUserStore().getMenuTree || []);
|
||||||
const rootSubmenuKeys = computed(()=>menuTree.value.map(item=>item.menuId));
|
const rootSubmenuKeys = computed(() => menuTree.value.map((item) => item.menuId));
|
||||||
|
|
||||||
|
|
||||||
//展开的菜单
|
//展开的菜单
|
||||||
let currentRoute = useRoute();
|
let currentRoute = useRoute();
|
||||||
@ -76,9 +75,15 @@
|
|||||||
let parentList = menuParentIdListMap.get(currentRoute.name) || [];
|
let parentList = menuParentIdListMap.get(currentRoute.name) || [];
|
||||||
|
|
||||||
// 如果是折叠菜单的话,则不需要设置openkey
|
// 如果是折叠菜单的话,则不需要设置openkey
|
||||||
if (!props.collapsed) {
|
if (props.collapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let needOpenKeys = _.map(parentList, 'name').map(Number);
|
||||||
|
if (menuSingleExpandFlag.value) {
|
||||||
|
openKeys.value = [...needOpenKeys];
|
||||||
|
} else {
|
||||||
// 使用lodash的union函数,进行 去重合并两个数组
|
// 使用lodash的union函数,进行 去重合并两个数组
|
||||||
let needOpenKeys = _.map(parentList, 'name').map(Number);
|
|
||||||
openKeys.value = _.union(openKeys.value, needOpenKeys);
|
openKeys.value = _.union(openKeys.value, needOpenKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,17 +97,18 @@
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
function onOpenChange(openKeysParams){
|
|
||||||
if(flatPattern.value){
|
function onOpenChange(openKeysParams) {
|
||||||
return;
|
if (!menuSingleExpandFlag.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const latestOpenKey = openKeysParams.find((key) => openKeys.value.indexOf(key) === -1);
|
||||||
|
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
|
||||||
|
openKeys.value = openKeysParams;
|
||||||
|
} else {
|
||||||
|
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const latestOpenKey = openKeysParams.find(key => openKeys.value.indexOf(key) === -1);
|
|
||||||
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
|
|
||||||
openKeys.value = openKeysParams;
|
|
||||||
} else {
|
|
||||||
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
updateOpenKeysAndSelectKeys,
|
updateOpenKeysAndSelectKeys,
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
*/
|
*/
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||||
|
|
||||||
export const useSpinStore = defineStore({
|
export const useSpinStore = defineStore({
|
||||||
id: 'spin',
|
id: 'spin',
|
||||||
@ -18,13 +19,27 @@ export const useSpinStore = defineStore({
|
|||||||
actions: {
|
actions: {
|
||||||
hide() {
|
hide() {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
let spins = document.querySelector('.ant-spin-nested-loading');
|
// 安全的DOM操作,避免null引用错误
|
||||||
spins.style.zIndex = 999;
|
try {
|
||||||
|
const spins = document.querySelector('.ant-spin-nested-loading');
|
||||||
|
if (spins) {
|
||||||
|
spins.style.zIndex = '999';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
smartSentry.captureError('Spin hide操作失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show() {
|
show() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let spins = document.querySelector('.ant-spin-nested-loading');
|
// 安全的DOM操作,避免null引用错误
|
||||||
spins.style.zIndex = 1001;
|
try {
|
||||||
|
const spins = document.querySelector('.ant-spin-nested-loading');
|
||||||
|
if (spins) {
|
||||||
|
spins.style.zIndex = '1001';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
smartSentry.captureError('Spin hide操作失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -149,7 +149,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryEmployee"
|
@change="queryEmployee"
|
||||||
@showSizeChange="queryEmployee"
|
|
||||||
:show-total="showTableTotal"
|
:show-total="showTableTotal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,7 +102,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryViewRecord"
|
@change="queryViewRecord"
|
||||||
@showSizeChange="queryViewRecord"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +72,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryNoticeList"
|
@change="queryNoticeList"
|
||||||
@showSizeChange="queryNoticeList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,7 +122,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryNoticeList"
|
@change="queryNoticeList"
|
||||||
@showSizeChange="queryNoticeList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +107,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,7 +74,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,12 +3,16 @@ import { convertUpperCamel } from '/@/utils/str-util';
|
|||||||
// -------------------------------- java 类型 --------------------------------
|
// -------------------------------- java 类型 --------------------------------
|
||||||
export const JavaTypeMap = new Map();
|
export const JavaTypeMap = new Map();
|
||||||
JavaTypeMap.set('bit', 'Boolean');
|
JavaTypeMap.set('bit', 'Boolean');
|
||||||
|
JavaTypeMap.set('bool', 'Boolean');
|
||||||
JavaTypeMap.set('int', 'Integer');
|
JavaTypeMap.set('int', 'Integer');
|
||||||
|
JavaTypeMap.set('int2', 'Integer');
|
||||||
|
JavaTypeMap.set('int4', 'Integer');
|
||||||
JavaTypeMap.set('tinyint', 'Integer');
|
JavaTypeMap.set('tinyint', 'Integer');
|
||||||
JavaTypeMap.set('smallint', 'Integer');
|
JavaTypeMap.set('smallint', 'Integer');
|
||||||
JavaTypeMap.set('integer', 'Integer');
|
JavaTypeMap.set('integer', 'Integer');
|
||||||
JavaTypeMap.set('year', 'Integer');
|
JavaTypeMap.set('year', 'Integer');
|
||||||
JavaTypeMap.set('bigint', 'Long');
|
JavaTypeMap.set('bigint', 'Long');
|
||||||
|
JavaTypeMap.set('int8', 'Long');
|
||||||
JavaTypeMap.set('float', 'BigDecimal');
|
JavaTypeMap.set('float', 'BigDecimal');
|
||||||
JavaTypeMap.set('double', 'BigDecimal');
|
JavaTypeMap.set('double', 'BigDecimal');
|
||||||
JavaTypeMap.set('decimal', 'BigDecimal');
|
JavaTypeMap.set('decimal', 'BigDecimal');
|
||||||
@ -20,6 +24,7 @@ JavaTypeMap.set('longtext', 'String');
|
|||||||
JavaTypeMap.set('blob', 'String');
|
JavaTypeMap.set('blob', 'String');
|
||||||
JavaTypeMap.set('date', 'LocalDate');
|
JavaTypeMap.set('date', 'LocalDate');
|
||||||
JavaTypeMap.set('datetime', 'LocalDateTime');
|
JavaTypeMap.set('datetime', 'LocalDateTime');
|
||||||
|
JavaTypeMap.set('timestamp', 'LocalDateTime');
|
||||||
|
|
||||||
export const JavaTypeList = [
|
export const JavaTypeList = [
|
||||||
'Boolean', //
|
'Boolean', //
|
||||||
@ -39,7 +44,11 @@ export function getJavaType(dataType) {
|
|||||||
// -------------------------------- js 类型 --------------------------------
|
// -------------------------------- js 类型 --------------------------------
|
||||||
export const JsTypeMap = new Map();
|
export const JsTypeMap = new Map();
|
||||||
JsTypeMap.set('bit', 'Boolean');
|
JsTypeMap.set('bit', 'Boolean');
|
||||||
|
JsTypeMap.set('bool', 'Boolean');
|
||||||
JsTypeMap.set('int', 'Number');
|
JsTypeMap.set('int', 'Number');
|
||||||
|
JsTypeMap.set('int2', 'Number');
|
||||||
|
JsTypeMap.set('int4', 'Number');
|
||||||
|
JsTypeMap.set('int8', 'Number');
|
||||||
JsTypeMap.set('tinyint', 'Number');
|
JsTypeMap.set('tinyint', 'Number');
|
||||||
JsTypeMap.set('smallint', 'Number');
|
JsTypeMap.set('smallint', 'Number');
|
||||||
JsTypeMap.set('integer', 'Number');
|
JsTypeMap.set('integer', 'Number');
|
||||||
@ -50,12 +59,14 @@ JsTypeMap.set('double', 'Number');
|
|||||||
JsTypeMap.set('decimal', 'Number');
|
JsTypeMap.set('decimal', 'Number');
|
||||||
JsTypeMap.set('char', 'String');
|
JsTypeMap.set('char', 'String');
|
||||||
JsTypeMap.set('varchar', 'String');
|
JsTypeMap.set('varchar', 'String');
|
||||||
|
JsTypeMap.set('character', 'String');
|
||||||
JsTypeMap.set('tinytext', 'String');
|
JsTypeMap.set('tinytext', 'String');
|
||||||
JsTypeMap.set('text', 'String');
|
JsTypeMap.set('text', 'String');
|
||||||
JsTypeMap.set('longtext', 'String');
|
JsTypeMap.set('longtext', 'String');
|
||||||
JsTypeMap.set('blob', 'String');
|
JsTypeMap.set('blob', 'String');
|
||||||
JsTypeMap.set('date', 'Date');
|
JsTypeMap.set('date', 'Date');
|
||||||
JsTypeMap.set('datetime', 'Date');
|
JsTypeMap.set('datetime', 'Date');
|
||||||
|
JsTypeMap.set('timestamp', 'Date');
|
||||||
|
|
||||||
export const JsTypeList = [
|
export const JsTypeList = [
|
||||||
'Boolean', //
|
'Boolean', //
|
||||||
@ -72,17 +83,23 @@ export function getJsType(dataType) {
|
|||||||
|
|
||||||
export const FrontComponentMap = new Map();
|
export const FrontComponentMap = new Map();
|
||||||
FrontComponentMap.set('bit', 'BooleanSelect');
|
FrontComponentMap.set('bit', 'BooleanSelect');
|
||||||
|
FrontComponentMap.set('bool', 'BooleanSelect');
|
||||||
FrontComponentMap.set('int', 'InputNumber');
|
FrontComponentMap.set('int', 'InputNumber');
|
||||||
|
FrontComponentMap.set('int2', 'InputNumber');
|
||||||
|
FrontComponentMap.set('int4', 'InputNumber');
|
||||||
|
FrontComponentMap.set('int8', 'InputNumber');
|
||||||
FrontComponentMap.set('tinyint', 'InputNumber');
|
FrontComponentMap.set('tinyint', 'InputNumber');
|
||||||
FrontComponentMap.set('smallint', 'InputNumber');
|
FrontComponentMap.set('smallint', 'InputNumber');
|
||||||
FrontComponentMap.set('integer', 'InputNumber');
|
FrontComponentMap.set('integer', 'InputNumber');
|
||||||
FrontComponentMap.set('year', 'Date');
|
FrontComponentMap.set('year', 'Date');
|
||||||
|
FrontComponentMap.set('timestamp', 'Date');
|
||||||
FrontComponentMap.set('bigint', 'InputNumber');
|
FrontComponentMap.set('bigint', 'InputNumber');
|
||||||
FrontComponentMap.set('float', 'InputNumber');
|
FrontComponentMap.set('float', 'InputNumber');
|
||||||
FrontComponentMap.set('double', 'InputNumber');
|
FrontComponentMap.set('double', 'InputNumber');
|
||||||
FrontComponentMap.set('decimal', 'InputNumber');
|
FrontComponentMap.set('decimal', 'InputNumber');
|
||||||
FrontComponentMap.set('char', 'Input');
|
FrontComponentMap.set('char', 'Input');
|
||||||
FrontComponentMap.set('varchar', 'Input');
|
FrontComponentMap.set('varchar', 'Input');
|
||||||
|
FrontComponentMap.set('character', 'Input');
|
||||||
FrontComponentMap.set('tinytext', 'Input');
|
FrontComponentMap.set('tinytext', 'Input');
|
||||||
FrontComponentMap.set('text', 'Textarea');
|
FrontComponentMap.set('text', 'Textarea');
|
||||||
FrontComponentMap.set('longtext', 'Textarea');
|
FrontComponentMap.set('longtext', 'Textarea');
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,7 +64,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryList"
|
@change="queryList"
|
||||||
@showSizeChange="queryList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,7 +103,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,7 +88,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryHelpDocList"
|
@change="queryHelpDocList"
|
||||||
@showSizeChange="queryHelpDocList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryViewRecord"
|
@change="queryViewRecord"
|
||||||
@showSizeChange="queryViewRecord"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,7 +117,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryJobList"
|
@change="queryJobList"
|
||||||
@showSizeChange="queryJobList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +71,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryLogList"
|
@change="queryLogList"
|
||||||
@showSizeChange="queryLogList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -136,7 +136,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryJobList"
|
@change="queryJobList"
|
||||||
@showSizeChange="queryJobList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,7 +102,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="onSearch"
|
@change="onSearch"
|
||||||
@showSizeChange="onSearch"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,7 +89,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +47,6 @@
|
|||||||
v-model:pageSize="queryParam.pageSize"
|
v-model:pageSize="queryParam.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryList"
|
@change="queryList"
|
||||||
@showSizeChange="queryList"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<a-row class="smart-margin-top10">
|
<a-row class="smart-margin-top10">
|
||||||
<a-col :span="16">
|
<a-col :span="16">
|
||||||
|
<a-row class="detail-info">
|
||||||
|
<a-col :span="12"> 用户id:{{ detail.operateUserId }}</a-col>
|
||||||
|
<a-col :span="12"> 用户名称: {{ detail.operateUserName }}</a-col>
|
||||||
|
</a-row>
|
||||||
<a-row class="detail-info">
|
<a-row class="detail-info">
|
||||||
<a-col :span="12"> 请求url: {{ detail.url }}</a-col>
|
<a-col :span="12"> 请求url: {{ detail.url }}</a-col>
|
||||||
<a-col :span="12"> 请求日期: {{ detail.createTime }}</a-col>
|
<a-col :span="12"> 请求日期: {{ detail.createTime }}</a-col>
|
||||||
@ -21,8 +25,7 @@
|
|||||||
<a-col :span="12"> IP地区: {{ detail.ipRegion }}</a-col>
|
<a-col :span="12"> IP地区: {{ detail.ipRegion }}</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row class="detail-info">
|
<a-row class="detail-info">
|
||||||
<a-col :span="12"> 用户id:{{ detail.operateUserId }}</a-col>
|
<a-col :span="12"> 客户端: {{ detail.os }} / {{ detail.browser }} {{ detail.device ? '/' + detail.device : detail.device }}</a-col>
|
||||||
<a-col :span="12"> 用户名称: {{ detail.operateUserName }}</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
@ -32,21 +35,26 @@
|
|||||||
</a-typography-text>
|
</a-typography-text>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
<a-row class="detail-info">
|
||||||
<div class="info-box">
|
<a-col :span="24"> 方法: {{ detail.method }}</a-col>
|
||||||
<h4>请求明细:</h4>
|
</a-row>
|
||||||
<a-col :span="24"> 方法: {{ detail.method }}</a-col>
|
<a-row class="detail-info">
|
||||||
<a-col :span="24"> 说明: {{ detail.module }} - {{ detail.content }}</a-col>
|
<a-col :span="24"> 说明: {{ detail.module }} - {{ detail.content }}</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h4>请求参数:</h4>
|
<h4>请求参数:</h4>
|
||||||
<JsonViewer :value="detail.param ? JSON.parse(detail.param) : ''" theme="jv-dark" copyable boxed sort />
|
<JsonViewer :value="detail.param ? JSON.parse(detail.param) : ''" :expanded="true" :expandDepth="10" copyable boxed sort theme="light" />
|
||||||
|
</div>
|
||||||
|
<div class="info-box" v-if="detail.successFlag">
|
||||||
|
<h4>返回结果:</h4>
|
||||||
|
<JsonViewer :value="detail.response ? JSON.parse(detail.response) : ''" :expanded="true" :expandDepth="10" copyable boxed sort theme="light" />
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box" v-if="detail.failReason">
|
<div class="info-box" v-if="detail.failReason">
|
||||||
<h4>请求失败原因:</h4>
|
<h4>请求失败原因:</h4>
|
||||||
<div>
|
<a-card>
|
||||||
{{ detail.failReason }}
|
{{ detail.failReason }}
|
||||||
</div>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
@ -57,6 +65,7 @@
|
|||||||
import { operateLogApi } from '/@/api/support/operate-log-api';
|
import { operateLogApi } from '/@/api/support/operate-log-api';
|
||||||
import { smartSentry } from '/@/lib/smart-sentry';
|
import { smartSentry } from '/@/lib/smart-sentry';
|
||||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||||
|
import uaparser from 'ua-parser-js';
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show,
|
show,
|
||||||
@ -87,11 +96,16 @@
|
|||||||
param: '',
|
param: '',
|
||||||
url: '',
|
url: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getDetail(operateLogId) {
|
async function getDetail(operateLogId) {
|
||||||
try {
|
try {
|
||||||
SmartLoading.show();
|
SmartLoading.show();
|
||||||
let res = await operateLogApi.detail(operateLogId);
|
let res = await operateLogApi.detail(operateLogId);
|
||||||
detail = Object.assign(detail, res.data);
|
detail = Object.assign(detail, res.data);
|
||||||
|
let ua = uaparser(res.data.userAgent);
|
||||||
|
detail.browser = ua.browser.name;
|
||||||
|
detail.os = ua.os.name;
|
||||||
|
detail.device = ua.device.vendor ? ua.device.vendor + ua.device.model : '';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
smartSentry.captureError(e);
|
smartSentry.captureError(e);
|
||||||
} finally {
|
} finally {
|
||||||
@ -107,10 +121,11 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box {
|
.info-box {
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
padding: 10px 8px;
|
padding: 10px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-info {
|
.detail-info {
|
||||||
.ant-col {
|
.ant-col {
|
||||||
line-height: 1.46;
|
line-height: 1.46;
|
||||||
@ -118,6 +133,7 @@
|
|||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-right-title {
|
.detail-right-title {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: grey;
|
color: grey;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" />
|
<a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="请求关键字" class="smart-query-form-item">
|
<a-form-item label="请求关键字" class="smart-query-form-item">
|
||||||
<a-input style="width: 220px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数" />
|
<a-input style="width: 270px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数/返回结果" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="用户名称" class="smart-query-form-item">
|
<a-form-item label="用户名称" class="smart-query-form-item">
|
||||||
<a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
<a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" />
|
<a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="快速筛选" class="smart-query-form-item">
|
<a-form-item label="状态:" class="smart-query-form-item">
|
||||||
<a-radio-group v-model:value="queryForm.successFlag" @change="onSearch">
|
<a-radio-group v-model:value="queryForm.successFlag" @change="onSearch">
|
||||||
<a-radio-button :value="undefined">全部</a-radio-button>
|
<a-radio-button :value="undefined">全部</a-radio-button>
|
||||||
<a-radio-button :value="true">成功</a-radio-button>
|
<a-radio-button :value="true">成功</a-radio-button>
|
||||||
@ -51,18 +51,23 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<a-card size="small" :bordered="false" :hoverable="true" style="height: 100%">
|
<a-card size="small" :bordered="false" :hoverable="true" >
|
||||||
<a-row justify="end">
|
<a-row justify="end">
|
||||||
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.CONFIG" :refresh="ajaxQuery" />
|
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.CONFIG" :refresh="ajaxQuery" />
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-table size="small" :loading="tableLoading" :dataSource="tableData" :columns="columns" bordered rowKey="operateLogId" :pagination="false">
|
<a-table size="small" :loading="tableLoading" :dataSource="tableData" :columns="columns" bordered rowKey="operateLogId" :pagination="false">
|
||||||
<template #bodyCell="{ text, record, column }">
|
<template #bodyCell="{ text, record, column }">
|
||||||
|
<template v-if="column.dataIndex === 'response'">
|
||||||
|
<a-typography-text v-if="text && text.ok">{{ text ? text.msg : '-' }}</a-typography-text>
|
||||||
|
<a-typography-text v-else type="warning">{{ text ? text.msg : '-' }}</a-typography-text>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="column.dataIndex === 'successFlag'">
|
<template v-if="column.dataIndex === 'successFlag'">
|
||||||
<a-tag :color="text ? 'success' : 'error'">{{ text ? '成功' : '失败' }}</a-tag>
|
<a-tag :color="text ? 'success' : 'error'">{{ text ? '成功' : '报错' }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="column.dataIndex === 'userAgent'">
|
<template v-if="column.dataIndex === 'userAgent'">
|
||||||
<div>{{ record.browser }} / {{ record.os }} / {{ record.device }}</div>
|
<div>{{ record.os }} / {{ record.browser }} {{ record.device ? '/' + record.device : record.device }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="column.dataIndex === 'operateUserType'">
|
<template v-if="column.dataIndex === 'operateUserType'">
|
||||||
@ -88,7 +93,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -135,14 +139,15 @@
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'IP',
|
title: '返回结果',
|
||||||
dataIndex: 'ip',
|
dataIndex: 'response',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'IP地区',
|
title: 'IP地区',
|
||||||
dataIndex: 'ipRegion',
|
dataIndex: 'ipRegion',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '客户端',
|
title: '客户端',
|
||||||
@ -150,20 +155,15 @@
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '请求方法',
|
title: '操作时间',
|
||||||
dataIndex: 'method',
|
|
||||||
ellipsis: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '请求结果',
|
|
||||||
dataIndex: 'successFlag',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '时间',
|
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'successFlag',
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'action',
|
dataIndex: 'action',
|
||||||
@ -212,6 +212,10 @@
|
|||||||
let responseModel = await operateLogApi.queryList(queryForm);
|
let responseModel = await operateLogApi.queryList(queryForm);
|
||||||
|
|
||||||
for (const e of responseModel.data.list) {
|
for (const e of responseModel.data.list) {
|
||||||
|
if(e.response){
|
||||||
|
e.response = JSON.parse(e.response);
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.userAgent) {
|
if (!e.userAgent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -234,12 +234,21 @@
|
|||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.center-container {
|
.center-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-form-area {
|
.center-form-area {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +68,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,7 +94,6 @@
|
|||||||
v-model:pageSize="params.pageSize"
|
v-model:pageSize="params.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryEmployee"
|
@change="queryEmployee"
|
||||||
@showSizeChange="queryEmployee"
|
|
||||||
:show-total="showTableTotal"
|
:show-total="showTableTotal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,23 +8,24 @@
|
|||||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<a-modal :open="visible" width="600px" :bodyStyle="{height:'480px'}" title="" :closable="false" :maskClosable="true">
|
<a-modal :open="visible" width="600px" :bodyStyle="{height:'360px'}" title="" :closable="false" :maskClosable="true">
|
||||||
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px">助力卓大抖音1000个粉丝,开播写代码🎉🎉</div> </a-row>
|
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px;color: red;">重磅更新:国产数据库支持🎉🎉</div> </a-row>
|
||||||
<a-row><div style="font-weight:bolder;margin: 20px auto;font-size: 15px">和1024创新实验室一起,热爱代码,热爱生活,永远年轻,永远前行🎉🎉</div> </a-row>
|
<a-row><div style="font-weight:bolder;margin: 10px auto;font-size: 16px;color: red;">支持:达梦、人大金仓、华为高斯GaussDB 等🎉🎉</div> </a-row>
|
||||||
<br />
|
<br />
|
||||||
<div class="app-qr-box">
|
<div class="app-qr-box">
|
||||||
<div class="app-qr">
|
<div class="app-qr">
|
||||||
<a-image
|
<a-image
|
||||||
:width="300"
|
:width="200"
|
||||||
style="border-radius: 15px;"
|
style="border-radius: 15px;"
|
||||||
src="https://img.smartadmin.1024lab.net/wechat/douyin.png"
|
src="https://img.smartadmin.1024lab.net/wechat/zhuoda-wechat.jpg"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span class="qr-desc strong"> 打开【抖音APP】-点击【左上角侧边栏】-【点击扫一扫】-【进行关注】</span>
|
<span class="qr-desc strong"> 添加“卓大”微信,备注对应数据库,如 达梦</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button type="primary" @click="hide">知道了</a-button>
|
<a-button type="default" @click="hide">知道了</a-button>
|
||||||
|
<a-button danger type="default" @click="goto" target="_blank" href="https://smartadmin.vip/views/other/china-db/">去看看国产数据库,了解一下</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
@ -36,6 +37,9 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const visible = ref(true);
|
const visible = ref(true);
|
||||||
|
function goto(){
|
||||||
|
|
||||||
|
}
|
||||||
function show() {
|
function show() {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
}
|
}
|
||||||
@ -64,8 +68,7 @@ defineExpose({
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
color: red;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
> img {
|
> img {
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
let lunarMonth = lunar.getMonthInChinese();
|
let lunarMonth = lunar.getMonthInChinese();
|
||||||
let lunarDay = lunar.getDayInChinese();
|
let lunarDay = lunar.getDayInChinese();
|
||||||
//节气
|
//节气
|
||||||
let jieqi = lunar.getPrevJieQi().getName();
|
let jieqi = lunar.getJieQi();
|
||||||
let next = lunar.getNextJieQi();
|
let next = lunar.getNextJieQi();
|
||||||
let nextJieqi = next.getName() + ' ' + next.getSolar().toYmd();
|
let nextJieqi = next.getName() + ' ' + next.getSolar().toYmd();
|
||||||
|
|
||||||
|
@ -52,11 +52,11 @@
|
|||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<OfficialAccountCard />
|
<OfficialAccountCard />
|
||||||
</a-col>
|
</a-col>
|
||||||
<!--待办、已办-->
|
<!--更新日志-->
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<ChangelogCard />
|
<ChangelogCard />
|
||||||
</a-col>
|
</a-col>
|
||||||
<!--更新日志-->
|
<!--待办、已办-->
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<ToBeDoneCard />
|
<ToBeDoneCard />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -86,7 +86,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +68,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryRoleEmployee"
|
@change="queryRoleEmployee"
|
||||||
@showSizeChange="queryRoleEmployee"
|
|
||||||
:show-total="showTableTotal"
|
:show-total="showTableTotal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"axios": "1.6.8",
|
"axios": "1.6.8",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"crypto-js": "4.1.1",
|
"crypto-js": "4.1.1",
|
||||||
"dayjs": "1.10.5",
|
"dayjs": "1.11.13",
|
||||||
"decimal.js": "10.3.1",
|
"decimal.js": "10.3.1",
|
||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
"diff": "5.2.0",
|
"diff": "5.2.0",
|
||||||
@ -44,7 +44,7 @@
|
|||||||
"vue": "3.4.27",
|
"vue": "3.4.27",
|
||||||
"vue-i18n": "9.13.1",
|
"vue-i18n": "9.13.1",
|
||||||
"vue-router": "4.3.2",
|
"vue-router": "4.3.2",
|
||||||
"vue3-json-viewer": "2.2.2"
|
"vue3-json-viewer": "2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "5.1.4",
|
"@vitejs/plugin-vue": "5.1.4",
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">
|
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">
|
||||||
|
@ -71,7 +71,6 @@
|
|||||||
v-model:pageSize="params.pageSize"
|
v-model:pageSize="params.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryEmployee"
|
@change="queryEmployee"
|
||||||
@showSizeChange="queryEmployee"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,7 @@ export const appDefaultConfig = {
|
|||||||
// 圆角
|
// 圆角
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
// 菜单展开模式
|
// 菜单展开模式
|
||||||
flatPattern: false,
|
menuSingleExpandFlag: true,
|
||||||
// 标签页
|
// 标签页
|
||||||
pageTagFlag: true,
|
pageTagFlag: true,
|
||||||
// 标签页样式: default、 antd、chrome
|
// 标签页样式: default、 antd、chrome
|
||||||
|
@ -21,11 +21,11 @@ export default {
|
|||||||
'setting.menu.layout': 'Menu Layout',
|
'setting.menu.layout': 'Menu Layout',
|
||||||
'setting.menu.width': 'Menu Width',
|
'setting.menu.width': 'Menu Width',
|
||||||
'setting.menu.theme': 'Menu Theme',
|
'setting.menu.theme': 'Menu Theme',
|
||||||
|
'setting.menu.expand': 'Menu Expand',
|
||||||
'setting.page.width': 'Page Width',
|
'setting.page.width': 'Page Width',
|
||||||
'setting.border.radius': 'Border Radius',
|
'setting.border.radius': 'Border Radius',
|
||||||
'setting.compact': 'Page Compact',
|
'setting.compact': 'Page Compact',
|
||||||
'setting.bread': 'Show Bread',
|
'setting.bread': 'Show Bread',
|
||||||
'setting.flatPattern': 'Flat Pattern',
|
|
||||||
'setting.pagetag': 'Show PageTag',
|
'setting.pagetag': 'Show PageTag',
|
||||||
'setting.pagetag.style': 'PageTag Style',
|
'setting.pagetag.style': 'PageTag Style',
|
||||||
'setting.footer': 'Show Footer',
|
'setting.footer': 'Show Footer',
|
||||||
|
@ -21,11 +21,11 @@ export default {
|
|||||||
'setting.menu.layout': '菜单布局',
|
'setting.menu.layout': '菜单布局',
|
||||||
'setting.menu.width': '菜单宽度',
|
'setting.menu.width': '菜单宽度',
|
||||||
'setting.menu.theme': '菜单主题',
|
'setting.menu.theme': '菜单主题',
|
||||||
|
'setting.menu.expand': '菜单展开',
|
||||||
'setting.compact': '页面紧凑',
|
'setting.compact': '页面紧凑',
|
||||||
'setting.border.radius': '页面圆角',
|
'setting.border.radius': '页面圆角',
|
||||||
'setting.page.width': '页面宽度',
|
'setting.page.width': '页面宽度',
|
||||||
'setting.bread': '面包屑',
|
'setting.bread': '面包屑',
|
||||||
'setting.flatPattern': '菜单展开模式',
|
|
||||||
'setting.pagetag': '标签页',
|
'setting.pagetag': '标签页',
|
||||||
'setting.pagetag.style': '标签页样式',
|
'setting.pagetag.style': '标签页样式',
|
||||||
'setting.footer': '页脚',
|
'setting.footer': '页脚',
|
||||||
|
@ -83,8 +83,8 @@
|
|||||||
<a-radio-button value="chrome">Chrome</a-radio-button>
|
<a-radio-button value="chrome">Chrome</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('setting.flatPattern')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
<a-form-item :label="$t('setting.menu.expand')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
||||||
<a-switch @change="changeFlatPattern" v-model:checked="formState.flatPattern" checked-children="多个" un-checked-children="单个" />
|
<a-switch @change="changeMenuExpandFlag" v-model:checked="formState.menuSingleExpandFlag" checked-children="单个" un-checked-children="多个" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('setting.pagetag')">
|
<a-form-item :label="$t('setting.pagetag')">
|
||||||
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
|
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||||
@ -213,8 +213,8 @@
|
|||||||
borderRadius: appConfigStore.borderRadius,
|
borderRadius: appConfigStore.borderRadius,
|
||||||
// 标签页
|
// 标签页
|
||||||
pageTagFlag: appConfigStore.pageTagFlag,
|
pageTagFlag: appConfigStore.pageTagFlag,
|
||||||
// 标签页
|
// 菜单展开方式
|
||||||
flatPattern: appConfigStore.flatPattern,
|
menuSingleExpandFlag: appConfigStore.menuSingleExpandFlag,
|
||||||
// 标签页 样式
|
// 标签页 样式
|
||||||
pageTagStyle: appConfigStore.pageTagStyle,
|
pageTagStyle: appConfigStore.pageTagStyle,
|
||||||
// 面包屑
|
// 面包屑
|
||||||
@ -303,9 +303,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeFlatPattern(e) {
|
function changeMenuExpandFlag(e) {
|
||||||
appConfigStore.$patch({
|
appConfigStore.$patch({
|
||||||
flatPattern: e,
|
menuSingleExpandFlag: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import { useUserStore } from '/@/store/modules/system/user';
|
||||||
|
|
||||||
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
|
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
|
||||||
const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
|
const menuSingleExpandFlag = computed(() => useAppConfigStore().$state.menuSingleExpandFlag);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
collapsed: {
|
collapsed: {
|
||||||
@ -46,8 +46,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const menuTree = computed(() => useUserStore().getMenuTree || []);
|
const menuTree = computed(() => useUserStore().getMenuTree || []);
|
||||||
const rootSubmenuKeys = computed(()=>menuTree.value.map(item=>item.menuId));
|
const rootSubmenuKeys = computed(() => menuTree.value.map((item) => item.menuId));
|
||||||
|
|
||||||
|
|
||||||
//展开的菜单
|
//展开的菜单
|
||||||
let currentRoute = useRoute();
|
let currentRoute = useRoute();
|
||||||
@ -76,9 +75,15 @@
|
|||||||
let parentList = menuParentIdListMap.get(currentRoute.name) || [];
|
let parentList = menuParentIdListMap.get(currentRoute.name) || [];
|
||||||
|
|
||||||
// 如果是折叠菜单的话,则不需要设置openkey
|
// 如果是折叠菜单的话,则不需要设置openkey
|
||||||
if (!props.collapsed) {
|
if (props.collapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let needOpenKeys = _.map(parentList, 'name').map(Number);
|
||||||
|
if (menuSingleExpandFlag.value) {
|
||||||
|
openKeys.value = [...needOpenKeys];
|
||||||
|
} else {
|
||||||
// 使用lodash的union函数,进行 去重合并两个数组
|
// 使用lodash的union函数,进行 去重合并两个数组
|
||||||
let needOpenKeys = _.map(parentList, 'name').map(Number);
|
|
||||||
openKeys.value = _.union(openKeys.value, needOpenKeys);
|
openKeys.value = _.union(openKeys.value, needOpenKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,17 +97,18 @@
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
function onOpenChange(openKeysParams){
|
|
||||||
if(flatPattern.value){
|
function onOpenChange(openKeysParams) {
|
||||||
return;
|
if (!menuSingleExpandFlag.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const latestOpenKey = openKeysParams.find((key) => openKeys.value.indexOf(key) === -1);
|
||||||
|
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
|
||||||
|
openKeys.value = openKeysParams;
|
||||||
|
} else {
|
||||||
|
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const latestOpenKey = openKeysParams.find(key => openKeys.value.indexOf(key) === -1);
|
|
||||||
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
|
|
||||||
openKeys.value = openKeysParams;
|
|
||||||
} else {
|
|
||||||
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
updateOpenKeysAndSelectKeys,
|
updateOpenKeysAndSelectKeys,
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
*/
|
*/
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||||
|
|
||||||
export const useSpinStore = defineStore({
|
export const useSpinStore = defineStore({
|
||||||
id: 'spin',
|
id: 'spin',
|
||||||
@ -18,13 +19,27 @@ export const useSpinStore = defineStore({
|
|||||||
actions: {
|
actions: {
|
||||||
hide() {
|
hide() {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
let spins = document.querySelector('.ant-spin-nested-loading');
|
// 安全的DOM操作,避免null引用错误
|
||||||
spins.style.zIndex = 999;
|
try {
|
||||||
|
const spins = document.querySelector('.ant-spin-nested-loading');
|
||||||
|
if (spins) {
|
||||||
|
spins.style.zIndex = '999';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
smartSentry.captureError('Spin hide操作失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show() {
|
show() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let spins = document.querySelector('.ant-spin-nested-loading');
|
// 安全的DOM操作,避免null引用错误
|
||||||
spins.style.zIndex = 1001;
|
try {
|
||||||
|
const spins = document.querySelector('.ant-spin-nested-loading');
|
||||||
|
if (spins) {
|
||||||
|
spins.style.zIndex = '1001';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
smartSentry.captureError('Spin hide操作失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -149,7 +149,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryData"
|
@change="queryData"
|
||||||
@showSizeChange="queryData"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="queryEmployee"
|
@change="queryEmployee"
|
||||||
@showSizeChange="queryEmployee"
|
|
||||||
:show-total="showTableTotal"
|
:show-total="showTableTotal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,6 @@
|
|||||||
v-model:pageSize="queryForm.pageSize"
|
v-model:pageSize="queryForm.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
@change="ajaxQuery"
|
@change="ajaxQuery"
|
||||||
@showSizeChange="ajaxQuery"
|
|
||||||
:show-total="(total) => `共${total}条`"
|
:show-total="(total) => `共${total}条`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user