pageOption) {
+ this.pageOption.set(pageOption);
+ }
+
+ protected abstract void init(HttpServletRequest request, JSONObject requestParams);
+
+ /**
+ * 新增
+ *
+ * // * @param t 实体对象
+ */
+ @Operation(summary = "新增", description = "新增信息,对应后端的实体类")
+ @PostMapping("/add")
+ protected R add(@RequestAttribute() JSONObject requestParams) {
+ String body = requestParams.getStr("body");
+ if (JSONUtil.isTypeJSONArray(body)) {
+ JSONArray array = JSONUtil.parseArray(body);
+ return R.ok(Dict.create()
+ .set("id", service.addBatch(requestParams, array.toList(currentEntityClass()))));
+ } else {
+ return R.ok(Dict.create().set("id",
+ service.add(requestParams, requestParams.toBean(currentEntityClass()))));
+ }
+ }
+
+ /**
+ * 删除
+ *
+ * @param params 请求参数 ids 数组 或者按","隔开
+ */
+ @Operation(summary = "删除", description = "支持批量删除 请求参数 ids 数组 或者按\",\"隔开")
+ @PostMapping("/delete")
+ protected R delete(HttpServletRequest request, @RequestBody Map params,
+ @RequestAttribute() JSONObject requestParams) {
+ service.delete(requestParams, Convert.toLongArray(getIds(params)));
+ return R.ok();
+ }
+
+ /**
+ * 修改
+ *
+ * @param t 修改对象
+ */
+ @Operation(summary = "修改", description = "根据ID修改")
+ @PostMapping("/update")
+ protected R update(@RequestBody T t, @RequestAttribute() JSONObject requestParams) {
+ Long id = t.getId();
+ JSONObject info = JSONUtil.parseObj(JSONUtil.toJsonStr(service.getById(id)));
+ requestParams.forEach(info::set);
+ info.set("updateTime", new Date());
+ service.update(requestParams, JSONUtil.toBean(info, currentEntityClass()));
+ return R.ok();
+ }
+
+ /**
+ * 信息
+ *
+ * @param id ID
+ */
+ @Operation(summary = "信息", description = "根据ID查询单个信息")
+ @GetMapping("/info")
+ protected R info(@RequestAttribute() JSONObject requestParams,
+ @RequestParam() Long id,
+ @RequestAttribute(COOL_INFO_OP) CrudOption option) {
+ T info = (T) service.info(requestParams, id);
+ invokerTransform(option, info);
+ return R.ok(info);
+ }
+
+ /**
+ * 列表查询
+ *
+ * @param requestParams 请求参数
+ */
+ @Operation(summary = "查询", description = "查询多个信息")
+ @PostMapping("/list")
+ protected R> list(@RequestAttribute() JSONObject requestParams,
+ @RequestAttribute(COOL_LIST_OP) CrudOption option) {
+ QueryModeEnum queryModeEnum = option.getQueryModeEnum();
+ List list = (List) switch (queryModeEnum) {
+ case ENTITY_WITH_RELATIONS -> service.listWithRelations(requestParams, option.getQueryWrapper(currentEntityClass()));
+ case CUSTOM -> transformList(service.list(requestParams, option.getQueryWrapper(currentEntityClass()), option.getAsType()), option.getAsType());
+ default -> service.list(requestParams, option.getQueryWrapper(currentEntityClass()));
+ };
+ invokerTransform(option, list);
+ return R.ok(list);
+ }
+
+ /**
+ * 分页查询
+ *
+ * @param requestParams 请求参数
+ */
+ @Operation(summary = "分页", description = "分页查询多个信息")
+ @PostMapping("/page")
+ protected R> page(@RequestAttribute() JSONObject requestParams,
+ @RequestAttribute(COOL_PAGE_OP) CrudOption option) {
+ Integer page = requestParams.getInt("page", 1);
+ Integer size = requestParams.getInt("size", 20);
+ QueryModeEnum queryModeEnum = option.getQueryModeEnum();
+ Object obj = switch (queryModeEnum) {
+ case ENTITY_WITH_RELATIONS -> service.pageWithRelations(requestParams, new Page<>(page, size), option.getQueryWrapper(currentEntityClass()));
+ case CUSTOM -> transformPage(service.page(requestParams, new Page<>(page, size), option.getQueryWrapper(currentEntityClass()), option.getAsType()), option.getAsType());
+ default -> service.page(requestParams, new Page<>(page, size), option.getQueryWrapper(currentEntityClass()));
+ };
+ Page pageResult = (Page) obj;
+ invokerTransform(option, pageResult.getRecords());
+ return R.ok(pageResult(pageResult));
+ }
+
+ /**
+ * 转换参数,组装数据
+ */
+ private void invokerTransform(CrudOption option, Object obj) {
+ if (ObjUtil.isNotEmpty(option.getTransform())) {
+ if (obj instanceof List) {
+ ((List)obj).forEach(o -> {
+ option.getTransform().apply(o);
+ });
+ } else {
+ option.getTransform().apply(obj);
+ }
+ }
+ }
+
+ /**
+ * 分页结果
+ *
+ * @param page 分页返回数据
+ */
+ protected PageResult pageResult(Page page) {
+ return PageResult.of(page);
+ }
+
+ public Class currentEntityClass() {
+ if (entityClass != null) {
+ return this.entityClass;
+ }
+ // 使用 获取泛型参数类型
+ Type type = TypeUtil.getTypeArgument(this.getClass(), 1); // 获取第二个泛型参数
+ if (type instanceof Class>) {
+ entityClass = (Class) type;
+ return entityClass;
+ }
+ throw new IllegalStateException("Unable to determine entity class type");
+ }
+
+ protected List getIds(Map params) {
+ Object ids = params.get("ids");
+ CoolPreconditions.checkEmpty(ids, "ids 参数错误");
+ if (!(ids instanceof ArrayList)) {
+ ids = ids.toString().split(",");
+ }
+ return Convert.toList(Long.class, ids);
+ }
+
+ /**
+ * 适用于自定义返回值为 map,map 的key为数据库字段,转驼峰命名
+ */
+ protected List transformList(List records, Class> asType) {
+ if (ObjUtil.isEmpty(asType) || !Map.class.isAssignableFrom(asType)) {
+ return records;
+ }
+ List list = new ArrayList<>();
+ Editor keyEditor = property -> StrUtil.toCamelCase(property);
+ records.forEach(o ->
+ list.add(BeanUtil.beanToMap(o, new HashMap(), false, keyEditor)));
+ return list;
+ }
+ protected Page transformPage(Page page, Class> asType) {
+ page.setRecords(transformList(page.getRecords(), asType));
+ return page;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/BaseEntity.java b/cool-admin-java/src/main/java/com/cool/core/base/BaseEntity.java
new file mode 100644
index 0000000..08f153f
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/BaseEntity.java
@@ -0,0 +1,38 @@
+package com.cool.core.base;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.core.activerecord.Model;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Ignore;
+
+/**
+ * 基础实体类
+ */
+@Getter
+@Setter
+public abstract class BaseEntity> extends Model implements Serializable {
+
+ @Id(keyType = KeyType.Auto, comment = "ID")
+ protected Long id;
+
+ @Column(onInsertValue = "now()")
+ @ColumnDefine(comment = "创建时间")
+ protected Date createTime;
+
+ @Column(onInsertValue = "now()", onUpdateValue = "now()")
+ @ColumnDefine(comment = "更新时间")
+ protected Date updateTime;
+
+ @Ignore
+ @Column(ignore = true)
+ @JsonIgnore
+ private QueryWrapper queryWrapper;
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/BaseService.java b/cool-admin-java/src/main/java/com/cool/core/base/BaseService.java
new file mode 100644
index 0000000..dff2fda
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/BaseService.java
@@ -0,0 +1,175 @@
+package com.cool.core.base;
+
+import cn.hutool.json.JSONObject;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.service.IService;
+import java.util.List;
+
+/**
+ * 基础service类
+ *
+ * @param 实体
+ */
+public interface BaseService extends IService {
+ /**
+ * 新增
+ *
+ * @param entity 对应的实体
+ */
+ Long add(T entity);
+
+ /**
+ * 新增
+ *
+ * @param requestParams 请求参数
+ * @param entity 对应的实体
+ * @return ID
+ */
+ Object add(JSONObject requestParams, T entity);
+
+ /**
+ * 批量添加
+ *
+ * @param requestParams 请求参数
+ * @param entitys 请求参数
+ * @return ID 集合
+ */
+ Object addBatch(JSONObject requestParams, List entitys);
+
+ /**
+ * 删除, 支持单个或者批量删除
+ *
+ * @param ids ID数组
+ */
+ boolean delete(Long... ids);
+
+ /**
+ * 多个删除,带请求参数
+ *
+ * @param requestParams 请求参数
+ * @param ids ID数组
+ */
+ boolean delete(JSONObject requestParams, Long... ids);
+
+ /**
+ * 更新
+ *
+ * @param entity 实体
+ */
+ boolean update(T entity);
+
+ /**
+ * 更新
+ *
+ * @param requestParams 请求参数
+ * @param entity 实体
+ */
+ boolean update(JSONObject requestParams, T entity);
+
+ /**
+ * 查询所有
+ *
+ * @param requestParams 请求参数
+ * @param queryWrapper 查询条件
+ * @return 列表信息
+ */
+ Object list(JSONObject requestParams, QueryWrapper queryWrapper);
+
+ /**
+ * 查询所有
+ *
+ * @param requestParams 请求参数
+ * @param queryWrapper 查询条件
+ * @return 列表信息
+ */
+ List list(JSONObject requestParams, QueryWrapper queryWrapper, Class asType);
+
+ /**
+ * 查询所有
+ * 带关联查询
+ * @param requestParams 请求参数
+ * @param queryWrapper 查询条件
+ * @return 列表信息
+ */
+ Object listWithRelations(JSONObject requestParams, QueryWrapper queryWrapper);
+
+ /**
+ * 分页查询
+ *
+ * @param requestParams 请求参数
+ * @param page 分页信息
+ * @param queryWrapper 查询条件
+ * @return 分页信息
+ */
+ Object page(JSONObject requestParams, Page page, QueryWrapper queryWrapper);
+
+ /**
+ * 分页查询
+ *
+ * @param requestParams 请求参数
+ * @param page 分页信息
+ * @param queryWrapper 查询条件
+ * @return 分页信息
+ */
+ Page page(JSONObject requestParams, Page page, QueryWrapper queryWrapper, Class asType);
+
+ /**
+ * 分页查询
+ * 带关联查询
+ * @param requestParams 请求参数
+ * @param page 分页信息
+ * @param queryWrapper 查询条件
+ * @return 分页信息
+ */
+ Object pageWithRelations(JSONObject requestParams, Page page, QueryWrapper queryWrapper);
+
+ /**
+ * 查询信息
+ *
+ * @param id ID
+ */
+ Object info(Long id);
+
+ /**
+ * 查询信息
+ *
+ * @param requestParams 请求参数
+ * @param id ID
+ */
+ Object info(JSONObject requestParams, Long id);
+
+ /**
+ * 修改之后
+ *
+ * @param requestParams 请求参数
+ * @param t 对应实体
+ */
+ void modifyAfter(JSONObject requestParams, T t);
+
+ /**
+ * 修改之后
+ *
+ * @param requestParams 请求参数
+ * @param t 对应实体
+ * @param type 修改类型
+ */
+ void modifyAfter(JSONObject requestParams, T t, ModifyEnum type);
+
+ /**
+ * 修改之前
+ *
+ * @param requestParams 请求参数
+ * @param t 对应实体
+ */
+ void modifyBefore(JSONObject requestParams, T t);
+
+ /**
+ * 修改之前
+ *
+ * @param requestParams 请求参数
+ * @param t 对应实体
+ * @param type 修改类型
+ */
+ void modifyBefore(JSONObject requestParams, T t, ModifyEnum type);
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/BaseServiceImpl.java b/cool-admin-java/src/main/java/com/cool/core/base/BaseServiceImpl.java
new file mode 100644
index 0000000..f4380cc
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/BaseServiceImpl.java
@@ -0,0 +1,137 @@
+package com.cool.core.base;
+
+import cn.hutool.json.JSONObject;
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 基础service实现类
+ *
+ * @param Mapper 类
+ * @param 实体
+ */
+public class BaseServiceImpl, T extends BaseEntity> extends
+ ServiceImpl
+ implements BaseService {
+
+ @Override
+ public Long add(T entity) {
+ mapper.insertSelective(entity);
+ return entity.getId();
+ }
+
+ @Override
+ public Object add(JSONObject requestParams, T entity) {
+ this.modifyBefore(requestParams, entity, ModifyEnum.ADD);
+ this.add(entity);
+ this.modifyAfter(requestParams, entity, ModifyEnum.ADD);
+ return entity.getId();
+ }
+
+ @Override
+ public Object addBatch(JSONObject requestParams, List entitys) {
+ this.modifyBefore(requestParams, null, ModifyEnum.ADD);
+ List ids = new ArrayList<>();
+ entitys.forEach(e -> ids.add(this.add(e)));
+ requestParams.set("ids", ids);
+ this.modifyAfter(requestParams, null, ModifyEnum.ADD);
+ return ids;
+ }
+
+ @Override
+ public boolean delete(Long... ids) {
+ return mapper.deleteBatchByIds(Arrays.asList(ids)) > 0;
+ }
+
+ @Override
+ public boolean delete(JSONObject requestParams, Long... ids) {
+ this.modifyBefore(requestParams, null, ModifyEnum.DELETE);
+ boolean flag = this.delete(ids);
+ if (flag) {
+ this.modifyAfter(requestParams, null, ModifyEnum.DELETE);
+ }
+ return flag;
+ }
+
+ @Override
+ public boolean update(T entity) {
+ return mapper.update(entity) > 0;
+ }
+
+ @Override
+ public boolean update(JSONObject requestParams, T entity) {
+ this.modifyBefore(requestParams, entity, ModifyEnum.UPDATE);
+ boolean flag = this.update(entity);
+ if (flag) {
+ this.modifyAfter(requestParams, entity, ModifyEnum.UPDATE);
+ }
+ return flag;
+ }
+
+ @Override
+ public Object list(JSONObject requestParams, QueryWrapper queryWrapper) {
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public List list(JSONObject requestParams, QueryWrapper queryWrapper, Class asType) {
+ return mapper.selectListByQueryAs(queryWrapper, asType);
+ }
+
+ @Override
+ public Object listWithRelations(JSONObject requestParams, QueryWrapper queryWrapper) {
+ return mapper.selectListWithRelationsByQuery(queryWrapper);
+ }
+
+ @Override
+ public Object page(JSONObject requestParams, Page page, QueryWrapper queryWrapper) {
+ return this.page(page, queryWrapper);
+ }
+
+ @Override
+ public Page page(JSONObject requestParams, Page page, QueryWrapper queryWrapper,
+ Class asType) {
+ return mapper.paginateAs(page, queryWrapper, asType);
+ }
+
+ @Override
+ public Object pageWithRelations(JSONObject requestParams, Page page,
+ QueryWrapper queryWrapper) {
+ return mapper.paginateWithRelations(page, queryWrapper);
+ }
+
+ @Override
+ public Object info(JSONObject requestParams, Long id) {
+ return info(id);
+ }
+
+ @Override
+ public Object info(Long id) {
+ return mapper.selectOneById(id);
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, T t) {
+
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, T t, ModifyEnum type) {
+ modifyAfter(requestParams, t);
+ }
+
+ @Override
+ public void modifyBefore(JSONObject requestParams, T t) {
+
+ }
+
+ @Override
+ public void modifyBefore(JSONObject requestParams, T t, ModifyEnum type) {
+
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/ModifyEnum.java b/cool-admin-java/src/main/java/com/cool/core/base/ModifyEnum.java
new file mode 100644
index 0000000..22b0d85
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/ModifyEnum.java
@@ -0,0 +1,13 @@
+package com.cool.core.base;
+
+/**
+ * 修改枚举
+ */
+public enum ModifyEnum {
+ // 新增
+ ADD,
+ // 修改
+ UPDATE,
+ // 删除
+ DELETE
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/TenantEntity.java b/cool-admin-java/src/main/java/com/cool/core/base/TenantEntity.java
new file mode 100644
index 0000000..4e258d5
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/TenantEntity.java
@@ -0,0 +1,16 @@
+package com.cool.core.base;
+
+import com.mybatisflex.core.activerecord.Model;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+
+/** 租户ID实体类 */
+@Getter
+@Setter
+public class TenantEntity> extends BaseEntity {
+ @Index
+ @ColumnDefine(comment = "租户id")
+ protected Long tenantId;
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/base/service/MapperProviderService.java b/cool-admin-java/src/main/java/com/cool/core/base/service/MapperProviderService.java
new file mode 100644
index 0000000..8492857
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/base/service/MapperProviderService.java
@@ -0,0 +1,52 @@
+package com.cool.core.base.service;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.TypeUtil;
+import com.cool.core.util.SpringContextUtils;
+import com.mybatisflex.core.BaseMapper;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MapperProviderService {
+
+ private Map, BaseMapper>> mapperMap;
+
+ /**
+ * 初始化mapperMap,key 为entityClass,value 为 mapper
+ */
+ private void init() {
+ // 获取所有BaseMapper类型的Bean
+ Map beansOfType = SpringContextUtils.getBeansOfType(BaseMapper.class);
+ mapperMap = new HashMap<>();
+ for (BaseMapper mapper : beansOfType.values()) {
+ // 通过反射获取泛型参数,即实体类
+ Class> entityClass = getGenericType(mapper);
+ if (entityClass != null) {
+ mapperMap.put(entityClass, mapper);
+ }
+ }
+ }
+
+ /**
+ * 通过entity类获取 对应的mapper接口
+ */
+ public BaseMapper getMapperByEntityClass(Class entityClass) {
+ if (ObjUtil.isEmpty(mapperMap)) {
+ init();
+ }
+ return (BaseMapper) mapperMap.get(entityClass);
+ }
+
+ /**
+ * 获取mapper对应的entity对象
+ */
+ private Class> getGenericType(BaseMapper> mapper) {
+ // 使用 获取泛型参数类型
+ Type[] types = mapper.getClass().getGenericInterfaces();
+ Type typeArgument = TypeUtil.getTypeArgument(types[0], 0);
+ return ObjUtil.isEmpty(typeArgument) ? null : (Class>) typeArgument;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/cache/CoolCache.java b/cool-admin-java/src/main/java/com/cool/core/cache/CoolCache.java
new file mode 100644
index 0000000..aa6d381
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/cache/CoolCache.java
@@ -0,0 +1,180 @@
+package com.cool.core.cache;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.util.ConvertUtil;
+import jakarta.annotation.PostConstruct;
+import java.time.Duration;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.cache.CacheType;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.stereotype.Component;
+
+/**
+ * 缓存工具类
+ */
+@EnableCaching
+@Configuration
+@Component
+@RequiredArgsConstructor
+public class CoolCache {
+
+ // 缓存类型
+ @Value("${spring.cache.type}")
+ private String type;
+
+ // redis
+ public RedisCacheWriter redisCache;
+
+ private Cache cache;
+
+ @Value("${cool.cacheName}")
+ private String cacheName;
+
+ private final static String NULL_VALUE = "@_NULL_VALUE$@";
+
+ final private CacheManager cacheManager;
+
+ @PostConstruct
+ private void init() {
+ cache = cacheManager.getCache(cacheName);
+ this.type = type.toLowerCase();
+ assert cache != null : "Cache not found: " + cacheName; // Ensure cache is not null
+ if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ redisCache = (RedisCacheWriter) cache.getNativeCache();
+ }
+ }
+
+ /**
+ * 数据来源
+ */
+ public interface ToCacheData {
+ Object apply();
+ }
+
+ /**
+ * 删除缓存
+ *
+ * @param keys 一个或多个key
+ */
+ public void del(String... keys) {
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
+ Arrays.stream(keys).forEach(o -> cache.evict(o));
+ }
+ if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ Arrays.stream(keys).forEach(key -> redisCache.remove(cacheName, key.getBytes()));
+ }
+ }
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ */
+ public Object get(String key) {
+ Object ifNullValue = getIfNullValue(key);
+ if (ObjUtil.equals(ifNullValue, NULL_VALUE)) {
+ return null;
+ }
+ return ifNullValue;
+ }
+
+ /**
+ * 普通缓存获取
+ *
+ * @param key 键
+ */
+ public Object get(String key, Duration duration, ToCacheData toCacheData) {
+ Object ifNullValue = getIfNullValue(key);
+ if (ObjUtil.equals(ifNullValue, NULL_VALUE)) {
+ return null;
+ }
+ if (ObjUtil.isEmpty(ifNullValue)) {
+ Object obj = toCacheData.apply();
+ set(key, obj, duration.toSeconds());
+ return obj;
+ }
+ return ifNullValue;
+ }
+
+ private Object getIfNullValue(String key) {
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
+ Cache.ValueWrapper valueWrapper = cache.get(key);
+ if (valueWrapper != null) {
+ return valueWrapper.get(); // 获取实际的缓存值
+ }
+ }
+ if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ byte[] bytes = redisCache.get(cacheName, key.getBytes());
+ if (bytes != null && bytes.length > 0) {
+ return ConvertUtil.toObject(bytes);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 获得对象
+ *
+ * @param key 键
+ * @param valueType 值类型
+ */
+ public T get(String key, Class valueType) {
+ Object result = get(key);
+ if (result != null && JSONUtil.isTypeJSONObject(result.toString())) {
+ return JSONUtil.parseObj(result).toBean(valueType);
+ }
+ return result != null ? (T) result : null;
+ }
+
+ /**
+ * 获得缓存类型
+ */
+ public String getMode() {
+ return this.type;
+ }
+
+ /**
+ * 获得原生缓存实例
+ */
+ public Object getMetaCache() {
+ return this.cache;
+ }
+
+ /**
+ * 普通缓存放入
+ *
+ * @param key 键
+ * @param value 值
+ */
+ public void set(String key, Object value) {
+ set(key, value, 0);
+ }
+
+ /**
+ * 普通缓存放入并设置时间
+ *
+ * @param key 键
+ * @param value 值
+ * @param ttl 时间(秒) time要大于0 如果time小于等于0 将设置无限期
+ */
+ public void set(String key, Object value, long ttl) {
+ if (ObjUtil.isNull(value)) {
+ value = NULL_VALUE;
+ }
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
+ // 放入缓存
+ cache.put(key, value);
+ } else if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value),
+ java.time.Duration.ofSeconds(ttl));
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/code/CodeGenerator.java b/cool-admin-java/src/main/java/com/cool/core/code/CodeGenerator.java
new file mode 100644
index 0000000..37a8400
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/code/CodeGenerator.java
@@ -0,0 +1,108 @@
+package com.cool.core.code;
+
+import cn.hutool.core.io.file.FileWriter;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.template.Template;
+import cn.hutool.extra.template.TemplateConfig;
+import cn.hutool.extra.template.TemplateEngine;
+import cn.hutool.extra.template.TemplateUtil;
+import jakarta.annotation.PostConstruct;
+import org.springframework.stereotype.Component;
+
+/**
+ * 代码生成器
+ */
+@Component
+public class CodeGenerator {
+
+ private TemplateEngine templateEngine;
+
+ private String baseSrcPath;
+
+ private String baseResPath;
+
+ @PostConstruct
+ public void init() {
+ templateEngine = coolTemplateEngine();
+ baseSrcPath = System.getProperty("user.dir") + "/src/main/java/com/cool/modules/";
+ baseResPath = System.getProperty("user.dir") + "/src/main/resources/";
+ }
+
+ public TemplateEngine coolTemplateEngine() {
+ return TemplateUtil.createEngine(
+ new TemplateConfig("cool/code", TemplateConfig.ResourceMode.CLASSPATH));
+ }
+
+ private String filePath(CodeModel codeModel, String type) {
+ if (type.equals("controller")) {
+ return StrUtil.isEmpty(codeModel.getSubModule())
+ ? baseSrcPath + codeModel.getModule() + "/" + type + "/" + codeModel.getType()
+ .value()
+ : baseSrcPath + codeModel.getModule() + "/" + type + "/" + codeModel.getType()
+ .value() + "/"
+ + codeModel.getSubModule();
+ }
+ if (type.equals("xmlMapper")) {
+ return StrUtil.isEmpty(codeModel.getSubModule()) ? baseResPath + "mapper/"
+ + codeModel.getModule()
+ : baseResPath + "mapper/" + codeModel.getModule() + "/" + codeModel.getSubModule();
+ }
+ return StrUtil.isEmpty(codeModel.getSubModule()) ? baseSrcPath + codeModel.getModule() + "/"
+ + type
+ : baseSrcPath + codeModel.getModule() + "/" + type + "/" + codeModel.getSubModule();
+ }
+
+ /**
+ * 生成Mapper
+ *
+ * @param codeModel 代码模型
+ */
+ public void mapper(CodeModel codeModel) {
+ Template template = templateEngine.getTemplate("/mapper/interface.th");
+ String result = template.render(Dict.parse(codeModel));
+ FileWriter writer = new FileWriter(
+ filePath(codeModel, "mapper") + "/" + codeModel.getEntity() + "Mapper.java");
+ writer.write(result);
+ }
+
+ /**
+ * 生成Service
+ *
+ * @param codeModel 代码模型
+ */
+ public void service(CodeModel codeModel) {
+ Template interfaceTemplate = templateEngine.getTemplate("/service/interface.th");
+ String interfaceResult = interfaceTemplate.render(Dict.parse(codeModel));
+ FileWriter interfaceWriter = new FileWriter(
+ filePath(codeModel, "service") + "/" + codeModel.getEntity() + "Service.java");
+ interfaceWriter.write(interfaceResult);
+
+ Template template = templateEngine.getTemplate("/service/impl.th");
+ String result = template.render(Dict.parse(codeModel));
+ FileWriter writer = new FileWriter(
+ filePath(codeModel, "service") + "/impl/" + codeModel.getEntity() + "ServiceImpl.java");
+ writer.write(result);
+ }
+
+ /**
+ * 生成Controller
+ *
+ * @param codeModel 代码模型
+ */
+ public void controller(CodeModel codeModel) {
+ Template template = templateEngine.getTemplate("controller.th");
+ System.out.println(codeModel.getType().value());
+ Dict data = Dict.create().set("upperType", StrUtil.upperFirst(codeModel.getType().value()))
+ .set("url",
+ "/" + codeModel.getType() + "/" + StrUtil.toUnderlineCase(codeModel.getEntity())
+ .replace("_", "/"));
+ data.putAll(Dict.parse(codeModel));
+ data.set("type", codeModel.getType().value());
+ String result = template.render(data);
+ FileWriter writer = new FileWriter(filePath(codeModel, "controller") + "/"
+ + StrUtil.upperFirst(codeModel.getType().value()) + codeModel.getEntity()
+ + "Controller.java");
+ writer.write(result);
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/code/CodeModel.java b/cool-admin-java/src/main/java/com/cool/core/code/CodeModel.java
new file mode 100644
index 0000000..a2566fd
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/code/CodeModel.java
@@ -0,0 +1,36 @@
+package com.cool.core.code;
+
+import lombok.Data;
+
+/**
+ * 代码模型
+ */
+@Data
+public class CodeModel {
+ /**
+ * 类型 后台还是对外的接口 admin app
+ */
+ private CodeTypeEnum type;
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 模块
+ */
+ private String module;
+
+ /**
+ * 子模块
+ */
+ private String subModule;
+
+ /**
+ * 实体类
+ */
+ private String entity;
+
+ public void setEntity(Class entity) {
+ this.entity = entity.getSimpleName().replace("Entity", "");
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/code/CodeTypeEnum.java b/cool-admin-java/src/main/java/com/cool/core/code/CodeTypeEnum.java
new file mode 100644
index 0000000..873ef49
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/code/CodeTypeEnum.java
@@ -0,0 +1,21 @@
+package com.cool.core.code;
+
+/**
+ * 代码类型
+ */
+public enum CodeTypeEnum {
+ ADMIN("admin", "后端接口"), APP("app", "对外接口");
+
+ private String value;
+
+ private String des;
+
+ CodeTypeEnum(String value, String des) {
+ this.value = value;
+ this.des = des;
+ }
+
+ public String value() {
+ return this.value;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/CoolProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/CoolProperties.java
new file mode 100644
index 0000000..88e3fe9
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/CoolProperties.java
@@ -0,0 +1,23 @@
+package com.cool.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * cool的配置
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "cool")
+public class CoolProperties {
+ // 是否自动导入数据
+ private Boolean initData = false;
+ // token配置
+ @NestedConfigurationProperty
+ private TokenProperties token;
+ // 文件配置
+ @NestedConfigurationProperty
+ private FileProperties file;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/CustomOpenApiResource.java b/cool-admin-java/src/main/java/com/cool/core/config/CustomOpenApiResource.java
new file mode 100644
index 0000000..e01d325
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/CustomOpenApiResource.java
@@ -0,0 +1,40 @@
+package com.cool.core.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.Locale;
+import org.springdoc.core.customizers.SpringDocCustomizers;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.SpringDocProviders;
+import org.springdoc.core.service.AbstractRequestService;
+import org.springdoc.core.service.GenericResponseService;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.OperationService;
+import org.springdoc.webmvc.api.OpenApiResource;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * 自定义 OpenApiResource
+ */
+@Component
+@ConditionalOnProperty(
+ name = "springdoc.api-docs.enabled",
+ havingValue = "true"
+)
+public class CustomOpenApiResource extends OpenApiResource {
+
+ public CustomOpenApiResource(ObjectFactory openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, GenericResponseService responseBuilder, OperationService operationParser, SpringDocConfigProperties springDocConfigProperties, SpringDocProviders springDocProviders, SpringDocCustomizers springDocCustomizers) {
+ super("springdocDefault", openAPIBuilderObjectFactory, requestBuilder, responseBuilder, operationParser, springDocConfigProperties, springDocProviders, springDocCustomizers);
+ }
+
+ @Override
+ protected String getServerUrl(HttpServletRequest request, String apiDocsUrl) {
+ return "";
+ }
+
+ public byte[] getOpenApiJson() throws JsonProcessingException {
+ return writeJsonValue(getOpenApi(Locale.getDefault()));
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/FileModeEnum.java b/cool-admin-java/src/main/java/com/cool/core/config/FileModeEnum.java
new file mode 100644
index 0000000..243520b
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/FileModeEnum.java
@@ -0,0 +1,28 @@
+package com.cool.core.config;
+
+/**
+ * 文件模式
+ */
+public enum FileModeEnum {
+ LOCAL("local", "local", "本地"), CLOUD("cloud", "oss", "云存储"), OTHER("other", "other", "其他");
+
+ private String value;
+
+ private String type;
+
+ private String des;
+
+ FileModeEnum(String value, String type, String des) {
+ this.value = value;
+ this.type = type;
+ this.des = des;
+ }
+
+ public String value() {
+ return this.value;
+ }
+
+ public String type() {
+ return this.type;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/FileProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/FileProperties.java
new file mode 100644
index 0000000..4af01c0
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/FileProperties.java
@@ -0,0 +1,22 @@
+package com.cool.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 文件
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "cool.file")
+public class FileProperties {
+ // 上传模式
+ private FileModeEnum mode;
+ // 上传类型
+ private String type;
+ // 本地文件上传
+ @NestedConfigurationProperty
+ private LocalFileProperties local;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/JacksonConfig.java b/cool-admin-java/src/main/java/com/cool/core/config/JacksonConfig.java
new file mode 100644
index 0000000..8351306
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/JacksonConfig.java
@@ -0,0 +1,67 @@
+package com.cool.core.config;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.SimpleDateFormat;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+@Configuration
+public class JacksonConfig {
+
+ @Bean
+ public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
+ final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
+ final ObjectMapper objectMapper = builder.build();
+ SimpleModule simpleModule = new SimpleModule();
+ // Long,BigInteger 转为 String 防止 js 丢失精度
+ simpleModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
+ simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
+ simpleModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
+ objectMapper.registerModule(simpleModule);
+ // 配置日期格式为 yyyy-MM-dd HH:mm:ss
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ objectMapper.setDateFormat(dateFormat);
+ return new MappingJackson2HttpMessageConverter(objectMapper);
+ }
+
+ /**
+ * 超出 JS 最大最小值 处理
+ */
+ @JacksonStdImpl
+ public static class BigNumberSerializer extends NumberSerializer {
+
+ /**
+ * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
+ */
+ private static final long MAX_SAFE_INTEGER = 9007199254740991L;
+ private static final long MIN_SAFE_INTEGER = -9007199254740991L;
+
+ /**
+ * 提供实例
+ */
+ public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
+
+ public BigNumberSerializer(Class extends Number> rawType) {
+ super(rawType);
+ }
+
+ @Override
+ public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ // 超出范围 序列化位字符串
+ if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
+ super.serialize(value, gen, provider);
+ } else {
+ gen.writeString(value.toString());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/LocalFileProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/LocalFileProperties.java
new file mode 100644
index 0000000..5ecad6f
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/LocalFileProperties.java
@@ -0,0 +1,30 @@
+package com.cool.core.config;
+
+import com.cool.core.util.PathUtils;
+import java.io.File;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 文件
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "cool.file.local")
+public class LocalFileProperties {
+
+ // 跟域名
+ private String baseUrl;
+
+ private String uploadPath = "assets/public/upload";
+
+ public String getAbsoluteUploadFolder() {
+ if (!PathUtils.isAbsolutePath(uploadPath)) {
+ // 相对路径
+ return System.getProperty("user.dir") + File.separator + uploadPath;
+ }
+ // 绝对路径
+ return uploadPath;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/LogDiscardPolicy.java b/cool-admin-java/src/main/java/com/cool/core/config/LogDiscardPolicy.java
new file mode 100644
index 0000000..a661aaa
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/LogDiscardPolicy.java
@@ -0,0 +1,13 @@
+package com.cool.core.config;
+
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class LogDiscardPolicy implements RejectedExecutionHandler {
+
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ log.warn("logTaskExecutor 当前已超过线程池最大队列容量,拒绝策略为丢弃该线程 {}", r.toString());
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/LogProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/LogProperties.java
new file mode 100644
index 0000000..0452ff8
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/LogProperties.java
@@ -0,0 +1,28 @@
+package com.cool.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "cool.log")
+public class LogProperties {
+
+ /**
+ * 请求参数最大字节,超过请求参数不记录
+ */
+ private int maxByteLength;
+ /**
+ * 核心线程数的倍数
+ */
+ private int corePoolSizeMultiplier;
+ /**
+ * 最大线程数的倍数
+ */
+ private int maxPoolSizeMultiplier;
+ /**
+ * 队列容量的倍数
+ */
+ private int queueCapacityMultiplier;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java b/cool-admin-java/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java
new file mode 100644
index 0000000..e7dcfa1
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java
@@ -0,0 +1,27 @@
+package com.cool.core.config;
+
+import com.cool.core.tenant.CoolTenantFactory;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.tenant.TenantFactory;
+import com.mybatisflex.spring.boot.MyBatisFlexCustomizer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisFlexConfiguration implements MyBatisFlexCustomizer {
+
+ @Override
+ public void customize(FlexGlobalConfig globalConfig) {
+ // 我们可以在这里进行一些列的初始化配置
+
+ // 指定多租户列的列名
+ FlexGlobalConfig.getDefaultConfig().setTenantColumn("tenant_id");
+ }
+
+ @Bean
+ @ConditionalOnProperty(name = "cool.multi-tenant.enable", havingValue = "true")
+ public TenantFactory tenantFactory(){
+ return new CoolTenantFactory();
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/OssFileProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/OssFileProperties.java
new file mode 100644
index 0000000..1f02b3d
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/OssFileProperties.java
@@ -0,0 +1,24 @@
+package com.cool.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 文件
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "cool.file.oss")
+public class OssFileProperties {
+ // accessKeyId
+ private String accessKeyId;
+ // accessKeySecret
+ private String accessKeySecret;
+ // 文件空间
+ private String bucket;
+ // 地址
+ private String endpoint;
+ // 超时时间
+ private Long timeout;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/PluginJson.java b/cool-admin-java/src/main/java/com/cool/core/config/PluginJson.java
new file mode 100644
index 0000000..ad39263
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/PluginJson.java
@@ -0,0 +1,57 @@
+package com.cool.core.config;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class PluginJson {
+ /**
+ * 插件名称
+ */
+ private String name;
+ /**
+ * 插件标识
+ */
+ private String key;
+ /**
+ * 插件钩子,比如替换系统的上传组件,upload
+ */
+ private String hook;
+ /**
+ * 版本号
+ */
+ private String version;
+ /**
+ * 插件描述
+ */
+ private String description;
+ /**
+ * 作者
+ */
+ private String author;
+ /**
+ * 插件 logo,建议尺寸 256x256
+ */
+ private String logo;
+ /**
+ * 插件介绍,会展示在插件的详情中
+ */
+ private String readme;
+ /**
+ * 插件配置, 每个插件的配置各不相同
+ */
+ private Map config;
+
+ /**
+ * jar包存放路径
+ */
+ private String jarPath;
+
+ /**
+ * 同名hook id
+ */
+ @JsonIgnore
+ private Long sameHookId;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/SwaggerConfig.java b/cool-admin-java/src/main/java/com/cool/core/config/SwaggerConfig.java
new file mode 100644
index 0000000..8772d16
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/SwaggerConfig.java
@@ -0,0 +1,16 @@
+package com.cool.core.config;
+
+import io.swagger.v3.oas.annotations.ExternalDocumentation;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.info.Contact;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+
+@OpenAPIDefinition(info = @Info(title = "COOL-ADMIN", version = "4.0", description = "一个很酷的后台权限管理系统开发框架", contact = @Contact(name = "闪酷科技")), security = @SecurityRequirement(name = "Authorization"), externalDocs = @ExternalDocumentation(description = "参考文档", url = "https://cool-js.com"))
+@SecurityScheme(type = SecuritySchemeType.APIKEY, name = "Authorization", in = SecuritySchemeIn.HEADER)
+public class SwaggerConfig {
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/ThreadPoolConfig.java b/cool-admin-java/src/main/java/com/cool/core/config/ThreadPoolConfig.java
new file mode 100644
index 0000000..9fc6dc7
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/ThreadPoolConfig.java
@@ -0,0 +1,41 @@
+package com.cool.core.config;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+@Configuration
+@RequiredArgsConstructor
+public class ThreadPoolConfig {
+
+ private final LogProperties logProperties;
+
+ @Bean(name = "logTaskExecutor")
+ public Executor loggingTaskExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+ int corePoolSize = Runtime.getRuntime().availableProcessors() * logProperties.getCorePoolSizeMultiplier();
+ int maxPoolSize = corePoolSize * logProperties.getMaxPoolSizeMultiplier();
+ int queueCapacity = maxPoolSize * logProperties.getQueueCapacityMultiplier();
+
+ executor.setCorePoolSize(corePoolSize);
+ executor.setMaxPoolSize(maxPoolSize);
+ executor.setQueueCapacity(queueCapacity);
+ executor.setThreadNamePrefix("logTask-");
+
+ // 自定义拒绝策略
+ executor.setRejectedExecutionHandler(new LogDiscardPolicy());
+ executor.initialize();
+ return executor;
+ }
+
+ @Bean(name = "cachedThreadPool")
+ public ExecutorService cachedThreadPool() {
+ // 创建一个虚拟线程池,每个任务使用一个虚拟线程执行
+ return Executors.newCachedThreadPool();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/TokenProperties.java b/cool-admin-java/src/main/java/com/cool/core/config/TokenProperties.java
new file mode 100644
index 0000000..12cd713
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/TokenProperties.java
@@ -0,0 +1,18 @@
+package com.cool.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * token配置
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "cool.token")
+public class TokenProperties {
+ // token 过期时间
+ private Long expire;
+ // refreshToken 过期时间
+ private Long refreshExpire;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/cache/CaffeineConfig.java b/cool-admin-java/src/main/java/com/cool/core/config/cache/CaffeineConfig.java
new file mode 100644
index 0000000..d22204c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/cache/CaffeineConfig.java
@@ -0,0 +1,122 @@
+package com.cool.core.config.cache;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cache.Cache;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
+
+@Slf4j
+@Configuration
+@EnableCaching
+@ConditionalOnProperty(name = "spring.cache.type", havingValue = "CAFFEINE")
+public class CaffeineConfig {
+
+ @Value("${spring.cache.file}")
+ private String cacheFile;
+
+ @Value("${cool.cacheName}")
+ private String cacheName;
+
+ @Bean
+ public Caffeine caffeine() {
+ return Caffeine.newBuilder().maximumSize(10000);
+ }
+
+ @Bean
+ public CaffeineCacheManager cacheManager(Caffeine caffeine) {
+ CaffeineCacheManager cacheManager = new CaffeineCacheManager();
+ cacheManager.setCaffeine(caffeine);
+ loadCache(cacheManager);
+ return cacheManager;
+ }
+
+ @PostConstruct
+ public void init() {
+ File cacheDir = new File(cacheFile).getParentFile();
+ if (!cacheDir.exists()) {
+ if (cacheDir.mkdirs()) {
+ log.info("Created directory: " + cacheDir.getAbsolutePath());
+ } else {
+ log.error("Failed to create directory: " + cacheDir.getAbsolutePath());
+ }
+ }
+ }
+
+ private void loadCache(CaffeineCacheManager cacheManager) {
+ if (cacheManager == null) {
+ log.error("CacheManager is null");
+ return;
+ }
+
+ if (cacheFile == null || cacheFile.isEmpty()) {
+ log.error("Cache file path is null or empty");
+ return;
+ }
+
+ File file = new File(cacheFile);
+ if (!file.exists()) {
+ log.warn("Cache file does not exist: " + cacheFile);
+ return;
+ }
+ try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file))) {
+ Map cacheMap = (Map) inputStream.readObject();
+ com.github.benmanes.caffeine.cache.Cache caffeineCache = Caffeine.newBuilder()
+ .build();
+ caffeineCache.putAll(cacheMap);
+ cacheManager.registerCustomCache(cacheName, caffeineCache);
+ } catch (IOException | ClassNotFoundException e) {
+ log.error("loadCacheErr", e);
+ }
+ }
+
+ @Bean
+ public CacheLoader cacheLoader(CaffeineCacheManager cacheManager) {
+ return new CacheLoader(cacheManager, cacheFile);
+ }
+
+ class CacheLoader {
+
+ private final CaffeineCacheManager cacheManager;
+ private final String cacheFile;
+
+ public CacheLoader(CaffeineCacheManager cacheManager, String cacheFile) {
+ this.cacheManager = cacheManager;
+ this.cacheFile = cacheFile;
+ }
+
+ @EventListener(ContextClosedEvent.class)
+ @Scheduled(fixedRate = 10000)
+ public void persistCache() {
+ Cache cache = cacheManager.getCache(cacheName);
+ if (cache != null
+ && cache.getNativeCache() instanceof com.github.benmanes.caffeine.cache.Cache) {
+ Map cacheMap = ((com.github.benmanes.caffeine.cache.Cache) cache
+ .getNativeCache()).asMap();
+ try (ObjectOutputStream outputStream = new ObjectOutputStream(
+ new FileOutputStream(cacheFile))) {
+ outputStream.writeObject(new HashMap<>(cacheMap));
+ } catch (IOException e) {
+ log.error("persistCacheErr", e);
+ }
+ }
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/config/cache/RedisConfig.java b/cool-admin-java/src/main/java/com/cool/core/config/cache/RedisConfig.java
new file mode 100644
index 0000000..9da37da
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/config/cache/RedisConfig.java
@@ -0,0 +1,29 @@
+package com.cool.core.config.cache;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+
+@Configuration
+@EnableCaching
+@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate redisTemplate(
+ RedisConnectionFactory redisConnectionFactory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(redisConnectionFactory);
+ return template;
+ }
+
+ @Bean
+ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ return RedisCacheManager.create(redisConnectionFactory);
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/enums/AdminComponentsEnum.java b/cool-admin-java/src/main/java/com/cool/core/enums/AdminComponentsEnum.java
new file mode 100644
index 0000000..e4c95f1
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/enums/AdminComponentsEnum.java
@@ -0,0 +1,105 @@
+package com.cool.core.enums;
+
+public class AdminComponentsEnum {
+
+
+ /**
+ * 省市区选择器 - 用户选择省市区
+ */
+ public static final String PCA = "pca";
+
+ /**
+ * 文本输入 - 文本编辑框
+ */
+ public static final String INPUT = "input";
+
+ /**
+ * 文本域 - 多行文本编辑框
+ */
+ public static final String TEXTAREA = "textarea";
+
+ /**
+ * 富文本编辑器 - 用于文章,商品详情的编辑
+ */
+ public static final String EDITOR_RICH = "editor-rich";
+
+ /**
+ * 代码编辑器 - 用于开发人员编写代码,支持多种语言,支持代码高亮,支持代码格式化
+ */
+ public static final String CODING = "coding";
+
+ /**
+ * 数字输入 - 数字输入编辑框
+ */
+ public static final String INPUT_NUMBER = "input-number";
+
+ /**
+ * 日期选择器 - 用户选择 年-月-日
+ */
+ public static final String DATE = "date";
+
+ /**
+ * 日期范围选择器 - 用户选择起始 年-月-日
+ */
+ public static final String DATERANGE = "daterange";
+
+ /**
+ * 时间选择器 - 用户选择 时:分:秒
+ */
+ public static final String DATETIME = "datetime";
+
+ /**
+ * 时间范围选择器 - 用户选择起始 年-月-日 时:分:秒
+ */
+ public static final String DATETIMERANGE = "datetimerange";
+
+ /**
+ * 单图上传 - 用户上传单张图片,如:头像、logo、封面
+ */
+ public static final String UPLOAD_IMG = "upload-img";
+
+ /**
+ * 多图上传 - 用户上传多张图片, 如:照片、图片
+ */
+ public static final String UPLOAD_IMG_MULTIPLE = "upload-img-multiple";
+
+ /**
+ * 单个文件上传 - 用户上传单个文件
+ */
+ public static final String UPLOAD_FILE = "upload-file";
+
+ /**
+ * 多个文件上传 - 用户上传多个文件
+ */
+ public static final String UPLOAD_FILE_MULTIPLE = "upload-file-multiple";
+
+ /**
+ * 状态选择器 - 用户开启或者关闭操作,如:是否启用、是否推荐、是否默认、置顶、启用禁用等
+ */
+ public static final String SWITCH = "switch";
+
+ /**
+ * 评分选择器 - 用户评分
+ */
+ public static final String RATE = "rate";
+
+ /**
+ * 滑块选择器 - 在一个固定区间内进行选择, 如:进度
+ */
+ public static final String PROGRESS = "progress";
+
+ /**
+ * 单选框 - 在一组备选项中进行单选,如:审批状态
+ */
+ public static final String RADIO = "radio";
+
+ /**
+ * 多选框 - 适用于选项比较少的情况,在一组备选项中进行多选, 如:学历、爱好等
+ */
+ public static final String CHECKBOX = "checkbox";
+
+ /**
+ * 下拉框 - 适用于当选项过多时,使用下拉菜单展示并选择内容,如:分类、标签等
+ */
+ public static final String SELECT = "select";
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/enums/Apis.java b/cool-admin-java/src/main/java/com/cool/core/enums/Apis.java
new file mode 100644
index 0000000..b8a4509
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/enums/Apis.java
@@ -0,0 +1,13 @@
+package com.cool.core.enums;
+
+public class Apis {
+ public static final String ADD = "add";
+ public static final String DELETE = "delete";
+ public static final String UPDATE = "update";
+ public static final String PAGE = "page";
+ public static final String LIST = "list";
+ public static final String INFO = "info";
+
+
+ public static final String[] ALL_API = new String[]{ ADD, DELETE, UPDATE, PAGE, LIST, INFO };
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/enums/QueryModeEnum.java b/cool-admin-java/src/main/java/com/cool/core/enums/QueryModeEnum.java
new file mode 100644
index 0000000..8ab1e25
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/enums/QueryModeEnum.java
@@ -0,0 +1,10 @@
+package com.cool.core.enums;
+
+/**
+ * 查询模式决定返回值
+ */
+public enum QueryModeEnum {
+ ENTITY, // 实体(默认)
+ ENTITY_WITH_RELATIONS, // 实体关联查询(如实体字段上加 @RelationOneToMany 等注解)
+ CUSTOM , // 自定义,默认为Map
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/enums/UserTypeEnum.java b/cool-admin-java/src/main/java/com/cool/core/enums/UserTypeEnum.java
new file mode 100644
index 0000000..a35be7c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/enums/UserTypeEnum.java
@@ -0,0 +1,10 @@
+package com.cool.core.enums;
+
+/**
+ * 用户类型
+ */
+public enum UserTypeEnum {
+ ADMIN, // 后台
+ APP, // app
+ UNKNOWN, // 未知
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/eps/CoolEps.java b/cool-admin-java/src/main/java/com/cool/core/eps/CoolEps.java
new file mode 100644
index 0000000..cf9e3b9
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/eps/CoolEps.java
@@ -0,0 +1,410 @@
+package com.cool.core.eps;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ClassUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.annotation.EpsField;
+import com.cool.core.annotation.TokenIgnore;
+import com.cool.core.config.CustomOpenApiResource;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.*;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * 实体信息与路径
+ */
+@Getter
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class CoolEps {
+
+ @Value("${server.port}")
+ private int serverPort;
+
+ private Dict entityInfo;
+
+ private Dict menuInfo;
+
+ private JSONObject swaggerInfo;
+
+ @Value("${springdoc.api-docs.enabled:false}")
+ private boolean apiDocsEnabled;
+
+ public Dict admin;
+
+ public Dict app;
+
+ final private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+ @Async
+ public void init() {
+ if (!apiDocsEnabled) {
+ log.info("服务启动成功,端口:{}", serverPort);
+ return;
+ }
+ entityInfo = Dict.create();
+ menuInfo = Dict.create();
+ swaggerInfo = swaggerInfo();
+ Runnable task = () -> {
+ entity();
+ urls();
+ log.info("初始化eps完成,服务启动成功,端口:{}", serverPort);
+ };
+ // ThreadUtil.safeSleep(3000);
+ ThreadUtil.execute(task);
+ }
+
+ /**
+ * 清空所有的数据
+ */
+ public void clear() {
+ admin = Dict.create();
+ app = Dict.create();
+ }
+
+ /**
+ * 构建所有的url
+ */
+ private void urls() {
+ Dict admin = Dict.create();
+ Dict app = Dict.create();
+ ArrayList emptyList = new ArrayList<>();
+ Map map = requestMappingHandlerMapping.getHandlerMethods();
+ for (Map.Entry methodEntry : map.entrySet()) {
+ RequestMappingInfo info = methodEntry.getKey();
+ HandlerMethod method = methodEntry.getValue();
+ TokenIgnore tokenIgnore = method.getMethodAnnotation(TokenIgnore.class);
+ String module = getModule(method);
+ if (StrUtil.isNotEmpty(module)) {
+ String entityName = getEntity(method.getBeanType());
+ String methodPath = getMethodUrl(method);
+ String escapedMethodPath = methodPath.replace("{", "\\{").replace("}", "\\}");
+ String prefix = Objects.requireNonNull(getUrl(info))
+ .replaceFirst("(?s)(.*)" + escapedMethodPath, "$1");
+ Dict result;
+ int type = 0;
+ if (prefix.startsWith("/admin")) {
+ result = admin;
+ } else if (prefix.startsWith("/app")) {
+ result = app;
+ type = 1;
+ } else {
+ continue;
+ }
+ if (result.get(module) == null) {
+ result.set(module, new ArrayList());
+ }
+
+ List urls = result.getBean(module);
+ Dict item = CollUtil.findOne(urls, dict -> {
+ if (dict != null) {
+ return dict.getStr("module").equals(module)
+ && dict.getStr("controller")
+ .equals(method.getBeanType().getSimpleName());
+ } else {
+ return false;
+ }
+ });
+ if (item != null) {
+ item.set("api", apis(prefix, methodPath, item.getBean("api"), tokenIgnore));
+ } else {
+ item = Dict.create();
+ item.set("controller", method.getBeanType().getSimpleName());
+ item.set("module", module);
+ item.set("info", Dict.create().set("type",
+ Dict.create()
+ .set("name", getLastPathSegment(prefix))
+ .set("description", "")
+ ));
+ item.set("api", apis(prefix, methodPath, item.getBean("api"), tokenIgnore));
+ item.set("name", entityName);
+ item.set("columns", entityInfo.get(entityName));
+ item.set("pageQueryOp", Dict.create().set("keyWordLikeFields", emptyList)
+ .set("fieldEq", emptyList)
+ .set("fieldLike", emptyList));
+ item.set("prefix", prefix);
+ item.set("menu", menuInfo.get(entityName));
+ urls.add(item);
+ }
+ if (type == 0) {
+ admin.set(module, urls);
+ }
+ if (type == 1) {
+ app.set(module, urls);
+ }
+
+ }
+ }
+ this.admin = admin;
+ this.app = app;
+
+ }
+ /**
+ * 提取URL路径中的最后一个路径段
+ * 示例:输入 "/api/getData" 返回 "getData"
+ */
+ private String getLastPathSegment(String url) {
+ if (StrUtil.isBlank(url)) {
+ return "";
+ }
+
+ int queryIndex = url.indexOf('?');
+ if (queryIndex != -1) {
+ url = url.substring(0, queryIndex);
+ }
+
+ int slashIndex = url.lastIndexOf('/');
+ if (slashIndex != -1 && slashIndex < url.length() - 1) {
+ return url.substring(slashIndex + 1);
+ } else {
+ return url;
+ }
+ }
+
+
+ /**
+ * 设置所有的api
+ *
+ * @param prefix 路由前缀
+ * @param methodPath 方法路由
+ * @param list api列表
+ * @return api列表
+ */
+ private List apis(String prefix, String methodPath, List list, TokenIgnore tokenIgnore) {
+ if (ObjUtil.isNull(list)) {
+ list = new ArrayList<>();
+ }
+ Dict item = Dict.create();
+ item.set("path", methodPath);
+ item.set("tag", "");
+ item.set("dts", Dict.create());
+ item.set("ignoreToken", false);
+ if (tokenIgnore != null) {
+ item.set("ignoreToken", true);
+ }
+ setSwaggerInfo(item, prefix + methodPath);
+ list.add(item);
+ return list;
+ }
+
+ /**
+ * 设置swagger相关信息
+ *
+ * @param item 信息载体
+ * @param url url地址
+ */
+ private void setSwaggerInfo(Dict item, String url) {
+ JSONObject paths = swaggerInfo.getJSONObject("paths");
+ JSONObject urlInfo = paths.getJSONObject(url);
+ String method = urlInfo.keySet().iterator().next();
+ JSONObject methodInfo = urlInfo.getJSONObject(method);
+ item.set("method", method);
+ item.set("summary", methodInfo.getStr("summary"));
+ }
+
+ /**
+ * 获得方法的url地址
+ *
+ * @param handlerMethod 方法
+ * @return 方法url地址
+ */
+ private String getMethodUrl(HandlerMethod handlerMethod) {
+ String url = "";
+ Method method = handlerMethod.getMethod();
+ Annotation[] annotations = method.getDeclaredAnnotations();
+
+ for (Annotation annotation : annotations) {
+ Class extends Annotation> annotationType = annotation.annotationType();
+ if (annotationType.getName().contains("org.springframework.web.bind.annotation")) {
+ Map attributes = Arrays.stream(annotationType.getDeclaredMethods())
+ .collect(Collectors.toMap(Method::getName, m -> {
+ try {
+ return m.invoke(annotation);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to access annotation attribute",
+ e);
+ }
+ }));
+
+ if (attributes.containsKey("value") && ObjUtil.isNotEmpty(attributes.get("value"))) {
+ url = ((String[]) attributes.get("value"))[0];
+ }
+ break;
+ }
+ }
+
+ return url;
+ }
+
+ /**
+ * 获得url地址
+ *
+ * @param info 路由信息
+ * @return url地址
+ */
+ private String getUrl(RequestMappingInfo info) {
+ if (info.getPathPatternsCondition() == null) {
+ return null;
+ }
+ Set paths = info.getPathPatternsCondition().getPatternValues();
+ return paths.iterator().next();
+ }
+
+ /**
+ * 获得模块
+ *
+ * @param method 方法
+ * @return 模块
+ */
+ private String getModule(HandlerMethod method) {
+ String beanName = method.getBeanType().getName();
+ String[] beanNames = beanName.split("[.]");
+ int index = ArrayUtil.indexOf(beanNames, "modules");
+ if (index > 0) {
+ return beanNames[index + 1];
+ }
+ return null;
+ }
+
+ /**
+ * 获得swagger的json信息
+ */
+ private JSONObject swaggerInfo() {
+ try {
+ byte[] bytes = SpringUtil.getBean(CustomOpenApiResource.class).getOpenApiJson();
+ return JSONUtil.parseObj(new String(bytes));
+ } catch (Exception e) {
+ return new JSONObject();
+ }
+ }
+
+ /**
+ * 获得Controller上的实体类型
+ *
+ * @param controller Controller类
+ * @return 实体名称
+ */
+ private String getEntity(Class> controller) {
+ try {
+ ParameterizedType parameterizedType = (ParameterizedType) controller.getGenericSuperclass();
+ Class> entityClass = (Class>) parameterizedType.getActualTypeArguments()[1];
+ return entityClass.getSimpleName();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ private void entity() {
+ // 扫描所有的实体类
+ Set> classes = ClassUtil.scanPackageByAnnotation("", Table.class);
+ classes.forEach(e -> {
+ // 获得属性
+ Field[] fields = getAllDeclaredFields(e);
+ List columns = columns(fields);
+ entityInfo.set(e.getSimpleName(), columns);
+
+
+ Table mergedAnnotation = AnnotatedElementUtils.findMergedAnnotation(e, Table.class);
+
+ menuInfo.set(e.getSimpleName(), mergedAnnotation.comment());
+
+
+ });
+ }
+
+ /**
+ * 获取类及其所有父类中声明的字段
+ *
+ * @param clazz 要检查的类
+ * @return 包含类及其所有父类中声明的所有字段的数组
+ */
+ public static Field[] getAllDeclaredFields(Class> clazz) {
+ // 参数校验
+ if (clazz == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+
+ List allFields = new ArrayList<>();
+ Class> currentClass = clazz;
+
+ // 循环遍历类及其父类
+ while (currentClass != null) {
+ Field[] declaredFields = currentClass.getDeclaredFields();
+ allFields.addAll(Arrays.asList(declaredFields));
+ currentClass = currentClass.getSuperclass();
+ }
+
+ // 将列表转换为数组返回
+ return allFields.toArray(new Field[0]);
+ }
+
+ /**
+ * 获得所有的列
+ *
+ * @param fields 字段名
+ * @return 所有的列
+ */
+ private List columns(Field[] fields) {
+ List dictList = new ArrayList<>();
+ for (Field field : fields) {
+ Dict dict = Dict.create();
+
+ EpsField epsField = AnnotatedElementUtils.findMergedAnnotation(field, EpsField.class);
+ if (epsField != null) {
+ dict.set("component", epsField.component());
+ }
+
+ ColumnDefine columnInfo = AnnotatedElementUtils.findMergedAnnotation(field, ColumnDefine.class);
+ if (columnInfo == null) {
+ continue;
+ }
+ dict.set("comment", columnInfo.comment());
+ dict.set("length", columnInfo.length());
+ dict.set("propertyName", field.getName());
+ dict.set("type", matchType(field.getType().getName()));
+ dict.set("nullable", !columnInfo.notNull());
+ dict.set("source", "a." + field.getName());
+ dictList.add(dict);
+ }
+ return dictList;
+ }
+
+ /**
+ * java类型转换成JavaScript对应的类型
+ *
+ * @param type 类型
+ * @return JavaScript类型
+ */
+ private String matchType(String type) {
+ return switch (type) {
+ case "java.lang.Boolean" -> "boolean";
+ case "java.lang.Long", "java.lang.Integer", "java.lang.Short", "java.lang.Float",
+ "java.lang.Double" -> "number";
+ case "java.util.Date" -> "date";
+ default -> "string";
+ };
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/eps/EpsEvent.java b/cool-admin-java/src/main/java/com/cool/core/eps/EpsEvent.java
new file mode 100644
index 0000000..1e40b01
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/eps/EpsEvent.java
@@ -0,0 +1,26 @@
+package com.cool.core.eps;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 事件监听
+ */
+@Slf4j
+@Component
+@Profile({"local"})
+@RequiredArgsConstructor
+public class EpsEvent {
+
+ final private CoolEps coolEps;
+
+ @EventListener
+ public void onApplicationEvent(ApplicationReadyEvent event) {
+ coolEps.init();
+ log.info("构建eps信息");
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/exception/CoolException.java b/cool-admin-java/src/main/java/com/cool/core/exception/CoolException.java
new file mode 100644
index 0000000..90498a5
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/exception/CoolException.java
@@ -0,0 +1,43 @@
+package com.cool.core.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 自定义异常处理
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class CoolException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private String msg;
+ private int code = 500;
+ private Object data;
+
+ public CoolException(String msg) {
+ super(msg);
+ this.msg = msg;
+ }
+
+ public CoolException(String msg, Throwable e) {
+ super(msg, e);
+ this.msg = msg;
+ }
+
+ public CoolException(String msg, int code) {
+ super(msg);
+ this.msg = msg;
+ this.code = code;
+ }
+
+ public CoolException(String msg, int code, Throwable e) {
+ super(msg, e);
+ this.msg = msg;
+ this.code = code;
+ }
+
+ public CoolException(Object data) {
+ this.data = data;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/exception/CoolExceptionHandler.java b/cool-admin-java/src/main/java/com/cool/core/exception/CoolExceptionHandler.java
new file mode 100644
index 0000000..01dec57
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/exception/CoolExceptionHandler.java
@@ -0,0 +1,71 @@
+package com.cool.core.exception;
+
+import cn.hutool.core.util.ObjUtil;
+import com.cool.core.request.R;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 异常处理器
+ */
+@RestControllerAdvice
+@Slf4j
+public class CoolExceptionHandler {
+
+ @ExceptionHandler(CoolException.class)
+ public R handleRRException(CoolException e) {
+ R r = new R();
+ if (ObjUtil.isNotEmpty(e.getData())) {
+ r.setData( e.getData() );
+ } else {
+ r.setCode( e.getCode() );
+ r.setMessage( e.getMessage() );
+ }
+ if (ObjUtil.isNotEmpty(e.getCause())) {
+ log.error(e.getCause().getMessage(), e.getCause());
+ }
+ return r;
+ }
+
+ @ExceptionHandler(DuplicateKeyException.class)
+ public R handleDuplicateKeyException(DuplicateKeyException e) {
+ log.error(e.getMessage(), e);
+ return R.error("已存在该记录或值不能重复");
+ }
+
+ @ExceptionHandler(BadCredentialsException.class)
+ public R handleBadCredentialsException(BadCredentialsException e) {
+ log.error(e.getMessage(), e);
+ return R.error("账户密码不正确");
+ }
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ public R handleHttpRequestMethodNotSupportedException(
+ HttpRequestMethodNotSupportedException e) {
+ log.error(e.getMessage(), e);
+ return R.error("不支持该请求方式,请区分POST、GET等请求方式是否正确");
+ }
+
+ @ExceptionHandler(IllegalArgumentException.class)
+ public R handleIllegalArgumentException(IllegalArgumentException e) {
+ log.error(e.getMessage(), e);
+ return R.error(e.getMessage());
+ }
+
+ @ExceptionHandler(Exception.class)
+ public R handleException(Exception e) {
+ log.error(e.getMessage(), e);
+ return R.error();
+ }
+
+ @ExceptionHandler(WxErrorException.class)
+ public R handleException(WxErrorException e) {
+ log.error(e.getMessage(), e);
+ return R.error(e.getMessage());
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/exception/CoolPreconditions.java b/cool-admin-java/src/main/java/com/cool/core/exception/CoolPreconditions.java
new file mode 100644
index 0000000..8779f3a
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/exception/CoolPreconditions.java
@@ -0,0 +1,102 @@
+package com.cool.core.exception;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.cool.core.util.I18nUtil;
+import java.util.Arrays;
+import java.util.Optional;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 校验处理
+ */
+public class CoolPreconditions {
+
+ /**
+ * 条件如果为真 就抛异常 如 CoolPreconditions.check(StrUtil.isEmptyIfStr(name), 500,
+ * "名称不能为空"); name 字段如果为 null或空字符串,就抛异常
+ */
+ public static void check(boolean flag, int code, String message, Object... arguments) {
+ if (flag) {
+ throw getCoolException(message, code, arguments);
+ }
+ }
+
+ public static void check(boolean flag, String message, Object... arguments) {
+ if (flag) {
+ throw getCoolException(message, arguments);
+ }
+ }
+
+ public static void alwaysThrow(String message, Object... arguments) {
+ throw getCoolException(message, arguments);
+ }
+
+ private static CoolException getCoolException(String message, Object... arguments) {
+ Optional first = Arrays.stream(arguments).filter(o -> o instanceof Throwable)
+ .findFirst();
+ return new CoolException(formatMessage(message, arguments), (Throwable) first.orElse(null));
+ }
+
+ private static CoolException getCoolException(String message, int code, Object... arguments) {
+ Optional first = Arrays.stream(arguments).filter(o -> o instanceof Throwable)
+ .findFirst();
+ return new CoolException(formatMessage(message, arguments), code, (Throwable) first.orElse(null));
+ }
+
+
+ /**
+ * 返回data
+ */
+ public static void returnData(boolean flag, Object data) {
+ if (flag) {
+ throw new CoolException(data);
+ }
+ }
+
+ public static void returnData(Object data) {
+ returnData(true, data);
+ }
+
+ /**
+ * 对象如果为空 就抛异常
+ */
+ public static void checkEmpty(Object object, String message, Object... arguments) {
+ check(ObjectUtil.isEmpty(object), formatMessage(message, arguments));
+ }
+
+ public static void checkEmpty(Object object) {
+ check(ObjectUtil.isEmpty(object), "参数不能为空");
+ }
+
+ private static String formatMessage(String messagePattern, Object... arguments) {
+ messagePattern = I18nUtil.getI18nMsg(messagePattern);
+ StringBuilder sb = new StringBuilder();
+ int argumentIndex = 0;
+ int placeholderIndex = messagePattern.indexOf("{}");
+ while (placeholderIndex != -1) {
+ sb.append(messagePattern, 0, placeholderIndex);
+ if (argumentIndex < arguments.length) {
+ sb.append(arguments[argumentIndex++]);
+ } else {
+ sb.append("{}"); // 如果参数不足,保留原样
+ }
+ messagePattern = messagePattern.substring(placeholderIndex + 2);
+ placeholderIndex = messagePattern.indexOf("{}");
+ }
+ sb.append(messagePattern); // 添加剩余部分
+ return sb.toString();
+ }
+
+ @Setter
+ @Getter
+ public static class ReturnData {
+ private Integer type;
+ private String message;
+
+ public ReturnData(Integer type, String message, Object... arguments) {
+ this.type = type;
+ this.message = formatMessage(message, arguments);
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java b/cool-admin-java/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java
new file mode 100644
index 0000000..ccbc7f4
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java
@@ -0,0 +1,53 @@
+package com.cool.core.file;
+
+import static com.cool.core.plugin.consts.PluginConsts.uploadHook;
+
+import cn.hutool.core.util.ObjUtil;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.file.strategy.FileUploadStrategy;
+import com.cool.core.plugin.service.CoolPluginService;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FileUploadStrategyFactory {
+
+ final private ApplicationContext applicationContext;
+
+ final private CoolPluginService coolPluginService;
+
+ private FileUploadStrategy getStrategy(PluginInfoEntity pluginInfoEntity) {
+ if (ObjUtil.isEmpty(pluginInfoEntity)) {
+ return applicationContext.getBean("localFileUploadStrategy", FileUploadStrategy.class);
+ }
+ return applicationContext.getBean("cloudFileUploadStrategy", FileUploadStrategy.class);
+ }
+
+ public Object upload(MultipartFile[] files, HttpServletRequest request) {
+ PluginInfoEntity pluginInfoEntity = coolPluginService.getPluginInfoEntityByHook(uploadHook);
+ try {
+ return getStrategy(pluginInfoEntity).upload(files, request, pluginInfoEntity);
+ } catch (IOException e) {
+ log.error("上传文件失败", e);
+ CoolPreconditions.alwaysThrow("上传文件失败 {}", e.getMessage());
+ }
+ return null;
+ }
+
+ public Object getMode() {
+ PluginInfoEntity pluginInfoEntity = coolPluginService.getPluginInfoEntityByHook(uploadHook);
+ String key = null;
+ if (ObjUtil.isNotEmpty(pluginInfoEntity)) {
+ key = pluginInfoEntity.getKey();
+ }
+ return getStrategy(pluginInfoEntity).getMode(key);
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/file/UpLoadModeType.java b/cool-admin-java/src/main/java/com/cool/core/file/UpLoadModeType.java
new file mode 100644
index 0000000..eb99140
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/file/UpLoadModeType.java
@@ -0,0 +1,26 @@
+package com.cool.core.file;
+
+import com.cool.core.config.FileModeEnum;
+import lombok.Data;
+
+/**
+ * 上传模式类型
+ */
+@Data
+public class UpLoadModeType {
+
+ /**
+ * 模式
+ */
+ private FileModeEnum mode;
+
+ /**
+ * 类型
+ */
+ private String type;
+
+ public UpLoadModeType(FileModeEnum mode) {
+ this.mode = mode;
+ this.type = mode.type();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/file/strategy/CloudFileUploadStrategy.java b/cool-admin-java/src/main/java/com/cool/core/file/strategy/CloudFileUploadStrategy.java
new file mode 100644
index 0000000..4e449a0
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/file/strategy/CloudFileUploadStrategy.java
@@ -0,0 +1,33 @@
+package com.cool.core.file.strategy;
+
+import com.cool.core.config.FileModeEnum;
+import com.cool.core.util.CoolPluginInvokers;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+@Component("cloudFileUploadStrategy")
+public class CloudFileUploadStrategy implements FileUploadStrategy {
+
+ @Override
+ public Object upload(MultipartFile[] files, HttpServletRequest request, PluginInfoEntity pluginInfoEntity)
+ throws IOException {
+ return CoolPluginInvokers.invokePlugin(pluginInfoEntity.getKey());
+ }
+
+ @Override
+ public Map getMode(String key) {
+ try{
+ Object mode = CoolPluginInvokers.invoke(key, "getMode");
+ if (Objects.nonNull(mode)) {
+ return (Map) mode;
+ }
+ } catch (Exception ignore){}
+ return Map.of("mode", FileModeEnum.CLOUD.value(),
+ "type", FileModeEnum.CLOUD.type());
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java b/cool-admin-java/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java
new file mode 100644
index 0000000..47bd605
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java
@@ -0,0 +1,38 @@
+package com.cool.core.file.strategy;
+
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface FileUploadStrategy {
+
+ /**
+ * 文件上传
+ */
+ Object upload(MultipartFile[] files, HttpServletRequest request, PluginInfoEntity pluginInfoEntity)
+ throws IOException;
+
+ /**
+ * 文件上传模式
+ *
+ * @return 上传模式
+ */
+ Map getMode(String key);
+
+ default boolean isAbsolutePath(String pathStr) {
+ Path path = Paths.get(pathStr);
+ return path.isAbsolute();
+ }
+
+ default String getExtensionName(String fileName) {
+ if (fileName.contains(".")) {
+ String[] names = fileName.split("[.]");
+ return "." + names[names.length - 1];
+ }
+ return "";
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java b/cool-admin-java/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java
new file mode 100644
index 0000000..0be9d8e
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java
@@ -0,0 +1,74 @@
+package com.cool.core.file.strategy;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import com.cool.core.config.FileModeEnum;
+import com.cool.core.config.LocalFileProperties;
+import com.cool.core.exception.CoolException;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+@Component("localFileUploadStrategy")
+@RequiredArgsConstructor
+public class LocalFileUploadStrategy implements FileUploadStrategy {
+
+ final private LocalFileProperties localFileProperties;
+
+ /**
+ * 上传文件
+ *
+ * @param files 上传的文件
+ * @return 文件路径
+ */
+ @Override
+ public Object upload(MultipartFile[] files, HttpServletRequest request,
+ PluginInfoEntity pluginInfoEntity) {
+ CoolPreconditions.check(StrUtil.isEmpty(localFileProperties.getBaseUrl()),
+ "filePath 或 baseUrl 未配置");
+ try {
+ List fileUrls = new ArrayList<>();
+ String baseUrl = localFileProperties.getBaseUrl();
+ String date = DateUtil.format(new Date(),
+ DatePattern.PURE_DATE_PATTERN);
+ String absoluteUploadFolder = localFileProperties.getAbsoluteUploadFolder();
+ String fullPath = absoluteUploadFolder + "/" + date;
+ FileUtil.mkdir(fullPath);
+ for (MultipartFile file : files) {
+ // 保存文件
+ String fileName = StrUtil.uuid().replaceAll("-", "") + getExtensionName(
+ Objects.requireNonNull(file.getOriginalFilename()));
+ file.transferTo(new File(fullPath
+ + "/" + fileName));
+ fileUrls.add(baseUrl + "/" + date + "/" + fileName);
+ }
+ if (fileUrls.size() == 1) {
+ return fileUrls.get(0);
+ }
+ return fileUrls;
+ } catch (Exception e) {
+ throw new CoolException("文件上传失败", e);
+ }
+ }
+
+ /**
+ * 文件上传模式
+ *
+ * @return 上传模式
+ */
+ public Map getMode(String key) {
+ return Map.of("mode", FileModeEnum.LOCAL.value(),
+ "type", FileModeEnum.LOCAL.type());
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/i18n/I18nGenerator.java b/cool-admin-java/src/main/java/com/cool/core/i18n/I18nGenerator.java
new file mode 100644
index 0000000..40741a5
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/i18n/I18nGenerator.java
@@ -0,0 +1,219 @@
+package com.cool.core.i18n;
+
+import static com.cool.core.util.I18nUtil.*;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.lock.CoolLock;
+import com.cool.core.util.CoolPluginInvokers;
+import com.cool.core.util.I18nUtil;
+import com.cool.core.util.PathUtils;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.service.sys.BaseSysMenuService;
+import com.cool.modules.dict.entity.DictInfoEntity;
+import com.cool.modules.dict.entity.DictTypeEntity;
+import com.cool.modules.dict.service.DictInfoService;
+import com.cool.modules.dict.service.DictTypeService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class I18nGenerator {
+ private final BaseSysMenuService baseSysMenuService;
+ private final DictTypeService dictTypeService;
+ private final DictInfoService dictInfoService;
+ private final CoolLock coolLock;
+ private final I18nUtil i18nUtil;
+
+ private List languages;
+ private static final Duration DURATION = Duration.ofSeconds(30);
+ public void run(Map map) {
+ log.info("国际化 翻译...");
+ languages = (List) map.getOrDefault("languages", List.of("zh-cn", "zh-tw", "en"));
+ path = (String) map.getOrDefault("path", "assets/i18n");
+ init();
+ log.info("✅国际化 翻译 成功!!!");
+ enable = true;
+ }
+
+ public void init() {
+ // 四个任务并发执行
+ CompletableFuture futureMsg = CompletableFuture.runAsync(this::genBaseMsg);
+ CompletableFuture futureMenu = CompletableFuture.runAsync(this::genBaseMenu);
+ CompletableFuture futureDictInfo = CompletableFuture.runAsync(this::genBaseDictInfo);
+ CompletableFuture futureDictType = CompletableFuture.runAsync(this::genBaseDictType);
+
+ // 等待全部执行完成
+ CompletableFuture.allOf(futureMsg, futureMenu, futureDictInfo, futureDictType).join();
+ }
+
+ private void genBaseMsg() {
+ try {
+ Map msgMap = new HashMap<>();
+ // 从idea本地启动时,从项目目录中读取
+ Files.walk(Paths.get(System.getProperty("user.dir")))
+ .filter(Files::isRegularFile)
+ .filter(path -> path.toString().endsWith(".java"))
+ .filter(path -> !path.toString().contains("/target/") && !path.toString().contains("/.git/"))
+ .forEach(path -> msgMap.putAll(processFile(path)));
+ if (ObjUtil.isNotEmpty(msgMap)) {
+ // 系统异常信息,输出到resources/i18n 文件夹下,只有本地运行会生成
+ File msgfile = FileUtil.file(PathUtils.getUserDir(),
+ "src", "main", "resources", "cool", "i18n", "msg", "template.json");
+ // 确保父目录存在
+ FileUtil.mkParentDirs(msgfile);
+ // 写入内容
+ FileUtil.writeUtf8String(JSONUtil.toJsonStr(msgMap), msgfile);
+ } else {
+ try {
+ // jar启动时,从jar包中读取
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource resource = resolver.getResource("classpath:cool/i18n/msg/template.json");
+ String content = FileUtil.readUtf8String(resource.getFile());
+ msgMap.putAll(JSONUtil.toBean(content, Map.class));
+ } catch (Exception e) {
+ log.error("获取系统异常信息失败", e);
+ }
+ }
+ extracted(MSG_PREFIX, msgMap);
+ } catch (Exception e) {
+ log.error("国际化系统异常信息失败", e);
+ }
+ }
+
+ /**
+ * 生成菜单信息国际化
+ */
+ @Async
+ public void asyncGenBaseMenu() {
+ if (coolLock.tryLock(MENU_PREFIX, DURATION)) {
+ genBaseMenu();
+ coolLock.unlock(MENU_PREFIX);
+ }
+ }
+ private void genBaseMenu() {
+ try {
+ Map menuMap = baseSysMenuService.list(QueryWrapper.create().select(BaseSysMenuEntity::getName))
+ .stream()
+ .collect(Collectors.toMap(
+ BaseSysMenuEntity::getName,
+ BaseSysMenuEntity::getName,
+ (oldValue, newValue) -> oldValue
+ ));
+ extracted(MENU_PREFIX, menuMap);
+ } catch (Exception e) {
+ log.error("国际化菜单信息失败", e);
+ }
+ }
+ @Async
+ public void asyncGenBaseDictType() {
+ if (coolLock.tryLock(DICT_TYPE_PREFIX, DURATION)) {
+ genBaseDictType();
+ coolLock.unlock(DICT_TYPE_PREFIX);
+ }
+ }
+ private void genBaseDictType() {
+ try {
+ Map dataMap = dictTypeService.list(QueryWrapper.create().select(DictTypeEntity::getName))
+ .stream()
+ .collect(Collectors.toMap(
+ DictTypeEntity::getName,
+ DictTypeEntity::getName,
+ (oldValue, newValue) -> oldValue
+ ));
+ extracted(DICT_TYPE_PREFIX, dataMap);
+ } catch (Exception e) {
+ log.error("国际化字段类型信息失败", e);
+ }
+ }
+ @Async
+ public void asyncGenBaseDictInfo() {
+ if (coolLock.tryLock(DICT_INFO_PREFIX, DURATION)) {
+ genBaseDictInfo();
+ coolLock.unlock(DICT_INFO_PREFIX);
+ }
+ }
+ private void genBaseDictInfo() {
+ try {
+ Map dataMap = dictInfoService.list(QueryWrapper.create().select(DictInfoEntity::getName))
+ .stream()
+ .collect(Collectors.toMap(
+ DictInfoEntity::getName,
+ DictInfoEntity::getName,
+ (oldValue, newValue) -> oldValue
+ ));
+ extracted(DICT_INFO_PREFIX, dataMap);
+ } catch (Exception e) {
+ log.error("国际化字段类型信息失败", e);
+ }
+ }
+
+ private void extracted(String prefix, Map dataMap) {
+ languages.forEach(language -> {
+ String key = prefix + language;
+ if (!i18nUtil.exist(key)) {
+ JSONObject jsonObject = invokeTranslate(dataMap, language);
+ if (ObjUtil.isNotNull(jsonObject)) {
+ i18nUtil.update(key, jsonObject);
+ }
+ }
+ });
+ }
+
+ private JSONObject invokeTranslate(Map map, String language) {
+ return (JSONObject) CoolPluginInvokers.invoke("i18n", "invokeTranslate", map, language);
+ }
+
+ // 匹配 CoolPreconditions 抛异常语句中的中文字符串
+ private static final Pattern EXCEPTION_PATTERN = Pattern.compile(
+ "CoolPreconditions\\.(\\w+)\\s*\\([^;]*?\"([^\"]*[\u4e00-\u9fa5]+[^\"]*)\"", Pattern.MULTILINE
+ );
+
+ private static Map processFile(Path path) {
+ Map map = new HashMap<>();
+ try {
+ String content = Files.readString(path, StandardCharsets.UTF_8);
+ // 去掉注释
+ content = removeComments(content);
+
+ // 仅查找方法体内的 CoolPreconditions 调用
+ Matcher matcher = EXCEPTION_PATTERN.matcher(content);
+ while (matcher.find()) {
+ String chineseText = matcher.group(2).trim();
+ map.put(chineseText, chineseText);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return map;
+ }
+
+ // 移除注释(单行与多行)
+ private static String removeComments(String code) {
+ String noMultiLine = code.replaceAll("/\\*.*?\\*/", ""); // 多行注释
+ return noMultiLine.replaceAll("//.*", ""); // 单行注释
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/init/CoolPluginInit.java b/cool-admin-java/src/main/java/com/cool/core/init/CoolPluginInit.java
new file mode 100644
index 0000000..846be54
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/init/CoolPluginInit.java
@@ -0,0 +1,24 @@
+package com.cool.core.init;
+
+import com.cool.core.plugin.service.CoolPluginService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 历史安装过的插件执行初始化
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CoolPluginInit {
+
+ final private CoolPluginService coolPluginService;
+
+ @EventListener(ApplicationReadyEvent.class)
+ public void run() {
+ coolPluginService.init();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/init/DBFromJsonInit.java b/cool-admin-java/src/main/java/com/cool/core/init/DBFromJsonInit.java
new file mode 100644
index 0000000..9f01048
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/init/DBFromJsonInit.java
@@ -0,0 +1,252 @@
+package com.cool.core.init;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.base.service.MapperProviderService;
+import com.cool.core.mybatis.pg.PostgresSequenceSyncService;
+import com.cool.core.util.DatabaseDialectUtils;
+import com.cool.core.util.EntityUtils;
+import com.cool.modules.base.entity.sys.BaseSysConfEntity;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.service.sys.BaseSysConfService;
+import com.cool.modules.base.service.sys.BaseSysMenuService;
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.stereotype.Component;
+
+/**
+ * 数据库初始数据初始化 在 classpath:cool/data/db 目录下创建.json文件 并定义表数据, 由该类统一执行初始化
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DBFromJsonInit {
+
+ final private BaseSysConfService baseSysConfService;
+
+ final private BaseSysMenuService baseSysMenuService;
+
+ final private MapperProviderService mapperProviderService;
+
+ final private ApplicationEventPublisher eventPublisher;
+
+ final private PostgresSequenceSyncService postgresSequenceSyncService;
+
+ @Value("${cool.initData}")
+ private boolean initData;
+
+ @EventListener(ApplicationReadyEvent.class)
+ public void run() {
+ if (!initData) {
+ return;
+ }
+ // 初始化自定义的数据
+ boolean initFlag = extractedDb();
+ // 初始化菜单数据
+ initFlag = extractedMenu() || initFlag;
+ // 发送数据库初始化完成事件
+ eventPublisher.publishEvent(new DbInitCompleteEvent(this));
+ if (initFlag) {
+ // 如果是postgresql,同步序列
+ syncIdentitySequences();
+ }
+ log.info("数据初始化完成!");
+ }
+
+ private void syncIdentitySequences() {
+ if (DatabaseDialectUtils.isPostgresql()) {
+ postgresSequenceSyncService.syncIdentitySequences();
+ }
+ }
+
+ @Getter
+ public static class DbInitCompleteEvent {
+ private final Object source;
+
+ public DbInitCompleteEvent(Object source) {
+ this.source = source;
+ }
+
+ }
+
+ /**
+ * 解析插入业务数据
+ */
+ private boolean extractedDb() {
+ try {
+ // 加载 JSON 文件
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource[] resources = resolver.getResources("classpath:cool/data/db/*.json");
+ // 遍历所有.json文件
+ return analysisResources(resources);
+ } catch (Exception e) {
+ log.error("Failed to initialize data", e);
+ }
+ return false;
+ }
+
+ private boolean analysisResources(Resource[] resources)
+ throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ String prefix = "db_";
+ boolean isInit = false;
+ for (Resource resource : resources) {
+ File resourceFile = new File(resource.getURL().getFile());
+ String fileName = prefix + resourceFile.getName();
+ String value = baseSysConfService.getValue(fileName);
+ if (StrUtil.isNotEmpty(value)) {
+ log.info("{} 业务数据已初始化过...", fileName);
+ continue;
+ }
+ String jsonStr = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
+ JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
+ // 遍历 JSON 文件中的数据
+ analysisJson(jsonObject);
+
+ BaseSysConfEntity baseSysUserEntity = new BaseSysConfEntity();
+ baseSysUserEntity.setCKey(fileName);
+ baseSysUserEntity.setCValue("success");
+ // 当前文件已加载
+ baseSysConfService.add(baseSysUserEntity);
+ isInit = true;
+ log.info("{} 业务数据初始化成功...", fileName);
+ }
+ return isInit;
+ }
+
+ private void analysisJson(JSONObject jsonObject)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Map> tableMap = EntityUtils.findTableMap();
+ for (String tableName : jsonObject.keySet()) {
+ JSONArray records = jsonObject.getJSONArray(tableName);
+ // 根据表名生成实体类名和 Mapper 接口名
+ Class> entityClass = tableMap.get(tableName);
+ BaseMapper> baseMapper = mapperProviderService.getMapperByEntityClass(entityClass);
+ // 插入
+ insertList(baseMapper, entityClass, records);
+ }
+ }
+
+ /**
+ * 插入列表数据
+ */
+ private void insertList(BaseMapper baseMapper, Class> entityClass,
+ JSONArray records)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ // 插入数据
+ for (int i = 0; i < records.size(); i++) {
+ JSONObject record = records.getJSONObject(i);
+ Object entity = JSONUtil.toBean(record, entityClass);
+ Method getIdMethod = entityClass.getMethod("getId");
+ Object id = getIdMethod.invoke(entity);
+ if (ObjUtil.isNotEmpty(id) && ObjUtil.isNotEmpty(
+ baseMapper.selectOneById((Long) id))) {
+ // 数据库已经有值了
+ continue;
+ }
+ if (ObjUtil.isNotEmpty(id)) {
+ // 带id插入
+ baseMapper.insertSelectiveWithPk(entity);
+ } else {
+ baseMapper.insert(entity);
+ }
+ }
+ }
+
+ /**
+ * 解析插入菜单数据
+ */
+ public boolean extractedMenu() {
+ boolean initFlag = false;
+ try {
+ String prefix = "menu_";
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource[] resources = resolver.getResources("classpath:cool/data/menu/*.json");
+ // 遍历所有.json文件
+ for (Resource resource : resources) {
+ File resourceFile = new File(resource.getURL().getFile());
+ String fileName = prefix + resourceFile.getName();
+ String value = baseSysConfService.getValue(fileName);
+ if (StrUtil.isNotEmpty(value)) {
+ log.info("{} 菜单数据已初始化过...", fileName);
+ continue;
+ }
+ analysisResources(resource, fileName);
+ initFlag = true;
+ }
+ } catch (Exception e) {
+ log.error("Failed to initialize data", e);
+ }
+ return initFlag;
+ }
+
+ private void analysisResources(Resource resource, String fileName) throws IOException {
+ String jsonStr = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
+
+ // 使用 解析 JSON 字符串
+ JSONArray jsonArray = JSONUtil.parseArray(jsonStr);
+
+ // 遍历 JSON 数组
+ for (Object obj : jsonArray) {
+ JSONObject jsonObj = (JSONObject) obj;
+ // 将 JSON 对象转换为 Menu 对象
+ parseMenu(jsonObj, null);
+ }
+ BaseSysConfEntity baseSysUserEntity = new BaseSysConfEntity();
+ baseSysUserEntity.setCKey(fileName);
+ baseSysUserEntity.setCValue("success");
+ // 当前文件已加载
+ baseSysConfService.add(baseSysUserEntity);
+ log.info("{} 菜单数据初始化成功...", fileName);
+ }
+
+ // 递归解析 JSON 对象为 Menu 对象
+ private void parseMenu(JSONObject jsonObj, BaseSysMenuEntity parentMenuEntity) {
+ BaseSysMenuEntity menuEntity = BeanUtil.copyProperties(jsonObj, BaseSysMenuEntity.class);
+ if (ObjUtil.isNotEmpty(parentMenuEntity)) {
+ menuEntity.setParentName(parentMenuEntity.getName());
+ menuEntity.setParentId(parentMenuEntity.getId());
+ }
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .eq(BaseSysMenuEntity::getName, menuEntity.getName());
+ if (ObjUtil.isNull(menuEntity.getParentId())) {
+ queryWrapper.isNull(BaseSysMenuEntity::getParentId);
+ } else {
+ queryWrapper.eq(BaseSysMenuEntity::getParentId, menuEntity.getParentId());
+ }
+ BaseSysMenuEntity dbBaseSysMenuEntity = baseSysMenuService.getOne(queryWrapper);
+ if (ObjUtil.isNull(dbBaseSysMenuEntity)) {
+ baseSysMenuService.add(menuEntity);
+ } else {
+ menuEntity = dbBaseSysMenuEntity;
+ }
+ // 递归处理子菜单
+ JSONArray childMenus = jsonObj.getJSONArray("childMenus");
+ if (childMenus != null) {
+ for (Object obj : childMenus) {
+ JSONObject childObj = (JSONObject) obj;
+ parseMenu(childObj, menuEntity);
+ }
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/init/IDGenInit.java b/cool-admin-java/src/main/java/com/cool/core/init/IDGenInit.java
new file mode 100644
index 0000000..446e59a
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/init/IDGenInit.java
@@ -0,0 +1,24 @@
+package com.cool.core.init;
+
+import com.cool.core.leaf.IDGenService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 唯一ID 组件初始化
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class IDGenInit {
+
+ final private IDGenService idGenService;
+
+ @EventListener(ApplicationReadyEvent.class)
+ public void run() {
+ idGenService.init();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/IDGenService.java b/cool-admin-java/src/main/java/com/cool/core/leaf/IDGenService.java
new file mode 100644
index 0000000..b004ce6
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/IDGenService.java
@@ -0,0 +1,6 @@
+package com.cool.core.leaf;
+
+public interface IDGenService {
+ long next(String key);
+ void init();
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/common/CheckVO.java b/cool-admin-java/src/main/java/com/cool/core/leaf/common/CheckVO.java
new file mode 100644
index 0000000..0d207a7
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/common/CheckVO.java
@@ -0,0 +1,27 @@
+package com.cool.core.leaf.common;
+
+public class CheckVO {
+ private long timestamp;
+ private int workID;
+
+ public CheckVO(long timestamp, int workID) {
+ this.timestamp = timestamp;
+ this.workID = workID;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getWorkID() {
+ return workID;
+ }
+
+ public void setWorkID(int workID) {
+ this.workID = workID;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/common/Result.java b/cool-admin-java/src/main/java/com/cool/core/leaf/common/Result.java
new file mode 100644
index 0000000..d85b1c6
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/common/Result.java
@@ -0,0 +1,39 @@
+package com.cool.core.leaf.common;
+
+public class Result {
+ private long id;
+ private Status status;
+
+ public Result() {
+
+ }
+ public Result(long id, Status status) {
+ this.id = id;
+ this.status = status;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Result{");
+ sb.append("id=").append(id);
+ sb.append(", status=").append(status);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/common/Status.java b/cool-admin-java/src/main/java/com/cool/core/leaf/common/Status.java
new file mode 100644
index 0000000..ec615e9
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/common/Status.java
@@ -0,0 +1,6 @@
+package com.cool.core.leaf.common;
+
+public enum Status {
+ SUCCESS,
+ EXCEPTION
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/package-info.java b/cool-admin-java/src/main/java/com/cool/core/leaf/package-info.java
new file mode 100644
index 0000000..8278bae
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * 全局唯一id生成
+ * 来源美团:https://github.com/Meituan-Dianping/Leaf
+ */
+package com.cool.core.leaf;
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/segment/SegmentIDGenImpl.java b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/SegmentIDGenImpl.java
new file mode 100644
index 0000000..0c5825b
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/SegmentIDGenImpl.java
@@ -0,0 +1,310 @@
+package com.cool.core.leaf.segment;
+
+import static com.cool.core.leaf.segment.entity.table.LeafAllocEntityTableDef.LEAF_ALLOC_ENTITY;
+
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.leaf.IDGenService;
+import com.cool.core.leaf.common.Result;
+import com.cool.core.leaf.common.Status;
+import com.cool.core.leaf.segment.entity.LeafAllocEntity;
+import com.cool.core.leaf.segment.mapper.LeafAllocMapper;
+import com.cool.core.leaf.segment.model.Segment;
+import com.cool.core.leaf.segment.model.SegmentBuffer;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.update.UpdateChain;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
+import lombok.RequiredArgsConstructor;
+import org.perf4j.StopWatch;
+import org.perf4j.slf4j.Slf4JStopWatch;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class SegmentIDGenImpl implements IDGenService, DisposableBean {
+
+ private static final Logger logger = LoggerFactory.getLogger(SegmentIDGenImpl.class);
+
+ @Value("${leaf.segment.enable:false}")
+ private boolean enable;
+
+ /**
+ * IDCache未初始化成功时的异常码
+ */
+ private static final long EXCEPTION_ID_IDCACHE_INIT_FALSE = -1;
+ /**
+ * key不存在时的异常码
+ */
+ private static final long EXCEPTION_ID_KEY_NOT_EXISTS = -2;
+ /**
+ * SegmentBuffer中的两个Segment均未从DB中装载时的异常码
+ */
+ private static final long EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL = -3;
+ /**
+ * 最大步长不超过100,0000
+ */
+ private static final int MAX_STEP = 1000000;
+ /**
+ * 一个Segment维持时间为15分钟
+ */
+ private static final long SEGMENT_DURATION = 15 * 60 * 1000L;
+ private final ExecutorService executorService = new ThreadPoolExecutor(5, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new UpdateThreadFactory());
+ private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setName("check-idCache-thread");
+ t.setDaemon(true);
+ return t;
+ });
+ private volatile boolean initOK = false;
+ private final Map cache = new ConcurrentHashMap<>();
+ private final LeafAllocMapper leafAllocMapper;
+
+ public static class UpdateThreadFactory implements ThreadFactory {
+
+ private static int threadInitNumber = 0;
+
+ private static synchronized int nextThreadNum() {
+ return threadInitNumber++;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "Thread-Segment-Update-" + nextThreadNum());
+ }
+ }
+
+ @Override
+ public long next(String key) {
+ Result result = get(key);
+ CoolPreconditions.check(result.getId() < 0, "获取失败,code值: {}", result.getId());
+ return result.getId();
+ }
+
+ @Override
+ public void init() {
+ if (enable) {
+ // 确保加载到kv后才初始化成功
+ updateCacheFromDb();
+ initOK = true;
+ updateCacheFromDbAtEveryMinute();
+ logger.info("唯一ID组件初始化成功 ...");
+ }
+ }
+
+ private void updateCacheFromDbAtEveryMinute() {
+ scheduledExecutorService.scheduleWithFixedDelay(this::updateCacheFromDb, 60, 60, TimeUnit.SECONDS);
+ }
+
+ private void updateCacheFromDb() {
+ logger.info("update cache from db");
+ StopWatch sw = new Slf4JStopWatch();
+ try {
+ List dbTags = leafAllocMapper.selectListByQuery(QueryWrapper.create().select(
+ LeafAllocEntity::getKey)).stream().map(LeafAllocEntity::getKey).toList();
+ if (dbTags.isEmpty()) {
+ return;
+ }
+ List cacheTags = new ArrayList(cache.keySet());
+ Set insertTagsSet = new HashSet<>(dbTags);
+ Set removeTagsSet = new HashSet<>(cacheTags);
+ //db中新加的tags灌进cache
+ for (String tmp : cacheTags) {
+ insertTagsSet.remove(tmp);
+ }
+ for (String tag : insertTagsSet) {
+ SegmentBuffer buffer = new SegmentBuffer();
+ buffer.setKey(tag);
+ Segment segment = buffer.getCurrent();
+ segment.setValue(new AtomicLong(0));
+ segment.setMax(0);
+ segment.setStep(0);
+ cache.put(tag, buffer);
+ logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer);
+ }
+ //cache中已失效的tags从cache删除
+ for (String tmp : dbTags) {
+ removeTagsSet.remove(tmp);
+ }
+ for (String tag : removeTagsSet) {
+ cache.remove(tag);
+ logger.info("Remove tag {} from IdCache", tag);
+ }
+ } catch (Exception e) {
+ logger.warn("update cache from db exception", e);
+ } finally {
+ sw.stop("updateCacheFromDb");
+ }
+ }
+
+ private Result get(final String key) {
+ if (!initOK) {
+ return new Result(EXCEPTION_ID_IDCACHE_INIT_FALSE, Status.EXCEPTION);
+ }
+ CoolPreconditions.check(!initOK, "IDCache未初始化成功");
+ if (cache.containsKey(key)) {
+ SegmentBuffer buffer = cache.get(key);
+ if (!buffer.isInitOk()) {
+ synchronized (buffer) {
+ if (!buffer.isInitOk()) {
+ try {
+ updateSegmentFromDb(key, buffer.getCurrent());
+ logger.info("Init buffer. Update leafkey {} {} from db", key, buffer.getCurrent());
+ buffer.setInitOk(true);
+ } catch (Exception e) {
+ logger.warn("Init buffer {} exception", buffer.getCurrent(), e);
+ }
+ }
+ }
+ }
+ return getIdFromSegmentBuffer(cache.get(key));
+ }
+ return new Result(EXCEPTION_ID_KEY_NOT_EXISTS, Status.EXCEPTION);
+ }
+
+ public void updateSegmentFromDb(String key, Segment segment) {
+ StopWatch sw = new Slf4JStopWatch();
+ SegmentBuffer buffer = segment.getBuffer();
+ LeafAllocEntity leafAllocEntity;
+ if (!buffer.isInitOk()) {
+ leafAllocEntity = updateMaxIdAndGetLeafAlloc(key);
+ buffer.setStep(leafAllocEntity.getStep());
+ buffer.setMinStep(leafAllocEntity.getStep());//leafAlloc中的step为DB中的step
+ } else if (buffer.getUpdateTimestamp() == 0) {
+ leafAllocEntity = updateMaxIdAndGetLeafAlloc(key);
+ buffer.setUpdateTimestamp(System.currentTimeMillis());
+ buffer.setStep(leafAllocEntity.getStep());
+ buffer.setMinStep(leafAllocEntity.getStep());//leafAlloc中的step为DB中的step
+ } else {
+ long duration = System.currentTimeMillis() - buffer.getUpdateTimestamp();
+ int nextStep = buffer.getStep();
+ if (duration < SEGMENT_DURATION) {
+ if (nextStep * 2 > MAX_STEP) {
+ //do nothing
+ } else {
+ nextStep = nextStep * 2;
+ }
+ } else if (duration < SEGMENT_DURATION * 2) {
+ //do nothing with nextStep
+ } else {
+ nextStep = nextStep / 2 >= buffer.getMinStep() ? nextStep / 2 : nextStep;
+ }
+ logger.info("leafKey[{}], step[{}], duration[{}mins], nextStep[{}]", key, buffer.getStep(), String.format("%.2f",((double)duration / (1000 * 60))), nextStep);
+ LeafAllocEntity temp = new LeafAllocEntity();
+ temp.setKey(key);
+ temp.setStep(nextStep);
+ leafAllocEntity = updateMaxIdByCustomStepAndGetLeafAlloc(temp);
+ buffer.setUpdateTimestamp(System.currentTimeMillis());
+ buffer.setStep(nextStep);
+ buffer.setMinStep(leafAllocEntity.getStep());//leafAlloc的step为DB中的step
+ }
+ // must set value before set max
+ long value = leafAllocEntity.getMaxId() - buffer.getStep();
+ segment.getValue().set(value);
+ segment.setMax(leafAllocEntity.getMaxId());
+ segment.setStep(buffer.getStep());
+ sw.stop("updateSegmentFromDb", key + " " + segment);
+ }
+
+ private LeafAllocEntity updateMaxIdByCustomStepAndGetLeafAlloc(LeafAllocEntity temp) {
+ UpdateChain.of(LeafAllocEntity.class)
+ .setRaw(LeafAllocEntity::getMaxId, LEAF_ALLOC_ENTITY.MAX_ID.getName() + " + " + temp.getStep())
+ .where(LeafAllocEntity::getKey).eq(temp.getKey())
+ .update();
+ return leafAllocMapper.selectOneByQuery(QueryWrapper.create().select(
+ LEAF_ALLOC_ENTITY.KEY, LEAF_ALLOC_ENTITY.MAX_ID, LEAF_ALLOC_ENTITY.STEP).eq(LeafAllocEntity::getKey, temp.getKey()));
+ }
+
+ private LeafAllocEntity updateMaxIdAndGetLeafAlloc(String key) {
+ UpdateChain.of(LeafAllocEntity.class)
+ .setRaw(LeafAllocEntity::getMaxId, LEAF_ALLOC_ENTITY.MAX_ID.getName() + " + " + LEAF_ALLOC_ENTITY.STEP.getName())
+ .where(LeafAllocEntity::getKey).eq(key)
+ .update();
+ return leafAllocMapper.selectOneByQuery(QueryWrapper.create().select(
+ LEAF_ALLOC_ENTITY.KEY, LEAF_ALLOC_ENTITY.MAX_ID, LEAF_ALLOC_ENTITY.STEP).eq(LeafAllocEntity::getKey, key));
+ }
+
+ public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) {
+ while (true) {
+ buffer.rLock().lock();
+ try {
+ final Segment segment = buffer.getCurrent();
+ if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) {
+ executorService.execute(() -> {
+ Segment next = buffer.getSegments()[buffer.nextPos()];
+ boolean updateOk = false;
+ try {
+ updateSegmentFromDb(buffer.getKey(), next);
+ updateOk = true;
+ logger.info("update segment {} from db {}", buffer.getKey(), next);
+ } catch (Exception e) {
+ logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e);
+ } finally {
+ if (updateOk) {
+ buffer.wLock().lock();
+ buffer.setNextReady(true);
+ buffer.getThreadRunning().set(false);
+ buffer.wLock().unlock();
+ } else {
+ buffer.getThreadRunning().set(false);
+ }
+ }
+ });
+ }
+ long value = segment.getValue().getAndIncrement();
+ if (value < segment.getMax()) {
+ return new Result(value, Status.SUCCESS);
+ }
+ } finally {
+ buffer.rLock().unlock();
+ }
+ waitAndSleep(buffer);
+ buffer.wLock().lock();
+ try {
+ final Segment segment = buffer.getCurrent();
+ long value = segment.getValue().getAndIncrement();
+ if (value < segment.getMax()) {
+ return new Result(value, Status.SUCCESS);
+ }
+ if (buffer.isNextReady()) {
+ buffer.switchPos();
+ buffer.setNextReady(false);
+ } else {
+ logger.error("Both two segments in {} are not ready!", buffer);
+ return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION);
+ }
+ } finally {
+ buffer.wLock().unlock();
+ }
+ }
+ }
+
+ private void waitAndSleep(SegmentBuffer buffer) {
+ int roll = 0;
+ while (buffer.getThreadRunning().get()) {
+ roll += 1;
+ if(roll > 10000) {
+ try {
+ TimeUnit.MILLISECONDS.sleep(10);
+ break;
+ } catch (InterruptedException e) {
+ logger.warn("Thread {} Interrupted",Thread.currentThread().getName());
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ executorService.shutdown();
+ executorService.awaitTermination(10, TimeUnit.SECONDS);
+
+ scheduledExecutorService.shutdown();
+ scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/segment/entity/LeafAllocEntity.java b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/entity/LeafAllocEntity.java
new file mode 100644
index 0000000..a78caba
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/entity/LeafAllocEntity.java
@@ -0,0 +1,27 @@
+package com.cool.core.leaf.segment.entity;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.tangzc.mybatisflex.autotable.annotation.UniIndex;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Table(value = "leaf_alloc", comment = "唯一id分配")
+public class LeafAllocEntity extends BaseEntity {
+
+ @UniIndex(name = "uk_key")
+ @ColumnDefine(comment = "业务key ,比如orderId", length = 20, notNull = true)
+ private String key;
+
+ @ColumnDefine(comment = "当前最大id", defaultValue = "1", notNull = true)
+ private Long maxId;
+
+ @ColumnDefine(comment = "步长", defaultValue = "500", notNull = true)
+ private Integer step;
+
+ @ColumnDefine(comment = "描述")
+ private String description;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/segment/mapper/LeafAllocMapper.java b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/mapper/LeafAllocMapper.java
new file mode 100644
index 0000000..1f492b6
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/mapper/LeafAllocMapper.java
@@ -0,0 +1,7 @@
+package com.cool.core.leaf.segment.mapper;
+
+import com.cool.core.leaf.segment.entity.LeafAllocEntity;
+import com.mybatisflex.core.BaseMapper;
+
+public interface LeafAllocMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/Segment.java b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/Segment.java
new file mode 100644
index 0000000..6908a36
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/Segment.java
@@ -0,0 +1,59 @@
+package com.cool.core.leaf.segment.model;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Segment {
+ private AtomicLong value = new AtomicLong(0);
+ private volatile long max;
+ private volatile int step;
+ private SegmentBuffer buffer;
+
+ public Segment(SegmentBuffer buffer) {
+ this.buffer = buffer;
+ }
+
+ public AtomicLong getValue() {
+ return value;
+ }
+
+ public void setValue(AtomicLong value) {
+ this.value = value;
+ }
+
+ public long getMax() {
+ return max;
+ }
+
+ public void setMax(long max) {
+ this.max = max;
+ }
+
+ public int getStep() {
+ return step;
+ }
+
+ public void setStep(int step) {
+ this.step = step;
+ }
+
+ public SegmentBuffer getBuffer() {
+ return buffer;
+ }
+
+ public long getIdle() {
+ return this.getMax() - getValue().get();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Segment(");
+ sb.append("value:");
+ sb.append(value);
+ sb.append(",max:");
+ sb.append(max);
+ sb.append(",step:");
+ sb.append(step);
+ sb.append(")");
+ return sb.toString();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/SegmentBuffer.java b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/SegmentBuffer.java
new file mode 100644
index 0000000..44c33da
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/leaf/segment/model/SegmentBuffer.java
@@ -0,0 +1,129 @@
+package com.cool.core.leaf.segment.model;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * 双buffer
+ */
+public class SegmentBuffer {
+ private String key;
+ private Segment[] segments; //双buffer
+ private volatile int currentPos; //当前的使用的segment的index
+ private volatile boolean nextReady; //下一个segment是否处于可切换状态
+ private volatile boolean initOk; //是否初始化完成
+ private final AtomicBoolean threadRunning; //线程是否在运行中
+ private final ReadWriteLock lock;
+
+ private volatile int step;
+ private volatile int minStep;
+ private volatile long updateTimestamp;
+
+ public SegmentBuffer() {
+ segments = new Segment[]{new Segment(this), new Segment(this)};
+ currentPos = 0;
+ nextReady = false;
+ initOk = false;
+ threadRunning = new AtomicBoolean(false);
+ lock = new ReentrantReadWriteLock();
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public Segment[] getSegments() {
+ return segments;
+ }
+
+ public Segment getCurrent() {
+ return segments[currentPos];
+ }
+
+ public int getCurrentPos() {
+ return currentPos;
+ }
+
+ public int nextPos() {
+ return (currentPos + 1) % 2;
+ }
+
+ public void switchPos() {
+ currentPos = nextPos();
+ }
+
+ public boolean isInitOk() {
+ return initOk;
+ }
+
+ public void setInitOk(boolean initOk) {
+ this.initOk = initOk;
+ }
+
+ public boolean isNextReady() {
+ return nextReady;
+ }
+
+ public void setNextReady(boolean nextReady) {
+ this.nextReady = nextReady;
+ }
+
+ public AtomicBoolean getThreadRunning() {
+ return threadRunning;
+ }
+
+ public Lock rLock() {
+ return lock.readLock();
+ }
+
+ public Lock wLock() {
+ return lock.writeLock();
+ }
+
+ public int getStep() {
+ return step;
+ }
+
+ public void setStep(int step) {
+ this.step = step;
+ }
+
+ public int getMinStep() {
+ return minStep;
+ }
+
+ public void setMinStep(int minStep) {
+ this.minStep = minStep;
+ }
+
+ public long getUpdateTimestamp() {
+ return updateTimestamp;
+ }
+
+ public void setUpdateTimestamp(long updateTimestamp) {
+ this.updateTimestamp = updateTimestamp;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("SegmentBuffer{");
+ sb.append("key='").append(key).append('\'');
+ sb.append(", segments=").append(Arrays.toString(segments));
+ sb.append(", currentPos=").append(currentPos);
+ sb.append(", nextReady=").append(nextReady);
+ sb.append(", initOk=").append(initOk);
+ sb.append(", threadRunning=").append(threadRunning);
+ sb.append(", step=").append(step);
+ sb.append(", minStep=").append(minStep);
+ sb.append(", updateTimestamp=").append(updateTimestamp);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/lock/CoolLock.java b/cool-admin-java/src/main/java/com/cool/core/lock/CoolLock.java
new file mode 100644
index 0000000..8ff0a80
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/lock/CoolLock.java
@@ -0,0 +1,108 @@
+package com.cool.core.lock;
+
+
+import jakarta.annotation.PostConstruct;
+import java.time.Duration;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.cache.CacheType;
+import org.springframework.cache.CacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class CoolLock {
+ // 缓存类型
+ @Value("${spring.cache.type}")
+ private String type;
+
+ @Value("${cool.cacheName}")
+ private String cacheName;
+
+ private final CacheManager cacheManager;
+ private RedisCacheWriter redisCache ;
+
+ // 非redis方式时使用
+ private static final Map lockMap = new ConcurrentHashMap<>();
+
+ private static final String LOCK_PREFIX = "lock:";
+
+ @PostConstruct
+ private void init() {
+ this.type = type.toLowerCase();
+ if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ redisCache = (RedisCacheWriter) Objects.requireNonNull(cacheManager.getCache(cacheName))
+ .getNativeCache();
+ }
+ }
+ /**
+ * 尝试获取锁
+ *
+ * @param key 锁的 key
+ * @param expireTime 锁的过期时间
+ * @return 如果成功获取锁则返回 true,否则返回 false
+ */
+ public boolean tryLock(String key, Duration expireTime) {
+ String lockKey = getLockKey(key);
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
+ Lock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
+ return lock.tryLock();
+ }
+ byte[] lockKeyBytes = lockKey.getBytes();
+ // 使用 putIfAbsent 来尝试设置锁,如果成功返回 true,否则返回 false
+ return redisCache.putIfAbsent(cacheName, lockKeyBytes, new byte[0], expireTime) == null;
+ }
+
+ /**
+ * 释放锁
+ */
+ public void unlock(String key) {
+ String lockKey = getLockKey(key);
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
+ Lock lock = lockMap.get(lockKey);
+ if (lock != null && lock.tryLock()) {
+ lock.unlock();
+ lockMap.remove(lockKey);
+ }
+ return;
+ }
+ redisCache.remove(cacheName, lockKey.getBytes());
+ }
+
+ /**
+ * 拼接锁前缀
+ */
+ private String getLockKey(String key) {
+ return LOCK_PREFIX + key;
+ }
+
+ /**
+ * 等待锁
+ *
+ * @param key 锁的 key
+ * @param expireTime 锁的过期时间
+ * @return 如果成功获取锁则返回 true,否则返回 false
+ */
+ public boolean waitForLock(String key, Duration expireTime, Duration waitTime) {
+ long endTime = System.currentTimeMillis() + waitTime.toMillis();
+ while (System.currentTimeMillis() < endTime) {
+ if (tryLock(key, expireTime)) {
+ return true;
+ }
+ // 等待锁释放
+ try {
+ Thread.sleep(100); // 可以根据需要调整等待时间
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ return false;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java
new file mode 100644
index 0000000..a403c46
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java
@@ -0,0 +1,48 @@
+package com.cool.core.mybatis.handler;
+
+import com.cool.core.util.DatabaseDialectUtils;
+import com.mybatisflex.core.util.StringUtil;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.postgresql.util.PGobject;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+public abstract class BaseJsonTypeHandler extends BaseTypeHandler {
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
+ if (DatabaseDialectUtils.isPostgresql()) {
+ PGobject jsonObject = new PGobject();
+ jsonObject.setType("json");
+ jsonObject.setValue(toJson(parameter));
+ ps.setObject(i, jsonObject);
+ } else {
+ ps.setString(i, toJson(parameter));
+ }
+ }
+
+ @Override
+ public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ final String json = rs.getString(columnName);
+ return StringUtil.noText(json) ? null : parseJson(json);
+ }
+
+ @Override
+ public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ final String json = rs.getString(columnIndex);
+ return StringUtil.noText(json) ? null : parseJson(json);
+ }
+
+ @Override
+ public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ final String json = cs.getString(columnIndex);
+ return StringUtil.noText(json) ? null : parseJson(json);
+ }
+
+ protected abstract T parseJson(String json);
+
+ protected abstract String toJson(T object);
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java
new file mode 100644
index 0000000..aad475e
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java
@@ -0,0 +1,73 @@
+package com.cool.core.mybatis.handler;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.TypeReference;
+
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+public class Fastjson2TypeHandler extends BaseJsonTypeHandler {
+
+ private final Class> propertyType;
+ private Class> genericType;
+ private Type type;
+
+ private boolean supportAutoType = false;
+
+ public Fastjson2TypeHandler(Class> propertyType) {
+ this.propertyType = propertyType;
+ this.supportAutoType = propertyType.isInterface() || Modifier.isAbstract(propertyType.getModifiers());
+ }
+
+
+ public Fastjson2TypeHandler(Class> propertyType, Class> genericType) {
+ this.propertyType = propertyType;
+ this.genericType = genericType;
+ this.type = TypeReference.collectionType((Class extends Collection>) propertyType, genericType);
+
+ Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
+ if (actualTypeArgument instanceof Class) {
+ this.supportAutoType = ((Class>) actualTypeArgument).isInterface()
+ || Modifier.isAbstract(((Class>) actualTypeArgument).getModifiers());
+ }
+ }
+
+ @Override
+ protected Object parseJson(String json) {
+ if (genericType != null && Collection.class.isAssignableFrom(propertyType)) {
+ if (supportAutoType) {
+ return JSON.parseArray(json, Object.class, JSONReader.Feature.SupportAutoType);
+ } else {
+ return JSON.parseObject(json, type);
+ }
+
+ } else {
+ if (supportAutoType) {
+ return JSON.parseObject(json, Object.class, JSONReader.Feature.SupportAutoType);
+ } else {
+ return JSON.parseObject(json, propertyType);
+ }
+ }
+ }
+
+ @Override
+ protected String toJson(Object object) {
+ if (supportAutoType) {
+ return JSON.toJSONString(object
+ , JSONWriter.Feature.WriteMapNullValue
+ , JSONWriter.Feature.WriteNullListAsEmpty
+ , JSONWriter.Feature.WriteNullStringAsEmpty, JSONWriter.Feature.WriteClassName
+ );
+ } else {
+ return JSON.toJSONString(object
+ , JSONWriter.Feature.WriteMapNullValue
+ , JSONWriter.Feature.WriteNullListAsEmpty
+ , JSONWriter.Feature.WriteNullStringAsEmpty
+ );
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java
new file mode 100644
index 0000000..e92d096
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java
@@ -0,0 +1,68 @@
+package com.cool.core.mybatis.handler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mybatisflex.core.exception.FlexExceptions;
+
+import java.io.IOException;
+import java.util.Collection;
+
+public class JacksonTypeHandler extends BaseJsonTypeHandler {
+
+ private static ObjectMapper objectMapper;
+ private final Class> propertyType;
+ private Class> genericType;
+ private JavaType javaType;
+
+ public JacksonTypeHandler(Class> propertyType) {
+ this.propertyType = propertyType;
+ }
+
+ public JacksonTypeHandler(Class> propertyType, Class> genericType) {
+ this.propertyType = propertyType;
+ this.genericType = genericType;
+ }
+
+ @Override
+ protected Object parseJson(String json) {
+ try {
+ if (genericType != null && Collection.class.isAssignableFrom(propertyType)) {
+ return getObjectMapper().readValue(json, getJavaType());
+ } else {
+ return getObjectMapper().readValue(json, propertyType);
+ }
+ } catch (IOException e) {
+ throw FlexExceptions.wrap(e, "Can not parseJson by JacksonTypeHandler: " + json);
+ }
+ }
+
+ @Override
+ protected String toJson(Object object) {
+ try {
+ return getObjectMapper().writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw FlexExceptions.wrap(e, "Can not convert object to Json by JacksonTypeHandler: " + object);
+ }
+ }
+
+
+ public JavaType getJavaType() {
+ if (javaType == null){
+ javaType = getObjectMapper().getTypeFactory().constructCollectionType((Class extends Collection>) propertyType, genericType);
+ }
+ return javaType;
+ }
+
+ public static ObjectMapper getObjectMapper() {
+ if (null == objectMapper) {
+ objectMapper = new ObjectMapper();
+ }
+ return objectMapper;
+ }
+
+ public static void setObjectMapper(ObjectMapper objectMapper) {
+ JacksonTypeHandler.objectMapper = objectMapper;
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java b/cool-admin-java/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java
new file mode 100644
index 0000000..d54b34c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java
@@ -0,0 +1,66 @@
+package com.cool.core.mybatis.pg;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.List;
+import java.util.Map;
+/**
+ * PostgreSQL Identity 序列同步服务
+ * 解决PostgreSQL 默认的序列机制,序列会自动递增,当手动插入指定id时需调用同步接口,否则id会重复。
+ */
+@Slf4j
+@Service
+public class PostgresSequenceSyncService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public PostgresSequenceSyncService(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ public void syncIdentitySequences() {
+ log.info("⏳ 开始同步 PostgreSQL Identity 序列...");
+
+ // 查询所有 identity 字段
+ String identityColumnQuery = """
+ SELECT table_schema, table_name, column_name
+ FROM information_schema.columns
+ WHERE is_identity = 'YES'
+ AND table_schema = 'public'
+ """;
+
+ List> identityColumns = jdbcTemplate.queryForList(identityColumnQuery);
+
+ for (Map col : identityColumns) {
+ String schema = (String) col.get("table_schema");
+ String table = (String) col.get("table_name");
+ String column = (String) col.get("column_name");
+
+ String fullTable = schema + "." + table;
+
+ // 获取对应的序列名
+ String seqNameSql = "SELECT pg_get_serial_sequence(?, ?)";
+ String seqName = jdbcTemplate.queryForObject(seqNameSql, String.class, fullTable, column);
+
+ if (seqName == null) {
+ log.warn("⚠️ 无法获取序列:{}.{}", table, column);
+ continue;
+ }
+
+ // 获取当前最大 ID
+ Long maxId = jdbcTemplate.queryForObject(
+ String.format("SELECT COALESCE(MAX(%s), 0) FROM %s", column, fullTable),
+ Long.class
+ );
+
+ if (maxId != null && maxId > 0) { // 正确的:setval 有返回值,必须用 queryForObject
+ String setvalSql = "SELECT setval(?, ?)";
+ Long newVal = jdbcTemplate.queryForObject(setvalSql, Long.class, seqName, maxId);
+ log.info("✅ 同步序列 [{}] -> 当前最大 ID: {}", seqName, newVal);
+ }
+ }
+
+ log.info("✅ PostgreSQL Identity 序列同步完成。");
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/CrudOption.java b/cool-admin-java/src/main/java/com/cool/core/request/CrudOption.java
new file mode 100644
index 0000000..c16954b
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/CrudOption.java
@@ -0,0 +1,202 @@
+package com.cool.core.request;
+
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import com.cool.core.enums.QueryModeEnum;
+import com.cool.core.util.ConvertUtil;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.query.QueryColumn;
+import com.mybatisflex.core.query.QueryCondition;
+import com.mybatisflex.core.query.QueryTable;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import lombok.Data;
+import org.springframework.core.env.Environment;
+
+/**
+ * 查询构建器
+ *
+ * @param
+ */
+@Data
+public class CrudOption {
+
+ private QueryWrapper queryWrapper;
+ private QueryColumn[] fieldEq;
+ private QueryColumn[] keyWordLikeFields;
+ private QueryColumn[] select;
+ private JSONObject requestParams;
+
+ private QueryModeEnum queryModeEnum;
+
+ private Transform transform;
+
+ public interface Transform {
+ void apply(B obj);
+ }
+
+ /**
+ * queryModeEnum 为 CUSTOM,可设置 默认为Map
+ */
+ private Class> asType;
+
+ private Environment evn;
+
+ public CrudOption(JSONObject requestParams) {
+ this.requestParams = requestParams;
+ this.queryWrapper = QueryWrapper.create();
+ this.evn = SpringUtil.getBean(Environment.class);
+ queryModeEnum = QueryModeEnum.ENTITY;
+ }
+
+ public QueryWrapper getQueryWrapper(Class entityClass) {
+ return build(this.queryWrapper, entityClass);
+ }
+
+ public CrudOption queryWrapper(QueryWrapper queryWrapper) {
+ this.queryWrapper = queryWrapper;
+ return this;
+ }
+
+ /**
+ * 按前端传上来的字段值做eq
+ */
+ public CrudOption fieldEq(QueryColumn... fields) {
+ this.fieldEq = fields;
+ return this;
+ }
+
+ /**
+ * 按前端传上来的字段值做like
+ */
+ public CrudOption keyWordLikeFields(QueryColumn... fields) {
+ this.keyWordLikeFields = fields;
+ return this;
+ }
+
+ /**
+ * 需要返回给前端的字段
+ */
+ public CrudOption select(QueryColumn... selects) {
+ this.select = selects;
+ return this;
+ }
+
+ /**
+ * 查询模式决定返回值
+ * 目前有三种模式,按实体查询返回、关联查询返回(实体字段上加 @RelationOneToMany 等注解)、自定义返回结果
+ */
+ public CrudOption queryModeEnum(QueryModeEnum queryModeEnum) {
+ this.queryModeEnum = queryModeEnum;
+ if (ObjUtil.equal(queryModeEnum, QueryModeEnum.CUSTOM)
+ && ObjUtil.isEmpty(asType)) {
+ asType = Map.class;
+ }
+ return this;
+ }
+
+ /**
+ * 自定义返回结果对象类型
+ */
+ public CrudOption asType(Class> asType) {
+ this.asType = asType;
+ return this;
+ }
+
+ /**
+ * 转换参数,组装数据
+ */
+ public CrudOption transform(Transform transform) {
+ this.transform = transform;
+ return this;
+ }
+
+ /**
+ * 构建查询条件
+ *
+ * @return QueryWrapper
+ */
+ private QueryWrapper build(QueryWrapper queryWrapper, Class entityClass) {
+ if (ObjectUtil.isNotEmpty(fieldEq)) {
+ Arrays.stream(fieldEq).toList().forEach(filed -> {
+ String filedName = StrUtil.toCamelCase(filed.getName());
+ Object obj = requestParams.get(filedName);
+ if (ObjUtil.isEmpty(obj)) {
+ return;
+ }
+ if (obj instanceof JSONArray) {
+ // 集合
+ queryWrapper.and(filed.in(ConvertUtil.covertListByClass(filedName, (JSONArray)obj, entityClass).toArray()));
+ } else {
+ // 对象
+ queryWrapper.and(filed.eq(ConvertUtil.convertByClass(filedName, obj, entityClass)));
+ }
+ });
+ }
+ if (ObjectUtil.isNotEmpty(this.keyWordLikeFields)) {
+ Object keyWord = requestParams.get("keyWord");
+ if (ObjectUtil.isEmpty(keyWord)) {
+ // // keyWord值为空,遍历keyWordLikeFields字段,根据queryColumn字段名构建查询条件
+ for (QueryColumn queryColumn : keyWordLikeFields) {
+ String fieldName = queryColumn.getName();
+ String paramName = StrUtil.toCamelCase(fieldName);
+ String paramValue = requestParams.getStr(paramName);
+ if (ObjectUtil.isNotEmpty(paramValue)) {
+ queryWrapper.and(queryColumn.like(paramValue));
+ }
+ }
+ } else {
+ // keyWord值非空,使用keyWord构建
+ // 初始化一个空的 QueryCondition
+ QueryCondition orCondition = null;
+ for (QueryColumn queryColumn : keyWordLikeFields) {
+ QueryCondition condition = queryColumn.like(keyWord);
+ if (orCondition == null) {
+ orCondition = condition;
+ } else {
+ orCondition = orCondition.or(condition);
+ }
+ }
+ queryWrapper.and(orCondition);
+ }
+ }
+ if (ObjectUtil.isNotEmpty(select)) {
+ queryWrapper.select(select);
+ }
+ // 排序
+ order(queryWrapper, entityClass);
+ return queryWrapper;
+ }
+
+ private void order(QueryWrapper queryWrapper, Class entityClass) {
+ Table tableAnnotation = AnnotationUtil.getAnnotation(entityClass, Table.class);
+ if (ObjectUtil.isEmpty(tableAnnotation)) {
+ // 该对象没有@Table注解,非Entity对象
+ return;
+ }
+ String tableAlias = "";
+ List queryTables = (List) ReflectUtil.getFieldValue(queryWrapper, "queryTables");
+ if (ObjectUtil.isNotEmpty(queryTables)) {
+ // 取主表作为排序字段别名
+ QueryTable queryTable = queryTables.get(0);
+ tableAlias = queryTable.getName() + ".";
+ }
+ String order = requestParams.getStr("order",
+ tableAnnotation.camelToUnderline() ? "create_time" : "createTime");
+ String sort = requestParams.getStr("sort", "desc");
+ if (StrUtil.isNotEmpty(order) && StrUtil.isNotEmpty(sort)) {
+ queryWrapper.orderBy(
+ tableAlias + (tableAnnotation.camelToUnderline() ? StrUtil.toUnderlineCase(order) : order),
+ sort.equals("asc"));
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/PageResult.java b/cool-admin-java/src/main/java/com/cool/core/request/PageResult.java
new file mode 100644
index 0000000..314a96f
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/PageResult.java
@@ -0,0 +1,33 @@
+package com.cool.core.request;
+
+import java.util.List;
+import com.mybatisflex.core.paginate.Page;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema( title = "分页数据模型" )
+public class PageResult {
+ @Schema( title = "分页数据" )
+ private List list;
+ private Pagination pagination = new Pagination();
+
+ @Data
+ public static class Pagination {
+ @Schema( title = "页码" )
+ private Long page;
+ @Schema( title = "本页数量" )
+ private Long size;
+ @Schema( title = "总页数" )
+ private Long total;
+ }
+
+ static public PageResult of(Page page ){
+ PageResult result = new PageResult();
+ result.setList(page.getRecords());
+ result.pagination.setPage( page.getPageNumber() );
+ result.pagination.setSize( page.getPageSize() );
+ result.pagination.setTotal( page.getTotalRow() );
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/R.java b/cool-admin-java/src/main/java/com/cool/core/request/R.java
new file mode 100644
index 0000000..c152ff9
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/R.java
@@ -0,0 +1,74 @@
+package com.cool.core.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 返回信息
+ */
+@Schema(title = "响应数据结构")
+@Data
+public class R implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+
+ @Schema(title = "编码:1000表示成功,其他值表示失败")
+ private int code = 1000;
+
+ @Schema(title = "消息内容")
+ private String message = "success";
+
+ @Schema(title = "响应数据")
+ private T data;
+
+ public R() {
+
+ }
+
+ public R( int code, String message, T data ) {
+ this.code = code;
+ this.message = message;
+ this.data = data;
+ }
+
+ public static R error() {
+ return error(1001, "请求方式不正确或服务出现异常");
+ }
+
+ public static R error(String msg) {
+ return error(1001, msg);
+ }
+
+ public static R error(int code, String msg) {
+ R r = new R();
+ r.code = code;
+ r.message = msg;
+ return r;
+ }
+
+ public static R okMsg(String msg) {
+ R r = new R();
+ r.message = msg;
+ return r;
+ }
+
+ public static R ok() {
+ return new R();
+ }
+
+ public static R ok(B data) {
+ return new R(1000 , "success", data);
+ }
+
+
+ public R put(String key, Object value) {
+ switch (key) {
+ case "code" -> this.code = (int) value;
+ case "message" -> this.message = (String) value;
+ case "data" -> this.data = (T) value;
+ }
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/RequestParamsFilter.java b/cool-admin-java/src/main/java/com/cool/core/request/RequestParamsFilter.java
new file mode 100644
index 0000000..cbcc551
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/RequestParamsFilter.java
@@ -0,0 +1,114 @@
+package com.cool.core.request;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import cn.hutool.jwt.JWT;
+import com.cool.core.enums.UserTypeEnum;
+import com.cool.core.util.BodyReaderHttpServletRequestWrapper;
+import com.cool.core.util.CoolSecurityUtil;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * 封装请求参数 URL参数 和 body JSON 到同一个 JSONObject 方便读取
+ */
+@Component
+@Order(2)
+public class RequestParamsFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ Filter.super.init(filterConfig);
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ // 防止流读取一次后就没有了, 所以需要将流继续写出去
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ JSONObject requestParams = new JSONObject();
+ String language = request.getHeader("language");
+ String coolEid = request.getHeader("cool-admin-eid");
+ Long tenantId = StrUtil.isEmpty(coolEid) ? null : Long.parseLong(coolEid);
+ if (StrUtil.isNotEmpty(request.getContentType()) && request.getContentType().contains("multipart/form-data")) {
+ servletRequest.setAttribute("requestParams", requestParams);
+ servletRequest.setAttribute("cool-language", language);
+ servletRequest.setAttribute("tenantId", tenantId);
+ filterChain.doFilter(servletRequest, servletResponse);
+ } else {
+ BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
+ String body = requestWrapper.getBodyString(requestWrapper);
+ if (StrUtil.isNotEmpty(body) && JSONUtil.isTypeJSON(body) && !JSONUtil.isTypeJSONArray(
+ body)) {
+ requestParams = JSONUtil.parseObj(body);
+ }
+ Object jwtObj = request.getAttribute("tokenInfo");
+ if (jwtObj != null) {
+ requestParams.set("tokenInfo", ((JWT) jwtObj).getPayload().getClaimsJson());
+ }
+ // 登录状态,设置用户id
+ Long currTenantId = setUserId(requestParams);
+ if (ObjUtil.isNotNull(currTenantId)) {
+ tenantId = currTenantId;
+ }
+ requestWrapper.setAttribute("cool-language", language);
+ request.setAttribute("tenantId", tenantId);
+ requestParams.set("body", body);
+ requestParams.putAll(getAllRequestParam(request));
+
+ requestWrapper.setAttribute("requestParams", requestParams);
+ filterChain.doFilter(requestWrapper, servletResponse);
+ }
+ }
+
+ private Long setUserId(JSONObject requestParams) {
+ UserTypeEnum userTypeEnum = CoolSecurityUtil.getCurrentUserType();
+ switch (userTypeEnum) {
+ // 只有登录了,才有用户类型, 不然为 UNKNOWN 状态
+ case ADMIN -> {
+ // 管理后台由于之前已经有逻辑再了,怕会影响到,如果自己有传了值不覆盖
+ Object o = requestParams.get("userId");
+ if (ObjUtil.isNull(o)) {
+ requestParams.set("userId", CoolSecurityUtil.getCurrentUserId());
+ }
+ }
+ // app端,userId 为当前登录的用户id
+ case APP -> requestParams.set("userId", CoolSecurityUtil.getCurrentUserId());
+ }
+ return CoolSecurityUtil.getTenantId(requestParams);
+ }
+
+ /**
+ * 获取客户端请求参数中所有的信息
+ *
+ */
+ private Map getAllRequestParam(final HttpServletRequest request) {
+ Map res = new HashMap<>();
+ Enumeration> temp = request.getParameterNames();
+ if (null != temp) {
+ while (temp.hasMoreElements()) {
+ String en = (String) temp.nextElement();
+ String value = request.getParameter(en);
+ res.put(en, value);
+ // 如果字段的值为空,判断若值为空,则删除这个字段>
+ if (null == res.get(en) || "".equals(res.get(en))) {
+ res.remove(en);
+ }
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public void destroy() {
+ Filter.super.destroy();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/RestInterceptor.java b/cool-admin-java/src/main/java/com/cool/core/request/RestInterceptor.java
new file mode 100644
index 0000000..21a85cb
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/RestInterceptor.java
@@ -0,0 +1,39 @@
+package com.cool.core.request;
+
+import com.cool.core.annotation.CoolRestController;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+
+/**
+ * 通用方法rest接口
+ */
+@Component
+public class RestInterceptor implements HandlerInterceptor {
+ private final static String[] rests = { "add", "delete", "update", "info", "list", "page" };
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ // 判断有无通用方法
+ if (handler instanceof HandlerMethod) {
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
+ CoolRestController coolRestController = handlerMethod.getBeanType().getAnnotation(CoolRestController.class);
+ if (null != coolRestController) {
+ String[] urls = request.getRequestURI().split("/");
+ String rest = urls[urls.length - 1];
+ if (Arrays.asList(rests).contains(rest)) {
+ if (!Arrays.asList(coolRestController.api()).contains(rest)) {
+ response.setStatus(HttpStatus.NOT_FOUND.value());
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixConfiguration.java b/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixConfiguration.java
new file mode 100644
index 0000000..c699a80
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixConfiguration.java
@@ -0,0 +1,17 @@
+package com.cool.core.request.prefix;
+
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * 自定义路由规则
+ */
+@Component
+public class AutoPrefixConfiguration implements WebMvcRegistrations {
+
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ return new AutoPrefixUrlMapping();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java b/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java
new file mode 100644
index 0000000..01f04ea
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java
@@ -0,0 +1,103 @@
+package com.cool.core.request.prefix;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.enums.Apis;
+import com.cool.core.util.ConvertUtil;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * 自动配置模块的路由
+ */
+@Slf4j
+public class AutoPrefixUrlMapping extends RequestMappingHandlerMapping {
+
+ @Override
+ protected RequestMappingInfo getMappingForMethod(Method method, Class> handlerType) {
+ CoolRestController[] annotations = handlerType.getAnnotationsByType(CoolRestController.class);
+ RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
+ String packageName = handlerType.getPackage().getName();
+ if (info != null && annotations.length > 0 && annotations[0].value().length == 0
+ && packageName.contains("modules")) {
+ if (!checkApis(annotations, info)) {
+ return null;
+ }
+ String prefix = getPrefix(packageName);
+ String cName = getCName(annotations[0].cname(), handlerType, prefix);
+ info = info.mutate().paths(prefix + "/" + cName).build().combine(info);
+ }
+ return info;
+ }
+
+ /**
+ * 根据配置检查是否构建路由
+ *
+ * @param annotations 注解
+ * @param info 路由信息
+ * @return 是否需要构建路由
+ */
+ private boolean checkApis(CoolRestController[] annotations, RequestMappingInfo info) {
+ String[] apis = Apis.ALL_API;
+ if (info.getPathPatternsCondition() == null) {
+ return true;
+ }
+ List setApis;
+ if (ArrayUtil.isNotEmpty(annotations)) {
+ CoolRestController coolRestController = annotations[0];
+ setApis = CollUtil.toList(coolRestController.api());
+
+ Set methodPaths = info.getPathPatternsCondition().getPatternValues();
+ String methodPath = methodPaths.iterator().next().replace("/", "");
+ if (!CollUtil.toList(apis).contains(methodPath)) {
+ return true;
+ } else {
+ return setApis.contains(methodPath);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 根据Controller名称构建路由地址
+ *
+ * @param handlerType 类
+ * @param prefix 路由前缀
+ * @return url地址
+ */
+ private String getCName(String cname, Class> handlerType, String prefix) {
+ if (ObjUtil.isNotEmpty(cname)) {
+ return cname;
+ }
+ String name = handlerType.getName();
+ String[] names = name.split("[.]");
+ name = names[names.length - 1];
+ return ConvertUtil.extractController2Path(ConvertUtil.pathToClassName(prefix), name);
+ }
+
+ /**
+ * 构建路由前缀
+ *
+ * @param packageName 包名
+ * @return 返回路由前缀
+ */
+ private String getPrefix(String packageName) {
+ String dotPath = packageName.split("modules")[1]; // 将包路径中多于的部分截取掉
+ String[] dotPaths = dotPath.replace(".controller", "").split("[.]");
+ List paths = CollUtil.toList(dotPaths);
+ paths.removeIf(String::isEmpty);
+ // 第一和第二位互换位置
+ String p0 = paths.get(0);
+ String p1 = paths.get(1);
+ paths.set(0, p1);
+ paths.set(1, p0);
+ dotPath = "/" + CollUtil.join(paths, "/");
+ return dotPath;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/EntryPointUnauthorizedHandler.java b/cool-admin-java/src/main/java/com/cool/core/security/EntryPointUnauthorizedHandler.java
new file mode 100644
index 0000000..1d86f8a
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/EntryPointUnauthorizedHandler.java
@@ -0,0 +1,34 @@
+package com.cool.core.security;
+
+import cn.hutool.json.JSONUtil;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * 自定401返回值
+ */
+@Component
+public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
+
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+ throws IOException {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/json; charset=utf-8");
+ response.getWriter().write(JSONUtil.toJsonStr(new HashMap() {
+ {
+ put("code", "401");
+ put("message", "未登录");
+ }
+ }));
+ response.setStatus(401);
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java b/cool-admin-java/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java
new file mode 100644
index 0000000..2443dae
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java
@@ -0,0 +1,22 @@
+package com.cool.core.security;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 忽略地址配置
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "ignored")
+public class IgnoredUrlsProperties {
+
+ // 忽略后台校验权限列表
+ private List adminAuthUrls = new ArrayList<>();
+
+ // 忽略记录请求日志列表
+ private List logUrls = new ArrayList<>();
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/JwtAuthenticationTokenFilter.java b/cool-admin-java/src/main/java/com/cool/core/security/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..05209df
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,110 @@
+package com.cool.core.security;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.jwt.JWT;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.enums.UserTypeEnum;
+import com.cool.core.security.jwt.JwtTokenUtil;
+import com.cool.core.security.jwt.JwtUser;
+import com.cool.core.util.PathUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * Token过滤器
+ */
+@Order(1)
+@Component
+@RequiredArgsConstructor
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+
+ final private JwtTokenUtil jwtTokenUtil;
+ final private CoolCache coolCache;
+ final private IgnoredUrlsProperties ignoredUrlsProperties;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain chain)
+ throws ServletException, IOException {
+ String requestURI = request.getRequestURI();
+ if (PathUtils.isMatch(ignoredUrlsProperties.getAdminAuthUrls(), requestURI)) {
+ // 请求路径在忽略后台鉴权url里支持通配符,放行
+ chain.doFilter(request, response);
+ return;
+ }
+ String authToken = request.getHeader("Authorization");
+ if (!StrUtil.isEmpty(authToken)) {
+ JWT jwt = jwtTokenUtil.getTokenInfo(authToken);
+
+ Object userType = jwt.getPayload("userType");
+ if (Objects.equals(userType, UserTypeEnum.APP.name())) {
+ // app
+ handlerAppRequest(request, jwt, authToken);
+ } else {
+ // admin
+ handlerAdminRequest(request, jwt, authToken);
+ }
+ }
+ chain.doFilter(request, response);
+ }
+ /**
+ * 处理app请求
+ */
+ private void handlerAppRequest(HttpServletRequest request, JWT jwt, String authToken) {
+ String userId = jwt.getPayload("userId").toString();
+ if (ObjectUtil.isNotEmpty(userId)
+ && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = coolCache.get("app:userDetails:" + userId,
+ JwtUser.class);
+ if (jwtTokenUtil.validateToken(authToken) && userDetails != null) {
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(
+ new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ request.setAttribute("userId", jwt.getPayload("userId"));
+ request.setAttribute("tokenInfo", jwt);
+ }
+ }
+ }
+
+ /**
+ * 处理后台请求
+ */
+ private void handlerAdminRequest(HttpServletRequest request, JWT jwt, String authToken) {
+ String username = jwt.getPayload("username").toString();
+ if (username != null
+ && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = coolCache.get("admin:userDetails:" + username,
+ JwtUser.class);
+ Integer passwordV = Convert.toInt(jwt.getPayload("passwordVersion"));
+ Integer rv = coolCache.get("admin:passwordVersion:" + jwt.getPayload("userId"),
+ Integer.class);
+ if (jwtTokenUtil.validateToken(authToken, username) && Objects.equals(passwordV, rv)
+ && userDetails != null) {
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(
+ new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ request.setAttribute("adminUsername", jwt.getPayload("username"));
+ request.setAttribute("adminUserId", jwt.getPayload("userId"));
+ request.setAttribute("tokenInfo", jwt);
+ }
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/JwtSecurityConfig.java b/cool-admin-java/src/main/java/com/cool/core/security/JwtSecurityConfig.java
new file mode 100644
index 0000000..6f9e722
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/JwtSecurityConfig.java
@@ -0,0 +1,152 @@
+package com.cool.core.security;
+
+import com.cool.core.annotation.TokenIgnore;
+import com.cool.core.enums.UserTypeEnum;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.util.DigestUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import org.springframework.web.util.pattern.PathPattern;
+
+@EnableWebSecurity
+@Configuration
+@Slf4j
+@RequiredArgsConstructor
+public class JwtSecurityConfig {
+
+ // 用户详情
+ final private UserDetailsService userDetailsService;
+ final private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
+ // 401
+ final private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
+ // 403
+ final private RestAccessDeniedHandler restAccessDeniedHandler;
+ // 忽略权限控制的地址
+ final private IgnoredUrlsProperties ignoredUrlsProperties;
+
+ final private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ // 动态获取忽略的URL
+ configureIgnoredUrls();
+
+ return httpSecurity
+ .authorizeHttpRequests(
+ conf -> {
+ conf.requestMatchers(
+ ignoredUrlsProperties.getAdminAuthUrls().toArray(String[]::new))
+ .permitAll();
+ conf.requestMatchers("/admin/**").authenticated();
+ conf.requestMatchers("/app/**").hasRole(UserTypeEnum.APP.name());
+ })
+ .headers(config -> config.frameOptions(FrameOptionsConfig::disable))
+ // 允许网页iframe
+ .csrf(AbstractHttpConfigurer::disable)
+ .sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .addFilterBefore(jwtAuthenticationTokenFilter,
+ UsernamePasswordAuthenticationFilter.class)
+ .exceptionHandling(config -> {
+ config.authenticationEntryPoint(entryPointUnauthorizedHandler);
+ config.accessDeniedHandler(restAccessDeniedHandler);
+ }).build();
+ }
+
+ private void configureIgnoredUrls() {
+ Map mappings = requestMappingHandlerMapping.getHandlerMethods();
+ List handlerCtr = new ArrayList<>();
+ mappings.forEach((requestMappingInfo, handlerMethod) -> {
+ Method method = handlerMethod.getMethod();
+ TokenIgnore tokenIgnore = AnnotatedElementUtils.findMergedAnnotation(method, TokenIgnore.class);
+ TokenIgnore tokenIgnoreCtr = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), TokenIgnore.class);
+ if (!handlerCtr.contains(handlerMethod.getBeanType().getName()) && tokenIgnoreCtr != null) {
+ requestMappingInfo.getPathPatternsCondition().getPatterns().forEach(pathPattern -> {
+ String[] prefixs = pathPattern.getPatternString().split("/");
+ // 去除最后一个路径
+ List urls = new ArrayList<>();
+ for (int i = 0; i < prefixs.length - 1; i++) {
+ urls.add(prefixs[i]);
+ }
+ // 遍历 tokenIgnoreCtr.value()
+ for (String path : tokenIgnoreCtr.value()) {
+ ignoredUrlsProperties.getAdminAuthUrls().add(String.join("/", urls) + "/" + path);
+ }
+ if (tokenIgnoreCtr.value().length == 0) {
+ // 通配
+ ignoredUrlsProperties.getAdminAuthUrls().add(String.join("/", urls)+ "/**");
+ }
+ handlerCtr.add(handlerMethod.getBeanType().getName());
+ });
+ }
+ if (tokenIgnore != null) {
+ StringBuilder url = new StringBuilder();
+ RequestMapping classRequestMapping = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), RequestMapping.class);
+ if (classRequestMapping != null) {
+ for (String path : classRequestMapping.value()) {
+ url.append(path);
+ }
+ }
+ if (requestMappingInfo.getPathPatternsCondition() == null) {
+ return;
+ }
+ for (PathPattern path : requestMappingInfo.getPathPatternsCondition().getPatterns()) {
+ url.append(path);
+ }
+ ignoredUrlsProperties.getAdminAuthUrls().add(url.toString());
+ }
+ });
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new PasswordEncoder() {
+ @Override
+ public String encode(CharSequence rawPassword) {
+ return DigestUtils.md5DigestAsHex(((String) rawPassword).getBytes());
+ }
+
+ @Override
+ public boolean matches(CharSequence rawPassword, String encodedPassword) {
+ return encodedPassword.equals(
+ DigestUtils.md5DigestAsHex(((String) rawPassword).getBytes()));
+ }
+ };
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userDetailsService);
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
+ throws Exception {
+ return config.getAuthenticationManager();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/MyAccessDecisionManager.java b/cool-admin-java/src/main/java/com/cool/core/security/MyAccessDecisionManager.java
new file mode 100644
index 0000000..3c410af
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/MyAccessDecisionManager.java
@@ -0,0 +1,61 @@
+package com.cool.core.security;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.stereotype.Component;
+
+/**
+ * 权限管理决断器 判断用户拥有的权限或角色是否有资源访问权限
+ */
+@RequiredArgsConstructor
+@Slf4j
+@Component
+public class MyAccessDecisionManager implements AccessDecisionManager {
+ // 忽略权限控制的地址
+ final private IgnoredUrlsProperties ignoredUrlsProperties;
+
+ @Override
+ public void decide(Authentication authentication, Object o, Collection configAttributes)
+ throws AccessDeniedException, InsufficientAuthenticationException {
+ if (configAttributes == null) {
+ return;
+ }
+ List urls = ignoredUrlsProperties.getAdminAuthUrls();
+ String url = ((FilterInvocation) o).getRequestUrl().split("[?]")[0];
+ if (urls.contains(url)) {
+ return;
+ }
+ Iterator iterator = configAttributes.iterator();
+ while (iterator.hasNext()) {
+ ConfigAttribute c = iterator.next();
+ String needPerm = c.getAttribute();
+ for (GrantedAuthority ga : authentication.getAuthorities()) {
+ // 匹配用户拥有的ga 和 系统中的needPerm
+ if (needPerm.trim().equals(ga.getAuthority())) {
+ return;
+ }
+ }
+ }
+ throw new AccessDeniedException("抱歉,您没有访问权限");
+ }
+
+ @Override
+ public boolean supports(ConfigAttribute configAttribute) {
+ return true;
+ }
+
+ @Override
+ public boolean supports(Class> aClass) {
+ return true;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/MyFilterSecurityInterceptor.java b/cool-admin-java/src/main/java/com/cool/core/security/MyFilterSecurityInterceptor.java
new file mode 100644
index 0000000..f22165b
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/MyFilterSecurityInterceptor.java
@@ -0,0 +1,68 @@
+package com.cool.core.security;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import java.io.IOException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.SecurityMetadataSource;
+import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
+import org.springframework.security.access.intercept.InterceptorStatusToken;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 权限管理拦截器 监控用户行为
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
+
+ final private FilterInvocationSecurityMetadataSource securityMetadataSource;
+
+ @Resource
+ public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
+ super.setAccessDecisionManager(myAccessDecisionManager);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ FilterInvocation fi = new FilterInvocation(request, response, chain);
+ invoke(fi);
+ }
+
+ public void invoke(FilterInvocation fi) throws IOException, ServletException {
+ InterceptorStatusToken token = super.beforeInvocation(fi);
+ try {
+ fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+ } finally {
+ super.afterInvocation(token, null);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public Class> getSecureObjectClass() {
+ return FilterInvocation.class;
+ }
+
+ @Override
+ public SecurityMetadataSource obtainSecurityMetadataSource() {
+ return this.securityMetadataSource;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/RestAccessDeniedHandler.java b/cool-admin-java/src/main/java/com/cool/core/security/RestAccessDeniedHandler.java
new file mode 100644
index 0000000..7954d05
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/RestAccessDeniedHandler.java
@@ -0,0 +1,34 @@
+package com.cool.core.security;
+
+import cn.hutool.json.JSONUtil;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * 自定403返回值
+ */
+@Component
+public class RestAccessDeniedHandler implements AccessDeniedHandler {
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+ throws IOException {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/json; charset=utf-8");
+ response.getWriter().write(JSONUtil.toJsonStr(new HashMap() {
+ {
+ put("code", "403");
+ put("message", "无权限");
+ }
+ }));
+ response.setStatus(403);
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java b/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java
new file mode 100644
index 0000000..5335726
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java
@@ -0,0 +1,165 @@
+package com.cool.core.security.jwt;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTUtil;
+import cn.hutool.jwt.JWTValidator;
+import com.cool.core.config.CoolProperties;
+import com.cool.modules.base.service.sys.BaseSysConfService;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ * JWT工具类
+ */
+@Component
+@RequiredArgsConstructor
+public class JwtTokenUtil implements Serializable {
+
+ final private CoolProperties coolProperties;
+ final private BaseSysConfService baseSysConfService;
+ final String tokenKey = "JWT_SECRET_TOKEN";
+ final String refreshTokenKey = "JWT_SECRET_REFRESH_TOKEN";
+
+ public long getExpire() {
+ return this.coolProperties.getToken().getExpire();
+ }
+
+ public long getRefreshExpire() {
+ return this.coolProperties.getToken().getRefreshExpire();
+ }
+
+ public String getTokenSecret() {
+ String secret = baseSysConfService.getValueWithCache(tokenKey);
+ if (StrUtil.isBlank(secret)) {
+ secret = StrUtil.uuid().replaceAll("-", "");
+ baseSysConfService.setValue(tokenKey, secret);
+ }
+ return secret;
+ }
+
+ public String getRefreshTokenSecret() {
+ String secret = baseSysConfService.getValueWithCache(refreshTokenKey);
+ if (StrUtil.isBlank(secret)) {
+ secret = StrUtil.uuid().replaceAll("-", "");
+ baseSysConfService.setValue(refreshTokenKey, secret);
+ }
+ return secret;
+ }
+
+ /**
+ * 生成令牌
+ *
+ * @param tokenInfo 保存的用户信息
+ * @return 令牌
+ */
+ public String generateToken(Map tokenInfo) {
+ tokenInfo.put("isRefresh", false);
+ Date expirationDate = new Date(System.currentTimeMillis() + getExpire() * 1000);
+ JWT jwt = JWT.create().setExpiresAt(expirationDate).setKey(getTokenSecret().getBytes())
+ .setPayload("created", new Date());
+ tokenInfo.forEach(jwt::setPayload);
+ return jwt.sign();
+ }
+
+ /**
+ * 生成令牌
+ *
+ * @param tokenInfo 保存的用户信息
+ * @return 令牌
+ */
+ public String generateRefreshToken(Map tokenInfo) {
+ tokenInfo.put("isRefresh", true);
+ Date expirationDate = new Date(System.currentTimeMillis() + getRefreshExpire() * 1000);
+ JWT jwt = JWT.create().setExpiresAt(expirationDate).setKey(getRefreshTokenSecret().getBytes())
+ .setPayload("created", new Date());
+ tokenInfo.forEach(jwt::setPayload);
+ return jwt.sign();
+ }
+
+ /**
+ * 从令牌中获取用户名
+ *
+ * @param token 令牌
+ * @return 用户名
+ */
+ public String getUsernameFromToken(String token) {
+ JWT jwt = JWT.of(token);
+ return jwt.getPayload("username").toString();
+ }
+
+ /**
+ * 获得token信息
+ *
+ * @param token 令牌
+ * @return token信息
+ */
+ public JWT getTokenInfo(String token) {
+ return JWT.of(token);
+ }
+
+ /**
+ * 判断令牌是否过期
+ *
+ * @param token 令牌
+ * @return 是否过期
+ */
+ public Boolean isTokenExpired(String token) {
+ try {
+ JWTValidator.of(token).validateDate(DateUtil.date());
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ /**
+ * 验证令牌
+ *
+ * @param token 令牌
+ * @param username 用户
+ * @return 是否有效
+ */
+ public Boolean validateToken(String token, String username) {
+ if (ObjectUtil.isEmpty(token)) {
+ return false;
+ }
+ String tokenUsername = getUsernameFromToken(token);
+ String secret = getTokenSecret();
+ boolean isValidSignature = JWTUtil.verify(token, secret.getBytes());
+ return (tokenUsername.equals(username) && !isTokenExpired(token) && isValidSignature);
+ }
+
+ /**
+ * 校验token是否有效
+ * @param token
+ * @return
+ */
+ public Boolean validateToken(String token) {
+ if (ObjectUtil.isEmpty(token)) {
+ return false;
+ }
+ String secret = getTokenSecret();
+ boolean isValidSignature = JWTUtil.verify(token, secret.getBytes());
+ return (!isTokenExpired(token) && isValidSignature);
+ }
+
+ /**
+ * 校验refresh token是否有效
+ * @param token
+ * @return
+ */
+ public Boolean validateRefreshToken(String token) {
+ if (ObjectUtil.isEmpty(token)) {
+ return false;
+ }
+ String secret = getRefreshTokenSecret();
+ boolean isValidSignature = JWTUtil.verify(token, secret.getBytes());
+ return (!isTokenExpired(token) && isValidSignature);
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtUser.java b/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtUser.java
new file mode 100644
index 0000000..c69cf9e
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/security/jwt/JwtUser.java
@@ -0,0 +1,78 @@
+package com.cool.core.security.jwt;
+
+import com.cool.core.enums.UserTypeEnum;
+import java.util.Collection;
+import java.util.List;
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * 后台用户信息
+ */
+@Data
+public class JwtUser implements UserDetails {
+
+ /******
+ * 后台用户
+ * ********/
+ private Long userId;
+ private String username;
+ private String password;
+ private Boolean status;
+ private UserTypeEnum userTypeEnum;
+ private List perms;
+ public JwtUser(Long userId, String username, String password, List perms, Boolean status) {
+ this.userId = userId;
+ this.username = username;
+ this.password = password;
+ this.perms = perms;
+ this.status = status;
+ this.userTypeEnum = UserTypeEnum.ADMIN;
+ }
+
+ /******
+ * app用户
+ * ********/
+ public JwtUser(Long userId, List perms, Boolean status) {
+ this.userId = userId;
+ this.perms = perms;
+ this.status = status;
+ this.userTypeEnum = UserTypeEnum.APP;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return perms;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return status;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/tenant/CoolTenantFactory.java b/cool-admin-java/src/main/java/com/cool/core/tenant/CoolTenantFactory.java
new file mode 100644
index 0000000..f79ebef
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/tenant/CoolTenantFactory.java
@@ -0,0 +1,14 @@
+package com.cool.core.tenant;
+
+import com.cool.core.util.TenantUtil;
+import com.mybatisflex.core.tenant.TenantFactory;
+
+public class CoolTenantFactory implements TenantFactory {
+ public Object[] getTenantIds(){
+ Long tenantId = TenantUtil.getTenantId();
+ if (tenantId == null) {
+ return null;
+ }
+ return new Object[]{tenantId};
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/AnnotationUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/AnnotationUtils.java
new file mode 100644
index 0000000..b65d6a3
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/AnnotationUtils.java
@@ -0,0 +1,73 @@
+package com.cool.core.util;
+
+import com.cool.core.annotation.CoolPlugin;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Controller;
+import org.springframework.stereotype.Repository;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+public class AnnotationUtils {
+
+ /**
+ * 判断一个类是否有 Spring 核心注解
+ *
+ * @param clazz 要检查的类
+ * @return true 如果该类上添加了相应的 Spring 注解;否则返回 false
+ */
+ public static boolean hasSpringAnnotation(Class> clazz) {
+ if (clazz == null) {
+ return false;
+ }
+ // 是否是接口
+ if (clazz.isInterface()) {
+ return false;
+ }
+ // 是否是抽象类
+ if (Modifier.isAbstract(clazz.getModifiers())) {
+ return false;
+ }
+
+ try {
+ if (clazz.getAnnotation(Component.class) != null || clazz.getAnnotation(Repository.class) != null
+ || clazz.getAnnotation(Service.class) != null || clazz.getAnnotation(Controller.class) != null
+ || clazz.getAnnotation(Configuration.class) != null) {
+ return true;
+ }
+ } catch (Exception e) {
+ log.error("出现异常:{}", e.getMessage());
+ }
+ return false;
+ }
+
+ /**
+ * 插件
+ */
+ public static boolean hasCoolPluginAnnotation(Class> clazz) {
+ if (clazz == null) {
+ return false;
+ }
+ // 是否是接口
+ if (clazz.isInterface()) {
+ return false;
+ }
+ // 是否是抽象类
+ if (Modifier.isAbstract(clazz.getModifiers())) {
+ return false;
+ }
+ try {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ if (clazz.getAnnotation(
+ (Class extends Annotation>) contextClassLoader.loadClass(CoolPlugin.class.getName())) != null) {
+ return true;
+ }
+ } catch (Exception e) {
+ log.error("出现异常:{}", e.getMessage(), e);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/AutoTypeConverter.java b/cool-admin-java/src/main/java/com/cool/core/util/AutoTypeConverter.java
new file mode 100644
index 0000000..45ed11c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/AutoTypeConverter.java
@@ -0,0 +1,27 @@
+package com.cool.core.util;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.NumberUtil;
+
+import java.io.Serializable;
+
+public class AutoTypeConverter {
+ /**
+ * 将字符串自动转换为数字或保留为字符串
+ *
+ * @param input 输入字符串
+ * @return Integer / Long / String
+ */
+ public static Serializable autoConvert(Object input) {
+ if (input == null) {
+ return null;
+ }
+ if (NumberUtil.isInteger(input.toString())) {
+ return Convert.convert(Integer.class, input);
+ } else if (NumberUtil.isLong(input.toString())) {
+ return Convert.convert(Long.class, input);
+ } else {
+ return (Serializable) input;
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/BodyReaderHttpServletRequestWrapper.java b/cool-admin-java/src/main/java/com/cool/core/util/BodyReaderHttpServletRequestWrapper.java
new file mode 100644
index 0000000..2c783d2
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/BodyReaderHttpServletRequestWrapper.java
@@ -0,0 +1,119 @@
+package com.cool.core.util;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+/**
+ * 保存流
+ */
+@Slf4j
+public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final byte[] body;
+
+ public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+ super(request);
+ String sessionStream = getBodyString(request);
+ body = sessionStream.getBytes(Charset.forName("UTF-8"));
+ }
+
+ /**
+ * 获取请求Body
+ *
+ * @param request
+ * @return
+ */
+ public String getBodyString(final ServletRequest request) {
+ StringBuilder sb = new StringBuilder();
+ InputStream inputStream = null;
+ BufferedReader reader = null;
+ try {
+ inputStream = cloneInputStream(request.getInputStream());
+ reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
+ String line = "";
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ log.error("err", e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ log.error("err", e);
+ }
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ log.error("err", e);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Description: 复制输入流
+ *
+ * @param inputStream
+ * @return
+ */
+ public InputStream cloneInputStream(ServletInputStream inputStream) {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ try {
+ while ((len = inputStream.read(buffer)) > -1) {
+ byteArrayOutputStream.write(buffer, 0, len);
+ }
+ byteArrayOutputStream.flush();
+ } catch (IOException e) {
+ log.error("err", e);
+ }
+ InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
+ return byteArrayInputStream;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+
+ return new ServletInputStream() {
+
+ @Override
+ public int read() throws IOException {
+ return bais.read();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+ };
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/CompilerUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/CompilerUtils.java
new file mode 100644
index 0000000..3e6506f
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/CompilerUtils.java
@@ -0,0 +1,180 @@
+package com.cool.core.util;
+
+import cn.hutool.core.io.FileUtil;
+import com.mybatisflex.processor.MybatisFlexProcessor;
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.annotation.processing.Processor;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class CompilerUtils {
+
+ public final static String META_INF_VERSIONS = "META-INF/versions/";
+
+ // jdk版本
+ private static String JVM_VERSION = null;
+
+ /**
+ * 获取jdk版本
+ */
+ public static String getJdkVersion() {
+ if (JVM_VERSION == null) {
+ RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
+ JVM_VERSION = runtimeMXBean.getSpecVersion();
+ }
+ return JVM_VERSION;
+ }
+
+ /**
+ * 创建文件, 先删除在创建
+ */
+ public static void createFile(String content, String filePathStr) {
+ FileUtil.del(filePathStr);
+ File file = FileUtil.touch(filePathStr);
+ FileUtil.appendString(content, file, StandardCharsets.UTF_8.name());
+ compileAndSave(filePathStr);
+ }
+
+ public static String createMapper(String actModulePath, String fileName, String mapper) {
+ String pathStr = actModulePath + File.separator + "mapper" + File.separator;
+ String filePathStr = pathStr + fileName + "Mapper.java";
+ createFile(mapper, filePathStr);
+ return filePathStr;
+ }
+
+ public static String createServiceImpl(String actModulePath, String fileName,
+ String serviceImpl) {
+ String pathStr = actModulePath + File.separator + "service" + File.separator + "impl" + File.separator;
+ String filePathStr = pathStr + fileName + "ServiceImpl.java";
+ createFile(serviceImpl, filePathStr);
+ return filePathStr;
+ }
+
+ public static String createService(String actModulePath, String fileName, String service) {
+ String pathStr = actModulePath + File.separator + "service" + File.separator;
+ String filePathStr = pathStr + fileName + "Service.java";
+ createFile(service, filePathStr);
+ return filePathStr;
+ }
+
+ public static String createEntity(String actModulePath, String fileName, String entity) {
+ String pathStr = actModulePath + File.separator + "entity" + File.separator;
+ String filePathStr = pathStr + fileName + "Entity.java";
+ createFile(entity, filePathStr);
+ return filePathStr;
+ }
+
+ public static String createController(String actModulePath, String fileName, String controller) {
+ String pathStr = actModulePath + File.separator + "controller" + File.separator + "admin" + File.separator;
+ String filePathStr = pathStr + "Admin" + fileName + "Controller.java";
+ createFile(controller, filePathStr);
+ return filePathStr;
+ }
+
+ public static String createModule(String modulesPath, String module) {
+ String pathStr = modulesPath + File.separator + module;
+ PathUtils.noExistsMk(pathStr);
+ return pathStr;
+ }
+
+ public static boolean compileAndSave(String sourceFile) {
+ // 获取系统 Java 编译器
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+ // 获取标准文件管理器
+ try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
+
+ // 设置编译输出目录
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(new File("target" + File.separator + "classes")));
+
+ // 获取源文件
+ List javaFiles = List.of(new File(sourceFile));
+ Iterable extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(javaFiles);
+
+ // 创建编译任务
+ JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);
+ // 执行编译任务
+ return task.call();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public static void compilerEntityTableDef(String actModulePath, String fileName, String entityPath, List javaPathList) {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
+ Iterable extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
+ entityPath);
+ // 设置注解处理器
+ Iterable extends Processor> processors = List.of(new MybatisFlexProcessor());
+ // 添加 -proc:only 选项
+ List options = List.of("-proc:only");
+ JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options,
+ null, compilationUnits);
+ task.setProcessors(processors);
+ task.call();
+ compilationUnits = fileManager.getJavaFileObjects(
+ javaPathList.toArray(new String[0]));
+ // 设置编译输出目录
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(new File("target/classes")));
+
+ task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);
+ String pathStr = actModulePath + File.separator + "entity" + File.separator + "table" + File.separator;
+ String filePathStr = pathStr + fileName + "EntityTableDef.java";
+ // 需在entity之后加载
+ javaPathList.add(1, filePathStr);
+ boolean success = task.call();
+ if (success) {
+ System.out.println("Compilation and annotation processing completed successfully.");
+ // 指定源文件夹和目标文件夹
+ File sourceDir = new File("com");
+ File destinationDir = new File(PathUtils.getTargetGeneratedAnnotations());
+ // 确保目标文件夹存在
+ destinationDir.mkdirs();
+ // 移动源文件夹内容到目标文件夹
+ if (sourceDir.exists()) {
+ FileUtil.move(sourceDir, destinationDir, true);
+ }
+ if (countFiles(sourceDir) <= 1) {
+ FileUtil.clean(sourceDir);
+ FileUtil.del(sourceDir);
+ }
+ } else {
+ System.out.println("Compilation and annotation processing failed.");
+ }
+ } catch (IOException e) {
+ log.error("compilerEntityTableDefError", e);
+ }
+ }
+ private static int countFiles(File directory) {
+ File[] files = directory.listFiles();
+ if (files == null) {
+ return 0;
+ }
+
+ int count = 0;
+ for (File file : files) {
+ if (file.isFile()) {
+ count++;
+ } else if (file.isDirectory()) {
+ count += countFiles(file);
+ }
+ // If more than one file is found, no need to continue counting
+ if (count > 1) {
+ break;
+ }
+ }
+ return count;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/ConvertUtil.java b/cool-admin-java/src/main/java/com/cool/core/util/ConvertUtil.java
new file mode 100644
index 0000000..be72f86
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/ConvertUtil.java
@@ -0,0 +1,257 @@
+package com.cool.core.util;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 转换
+ */
+public class ConvertUtil {
+
+ /**
+ * 对象转数组
+ *
+ * @param obj
+ * @return
+ */
+ public static byte[] toByteArray(Object obj) {
+ byte[] bytes = null;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+ oos.writeObject(obj);
+ oos.flush();
+ bytes = bos.toByteArray();
+ oos.close();
+ bos.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return bytes;
+ }
+
+ /**
+ * 数组转对象
+ *
+ * @param bytes
+ * @return
+ */
+ public static Object toObject(byte[] bytes) {
+ Object obj = null;
+ try {
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bis);
+ obj = ois.readObject();
+ ois.close();
+ bis.close();
+ } catch (IOException | ClassNotFoundException ex) {
+ ex.printStackTrace();
+ }
+ return obj;
+ }
+
+ public static MultipartFile convertToMultipartFile(File file) {
+ FileInputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(file);
+ return new SimpleMultipartFile(file.getName(), inputStream);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ return null;
+ }
+ }
+
+ // 简单的MultipartFile实现,用于模拟Spring中的MultipartFile对象
+ static class SimpleMultipartFile implements MultipartFile {
+
+ private String filename;
+ private InputStream inputStream;
+
+ public SimpleMultipartFile(String filename, InputStream inputStream) {
+ this.filename = filename;
+ this.inputStream = inputStream;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public String getOriginalFilename() {
+ return filename;
+ }
+
+ @Override
+ public String getContentType() {
+ return "application/octet-stream";
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public long getSize() {
+ try {
+ return inputStream.available();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+
+ @Override
+ public byte[] getBytes() throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ output.write(buffer, 0, len);
+ }
+ return output.toByteArray();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return inputStream;
+ }
+
+ @Override
+ public void transferTo(File dest) throws IOException, IllegalStateException {
+ try (FileOutputStream outputStream = new FileOutputStream(dest)) {
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * /admin/goods 转 AdminGoods
+ */
+ public static String pathToClassName(String path) {
+ // 按斜杠分割字符串
+ String[] parts = path.split("/");
+ StringBuilder className = new StringBuilder();
+ for (String part : parts) {
+ // 将每个部分的首字母大写,并追加到 StringBuilder 中
+ className.append(StrUtil.upperFirst(part));
+ }
+ return className.toString();
+ }
+
+ public static String extractController2Path(String prefix, String className) {
+ Pattern pattern = Pattern.compile("([A-Za-z0-9]+)Controller$");
+ Matcher matcher = pattern.matcher(className);
+
+ if (matcher.find()) {
+ String extracted = matcher.group(1);
+
+ // 将前缀拆分为单词数组
+ String[] prefixWords = splitCamelCase(prefix);
+ String[] classWords = splitCamelCase(extracted);
+
+ // 从前缀和类名中逐个匹配并去除匹配的部分
+ int i = 0;
+ for (int j = 0; i < prefixWords.length; j++) {
+ if (j >= classWords.length) {
+ break;
+ }
+ for (String prefixWord : prefixWords) {
+ if (prefixWord.equalsIgnoreCase(classWords[i])) {
+ i++;
+ break;
+ }
+ }
+ }
+ // 从当前位置开始,拼接剩余部分
+ return String.join("/", java.util.Arrays.copyOfRange(classWords, i, classWords.length)).toLowerCase();
+ }
+ return "";
+ }
+
+ // 拆分驼峰命名的字符串为单词数组
+ private static String[] splitCamelCase(String input) {
+ return input.split("(?<=.)(?=[A-Z])");
+ }
+
+ /**
+ * 将给定的字段值转换为可序列化的形式
+ * 此方法旨在将一个对象的特定字段值转换为其相应的可序列化类型
+ * 它在序列化和反序列化过程中特别有用,确保字段值可以被正确处理
+ *
+ * @param fieldName 字段名称,用于查找字段类型
+ * @param fieldValue 待转换的字段值
+ * @param clazz 包含该字段的类
+ * @return 转换后的可序列化字段值,如果无法确定字段类型,则返回原始值
+ */
+ public static Object convertByClass(String fieldName, Object fieldValue, Class> clazz) {
+ // 检查输入参数是否为空,如果字段名或字段值为空,则直接返回字段值
+ if (fieldName == null || fieldValue == null) {
+ return fieldValue;
+ }
+
+ // 获取字段类型
+ Class> fieldType = getFieldType(clazz, fieldName);
+ // 如果字段类型为空,则直接返回字段值
+ if (fieldType == null) {
+ return fieldValue;
+ }
+
+ // 使用Convert类的convert方法将字段值转换为字段类型
+ return Convert.convert(fieldType, fieldValue);
+ }
+
+ public static List covertListByClass(String fieldName, List fieldValue, Class> clazz) {
+ // 检查输入参数是否为空,如果字段名或字段值为空,则直接返回字段值
+ if (fieldName == null || fieldValue == null) {
+ return fieldValue;
+ }
+
+ // 获取字段类型
+ Class> fieldType = getFieldType(clazz, fieldName);
+ // 如果字段类型为空,则直接返回字段值
+ if (fieldType == null) {
+ return fieldValue;
+ }
+
+ return Collections.singletonList(Convert.toList(fieldType, fieldValue));
+ }
+ /**
+ * 获取指定类中指定字段的类型
+ *
+ * @param clazz 目标类
+ * @param fieldName 字段名称
+ * @return 字段的类型 Class,如果字段不存在则返回 null
+ */
+ public static Class> getFieldType(Class> clazz, String fieldName) {
+ Field field = ReflectUtil.getField(clazz, fieldName);
+ return field != null ? field.getType() : null;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/CoolPluginInvokers.java b/cool-admin-java/src/main/java/com/cool/core/util/CoolPluginInvokers.java
new file mode 100644
index 0000000..2a2fdf2
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/CoolPluginInvokers.java
@@ -0,0 +1,165 @@
+package com.cool.core.util;
+
+import cn.hutool.json.JSONUtil;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.plugin.consts.PluginConsts;
+import com.cool.core.plugin.service.DynamicJarLoaderService;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * 插件调用封装
+ */
+@Slf4j
+public class CoolPluginInvokers {
+
+ private static final DynamicJarLoaderService dynamicJarLoaderService = SpringContextUtils
+ .getBean(DynamicJarLoaderService.class);
+
+ /**
+ * 插件默认调用入口
+ */
+ public static Object invokePlugin(String key, String... params) {
+ return invoke(key, PluginConsts.invokePluginMethodName, params);
+ }
+
+ /**
+ * 设置插件配置信息
+ */
+ public static void setPluginJson(String key, PluginInfoEntity entity) {
+ invoke(key, PluginConsts.setPluginJson, JSONUtil.toJsonStr(entity.getPluginJson()));
+ setApplicationContext(key);
+ }
+
+ /**
+ * 设置 ApplicationContext 到插件类中
+ */
+ public static void setApplicationContext(String key) {
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread()
+ .setContextClassLoader(dynamicJarLoaderService.getDynamicJarClassLoader(key));
+ Object beanInstance = dynamicJarLoaderService.getBeanInstance(key);
+ Method method = beanInstance.getClass().getSuperclass()
+ .getMethod(PluginConsts.setApplicationContext,
+ ApplicationContext.class);
+ method.invoke(beanInstance, SpringContextUtils.applicationContext);
+ } catch (Exception e) {
+ log.error("setApplicationContext err", e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ }
+
+ /**
+ * 反射调用插件
+ *
+ * @param key 插件key
+ * @param methodName 插件方法
+ * @param params 参数
+ */
+ public static Object invoke(String key, String methodName, Object... params) {
+ Object beanInstance = dynamicJarLoaderService.getBeanInstance(key);
+ CoolPreconditions.checkEmpty(beanInstance, "未找到该插件:{}, 请前往插件市场进行安装",key);
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ // 设置当前线程的上下文类加载器为插件的类加载器
+ Thread.currentThread()
+ .setContextClassLoader(dynamicJarLoaderService.getDynamicJarClassLoader(key));
+ log.info("调用插件类: {}, 方法: {} 参数: {}", key, methodName, params);
+ return invoke(beanInstance, methodName, params);
+ } catch (Exception e) {
+ log.error("调用插件{}.{}失败", key, methodName, e);
+ CoolPreconditions.alwaysThrow("调用插件{}.{}失败 {}", key, methodName, e.getMessage());
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ return null;
+ }
+
+ /**
+ * 反射调用插件
+ *
+ * @param beanInstance 插件实例对象
+ * @param methodName 插件方法
+ * @param params 参数
+ */
+ private static Object invoke(Object beanInstance, String methodName, Object[] params)
+ throws InvocationTargetException, IllegalAccessException {
+ Class>[] paramTypes = Arrays.stream(params).map(Object::getClass)
+ .toArray(Class>[]::new);
+ Method method = findMethod(beanInstance.getClass(), methodName, paramTypes);
+ CoolPreconditions.check(method == null, "No such method: {} with parameters {}", methodName,
+ Arrays.toString(paramTypes));
+ if (method.isVarArgs()) {
+ // 处理可变参数调用
+ int varArgIndex = method.getParameterTypes().length - 1;
+ Object[] varArgs = (Object[]) java.lang.reflect.Array.newInstance(
+ method.getParameterTypes()[varArgIndex].getComponentType(),
+ params.length - varArgIndex);
+ System.arraycopy(params, varArgIndex, varArgs, 0, varArgs.length);
+ Object[] methodArgs = new Object[varArgIndex + 1];
+ System.arraycopy(params, 0, methodArgs, 0, varArgIndex);
+ methodArgs[varArgIndex] = varArgs;
+ return method.invoke(beanInstance, methodArgs);
+ } else {
+ // 正常调用
+ return method.invoke(beanInstance, params);
+ }
+ }
+
+ // 查找方法,包括处理可变参数
+ private static Method findMethod(Class> clazz, String methodName, Class>... paramTypes) {
+ try {
+ return clazz.getMethod(methodName, paramTypes);
+ } catch (NoSuchMethodException e) {
+ // Try to find a varargs method
+ for (Method method : clazz.getMethods()) {
+ if (method.getName().equals(methodName) && isAssignable(paramTypes,
+ method.getParameterTypes(), method.isVarArgs())) {
+ return method;
+ }
+ }
+ // If not found, try to find in superclass
+ if (clazz.getSuperclass() != null) {
+ return findMethod(clazz.getSuperclass(), methodName, paramTypes);
+ }
+ }
+ return null;
+ }
+
+ private static boolean isAssignable(Class>[] paramTypes, Class>[] methodParamTypes,
+ boolean isVarArgs) {
+ if (isVarArgs) {
+ if (paramTypes.length < methodParamTypes.length - 1) {
+ return false;
+ }
+ for (int i = 0; i < methodParamTypes.length - 1; i++) {
+ if (!methodParamTypes[i].isAssignableFrom(paramTypes[i])) {
+ return false;
+ }
+ }
+ Class> varArgType = methodParamTypes[methodParamTypes.length - 1].getComponentType();
+ for (int i = methodParamTypes.length - 1; i < paramTypes.length; i++) {
+ if (!varArgType.isAssignableFrom(paramTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ if (paramTypes.length != methodParamTypes.length) {
+ return false;
+ }
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (!methodParamTypes[i].isAssignableFrom(paramTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/CoolSecurityUtil.java b/cool-admin-java/src/main/java/com/cool/core/util/CoolSecurityUtil.java
new file mode 100644
index 0000000..5173cca
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/CoolSecurityUtil.java
@@ -0,0 +1,110 @@
+package com.cool.core.util;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONObject;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.enums.UserTypeEnum;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.security.jwt.JwtUser;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * Security 工具类
+ */
+public class CoolSecurityUtil {
+
+ private static final CoolCache coolCache = SpringUtil.getBean(CoolCache.class);
+
+ /***************后台********************/
+ /**
+ * 获取后台登录的用户名
+ */
+ public static String getAdminUsername() {
+ return SecurityContextHolder.getContext().getAuthentication().getName();
+ }
+
+ /**
+ * 获得jwt中的信息
+ *
+ * @param requestParams 请求参数
+ * @return jwt
+ */
+ public static JSONObject getAdminUserInfo(JSONObject requestParams) {
+ JSONObject tokenInfo = requestParams.getJSONObject("tokenInfo");
+ if (tokenInfo != null) {
+ tokenInfo.set("department",
+ coolCache.get("admin:department:" + tokenInfo.get("userId")));
+ tokenInfo.set("roleIds", coolCache.get("admin:roleIds:" + tokenInfo.get("userId")));
+ }
+ return tokenInfo;
+ }
+
+ public static Long getTenantId(JSONObject requestParams) {
+ JSONObject tokenInfo = requestParams.getJSONObject("tokenInfo");
+ if (tokenInfo != null) {
+ return tokenInfo.getLong("tenantId");
+ }
+ return null;
+ }
+
+ /**
+ * 后台账号退出登录
+ *
+ * @param adminUserId 用户ID
+ * @param username 用户名
+ */
+ public static void adminLogout(Long adminUserId, String username) {
+ coolCache.del("admin:department:" + adminUserId, "admin:passwordVersion:" + adminUserId,
+ "admin:userInfo:" + adminUserId, "admin:userDetails:" + username);
+ }
+
+ /**
+ * 后台账号退出登录
+ *
+ * @param userEntity 用户
+ */
+ public static void adminLogout(BaseSysUserEntity userEntity) {
+ adminLogout(userEntity.getId(), userEntity.getUsername());
+ }
+
+
+ /**
+ * 获取当前用户id
+ */
+ public static Long getCurrentUserId() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication != null) {
+ Object principal = authentication.getPrincipal();
+ if (principal instanceof UserDetails) {
+ return ((JwtUser) principal).getUserId();
+ }
+ }
+ CoolPreconditions.check(true, 401, "未登录");
+ return null;
+ }
+
+ /**
+ * 获取当前用户类型
+ */
+ public static UserTypeEnum getCurrentUserType() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication != null) {
+ Object principal = authentication.getPrincipal();
+ if (principal instanceof UserDetails) {
+ return ((JwtUser) principal).getUserTypeEnum();
+ }
+ }
+ // 还未登录,未知类型
+ return UserTypeEnum.UNKNOWN;
+ }
+
+ /**
+ * app退出登录,移除缓存信息
+ */
+ public static void appLogout() {
+ coolCache.del("app:userDetails"+ getCurrentUserId());
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/DatabaseDialectUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/DatabaseDialectUtils.java
new file mode 100644
index 0000000..f4f2554
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/DatabaseDialectUtils.java
@@ -0,0 +1,56 @@
+package com.cool.core.util;
+import com.cool.core.exception.CoolPreconditions;
+import org.dromara.autotable.core.constants.DatabaseDialect;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+/**
+ * 获取数据库方言
+ */
+public class DatabaseDialectUtils {
+ private static String dialect;
+
+ public static String getDatabaseDialect(DataSource dataSource) {
+ if (dialect == null) {
+ dialect = determineDatabaseType(dataSource);
+ }
+ return dialect;
+ }
+
+ public static boolean isPostgresql() {
+ DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
+ return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource));
+ }
+
+ public static boolean isPostgresql(DataSource dataSource) {
+ return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource));
+ }
+
+
+ private static String determineDatabaseType(DataSource dataSource) {
+ // 从 DataSource 获取连接
+ try (Connection connection = dataSource.getConnection()) {
+ // 获取元数据
+ DatabaseMetaData metaData = connection.getMetaData();
+ String productName = metaData.getDatabaseProductName();
+
+ return inferDatabaseTypeFromProductName(productName);
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to determine database dialect", e);
+ }
+ }
+
+ private static String inferDatabaseTypeFromProductName(String productName) {
+ if (productName.startsWith(DatabaseDialect.MySQL)) {
+ return DatabaseDialect.MySQL;
+ } else if (productName.startsWith(DatabaseDialect.PostgreSQL)) {
+ return DatabaseDialect.PostgreSQL;
+ } else if (productName.startsWith(DatabaseDialect.SQLite)) {
+ return DatabaseDialect.SQLite;
+ }
+ CoolPreconditions.alwaysThrow("暂不支持!");
+ return "unknown";
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/EntityUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/EntityUtils.java
new file mode 100644
index 0000000..b564a0c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/EntityUtils.java
@@ -0,0 +1,121 @@
+package com.cool.core.util;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Editor;
+import cn.hutool.core.util.ObjUtil;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.query.QueryColumn;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+public class EntityUtils {
+
+ private static Map> TABLE_MAP;
+
+ public static Set findEntityClassName() {
+ Set entitySet = new HashSet<>();
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource[] resources = null;
+ try {
+ resources = resolver.getResources("classpath*:com/cool/**/entity/**/*Entity.class");
+ for (Resource r : resources) {
+ String path = r.getURL().getPath();
+ String className = path.substring(path.indexOf("com/cool"),
+ path.lastIndexOf('.')).replace('/', '.');
+ entitySet.add(className);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return entitySet;
+ }
+
+ public static Map> findTableMap() {
+ if (ObjUtil.isEmpty(TABLE_MAP)) {
+ init();
+ }
+ return TABLE_MAP;
+ }
+
+ private static void init() {
+ Set classNames = EntityUtils.findEntityClassName();
+ TABLE_MAP = new HashMap<>();
+ classNames.forEach(className -> {
+ Class> entityClass;
+ try {
+ entityClass = Class.forName(className);
+ Table tableAnnotation = AnnotationUtil.getAnnotation(entityClass, Table.class);
+ // key表名,value 实体对象
+ TABLE_MAP.put(tableAnnotation.value(), entityClass);
+ } catch (Exception e) {
+ // do nothing
+ }
+ });
+ }
+
+ /**
+ * 获取实体类及其父类的字段名数组(排除指定字段)
+ *
+ * @return 字段名数组
+ */
+ public static QueryColumn[] getFieldNamesWithSuperClass(QueryColumn[] queryColumns,
+ String... excludeNames) {
+ return getFieldNamesListWithSuperClass(queryColumns, excludeNames).toArray(
+ new QueryColumn[0]);
+ }
+
+ public static List getFieldNamesListWithSuperClass(QueryColumn[] queryColumns,
+ String... excludeNames) {
+ ArrayList excludeList = new ArrayList<>(List.of(excludeNames));
+ return Arrays.stream(queryColumns).toList().stream()
+ .filter(o -> !excludeList.contains(o.getName())).toList();
+ }
+
+ /**
+ * 将bean的部分属性转换成map
+ * 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。
+ *
+ * @param bean bean
+ * @param ignoreProperties 需要忽略拷贝的属性值,{@code null}或空表示拷贝所有值
+ * @return Map
+ * @since 5.8.0
+ */
+ public static Map toMap(Object bean, String... ignoreProperties) {
+ int mapSize = 16;
+ Editor keyEditor = null;
+ final Set propertiesSet = CollUtil.set(false, ignoreProperties);
+ propertiesSet.add("queryWrapper");
+ mapSize = ignoreProperties.length;
+ keyEditor = property -> !propertiesSet.contains(property) ? property : null;
+ // 指明了要复制的属性 所以不忽略null值
+ return BeanUtil.beanToMap(bean, new LinkedHashMap<>(mapSize, 1), false, keyEditor);
+ }
+
+ /**
+ * 检查字段名是否在排除列表中
+ *
+ * @param fieldName 要检查的字段名
+ * @param excludeNames 排除的字段名数组
+ * @return 是否在排除列表中
+ */
+ private static boolean isExcluded(String fieldName, String[] excludeNames) {
+ for (String excludeName : excludeNames) {
+ if (fieldName.equals(excludeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/I18nUtil.java b/cool-admin-java/src/main/java/com/cool/core/util/I18nUtil.java
new file mode 100644
index 0000000..13d28d5
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/I18nUtil.java
@@ -0,0 +1,125 @@
+package com.cool.core.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+@Slf4j
+@Component
+public class I18nUtil {
+
+ public static final String MSG_PREFIX = "msg_";
+ public static final String MENU_PREFIX = "menu_";
+ public static final String DICT_INFO_PREFIX = "dictInfo_";
+ public static final String DICT_TYPE_PREFIX = "dictType_";
+
+ public static boolean enable = false;
+
+ public static String path;
+ public static String getLanguage() {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ if (attributes == null) {
+ return null;
+ }
+ return (String) attributes.getAttribute("cool-language", RequestAttributes.SCOPE_REQUEST);
+ }
+
+ private static final Map data = new ConcurrentHashMap<>();
+
+ private void load(String key, File file) {
+ try {
+ String content = FileUtil.readUtf8String(file);
+ data.put(key, JSONUtil.parseObj(content));
+ } catch (Exception e) {
+ log.error("读取国际化文件失败", e);
+ }
+ }
+
+ public boolean exist(String name) {
+ // 获取该目录下所有的 .json 文件
+ List jsonFiles = FileUtil.loopFiles(getPath(), file ->
+ file.isFile() && file.getName().endsWith(".json")
+ );
+ AtomicReference flag = new AtomicReference<>(false);
+ jsonFiles.forEach(file -> {
+ String parentName = file.getParentFile().getName();
+ String key = parentName + "_" + file.getName().replace(".json", "");
+ if (key.equals(name)) {
+ flag.set(true);
+ // 加载
+ load(key, file);
+ }
+ });
+ return flag.get();
+ }
+
+ public static String getI18nMenu(String name) {
+ return getI18n(name, MENU_PREFIX);
+ }
+
+ public static String getI18nMsg(String name) {
+ return getI18n(name, MSG_PREFIX);
+ }
+
+ public static String getI18nDictInfo(String name) {
+ return getI18n(name, DICT_INFO_PREFIX);
+ }
+ public static String getI18nDictType(String name) {
+ return getI18n(name, DICT_TYPE_PREFIX);
+ }
+ private static String getI18n(String name, String prefix) {
+ if (!enable) {
+ return name;
+ }
+ String language = I18nUtil.getLanguage();
+ if (language == null) {
+ return name;
+ }
+ JSONObject jsonObject = data.get(prefix + language);
+ if (jsonObject == null) {
+ return name;
+ }
+ String str = jsonObject.getStr(name);
+ if (str == null) {
+ return name;
+ }
+ return str;
+ }
+
+ public void update(String key, JSONObject object) {
+ data.put(key, object);
+ String[] split = key.split("_");
+ String absolutePath = getPath();
+ File file = FileUtil.file(absolutePath, split[0], split[1] + ".json");
+ // 确保父目录存在
+ FileUtil.mkParentDirs(file);
+ // 写入内容
+ FileUtil.writeUtf8String(JSONUtil.toJsonStr(object), file);
+ }
+
+ private String getPath() {
+ String absolutePath = path;
+ if (!PathUtils.isAbsolutePath(absolutePath)) {
+ absolutePath = PathUtils.getUserDir() + File.separator + absolutePath;
+ }
+ return absolutePath;
+ }
+
+ public void clear() {
+ data.clear();
+ List jsonFiles = FileUtil.loopFiles(getPath(), file ->
+ file.isFile() && file.getName().endsWith(".json")
+ );
+ jsonFiles.forEach(File::delete);
+ enable = false;
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/IPUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/IPUtils.java
new file mode 100644
index 0000000..ed33d80
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/IPUtils.java
@@ -0,0 +1,46 @@
+package com.cool.core.util;
+
+import cn.hutool.core.util.StrUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * IP地址
+ */
+@Slf4j
+@Component
+public class IPUtils {
+
+ /**
+ * 获取IP地址
+ *
+ * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
+ * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
+ */
+ public String getIpAddr(HttpServletRequest request) {
+ String ip = null;
+ try {
+ ip = request.getHeader("x-forwarded-for");
+ if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("Proxy-Client-IP");
+ }
+ if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_CLIENT_IP");
+ }
+ if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+ }
+ if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getRemoteAddr();
+ }
+ } catch (Exception e) {
+ log.error("IP extraction error", e);
+ }
+ return ip;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/MapExtUtil.java b/cool-admin-java/src/main/java/com/cool/core/util/MapExtUtil.java
new file mode 100644
index 0000000..3179ea2
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/MapExtUtil.java
@@ -0,0 +1,30 @@
+package com.cool.core.util;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.Map;
+
+public class MapExtUtil extends MapUtil {
+
+ /**
+ * 比较两个map key 和 value 是否一致
+ */
+ public static boolean compareMaps(Map map1, Map map2) {
+ if (ObjectUtil.isEmpty(map1) || ObjectUtil.isEmpty(map2)) {
+ return true;
+ }
+ if (map1.size() != map2.size()) {
+ return false;
+ }
+ for (Map.Entry entry : map1.entrySet()) {
+ if (!map2.containsKey(entry.getKey())) {
+ return false;
+ }
+ if (!ObjectUtil.equal(entry.getValue(), map2.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/MappingAlgorithm.java b/cool-admin-java/src/main/java/com/cool/core/util/MappingAlgorithm.java
new file mode 100644
index 0000000..783aa77
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/MappingAlgorithm.java
@@ -0,0 +1,21 @@
+package com.cool.core.util;
+
+/**
+ * 自定义映射算法
+ * 将 ID 转换为一个混淆形式的数字,然后能够逆向转换回原始 ID。
+ * 场景:混淆订单id
+ */
+public class MappingAlgorithm {
+
+ private static final long ENCRYPTION_KEY = 123456789L; // 任意密钥
+
+ // 将 ID 转换为混淆的数字
+ public static long encrypt(long id) {
+ return id ^ ENCRYPTION_KEY; // 使用异或操作进行混淆
+ }
+
+ // 将混淆的数字恢复为原始的 ID
+ public static long decrypt(long encryptedId) {
+ return encryptedId ^ ENCRYPTION_KEY; // 逆操作恢复原始 ID
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/PathUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/PathUtils.java
new file mode 100644
index 0000000..0f61184
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/PathUtils.java
@@ -0,0 +1,70 @@
+package com.cool.core.util;
+
+import cn.hutool.core.io.file.PathUtil;
+import cn.hutool.core.text.AntPathMatcher;
+import com.cool.CoolApplication;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class PathUtils {
+ private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+ public static boolean isAbsolutePath(String pathStr) {
+ Path path = Paths.get(pathStr);
+ return path.isAbsolute();
+ }
+
+ public static String getUserDir() {
+ return System.getProperty("user.dir");
+ }
+
+ public static String getModulesPath() {
+ return getUserDir() + getSrcMainJava() + File.separator + CoolApplication.class.getPackageName()
+ .replace(".", File.separator) + File.separator + "modules";
+ }
+
+ public static String getSrcMainJava() {
+ return File.separator + "src" + File.separator + "main" + File.separator + "java";
+ }
+ public static String getTargetGeneratedAnnotations() {
+ return "target" + File.separator + "generated-sources" + File.separator + "annotations";
+ }
+
+ public static String getClassName(String filePath) {
+ // 定位 "/src/main/java" 在路径中的位置
+ int srcMainJavaIndex = filePath.indexOf(getSrcMainJava());
+ if (srcMainJavaIndex == -1) {
+ throw new IllegalArgumentException("File path does not contain 'src/main/java'");
+ }
+
+ // 提取 "src/main/java" 之后的路径
+ // 将文件分隔符替换为包分隔符
+ return filePath.substring(srcMainJavaIndex + ("src" + File.separator + "main" + File.separator + "java").length() + 2)
+ .replace(File.separator, ".").replace(".java", "");
+ }
+
+ /**
+ * 路径不存在创建
+ */
+ public static void noExistsMk(String pathStr) {
+ Path path = Paths.get(pathStr);
+ if (PathUtil.exists(path, false)) {
+ PathUtil.mkParentDirs(path);
+ }
+ }
+
+ /**
+ * 判断给定的请求URI是否匹配列表中的任意一个URL模式
+ * 使用Ant风格的路径匹配来处理URL模式,提供了一种通配符匹配的方法
+ *
+ * @param urls 待匹配的URL模式列表
+ * @param requestURI 请求的URI
+ * @return 如果请求URI匹配列表中的任意一个URL模式,则返回true;否则返回false
+ */
+ public static boolean isMatch(List urls, String requestURI) {
+ return urls.stream()
+ .anyMatch(url -> antPathMatcher.match(url, requestURI));
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/SpringContextUtils.java b/cool-admin-java/src/main/java/com/cool/core/util/SpringContextUtils.java
new file mode 100644
index 0000000..f026531
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/SpringContextUtils.java
@@ -0,0 +1,45 @@
+package com.cool.core.util;
+
+import java.util.Map;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Spring Context 工具类
+ */
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+
+ public static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ SpringContextUtils.applicationContext = applicationContext;
+ }
+
+ public static Object getBean(String name) {
+ return applicationContext.getBean(name);
+ }
+
+ public static T getBean(Class requiredType) {
+ return applicationContext.getBean(requiredType);
+ }
+
+ public static boolean containsBean(String name) {
+ return applicationContext.containsBean(name);
+ }
+
+ public static boolean isSingleton(String name) {
+ return applicationContext.isSingleton(name);
+ }
+
+ public static Class extends Object> getType(String name) {
+ return applicationContext.getType(name);
+ }
+
+ public static Map getBeansOfType(Class requiredType) {
+ return applicationContext.getBeansOfType(requiredType);
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/core/util/TenantUtil.java b/cool-admin-java/src/main/java/com/cool/core/util/TenantUtil.java
new file mode 100644
index 0000000..66d20e4
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/core/util/TenantUtil.java
@@ -0,0 +1,14 @@
+package com.cool.core.util;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+public class TenantUtil {
+ public static Long getTenantId() {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ if (attributes == null) {
+ return null;
+ }
+ return (Long) attributes.getAttribute("tenantId", RequestAttributes.SCOPE_REQUEST);
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCodingController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCodingController.java
new file mode 100644
index 0000000..c1cce5c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCodingController.java
@@ -0,0 +1,43 @@
+package com.cool.modules.base.controller.admin;
+
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.annotation.TokenIgnore;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.request.R;
+import com.cool.modules.base.dto.sys.CodeContentDto;
+import com.cool.modules.base.service.sys.BaseCodingService;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+
+/**
+ * ai 编码
+ */
+@CoolRestController
+@RequiredArgsConstructor
+public class AdminBaseCodingController {
+
+ private final BaseCodingService baseCodingService;
+
+ @TokenIgnore
+ @Operation(summary = "获取模块目录结构", description = "获取模块目录结构")
+ @GetMapping("/getModuleTree")
+ public R getModuleTree() {
+ return R.ok(baseCodingService.getModuleTree());
+ }
+
+ @TokenIgnore
+ @Operation(summary = "创建代码", description = "创建代码")
+ @PostMapping("/createCode")
+ public R createCode(@RequestAttribute JSONObject requestParams) {
+ JSONArray codes = requestParams.get("codes", JSONArray.class);
+ CoolPreconditions.checkEmpty(codes);
+ this.baseCodingService.createCode(codes.toList(CodeContentDto.class));
+ return R.ok();
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java
new file mode 100644
index 0000000..451ab3c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java
@@ -0,0 +1,107 @@
+package com.cool.modules.base.controller.admin;
+
+import cn.hutool.core.lang.Dict;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.annotation.TokenIgnore;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.file.FileUploadStrategyFactory;
+import com.cool.core.request.R;
+import com.cool.core.util.I18nUtil;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.service.sys.BaseSysLoginService;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.cool.modules.base.service.sys.BaseSysUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 系统通用接口, 每个人都有权限操作
+ */
+@RequiredArgsConstructor
+@Tag(name = "系统通用", description = "系统通用")
+@CoolRestController()
+public class AdminBaseCommController {
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ final private BaseSysUserService baseSysUserService;
+
+ final private BaseSysLoginService baseSysLoginService;
+
+ final private CoolEps coolEps;
+
+ final private FileUploadStrategyFactory fileUploadStrategyFactory;
+
+ @TokenIgnore
+ @Operation(summary = "实体信息与路径", description = "系统所有的实体信息与路径,供前端自动生成代码与服务")
+ @GetMapping("/eps")
+ public R eps() {
+ return R.ok(coolEps.getAdmin());
+ }
+
+ @Operation(summary = "个人信息")
+ @GetMapping("/person")
+ public R person(@RequestAttribute() Long adminUserId) {
+ BaseSysUserEntity baseSysUserEntity = baseSysUserService.getById(adminUserId);
+ baseSysUserEntity.setPassword(null);
+ baseSysUserEntity.setPasswordV(null);
+ return R.ok(baseSysUserEntity);
+ }
+
+ @Operation(summary = "修改个人信息")
+ @PostMapping("/personUpdate")
+ public R personUpdate(@RequestAttribute Long adminUserId, @RequestBody Dict body) {
+ baseSysUserService.personUpdate(adminUserId, body);
+ return R.ok();
+ }
+
+ @Operation(summary = "权限与菜单")
+ @GetMapping("/permmenu")
+ public R permmenu(@RequestAttribute() Long adminUserId) {
+ Dict permmenu = baseSysPermsService.permmenu(adminUserId);
+ List list = (List) permmenu.getObj("menus");
+ list.forEach(o -> o.setName(I18nUtil.getI18nMenu(o.getName())));
+ return R.ok(permmenu);
+ }
+
+ @Operation(summary = "文件上传")
+ @PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE,
+ MediaType.ALL_VALUE})
+ public R upload(
+ @RequestPart(value = "file", required = false) @Parameter(description = "文件") MultipartFile[] files,
+ HttpServletRequest request) {
+ return R.ok(fileUploadStrategyFactory.upload(files, request));
+ }
+
+ @Operation(summary = "文件上传模式")
+ @GetMapping("/uploadMode")
+ public R uploadMode() {
+ return R.ok(fileUploadStrategyFactory.getMode());
+ }
+
+ @Operation(summary = "退出")
+ @PostMapping("/logout")
+ public R logout(@RequestAttribute Long adminUserId, @RequestAttribute String adminUsername) {
+ baseSysLoginService.logout(adminUserId, adminUsername);
+ return R.ok();
+ }
+
+ @TokenIgnore
+ @Operation(summary = "编程")
+ @GetMapping("/program")
+ public R program() {
+ return R.ok("Java");
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java
new file mode 100644
index 0000000..ed87680
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java
@@ -0,0 +1,121 @@
+package com.cool.modules.base.controller.admin;
+
+import static com.cool.core.plugin.consts.PluginConsts.captchaHook;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.enums.UserTypeEnum;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.plugin.service.CoolPluginService;
+import com.cool.core.request.R;
+import com.cool.core.util.CoolPluginInvokers;
+import com.cool.modules.base.dto.sys.BaseSysLoginDto;
+import com.cool.modules.base.service.sys.BaseSysLoginService;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import io.micrometer.common.util.StringUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 系统开放接口,无需权限校验
+ */
+@RequiredArgsConstructor
+@Tag(name = "系统开放", description = "系统开放")
+@CoolRestController()
+public class AdminBaseOpenController {
+
+ final private BaseSysLoginService baseSysLoginService;
+ final private CoolPluginService coolPluginService;
+ final private CoolEps coolEps;
+ final private CoolCache coolCache;
+
+ @Operation(summary = "实体信息与路径", description = "系统所有的实体信息与路径,供前端自动生成代码与服务")
+ @GetMapping("/eps")
+ public R eps() {
+ return R.ok(coolEps.getAdmin());
+ }
+
+ @Operation(summary = "获得网页内容的参数值")
+ @GetMapping("/html")
+ public R html() {
+ return R.ok();
+ }
+
+ @Operation(summary = "登录")
+ @PostMapping("/login")
+ public R login(@RequestBody BaseSysLoginDto baseSysLoginDto) {
+ return R.ok(baseSysLoginService.login(baseSysLoginDto));
+ }
+
+ @Operation(summary = "验证码")
+ @GetMapping("/captcha")
+ public R captcha(@Parameter(description = "类型:svg|base64") @RequestParam(defaultValue = "base64") String type,
+ @Parameter(description = "宽度") @RequestParam(defaultValue = "150") Integer width,
+ @Parameter(description = "高度") @RequestParam(defaultValue = "50") Integer height) {
+ return R.ok(baseSysLoginService.captcha(UserTypeEnum.ADMIN, type, width, height));
+ }
+
+ @Operation(summary = "刷新token")
+ @GetMapping("/refreshToken")
+ public R refreshToken(String refreshToken) {
+ return R.ok(baseSysLoginService.refreshToken(refreshToken));
+ }
+
+ @RequestMapping("/gen")
+ @ResponseBody
+ public Object genCaptcha(@RequestParam(value = "type", required = false)String type) {
+ if (StringUtils.isBlank(type)) {
+ type = "SLIDER";
+ }
+ if ("RANDOM".equals(type)) {
+ int i = ThreadLocalRandom.current().nextInt(0, 4);
+ if (i == 0) {
+ type = "SLIDER";
+ } else if (i == 1) {
+ type = "CONCAT";
+ } else if (i == 2) {
+ type = "ROTATE";
+ } else{
+ type = "WORD_IMAGE_CLICK";
+ }
+
+ }
+ return CoolPluginInvokers.invoke("tianai", "generateCaptcha", type);
+ }
+
+ @PostMapping("/check")
+ @ResponseBody
+ public Object checkCaptcha(@RequestAttribute() JSONObject requestParams) {
+ Object result = CoolPluginInvokers.invoke("tianai", "matching", requestParams);
+ Map map = BeanUtil.beanToMap(result);
+ if (ObjUtil.equals(map.get("code"), 200)) {
+ String code = ThreadLocalRandom.current().nextInt(100000, 999999) + "";
+ coolCache.set("verify:img:" + requestParams.getStr("id"), code, 1800);
+ R r = new R();
+ r.put("data", Map.of("id", requestParams.getStr("id"),
+ "code", code));
+ r.put("code", map.get("code"));
+ return r;
+ }
+ return result;
+ }
+
+ @Operation(summary = "验证码类型")
+ @GetMapping("/captchaMode")
+ public R captchaMode() {
+ PluginInfoEntity pluginInfoEntity = coolPluginService.getPluginInfoEntityByHook(
+ captchaHook);
+ if (pluginInfoEntity != null) {
+ return R.ok(CoolPluginInvokers.invoke(pluginInfoEntity.getKey(), "getMode"));
+ }
+ return R.ok(Map.of("mode", "common"));
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysDepartmentController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysDepartmentController.java
new file mode 100644
index 0000000..b969055
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysDepartmentController.java
@@ -0,0 +1,35 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.core.request.R;
+import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity;
+import com.cool.modules.base.service.sys.BaseSysDepartmentService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * 系统部门
+ */
+@Tag(name = "系统部门", description = "系统部门")
+@CoolRestController(api = { "add", "delete", "update", "list" })
+public class AdminBaseSysDepartmentController
+ extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ }
+
+ @Operation(summary = "排序")
+ @PostMapping("/order")
+ public R order(@RequestBody List list) {
+ this.service.order(list);
+ return R.ok();
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysLogController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysLogController.java
new file mode 100644
index 0000000..7b1ff35
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysLogController.java
@@ -0,0 +1,57 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.core.request.R;
+import com.cool.modules.base.entity.sys.BaseSysLogEntity;
+import com.cool.modules.base.entity.sys.table.BaseSysLogEntityTableDef;
+import com.cool.modules.base.entity.sys.table.BaseSysUserEntityTableDef;
+import com.cool.modules.base.service.sys.BaseSysConfService;
+import com.cool.modules.base.service.sys.BaseSysLogService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+
+/**
+ * 系统日志
+ */
+@RequiredArgsConstructor
+@Tag(name = "系统日志", description = "系统日志")
+@CoolRestController(api = {"page"})
+public class AdminBaseSysLogController extends BaseController {
+
+ private final BaseSysConfService baseSysConfService;
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ setPageOption(
+ createOp()
+ .keyWordLikeFields(
+ BaseSysUserEntityTableDef.BASE_SYS_USER_ENTITY.NAME,
+ BaseSysLogEntityTableDef.BASE_SYS_LOG_ENTITY.PARAMS));
+ }
+
+ @Operation(summary = "清理日志")
+ @PostMapping("/clear")
+ public R clear() {
+ service.clear(true);
+ return R.ok();
+ }
+
+ @Operation(summary = "设置日志保存时间")
+ @PostMapping("/setKeep")
+ public R setKeep(@RequestAttribute JSONObject requestParams) {
+ baseSysConfService.updateValue("logKeep", requestParams.getStr("value"));
+ return R.ok();
+ }
+
+ @Operation(summary = "获得日志报错时间")
+ @PostMapping("/getKeep")
+ public R getKeep() {
+ return R.ok(baseSysConfService.getValue("logKeep"));
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java
new file mode 100644
index 0000000..fbe96e2
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java
@@ -0,0 +1,66 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.request.CrudOption;
+import com.cool.core.request.R;
+import com.cool.core.util.I18nUtil;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.service.sys.BaseSysMenuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+/**
+ * 系统菜单
+ */
+@Tag(name = "系统菜单", description = "系统菜单")
+@CoolRestController(api = {"add", "delete", "update", "page", "list", "info"})
+public class AdminBaseSysMenuController extends
+ BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ CrudOption transform = createOp()
+ .transform(o -> {
+ BaseSysMenuEntity entity = (BaseSysMenuEntity) o;
+ entity.setName(I18nUtil.getI18nMenu(entity.getName()));
+ });
+ setPageOption(transform);
+ setListOption(transform);
+ setInfoOption(transform);
+ }
+
+ @Operation(summary = "创建代码", description = "创建代码")
+ @PostMapping("/create")
+ public R create(@RequestBody() Map params) {
+ CoolPreconditions.checkEmpty(params.get("module"), "module参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("entity"), "entity参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("controller"), "controller参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("service"), "service参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("service-impl"), "service-impl参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("mapper"), "mapper参数不能为空");
+ CoolPreconditions.checkEmpty(params.get("fileName"), "fileName参数不能为空");
+ this.service.create(params);
+ return R.ok();
+ }
+
+ @Operation(summary = "导出", description = "导出")
+ @PostMapping("/export")
+ public R export(@RequestBody Map params) {
+ return R.ok(this.service.export(getIds(params)));
+ }
+
+ @Operation(summary = "导入", description = "导入")
+ @PostMapping("/import")
+ public R importMenu(@RequestBody Map> params) {
+ CoolPreconditions.checkEmpty(params.get("menus"), "参数不能为空");
+ return R.ok(this.service.importMenu(params.get("menus")));
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java
new file mode 100644
index 0000000..56f7291
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java
@@ -0,0 +1,33 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import static com.cool.modules.base.entity.sys.table.BaseSysParamEntityTableDef.BASE_SYS_PARAM_ENTITY;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.modules.base.entity.sys.BaseSysParamEntity;
+import com.cool.modules.base.service.sys.BaseSysParamService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * 系统参数配置
+ */
+@Tag(name = "系统参数配置", description = "系统参数配置")
+@CoolRestController(api = { "add", "delete", "update", "page", "info" })
+public class AdminBaseSysParamController extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ setPageOption(createOp().fieldEq(BASE_SYS_PARAM_ENTITY.DATA_TYPE)
+ .keyWordLikeFields(BASE_SYS_PARAM_ENTITY.NAME, BASE_SYS_PARAM_ENTITY.KEY_NAME));
+ }
+
+ @Operation(summary = "根据键返回网页的参数值")
+ @GetMapping("/html")
+ public String html(String key) {
+ return service.htmlByKey(key);
+ }
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java
new file mode 100644
index 0000000..51f0993
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java
@@ -0,0 +1,37 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import static com.cool.modules.base.entity.sys.table.BaseSysRoleEntityTableDef.BASE_SYS_ROLE_ENTITY;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.modules.base.entity.sys.BaseSysRoleEntity;
+import com.cool.modules.base.service.sys.BaseSysRoleService;
+import com.mybatisflex.core.query.QueryWrapper;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 系统角色
+ */
+@Tag(name = "系统角色", description = "系统角色")
+@CoolRestController(api = { "add", "delete", "update", "page", "list", "info" })
+public class AdminBaseSysRoleController extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ JSONObject tokenInfo = requestParams.getJSONObject("tokenInfo");
+ boolean isAdmin = tokenInfo.getStr("username").equals("admin");
+
+ setPageOption(createOp().keyWordLikeFields(BASE_SYS_ROLE_ENTITY.NAME, BASE_SYS_ROLE_ENTITY.LABEL).queryWrapper(QueryWrapper.create().and(qw -> {
+ qw.eq(BASE_SYS_ROLE_ENTITY.USER_ID.getName(), tokenInfo.getLong("userId")).or(w -> {
+ Object o = tokenInfo.get("roleIds");
+ if (o != null) {
+ w.in(BASE_SYS_ROLE_ENTITY.ID.getName(), new JSONArray(o).toList(Long.class));
+ }
+ });
+ }, !isAdmin).and(BASE_SYS_ROLE_ENTITY.LABEL.ne("admin"))));
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysUserController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysUserController.java
new file mode 100644
index 0000000..aa15e18
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysUserController.java
@@ -0,0 +1,34 @@
+package com.cool.modules.base.controller.admin.sys;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.core.request.R;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.service.sys.BaseSysUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 系统用户
+ */
+@Tag(name = "系统用户", description = "系统用户")
+@CoolRestController(api = { "add", "delete", "update", "page", "info" })
+public class AdminBaseSysUserController extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ }
+
+ @Operation(summary = "移动部门")
+ @PostMapping("/move")
+ public R move(@RequestAttribute JSONObject requestParams) {
+ service.move(requestParams.getLong("departmentId"), requestParams.get("userIds", Long[].class));
+ return R.ok();
+ }
+
+}
\ No newline at end of file
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java b/cool-admin-java/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java
new file mode 100644
index 0000000..d326b0a
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java
@@ -0,0 +1,71 @@
+package com.cool.modules.base.controller.app;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.annotation.TokenIgnore;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.file.FileUploadStrategyFactory;
+import com.cool.core.request.R;
+import com.cool.modules.base.service.sys.BaseSysParamService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * app通用接口
+ */
+@RequiredArgsConstructor
+@Tag(name = "应用通用", description = "应用通用")
+@CoolRestController
+public class AppBaseCommController {
+
+ private final CoolEps coolEps;
+
+ private final BaseSysParamService baseSysParamService;
+
+ @Value("${cool.sysParam.allowKeys:[]}")
+ private List allowKeys;
+
+ final private FileUploadStrategyFactory fileUploadStrategyFactory;
+
+ @TokenIgnore
+ @Operation(summary = "参数配置")
+ @GetMapping("/param")
+ public R param(@RequestAttribute() JSONObject requestParams) {
+ String key = requestParams.get("key", String.class);
+ CoolPreconditions.check(!allowKeys.contains(key), "非法操作");
+ return R.ok(baseSysParamService.dataByKey(key));
+ }
+
+ @TokenIgnore
+ @Operation(summary = "实体信息与路径", description = "系统所有的实体信息与路径,供前端自动生成代码与服务")
+ @GetMapping("/eps")
+ public R eps() {
+ return R.ok(coolEps.getApp());
+ }
+
+
+ @Operation(summary = "文件上传")
+ @PostMapping(value = "/upload", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.ALL_VALUE })
+ public R upload(@RequestPart(value = "file", required = false) @Parameter(description = "文件") MultipartFile[] files,
+ HttpServletRequest request) {
+ return R.ok(fileUploadStrategyFactory.upload(files, request));
+ }
+
+ @Operation(summary = "文件上传模式")
+ @GetMapping("/uploadMode")
+ public R uploadMode() {
+ return R.ok(fileUploadStrategyFactory.getMode());
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/BaseSysLoginDto.java b/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/BaseSysLoginDto.java
new file mode 100644
index 0000000..20f3149
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/BaseSysLoginDto.java
@@ -0,0 +1,30 @@
+package com.cool.modules.base.dto.sys;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotBlank;
+
+/**
+ * 登录
+ */
+@Data
+@Schema(description = "登录参数")
+public class BaseSysLoginDto {
+
+ @Schema(description = "用户名")
+ @NotBlank
+ private String username;
+
+ @Schema(description = "密码")
+ @NotBlank
+ private String password;
+
+ @Schema(description = "验证码ID")
+ @NotBlank
+ private String captchaId;
+
+ @Schema(description = "验证码")
+ @NotBlank
+ private String verifyCode;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/CodeContentDto.java b/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/CodeContentDto.java
new file mode 100644
index 0000000..bcff941
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/dto/sys/CodeContentDto.java
@@ -0,0 +1,9 @@
+package com.cool.modules.base.dto.sys;
+
+import lombok.Data;
+
+@Data
+public class CodeContentDto {
+ private String path;
+ private String content;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java
new file mode 100644
index 0000000..73ffbb1
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java
@@ -0,0 +1,24 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+import org.dromara.autotable.annotation.enums.IndexTypeEnum;
+
+/**
+ * 系统配置
+ */
+@Getter
+@Setter
+@Table(value = "base_sys_conf", comment = "系统配置表")
+public class BaseSysConfEntity extends BaseEntity {
+ @Index(type = IndexTypeEnum.UNIQUE)
+ @ColumnDefine(comment = "配置键", notNull = true)
+ private String cKey;
+
+ @ColumnDefine(comment = "值", notNull = true, type = "text")
+ private String cValue;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java
new file mode 100644
index 0000000..200a126
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java
@@ -0,0 +1,30 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 系统部门
+ */
+@Getter
+@Setter
+@Table(value = "base_sys_department", comment = "系统部门")
+public class BaseSysDepartmentEntity extends BaseEntity {
+ @ColumnDefine(comment = "部门名称", notNull = true)
+ private String name;
+
+ @ColumnDefine(comment = "上级部门ID", type = "bigint")
+ private Long parentId;
+
+ @ColumnDefine(comment = "排序", defaultValue = "0")
+ private Integer orderNum;
+
+ // 父菜单名称
+ @Column(ignore = true)
+ private String parentName;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java
new file mode 100644
index 0000000..450960c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java
@@ -0,0 +1,34 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+
+@Getter
+@Setter
+@Table(value = "base_sys_log", comment = "系统日志表")
+public class BaseSysLogEntity extends BaseEntity {
+
+ @Index
+ @ColumnDefine(comment = "用户ID", type = "bigint")
+ private Long userId;
+
+ @ColumnDefine(comment = "行为", length = 1000)
+ private String action;
+
+ @ColumnDefine(comment = "IP", length = 50)
+ private String ip;
+
+ @ColumnDefine(comment = "参数", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private Object params;
+
+ // 用户名称
+ @Column(ignore = true)
+ private String name;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java
new file mode 100644
index 0000000..96c4ca4
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java
@@ -0,0 +1,52 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+
+@Getter
+@Setter
+@Table(value = "base_sys_menu", comment = "系统菜单表")
+public class BaseSysMenuEntity extends BaseEntity {
+ @Index
+ @ColumnDefine(comment = "父菜单ID", type = "bigint")
+ private Long parentId;
+
+ @ColumnDefine(comment = "菜单名称")
+ private String name;
+
+ @ColumnDefine(comment = "权限", type = "text")
+ private String perms;
+
+ @ColumnDefine(comment = "类型 0:目录 1:菜单 2:按钮", defaultValue = "0")
+ private Integer type;
+
+ @ColumnDefine(comment = "图标")
+ private String icon;
+
+ @ColumnDefine(comment = "排序", defaultValue = "0")
+ private Integer orderNum;
+
+ @ColumnDefine(comment = "菜单地址")
+ private String router;
+
+ @ColumnDefine(comment = "视图地址")
+ private String viewPath;
+
+ @ColumnDefine(comment = "路由缓存", defaultValue = "true")
+ private Boolean keepAlive;
+
+ @ColumnDefine(comment = "是否显示", defaultValue = "true")
+ private Boolean isShow;
+
+ @Column(ignore = true)
+ private String parentName;
+
+ @Column(ignore = true)
+ private List childMenus;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java
new file mode 100644
index 0000000..d5f1001
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java
@@ -0,0 +1,29 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+
+@Getter
+@Setter
+@Table(value = "base_sys_param", comment = "系统参数配置")
+public class BaseSysParamEntity extends BaseEntity {
+ @Index
+ @ColumnDefine(comment = "键", notNull = true)
+ private String keyName;
+
+ @ColumnDefine(comment = "名称")
+ private String name;
+
+ @ColumnDefine(comment = "数据", type = "text")
+ private String data;
+
+ @ColumnDefine(comment = "数据类型 0:字符串 1:数组 2:键值对", defaultValue = "0")
+ private Integer dataType;
+
+ @ColumnDefine(comment = "备注")
+ private String remark;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleDepartmentEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleDepartmentEntity.java
new file mode 100644
index 0000000..f7a48ff
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleDepartmentEntity.java
@@ -0,0 +1,20 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Table(value = "base_sys_role_department", comment = "系统角色部门")
+public class BaseSysRoleDepartmentEntity extends BaseEntity {
+
+ @ColumnDefine(comment = "角色ID", type = "bigint")
+ private Long roleId;
+
+ @ColumnDefine(comment = "部门ID", type = "bigint")
+ private Long departmentId;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java
new file mode 100644
index 0000000..8102e2d
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java
@@ -0,0 +1,43 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+import org.dromara.autotable.annotation.enums.IndexTypeEnum;
+
+@Getter
+@Setter
+@Table(value = "base_sys_role", comment = "系统角色表")
+public class BaseSysRoleEntity extends BaseEntity {
+
+ @Index
+ @ColumnDefine(comment = "用户ID", notNull = true, type = "bigint")
+ private Long userId;
+
+ @ColumnDefine(comment = "名称", notNull = true)
+ private String name;
+
+ @Index(type = IndexTypeEnum.UNIQUE)
+ @ColumnDefine(comment = "角色标签", notNull = true)
+ private String label;
+
+ @ColumnDefine(comment = "备注")
+ private String remark;
+
+ @ColumnDefine(comment = "数据权限是否关联上下级", defaultValue = "1")
+ private Integer relevance;
+
+ @ColumnDefine(comment = "菜单权限", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private List menuIdList;
+
+ @ColumnDefine(comment = "部门权限", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private List departmentIdList;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleMenuEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleMenuEntity.java
new file mode 100644
index 0000000..0886904
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleMenuEntity.java
@@ -0,0 +1,19 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Table(value = "base_sys_role_menu", comment = "系统角色菜单表")
+public class BaseSysRoleMenuEntity extends BaseEntity {
+ @ColumnDefine(comment = "菜单", type = "bigint")
+ private Long menuId;
+
+ @ColumnDefine(comment = "角色ID", type = "bigint")
+ private Long roleId;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java
new file mode 100644
index 0000000..fa5b5c0
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java
@@ -0,0 +1,72 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.TenantEntity;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Ignore;
+import org.dromara.autotable.annotation.Index;
+import org.dromara.autotable.annotation.enums.IndexTypeEnum;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Table(value = "base_sys_user", comment = "系统用户表")
+public class BaseSysUserEntity extends TenantEntity {
+ @Index
+ @ColumnDefine(comment = "部门ID", type = "bigint")
+ private Long departmentId;
+
+ @ColumnDefine(comment = "姓名")
+ private String name;
+
+ @Index(type = IndexTypeEnum.UNIQUE)
+ @ColumnDefine(comment = "用户名", length = 100, notNull = true)
+ private String username;
+
+ @ColumnDefine(comment = "密码", notNull = true)
+ private String password;
+
+ @ColumnDefine(comment = "密码版本", defaultValue = "1")
+ private Integer passwordV;
+
+ @ColumnDefine(comment = "昵称", notNull = true)
+ private String nickName;
+
+ @ColumnDefine(comment = "头像")
+ private String headImg;
+
+ @ColumnDefine(comment = "手机号")
+ private String phone;
+
+ @ColumnDefine(comment = "邮箱")
+ private String email;
+
+ @ColumnDefine(comment = "备注")
+ private String remark;
+
+ @ColumnDefine(comment = "状态 0:禁用 1:启用", defaultValue = "1")
+ private Integer status;
+
+ // 部门名称
+ @Column(ignore = true)
+ private String departmentName;
+
+ // 角色名称
+ @Column(ignore = true)
+ private String roleName;
+
+ @ColumnDefine(comment = "socketId")
+ private String socketId;
+
+
+ @Ignore
+ @Schema( description = "角色列表" )
+ private List roleIdList;
+
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.java b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.java
new file mode 100644
index 0000000..c8ffc67
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.java
@@ -0,0 +1,21 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.autotable.annotation.Index;
+
+@Getter
+@Setter
+@Table(value = "base_sys_user_role", comment = "系统用户角色表")
+public class BaseSysUserRoleEntity extends BaseEntity {
+ @Index
+ @ColumnDefine(comment = "用户ID", type = "bigint")
+ private Long userId;
+
+ @Index
+ @ColumnDefine(comment = "角色ID", type = "bigint")
+ private Long roleId;
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/filter/BaseLogFilter.java b/cool-admin-java/src/main/java/com/cool/modules/base/filter/BaseLogFilter.java
new file mode 100644
index 0000000..9e10474
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/filter/BaseLogFilter.java
@@ -0,0 +1,33 @@
+package com.cool.modules.base.filter;
+
+import cn.hutool.json.JSONObject;
+import com.cool.modules.base.service.sys.BaseSysLogService;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Component
+@Order(10)
+@RequiredArgsConstructor
+public class BaseLogFilter implements Filter {
+
+ final private BaseSysLogService baseSysLogService;
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+ FilterChain filterChain)
+ throws IOException, ServletException {
+ // 记录日志
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ baseSysLogService.record(request, (JSONObject) request.getAttribute("requestParams"));
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysConfMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysConfMapper.java
new file mode 100644
index 0000000..298448c
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysConfMapper.java
@@ -0,0 +1,11 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.cool.modules.base.entity.sys.BaseSysConfEntity;
+import com.mybatisflex.core.BaseMapper;
+
+/**
+ * 系统配置
+ */
+public interface BaseSysConfMapper extends BaseMapper {
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysDepartmentMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysDepartmentMapper.java
new file mode 100644
index 0000000..cc3148e
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysDepartmentMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity;
+
+/**
+ * 系统部门
+ */
+public interface BaseSysDepartmentMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysLogMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysLogMapper.java
new file mode 100644
index 0000000..5bd6f09
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysLogMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysLogEntity;
+
+/**
+ * 系统日志
+ */
+public interface BaseSysLogMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysMenuMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysMenuMapper.java
new file mode 100644
index 0000000..ab39f68
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysMenuMapper.java
@@ -0,0 +1,20 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 系统菜单
+ */
+public interface BaseSysMenuMapper extends BaseMapper {
+ /**
+ * 根据角色ID获得所有菜单
+ *
+ * @param roleIds 角色ID集合
+ * @return SysMenuEntity
+ */
+ List getMenus(@Param("roleIds") Long[] roleIds);
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysParamMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysParamMapper.java
new file mode 100644
index 0000000..49ce1bd
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysParamMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysParamEntity;
+
+/**
+ * 系统参数配置
+ */
+public interface BaseSysParamMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleDepartmentMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleDepartmentMapper.java
new file mode 100644
index 0000000..9f3f027
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleDepartmentMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysRoleDepartmentEntity;
+
+/**
+ * 系统角色部门
+ */
+public interface BaseSysRoleDepartmentMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMapper.java
new file mode 100644
index 0000000..6264504
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysRoleEntity;
+
+/**
+ * 系统角色
+ */
+public interface BaseSysRoleMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMenuMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMenuMapper.java
new file mode 100644
index 0000000..4d488f0
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMenuMapper.java
@@ -0,0 +1,18 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysRoleMenuEntity;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 系统角色菜单
+ */
+public interface BaseSysRoleMenuMapper extends BaseMapper {
+ /**
+ * 跟菜单关联的所有用户
+ *
+ * @param menuId 菜单
+ * @return 所有用户ID
+ */
+ Long[] userIds(@Param("menuId") Long menuId);
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserMapper.java
new file mode 100644
index 0000000..37f4c90
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserMapper.java
@@ -0,0 +1,11 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+
+/**
+ * 系统用户
+ */
+public interface BaseSysUserMapper extends BaseMapper {
+
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserRoleMapper.java b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserRoleMapper.java
new file mode 100644
index 0000000..51b8105
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserRoleMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.mapper.sys;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.base.entity.sys.BaseSysUserRoleEntity;
+
+/**
+ * 系统用户角色
+ */
+public interface BaseSysUserRoleMapper extends BaseMapper {
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/security/JwtUserDetailsServiceImpl.java b/cool-admin-java/src/main/java/com/cool/modules/base/security/JwtUserDetailsServiceImpl.java
new file mode 100644
index 0000000..574dce7
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/security/JwtUserDetailsServiceImpl.java
@@ -0,0 +1,57 @@
+package com.cool.modules.base.security;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.security.jwt.JwtUser;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.cool.modules.base.service.sys.BaseSysUserService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+/**
+ * 获得用户信息
+ */
+@Component
+@RequiredArgsConstructor
+public class JwtUserDetailsServiceImpl implements UserDetailsService {
+
+ final private BaseSysUserService baseSysUserService;
+ final private BaseSysPermsService baseSysPermsService;
+ final private CoolCache coolCache;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ BaseSysUserEntity sysUserEntity = baseSysUserService.getMapper().selectOneByQuery(
+ QueryWrapper.create().eq(BaseSysUserEntity::getUsername, username)
+ .eq(BaseSysUserEntity::getStatus, 1));
+ if (ObjectUtil.isEmpty(sysUserEntity)) {
+ throw new UsernameNotFoundException("用户名不存在");
+ }
+ List authority = new ArrayList<>();
+ String[] perms = baseSysPermsService.getPerms(sysUserEntity.getId());
+ for (String perm : perms) {
+ authority.add(new SimpleGrantedAuthority(perm));
+ }
+ Long[] departmentIds = baseSysPermsService.getDepartmentIdsByRoleIds(sysUserEntity.getId());
+ JwtUser jwtUser = new JwtUser(sysUserEntity.getId(), sysUserEntity.getUsername(), sysUserEntity.getPassword(),
+ authority,
+ sysUserEntity.getStatus() == 1);
+ Long[] roleIds = baseSysPermsService.getRoles(sysUserEntity);
+ coolCache.set("admin:userDetails:" + jwtUser.getUsername(), jwtUser);
+ coolCache.set("admin:passwordVersion:" + sysUserEntity.getId(),
+ sysUserEntity.getPasswordV());
+ coolCache.set("admin:userInfo:" + sysUserEntity.getId(), sysUserEntity);
+ coolCache.set("admin:department:" + sysUserEntity.getId(), departmentIds);
+ coolCache.set("admin:roleIds:" + sysUserEntity.getId(), roleIds);
+ return jwtUser;
+ }
+}
diff --git a/cool-admin-java/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java b/cool-admin-java/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java
new file mode 100644
index 0000000..2f97be4
--- /dev/null
+++ b/cool-admin-java/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java
@@ -0,0 +1,81 @@
+package com.cool.modules.base.security;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.cool.core.enums.UserTypeEnum;
+import com.cool.core.util.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 权限资源管理器 为权限决断器提供支持
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ private Map> map = null;
+
+ /**
+ * 加载权限表中所有操作请求权限
+ */
+ public void loadResourceDefine() {
+ map = new HashMap<>();
+ Collection configAttributes;
+ ConfigAttribute cfg;
+ String[] perms = baseSysPermsService.getAllPerms();
+ // 获取启用的权限操作请求
+ for (String perm : perms) {
+ configAttributes = new ArrayList<>();
+ cfg = new SecurityConfig(perm);
+ // 作为MyAccessDecisionManager类的decide的第三个参数
+ configAttributes.add(cfg);
+ // 用权限的path作为map的key,用ConfigAttribute的集合作为value
+ map.put(perm.replaceAll(":", "/"), configAttributes);
+ }
+ }
+
+ /**
+ * 判定用户请求的url是否在权限表中 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限 如果不在权限表中则放行
+ *
+ * @param o
+ * @return
+ * @throws IllegalArgumentException
+ */
+ @Override
+ public Collection getAttributes(Object o) throws IllegalArgumentException {
+ UserTypeEnum userTypeEnum = CoolSecurityUtil.getCurrentUserType();
+ if (ObjectUtil.equal(userTypeEnum, UserTypeEnum.APP)) {
+ // app用户不需要权限拦截
+ return null;
+ }
+ if (map == null) {
+ loadResourceDefine();
+ }
+ // Object中包含用户请求request
+ String url = ((FilterInvocation) o).getRequestUrl();
+ return map.get(url.replace("/admin/", "").split("[?]")[0]);
+ }
+
+ @Override
+ public Collection