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("ids", service.addBatch(requestParams, array.toList(currentEntityClass()))));
+ } else {
+ return R.ok(Dict.create().set("id",
+ service.add(requestParams, JSONUtil.parseObj(body).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.info(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) {
+ return R.ok(service.info(requestParams, id));
+ }
+
+ /**
+ * 列表查询
+ *
+ * @param requestParams 请求参数
+ */
+ @Operation(summary = "查询", description = "查询多个信息")
+ @PostMapping("/list")
+ protected R list(@RequestAttribute() JSONObject requestParams,
+ @RequestAttribute(COOL_LIST_OP) CrudOption option) {
+ return R.ok(service.list(requestParams, option.getQueryWrapper(entityClass)));
+ }
+
+ /**
+ * 分页查询
+ *
+ * @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);
+ return R.ok(
+ pageResult((Page) service.page(requestParams, new Page<>(page, size),
+ option.getQueryWrapper(entityClass))));
+ }
+
+ /**
+ * 分页结果
+ *
+ * @param page 分页返回数据
+ */
+ protected Map pageResult(Page page) {
+ Map result = new HashMap<>();
+ Map pagination = new HashMap<>();
+ pagination.put("size", page.getPageSize());
+ pagination.put("page", page.getPageNumber());
+ pagination.put("total", page.getTotalRow());
+ result.put("list", page.getRecords());
+ result.put("pagination", pagination);
+ return result;
+ }
+
+ public Class currentEntityClass() {
+ // 使用 获取泛型参数类型
+ Type type = TypeUtil.getTypeArgument(this.getClass(), 1); // 获取第二个泛型参数
+ if (type instanceof Class>) {
+ return (Class) type;
+ }
+ throw new IllegalStateException("Unable to determine entity class type");
+ }
+
+ public Class currentServiceClass() {
+ // 使用 获取泛型参数类型
+ Type type = TypeUtil.getTypeArgument(this.getClass(), 0); // 获取第一个泛型参数
+ if (type instanceof Class>) {
+ return (Class) type;
+ }
+ 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/base/BaseEntity.java b/src/main/java/com/cool/core/base/BaseEntity.java
new file mode 100644
index 0000000..978b74e
--- /dev/null
+++ b/src/main/java/com/cool/core/base/BaseEntity.java
@@ -0,0 +1,36 @@
+package com.cool.core.base;
+
+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.autotable.annotation.Ignore;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 基础实体类
+ */
+@Getter
+@Setter
+public abstract class BaseEntity> extends Model implements Serializable {
+
+ @Id(keyType = KeyType.Auto, comment = "ID")
+ private Long id;
+
+ @Column(onInsertValue = "now()")
+ @ColumnDefine(comment = "创建时间")
+ private Date createTime;
+
+ @Column(onInsertValue = "now()", onUpdateValue = "now()")
+ @ColumnDefine(comment = "更新时间")
+ private Date updateTime;
+
+ @Ignore
+ @Column(ignore = true)
+ private QueryWrapper queryWrapper;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/base/BaseService.java b/src/main/java/com/cool/core/base/BaseService.java
new file mode 100644
index 0000000..3f3c882
--- /dev/null
+++ b/src/main/java/com/cool/core/base/BaseService.java
@@ -0,0 +1,121 @@
+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 page 分页信息
+ * @param queryWrapper 查询条件
+ * @return 分页信息
+ */
+ Object page(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);
+}
diff --git a/src/main/java/com/cool/core/base/BaseServiceImpl.java b/src/main/java/com/cool/core/base/BaseServiceImpl.java
new file mode 100644
index 0000000..8ab1d69
--- /dev/null
+++ b/src/main/java/com/cool/core/base/BaseServiceImpl.java
@@ -0,0 +1,101 @@
+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.insert(entity);
+ return entity.getId();
+ }
+
+ @Override
+ public Object add(JSONObject requestParams, T entity) {
+ this.add(entity);
+ this.modifyAfter(requestParams, entity, ModifyEnum.ADD);
+ return entity.getId();
+ }
+
+ @Override
+ public Object addBatch(JSONObject requestParams, List entitys) {
+ 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) {
+ 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) {
+ 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 Object page(JSONObject requestParams, Page page, QueryWrapper queryWrapper) {
+ return this.page(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);
+ }
+}
diff --git a/src/main/java/com/cool/core/base/ModifyEnum.java b/src/main/java/com/cool/core/base/ModifyEnum.java
new file mode 100644
index 0000000..22b0d85
--- /dev/null
+++ b/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/src/main/java/com/cool/core/base/controller/CommonController.java b/src/main/java/com/cool/core/base/controller/CommonController.java
new file mode 100644
index 0000000..b2b74a4
--- /dev/null
+++ b/src/main/java/com/cool/core/base/controller/CommonController.java
@@ -0,0 +1,53 @@
+package com.cool.core.base.controller;
+
+import cn.hutool.core.util.ObjUtil;
+import com.cool.core.plugin.service.CoolPluginService;
+import com.cool.core.util.ConvertUtil;
+import com.cool.core.util.CoolPluginInvokers;
+import java.io.File;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+@Controller
+@RequiredArgsConstructor
+public class CommonController {
+
+ final private CoolPluginService coolPluginService;
+
+ @RequestMapping("/")
+ public String welcome() {
+ return "welcome";
+ }
+
+ @PostMapping("/testPlugin/invokeMethod")
+ @ResponseBody
+ public String invokeMethod(@RequestParam String key, @RequestParam String methodName) {
+ Object result = null;
+ if (ObjUtil.isEmpty(methodName)) {
+ result = CoolPluginInvokers.invokePlugin(key);
+ } else {
+ result = CoolPluginInvokers.invokePlugin(key, methodName);
+ }
+ System.out.println(result);
+ return "invokeMethod Result: " + result;
+ }
+
+ /**
+ * 指定目录加载插件
+ */
+ @PostMapping("/testPlugin/reload")
+ @ResponseBody
+ public String reload() {
+ // 替换掉自己插件编译的路径,无需在页面上上传
+ File file = new File(
+ "/Users/mac/work/cool_new/cool-admin-java-plugin/target/my_cool_plugin.cool");
+ MultipartFile multipartFile = ConvertUtil.convertToMultipartFile(file);
+ coolPluginService.install(multipartFile, true);
+ return "reload Success";
+ }
+}
diff --git a/src/main/java/com/cool/core/base/service/MapperProviderService.java b/src/main/java/com/cool/core/base/service/MapperProviderService.java
new file mode 100644
index 0000000..8492857
--- /dev/null
+++ b/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/src/main/java/com/cool/core/cache/CoolCache.java b/src/main/java/com/cool/core/cache/CoolCache.java
new file mode 100644
index 0000000..fc827a9
--- /dev/null
+++ b/src/main/java/com/cool/core/cache/CoolCache.java
@@ -0,0 +1,159 @@
+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.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();
+ }
+ }
+
+ /**
+ * 删除缓存
+ *
+ * @param keys 一个或多个key
+ */
+ public void del(String... keys) {
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase(
+ CacheType.JCACHE.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;
+ }
+
+ private Object getIfNullValue(String key) {
+ if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase(
+ CacheType.JCACHE.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()) || type.equalsIgnoreCase(
+ CacheType.JCACHE.name())) {
+ // 放入缓存
+ cache.put(key, value);
+ }
+ if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
+ redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value),
+ java.time.Duration.ofSeconds(ttl));
+ }
+ }
+
+}
diff --git a/src/main/java/com/cool/core/code/CodeGenerator.java b/src/main/java/com/cool/core/code/CodeGenerator.java
new file mode 100644
index 0000000..37a8400
--- /dev/null
+++ b/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/src/main/java/com/cool/core/code/CodeModel.java b/src/main/java/com/cool/core/code/CodeModel.java
new file mode 100644
index 0000000..a2566fd
--- /dev/null
+++ b/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/src/main/java/com/cool/core/code/CodeTypeEnum.java b/src/main/java/com/cool/core/code/CodeTypeEnum.java
new file mode 100644
index 0000000..873ef49
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/CoolProperties.java b/src/main/java/com/cool/core/config/CoolProperties.java
new file mode 100644
index 0000000..88e3fe9
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/CustomOpenApiResource.java b/src/main/java/com/cool/core/config/CustomOpenApiResource.java
new file mode 100644
index 0000000..4cce955
--- /dev/null
+++ b/src/main/java/com/cool/core/config/CustomOpenApiResource.java
@@ -0,0 +1,36 @@
+package com.cool.core.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import jakarta.servlet.http.HttpServletRequest;
+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.stereotype.Component;
+
+import java.util.Locale;
+
+/**
+ * 自定义 OpenApiResource
+ */
+@Component
+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/src/main/java/com/cool/core/config/FileModeEnum.java b/src/main/java/com/cool/core/config/FileModeEnum.java
new file mode 100644
index 0000000..243520b
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/FileProperties.java b/src/main/java/com/cool/core/config/FileProperties.java
new file mode 100644
index 0000000..4af01c0
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/LocalFileProperties.java b/src/main/java/com/cool/core/config/LocalFileProperties.java
new file mode 100644
index 0000000..b901322
--- /dev/null
+++ b/src/main/java/com/cool/core/config/LocalFileProperties.java
@@ -0,0 +1,23 @@
+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.local")
+public class LocalFileProperties {
+
+ // 跟域名
+ private String baseUrl;
+
+ private String uploadPath = "assets/public/upload";
+
+ public String getAbsoluteUploadFolder() {
+ return System.getProperty("user.dir") + "/" + uploadPath;
+ }
+}
diff --git a/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java b/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java
new file mode 100644
index 0000000..762c4d2
--- /dev/null
+++ b/src/main/java/com/cool/core/config/MyBatisFlexConfiguration.java
@@ -0,0 +1,15 @@
+package com.cool.core.config;
+
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.spring.boot.MyBatisFlexCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisFlexConfiguration implements MyBatisFlexCustomizer {
+
+ @Override
+ public void customize(FlexGlobalConfig globalConfig) {
+ // 我们可以在这里进行一些列的初始化配置
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/config/OssFileProperties.java b/src/main/java/com/cool/core/config/OssFileProperties.java
new file mode 100644
index 0000000..1f02b3d
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/PluginJson.java b/src/main/java/com/cool/core/config/PluginJson.java
new file mode 100644
index 0000000..ad39263
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/SwaggerConfig.java b/src/main/java/com/cool/core/config/SwaggerConfig.java
new file mode 100644
index 0000000..8772d16
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/TokenProperties.java b/src/main/java/com/cool/core/config/TokenProperties.java
new file mode 100644
index 0000000..12cd713
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/cache/CaffeineConfig.java b/src/main/java/com/cool/core/config/cache/CaffeineConfig.java
new file mode 100644
index 0000000..d22204c
--- /dev/null
+++ b/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/src/main/java/com/cool/core/config/cache/RedisConfig.java b/src/main/java/com/cool/core/config/cache/RedisConfig.java
new file mode 100644
index 0000000..b892683
--- /dev/null
+++ b/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/src/main/java/com/cool/core/eps/CoolEps.java b/src/main/java/com/cool/core/eps/CoolEps.java
new file mode 100644
index 0000000..a3bdaf2
--- /dev/null
+++ b/src/main/java/com/cool/core/eps/CoolEps.java
@@ -0,0 +1,331 @@
+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.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+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.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+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 JSONObject swaggerInfo;
+
+ @Value("${springdoc.api-docs.enabled}")
+ private boolean apiDocsEnabled;
+
+ public Dict admin;
+
+ public Dict app;
+
+ final private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+ final private CustomOpenApiResource customOpenApiResource;
+
+ @Async
+ public void init() {
+ if (!apiDocsEnabled) {
+ return;
+ }
+ entityInfo = Dict.create();
+ swaggerInfo = swaggerInfo();
+ Runnable task = () -> {
+ entity();
+ urls();
+ log.info("初始化eps完成,服务启动成功,端口:{}", serverPort);
+ };
+ // ThreadUtil.safeSleep(3000);
+ ThreadUtil.execute(task);
+ }
+
+ /**
+ * 构建所有的url
+ */
+ private void urls() {
+ Dict admin = Dict.create();
+ Dict app = Dict.create();
+ Map map = requestMappingHandlerMapping.getHandlerMethods();
+ for (Map.Entry methodEntry : map.entrySet()) {
+ RequestMappingInfo info = methodEntry.getKey();
+ HandlerMethod method = methodEntry.getValue();
+ String module = getModule(method);
+ if (StrUtil.isNotEmpty(module)) {
+ String entityName = getEntity(method.getBeanType());
+ String methodPath = getMethodUrl(method);
+ String prefix = Objects.requireNonNull(getUrl(info))
+ .replaceFirst("(?s)(.*)" + methodPath, "$1");
+ Dict result = Dict.create();
+ int type = 0;
+ if (prefix.startsWith("/admin")) {
+ result = admin;
+ }
+ if (prefix.startsWith("/app")) {
+ result = app;
+ type = 1;
+ }
+ 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")));
+ } else {
+ item = Dict.create();
+ item.set("controller", method.getBeanType().getSimpleName());
+ item.set("module", module);
+ item.set("name", entityName);
+ item.set("api", new ArrayList());
+ item.set("prefix", prefix);
+ item.set("columns", entityInfo.get(entityName));
+ item.set("api", apis(prefix, methodPath, item.getBean("api")));
+ urls.add(item);
+ }
+ if (type == 0) {
+ admin.set(module, urls);
+ }
+ if (type == 1) {
+ app.set(module, urls);
+ }
+
+ }
+ }
+ this.admin = admin;
+ this.app = app;
+
+ }
+
+ /**
+ * 设置所有的api
+ *
+ * @param prefix 路由前缀
+ * @param methodPath 方法路由
+ * @param list api列表
+ * @return api列表
+ */
+ private List apis(String prefix, String methodPath, List list) {
+ Dict item = Dict.create();
+ item.set("method", "");
+ item.set("path", methodPath);
+ item.set("summary", "");
+ item.set("tag", "");
+ item.set("dts", new Object());
+ 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("dts", methodInfo);
+ item.set("method", method);
+ item.set("summary", methodInfo.getStr("summary"));
+ item.set("description", methodInfo.get("description"));
+ }
+
+ /**
+ * 获得方法的url地址
+ *
+ * @param handlerMethod 方法
+ * @return 方法url地址
+ */
+ private String getMethodUrl(HandlerMethod handlerMethod) {
+ String url = null;
+ 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")) {
+ 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 = customOpenApiResource.getOpenApiJson();
+ return JSONUtil.parseObj(new String(bytes));
+ } catch (Exception e) {
+ return new JSONObject();
+ }
+ }
+
+ /**
+ * 获得Controller上的实体类型
+ *
+ * @param controller Controller类
+ * @return 实体名称
+ */
+ private String getEntity(Class> controller) {
+ try {
+ Type type = ((ParameterizedType) SpringUtil.getBean(controller).getClass()
+ .getGenericSuperclass())
+ .getActualTypeArguments()[1];
+ String[] names = type.getTypeName().split("[.]");
+ return names[names.length - 1];
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ private void entity() {
+ // 扫描所有的实体类
+ Set> classes = ClassUtil.scanPackageByAnnotation("", Table.class);
+ classes.forEach(e -> {
+ // 获得属性
+ Field[] fields = ClassUtil.getDeclaredFields(e);
+ List columns = columns(fields);
+ entityInfo.set(e.getSimpleName(), columns);
+ });
+ }
+
+ /**
+ * 获得所有的列
+ *
+ * @param fields 字段名
+ * @return 所有的列
+ */
+ private List columns(Field[] fields) {
+ List dictList = new ArrayList<>();
+ for (Field field : fields) {
+ Dict dict = Dict.create();
+ 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());
+ 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/src/main/java/com/cool/core/eps/EpsEvent.java b/src/main/java/com/cool/core/eps/EpsEvent.java
new file mode 100644
index 0000000..1e40b01
--- /dev/null
+++ b/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/src/main/java/com/cool/core/exception/CoolException.java b/src/main/java/com/cool/core/exception/CoolException.java
new file mode 100644
index 0000000..90498a5
--- /dev/null
+++ b/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/src/main/java/com/cool/core/exception/CoolExceptionHandler.java b/src/main/java/com/cool/core/exception/CoolExceptionHandler.java
new file mode 100644
index 0000000..39cd40e
--- /dev/null
+++ b/src/main/java/com/cool/core/exception/CoolExceptionHandler.java
@@ -0,0 +1,61 @@
+package com.cool.core.exception;
+
+import cn.hutool.core.util.ObjUtil;
+import com.cool.core.request.R;
+import lombok.extern.slf4j.Slf4j;
+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.put("data", e.getData());
+ } else {
+ r.put("code", e.getCode());
+ r.put("message", e.getMessage());
+ }
+ 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();
+ }
+}
diff --git a/src/main/java/com/cool/core/exception/CoolPreconditions.java b/src/main/java/com/cool/core/exception/CoolPreconditions.java
new file mode 100644
index 0000000..ebb6ade
--- /dev/null
+++ b/src/main/java/com/cool/core/exception/CoolPreconditions.java
@@ -0,0 +1,81 @@
+package com.cool.core.exception;
+
+import cn.hutool.core.util.ObjectUtil;
+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 new CoolException(formatMessage(message, arguments), code);
+ }
+ }
+
+ public static void check(boolean flag, String message, Object... arguments) {
+ if (flag) {
+ throw new CoolException(formatMessage(message, arguments));
+ }
+ }
+
+ public static void alwaysThrow(String message, Object... arguments) {
+ throw new CoolException(formatMessage(message, arguments));
+ }
+
+ /**
+ * 返回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));
+ }
+
+ private static String formatMessage(String messagePattern, Object... arguments) {
+ 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/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java b/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java
new file mode 100644
index 0000000..34a9063
--- /dev/null
+++ b/src/main/java/com/cool/core/file/FileUploadStrategyFactory.java
@@ -0,0 +1,57 @@
+package com.cool.core.file;
+
+import static com.cool.core.plugin.consts.PluginConsts.uploadHook;
+
+import cn.hutool.core.lang.Dict;
+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;
+
+ public FileUploadStrategy getStrategy() {
+ PluginInfoEntity pluginInfoEntity = coolPluginService.getPluginInfoEntityByHook(uploadHook);
+ return getStrategy(pluginInfoEntity);
+ }
+
+ private FileUploadStrategy getStrategy(PluginInfoEntity pluginInfoEntity) {
+ if (ObjUtil.isEmpty(pluginInfoEntity)) {
+ return applicationContext.getBean("localFileUploadStrategy", FileUploadStrategy.class);
+ }
+ return applicationContext.getBean("ossFileUploadStrategy", 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() {
+ FileUploadStrategy strategy = getStrategy();
+ UpLoadModeType upLoadModeType = strategy.getMode();
+ return Dict.create().set("mode", upLoadModeType.getMode().value())
+ .set("type", upLoadModeType.getType());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/file/UpLoadModeType.java b/src/main/java/com/cool/core/file/UpLoadModeType.java
new file mode 100644
index 0000000..eb99140
--- /dev/null
+++ b/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/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java b/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java
new file mode 100644
index 0000000..fa72664
--- /dev/null
+++ b/src/main/java/com/cool/core/file/strategy/FileUploadStrategy.java
@@ -0,0 +1,39 @@
+package com.cool.core.file.strategy;
+
+import com.cool.core.file.UpLoadModeType;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public interface FileUploadStrategy {
+
+ /**
+ * 文件上传
+ */
+ Object upload(MultipartFile[] files, HttpServletRequest request, PluginInfoEntity pluginInfoEntity)
+ throws IOException;
+
+ /**
+ * 文件上传模式
+ *
+ * @return 上传模式
+ */
+ UpLoadModeType getMode();
+
+ 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/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java b/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java
new file mode 100644
index 0000000..904d0be
--- /dev/null
+++ b/src/main/java/com/cool/core/file/strategy/LocalFileUploadStrategy.java
@@ -0,0 +1,78 @@
+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.core.file.UpLoadModeType;
+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.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 filePath = System.getProperty("user.dir");
+ String date = DateUtil.format(new Date(),
+ DatePattern.PURE_DATE_PATTERN);
+ String relativePath =
+ "/" + localFileProperties.getUploadPath() + "/" + date;
+
+ FileUtil.mkdir(filePath + relativePath);
+ for (MultipartFile file : files) {
+ // 保存文件
+ String fileName = StrUtil.uuid().replaceAll("-", "") + getExtensionName(
+ Objects.requireNonNull(file.getOriginalFilename()));
+ file.transferTo(new File(filePath + "/" + relativePath
+ + "/" + 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 UpLoadModeType getMode() {
+ return new UpLoadModeType(FileModeEnum.LOCAL);
+ }
+}
diff --git a/src/main/java/com/cool/core/file/strategy/OssFileUploadStrategy.java b/src/main/java/com/cool/core/file/strategy/OssFileUploadStrategy.java
new file mode 100644
index 0000000..a9e4384
--- /dev/null
+++ b/src/main/java/com/cool/core/file/strategy/OssFileUploadStrategy.java
@@ -0,0 +1,26 @@
+package com.cool.core.file.strategy;
+
+import com.cool.core.config.FileModeEnum;
+import com.cool.core.file.UpLoadModeType;
+import com.cool.core.util.CoolPluginInvokers;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+@Component("ossFileUploadStrategy")
+public class OssFileUploadStrategy implements FileUploadStrategy {
+
+ @Override
+ public Object upload(MultipartFile[] files, HttpServletRequest request, PluginInfoEntity pluginInfoEntity)
+ throws IOException {
+ return CoolPluginInvokers.invokePlugin(pluginInfoEntity.getKey());
+ }
+
+ @Override
+ public UpLoadModeType getMode() {
+ return new UpLoadModeType(FileModeEnum.CLOUD);
+ }
+}
diff --git a/src/main/java/com/cool/core/init/CoolPluginInit.java b/src/main/java/com/cool/core/init/CoolPluginInit.java
new file mode 100644
index 0000000..36ca4ff
--- /dev/null
+++ b/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.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * 历史安装过的插件执行初始化
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CoolPluginInit implements ApplicationRunner {
+
+ final private CoolPluginService coolPluginService;
+
+ @Override
+ public void run(ApplicationArguments args) {
+ coolPluginService.init();
+ }
+}
diff --git a/src/main/java/com/cool/core/init/DBFromJsonInit.java b/src/main/java/com/cool/core/init/DBFromJsonInit.java
new file mode 100644
index 0000000..68c0e5c
--- /dev/null
+++ b/src/main/java/com/cool/core/init/DBFromJsonInit.java
@@ -0,0 +1,218 @@
+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.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 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.ArrayList;
+import java.util.List;
+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.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.stereotype.Component;
+import org.springframework.context.ApplicationEventPublisher;
+
+/**
+ * 数据库初始数据初始化 在 classpath:cool/data/db 目录下创建.json文件 并定义表数据, 由该类统一执行初始化
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DBFromJsonInit implements ApplicationRunner {
+
+ final private BaseSysConfService baseSysConfService;
+
+ final private BaseSysMenuService baseSysMenuService;
+
+ final private MapperProviderService mapperProviderService;
+
+ final private ApplicationEventPublisher eventPublisher;
+
+ @Value("${cool.initData}")
+ private boolean initData;
+
+ @Override
+ public void run(ApplicationArguments args) {
+ if (!initData) {
+ return;
+ }
+ // 初始化自定义的数据
+ extractedDb();
+ // 初始化菜单数据
+ extractedMenu();
+ // 发送数据库初始化完成事件
+ eventPublisher.publishEvent(new DbInitCompleteEvent(this));
+ log.info("数据初始化完成!");
+ }
+
+ @Getter
+ public static class DbInitCompleteEvent {
+ private final Object source;
+
+ public DbInitCompleteEvent(Object source) {
+ this.source = source;
+ }
+
+ }
+
+ /**
+ * 解析插入业务数据
+ */
+ private void extractedDb() {
+ try {
+ // 加载 JSON 文件
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource[] resources = resolver.getResources("classpath:cool/data/db/*.json");
+ // 遍历所有.json文件
+ analysisResources(resources);
+ } catch (Exception e) {
+ log.error("Failed to initialize data", e);
+ }
+ }
+
+ private void analysisResources(Resource[] resources)
+ throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ String prefix = "db_";
+ 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);
+ log.info("{} 业务数据初始化成功...", fileName);
+ }
+ }
+
+ 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 {
+ // 插入数据
+ List list = new ArrayList<>();
+ 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;
+ }
+ list.add(entity);
+ }
+ baseMapper.insertBatch(list);
+ }
+
+ /**
+ * 解析插入菜单数据
+ */
+ public void extractedMenu() {
+ 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);
+ }
+ } catch (Exception e) {
+ log.error("Failed to initialize data", e);
+ }
+ }
+
+ 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());
+ }
+ baseSysMenuService.add(menuEntity);
+ // 递归处理子菜单
+ JSONArray childMenus = jsonObj.getJSONArray("childMenus");
+ if (childMenus != null) {
+ for (Object obj : childMenus) {
+ JSONObject childObj = (JSONObject) obj;
+ parseMenu(childObj, menuEntity);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java b/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java
new file mode 100644
index 0000000..08a6797
--- /dev/null
+++ b/src/main/java/com/cool/core/plugin/config/DynamicJarClassLoader.java
@@ -0,0 +1,55 @@
+package com.cool.core.plugin.config;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * * 自定义类加载器
+ */
+@Slf4j
+public class DynamicJarClassLoader extends URLClassLoader {
+
+ private final Map> loadedClasses = new ConcurrentHashMap<>();
+
+ public DynamicJarClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ }
+
+ @Override
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ // 从已加载的类集合中获取指定名称的类
+ Class> clazz = loadedClasses.get(name);
+ if (clazz != null) {
+ return clazz;
+ }
+ clazz = super.findClass(name);
+ // 将加载的类添加到已加载的类集合中
+ loadedClasses.put(name, clazz);
+ return clazz;
+ }
+
+ public void unload() {
+ try {
+ for (Map.Entry> entry : loadedClasses.entrySet()) {
+ // 从已加载的类集合中移除该类
+ String className = entry.getKey();
+ loadedClasses.remove(className);
+ try {
+ // 调用该类的destory方法,回收资源
+ Class> clazz = entry.getValue();
+ Method destory = clazz.getDeclaredMethod("destory");
+ destory.invoke(clazz);
+ } catch (NoClassDefFoundError | Exception ignored) {
+ }
+ }
+ // 从其父类加载器的加载器层次结构中移除该类加载器
+ close();
+ } catch (Exception e) {
+ log.error("unload error", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/plugin/consts/PluginConsts.java b/src/main/java/com/cool/core/plugin/consts/PluginConsts.java
new file mode 100644
index 0000000..d0d1ddb
--- /dev/null
+++ b/src/main/java/com/cool/core/plugin/consts/PluginConsts.java
@@ -0,0 +1,27 @@
+package com.cool.core.plugin.consts;
+
+/**
+ * 常量工具
+ */
+public interface PluginConsts {
+
+ /**
+ * 上传文件hook
+ */
+ String uploadHook = "upload";
+
+ /**
+ * 插件调用入口方法
+ */
+ String invokePluginMethodName = "invokePlugin";
+
+ /**
+ * 设置插件信息
+ */
+ String setPluginJson = "setPluginJson";
+
+ /**
+ * 设置 ApplicationContext,使得插件能够调用主应用spring容器中的bean
+ */
+ String setApplicationContext = "setApplicationContext";
+}
diff --git a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java
new file mode 100644
index 0000000..4155a7c
--- /dev/null
+++ b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java
@@ -0,0 +1,273 @@
+package com.cool.core.plugin.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import cn.hutool.core.codec.Base64Encoder;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import com.cool.core.config.PluginJson;
+import com.cool.core.exception.CoolException;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.plugin.config.DynamicJarClassLoader;
+import com.cool.core.util.CoolPluginInvokers;
+import com.cool.core.util.MapExtUtil;
+import com.cool.core.util.PathUtils;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import com.cool.modules.plugin.service.PluginInfoService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 插件服务类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CoolPluginService {
+
+ final private DynamicJarLoaderService dynamicJarLoaderService;
+
+ final private PluginInfoService pluginInfoService;
+
+ @Value("${cool.pluginPath}")
+ private String pluginPath;
+
+ public void init() {
+ List list = pluginInfoService
+ .list(QueryWrapper
+ .create().select(PluginInfoEntity::getId, PluginInfoEntity::getPluginJson,
+ PluginInfoEntity::getKey, PluginInfoEntity::getName)
+ .eq(PluginInfoEntity::getStatus, 1));
+ if (ObjUtil.isEmpty(list)) {
+ log.info("没有可初始化的插件");
+ return;
+ }
+ list.forEach(this::initInstall);
+ }
+
+ /**
+ * 系统启动初始化安装插件
+ */
+ private void initInstall(PluginInfoEntity entity) {
+ PluginJson pluginJson = entity.getPluginJson();
+ File file = new File(pluginJson.getJarPath());
+ // 检查路径是否存在
+ if (!file.exists()) {
+ PluginInfoEntity pluginInfoEntity = pluginInfoService.getById(entity.getId());
+ FileUtil.writeBytes(pluginInfoEntity.getJarFile(), file);
+ }
+ file = new File(pluginJson.getJarPath());
+ if (file.exists()) {
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ dynamicJarLoaderService.install(pluginJson.getJarPath(), true);
+ // 设置配置
+ CoolPluginInvokers.setPluginJson(entity.getKey(), entity);
+ } catch (Exception e) {
+ log.error("初始化{}插件失败", entity.getName(), e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ }
+ }
+
+ /**
+ * 安装jar
+ */
+ public void install(MultipartFile file, boolean force) {
+ String fileName;
+ File jarFile = null;
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ String pathStr = pluginPath;
+ if (!PathUtils.isAbsolutePath(pluginPath)) {
+ // 相对路径
+ pathStr = System.getProperty("user.dir") + "/" + pluginPath;
+ }
+ // 将路径字符串转换为 Path 对象
+ Path path = Paths.get(pathStr);
+ // 检查路径是否存在
+ if (!Files.exists(path)) {
+ // 如果路径不存在,则创建目录(包括父目录)
+ Files.createDirectories(path);
+ }
+ fileName =
+ path + "/" + System.currentTimeMillis() + "_" + file.getOriginalFilename() + ".jar";
+ jarFile = new File(fileName);
+ file.transferTo(jarFile);
+ // 加载jar
+ PluginJson pluginJson = dynamicJarLoaderService.install(fileName, force);
+ // 保存插件信息入库
+ savePluginInfo(pluginJson, fileName, jarFile, force);
+ // 把 ApplicationContext 对象传递打插件类中,使其在插件中也能正常使用spring bean对象
+ CoolPluginInvokers.setApplicationContext(pluginJson.getKey());
+ } catch (CoolException e) {
+ FileUtil.del(jarFile);
+ throw e;
+ } catch (Exception e) {
+ log.error("插件安装失败", e);
+ CoolPreconditions.alwaysThrow("插件安装失败", e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ }
+
+ /**
+ * 卸载
+ */
+ public void uninstall(Long id) {
+ PluginInfoEntity pluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile(id);
+ CoolPreconditions.checkEmpty(pluginInfoEntity, "插件不存在");
+ if (dynamicJarLoaderService.uninstall(pluginInfoEntity.getKey())) {
+ boolean flag = pluginInfoEntity.removeById();
+ if (flag) {
+ FileUtil.del(pluginInfoEntity.getPluginJson().getJarPath());
+ }
+ }
+ }
+
+ /**
+ * 保存插件信息
+ */
+ private void savePluginInfo(PluginJson pluginJson, String fileName, File jarFile,
+ boolean force) {
+ CoolPreconditions.checkEmpty(pluginJson, "插件安装失败");
+ pluginJson.setJarPath(fileName);
+ PluginInfoEntity pluginInfo = new PluginInfoEntity();
+ BeanUtil.copyProperties(pluginJson, pluginInfo);
+ if (ObjUtil.isNotEmpty(pluginJson.getLogo())) {
+ DynamicJarClassLoader dynamicJarClassLoader = dynamicJarLoaderService
+ .getDynamicJarClassLoader(pluginJson.getKey());
+ InputStream inputStream = dynamicJarClassLoader.getResourceAsStream(
+ pluginJson.getLogo());
+ if (ObjUtil.isNotEmpty(inputStream)) {
+ pluginInfo.setLogo(Base64Encoder.encode(IoUtil.readBytes(inputStream)));
+ }
+ }
+ if (ObjUtil.isNotEmpty(pluginJson.getReadme())) {
+ DynamicJarClassLoader dynamicJarClassLoader = dynamicJarLoaderService
+ .getDynamicJarClassLoader(pluginJson.getKey());
+ InputStream inputStream = dynamicJarClassLoader.getResourceAsStream(
+ pluginJson.getReadme());
+ if (ObjUtil.isNotEmpty(inputStream)) {
+ pluginInfo.setReadme(StrUtil.str(IoUtil.readBytes(inputStream), "UTF-8"));
+ }
+ }
+ pluginInfo.setKey(pluginJson.getKey());
+ pluginInfo.setPluginJson(pluginJson);
+ // 转二进制
+ pluginInfo.setJarFile(FileUtil.readBytes(jarFile));
+
+ if (force) {
+ CopyOptions options = CopyOptions.create().setIgnoreNullValue(true);
+ if (ObjUtil.isNotEmpty(pluginJson.getSameHookId())) {
+ // 存在同名,已强制安装,需将原插件关闭
+ PluginInfoEntity sameHookPlugin = new PluginInfoEntity();
+ sameHookPlugin.setStatus(0);
+ sameHookPlugin.setId(pluginJson.getSameHookId());
+ updatePlugin(sameHookPlugin);
+ }
+ // 通过key 找到id
+ PluginInfoEntity one = pluginInfoService.getByKeyNoJarFile(pluginJson.getKey());
+ if (ObjUtil.isNotEmpty(one)) {
+ // 重新加载配置不更新
+ pluginInfo.setConfig(one.getConfig());
+ pluginInfo.getPluginJson().setConfig(one.getConfig());
+ BeanUtil.copyProperties(pluginInfo, one, options);
+ one.updateById();
+ // 重新安装了调用设置插件历史配置信息
+ CoolPluginInvokers.setPluginJson(pluginJson.getKey(), one);
+ return;
+ }
+ }
+ pluginInfo.save();
+ }
+
+ public void updatePlugin(PluginInfoEntity entity) {
+ PluginInfoEntity dbPluginInfoEntity = pluginInfoService.getPluginInfoEntityByIdNoJarFile(
+ entity.getId());
+ boolean updateConfig = false;
+ if (!MapExtUtil.compareMaps(entity.getConfig(), dbPluginInfoEntity.getConfig())) {
+ // 不一致,说明更新了配置
+ entity.getPluginJson().setConfig(entity.getConfig());
+ updateConfig = true;
+ }
+ if (!ObjUtil.equals(entity.getStatus(), dbPluginInfoEntity.getStatus())) {
+ // 更新状态
+ updateConfig = updateStatus(entity, dbPluginInfoEntity, updateConfig);
+ }
+ if (updateConfig) {
+ // 更新配置
+ CoolPluginInvokers.setPluginJson(getKeyById(entity.getId()), entity);
+ }
+ pluginInfoService.update(entity);
+ }
+
+ /**
+ * 更新插件状态
+ */
+ private boolean updateStatus(PluginInfoEntity entity, PluginInfoEntity dbPluginInfoEntity,
+ boolean updateConfig) {
+ // 更新状态
+ Integer status = entity.getStatus();
+ if (ObjUtil.equals(status, 1)) {
+ if (ObjUtil.isNotEmpty(dbPluginInfoEntity.getHook())) {
+ // 查找是否有同名hook,有同名hook,如果状态为开启不允许在开启,需先关闭原来
+ PluginInfoEntity hookPlugin = pluginInfoService
+ .getPluginInfoEntityByHookNoJarFile(dbPluginInfoEntity.getHook());
+ if (ObjUtil.isNotEmpty(hookPlugin)) {
+ CoolPreconditions.check(
+ !ObjUtil.equals(hookPlugin.getKey(), dbPluginInfoEntity.getKey())
+ && ObjUtil.equals(hookPlugin.getStatus(), 1),
+ "插件已存在相同hook: {},请选关闭{}插件,在开启当前插件(同名hook,只能有一个插件开启)",
+ hookPlugin.getHook(),
+ hookPlugin.getName());
+ }
+ }
+ // 充关闭置为开启,触发重新加载jar
+ initInstall(dbPluginInfoEntity);
+ updateConfig = false;
+ } else if (ObjUtil.equals(status, 0)) {
+ // 插件关闭 卸载jar
+ dynamicJarLoaderService.uninstall(dbPluginInfoEntity.getKey());
+ }
+ return updateConfig;
+ }
+
+ /**
+ * 通过hook获取插件
+ */
+ public PluginInfoEntity getPluginInfoEntityByHook(String hook) {
+ return pluginInfoService.getPluginInfoEntityByHookNoJarFile(hook);
+ }
+
+ /**
+ * 通过id 获取key
+ */
+ private String getKeyById(Long id) {
+ PluginInfoEntity one = pluginInfoService.getPluginInfoEntityByIdNoJarFile(id);
+ if (ObjUtil.isNotEmpty(one)) {
+ return one.getKey();
+ }
+ return null;
+ }
+
+ /**
+ * 获取插件实例对象
+ */
+ public Object getInstance(String key) {
+ return dynamicJarLoaderService.getBeanInstance(key);
+ }
+}
diff --git a/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java b/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java
new file mode 100644
index 0000000..11087e7
--- /dev/null
+++ b/src/main/java/com/cool/core/plugin/service/DynamicJarLoaderService.java
@@ -0,0 +1,171 @@
+package com.cool.core.plugin.service;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.config.PluginJson;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.plugin.config.DynamicJarClassLoader;
+import com.cool.core.util.AnnotationUtils;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import com.cool.modules.plugin.service.PluginInfoService;
+import java.io.File;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 动态加载jar包
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DynamicJarLoaderService {
+
+ final private PluginInfoService pluginInfoService;
+
+ private final Map dynamicJarClassLoaderMap = new ConcurrentHashMap<>();
+
+ private final Map pluginMap = new ConcurrentHashMap<>();
+
+ public PluginJson install(String jarFilePath, boolean force) throws Exception {
+ URL jarUrl = new URL("jar:file:" + new File(jarFilePath).getAbsolutePath() + "!/");
+ DynamicJarClassLoader dynamicJarClassLoader = new DynamicJarClassLoader(new URL[]{jarUrl},
+ Thread.currentThread().getContextClassLoader());
+ Thread.currentThread().setContextClassLoader(dynamicJarClassLoader);
+ InputStream inputStream = dynamicJarClassLoader.getResourceAsStream("plugin.json");
+ CoolPreconditions.check(ObjUtil.isEmpty(inputStream), "不合规插件:未找到plugin.json文件");
+ String pluginJsonStr = StrUtil.str(IoUtil.readBytes(inputStream), "UTF-8");
+ PluginJson pluginJson = JSONUtil.toBean(pluginJsonStr, PluginJson.class);
+ CoolPreconditions.check(ObjUtil.isEmpty(pluginJson.getKey()), "该插件缺少唯一标识");
+ if (ObjUtil.isNotEmpty(pluginJson.getHook())) {
+ // 查找hook是否已经存在,提示是否要替换,原hook将关闭
+ PluginInfoEntity pluginInfoEntity = pluginInfoService
+ .getPluginInfoEntityByHookNoJarFile(pluginJson.getHook());
+ if (!force) {
+ CoolPreconditions.returnData(
+ ObjUtil.isNotEmpty(pluginInfoEntity)
+ && !ObjUtil.equals(pluginInfoEntity.getKey(), pluginJson.getKey()),
+ new CoolPreconditions.ReturnData(1,
+ "插件已存在相同hook: {},继续安装将关闭原来插件(同名hook,只能有一个状态开启)",
+ pluginJson.getHook()));
+
+ } else if (ObjUtil.isNotEmpty(pluginInfoEntity)
+ && !ObjUtil.equals(pluginInfoEntity.getKey(), pluginJson.getKey())) {
+ // 存在同名hook,需将原hook修改为关闭
+ pluginJson.setSameHookId(pluginInfoEntity.getId());
+ }
+ }
+
+ // 加载类
+ List> plugins = new ArrayList<>();
+ try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ if (jarEntry.getName().endsWith(".class")) {
+ String className = jarEntry.getName().replace('/', '.').substring(0,
+ jarEntry.getName().length() - 6);
+ try {
+ // 加载类
+ Class> clazz = dynamicJarClassLoader.loadClass(className);
+ if (AnnotationUtils.hasCoolPluginAnnotation(clazz)) {
+ plugins.add(clazz);
+ }
+ } catch (NoClassDefFoundError ignored) {
+ }
+ }
+ }
+ }
+
+ // 校验插件
+ checkPlugin(plugins);
+ registerPlugin(pluginJson.getKey(), plugins.get(0), dynamicJarClassLoader, force);
+ dynamicJarClassLoaderMap.put(pluginJson.getKey(), dynamicJarClassLoader);
+
+ log.info("插件{}初始化成功.", pluginJson.getKey());
+ return pluginJson;
+ }
+
+ /**
+ * 注册插件
+ */
+ private void registerPlugin(String key, Class> pluginClazz,
+ DynamicJarClassLoader dynamicJarClassLoader,
+ boolean force) {
+ if (!force && pluginMap.containsKey(key)) {
+ dynamicJarClassLoader.unload();
+ CoolPreconditions.returnData(
+ new CoolPreconditions.ReturnData(1, "插件已存在,继续安装将覆盖"));
+ }
+ if (ObjUtil.isNotEmpty(key)) {
+ pluginMap.remove(key);
+ pluginMap.put(key, ReflectUtil.newInstance(pluginClazz));
+ }
+ }
+
+ /**
+ * 卸载
+ */
+ public boolean uninstall(String key) {
+ DynamicJarClassLoader dynamicJarClassLoader = getDynamicJarClassLoader(key);
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(dynamicJarClassLoader);
+ pluginMap.remove(key);
+ if (dynamicJarClassLoader != null) {
+ dynamicJarClassLoader.unload();
+ }
+ } catch (Exception e) {
+ log.error("uninstall {}失败", key, e);
+ CoolPreconditions.alwaysThrow("卸载失败");
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ log.info("卸载插件{}", key);
+ return true;
+ }
+
+ /**
+ * 校验插件是否合法
+ */
+ private void checkPlugin(List> plugins) {
+ CoolPreconditions.checkEmpty(plugins, "未找到插件程序");
+ int size = plugins.size();
+ CoolPreconditions.check(size == 0,
+ "没找到符合规范的插件,插件需有@CoolPlugin注解,且以Plugin结尾(如:DemoPlugin)");
+ CoolPreconditions.check(size > 1,
+ "识别到当前安装包有多个插件(只能有一个@CoolPlugin注解),一次只支持一个插件导入");
+ }
+
+ /**
+ * 获取插件实例对象
+ */
+ public Object getBeanInstance(String key) {
+ CoolPreconditions.checkEmpty(key, "插件key is null");
+ if (pluginMap.containsKey(key)) {
+ return pluginMap.get(key);
+ }
+ CoolPreconditions.alwaysThrow("插件 {} 未找到", key);
+ return null;
+ }
+
+ /**
+ * 获取自定义类加载器
+ */
+ public DynamicJarClassLoader getDynamicJarClassLoader(String key) {
+ return dynamicJarClassLoaderMap.get(key);
+ }
+}
diff --git a/src/main/java/com/cool/core/request/CrudOption.java b/src/main/java/com/cool/core/request/CrudOption.java
new file mode 100644
index 0000000..fd18a2a
--- /dev/null
+++ b/src/main/java/com/cool/core/request/CrudOption.java
@@ -0,0 +1,110 @@
+package com.cool.core.request;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONObject;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.query.QueryColumn;
+import com.mybatisflex.core.query.QueryCondition;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.Arrays;
+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 Environment evn;
+
+ public CrudOption(JSONObject requestParams) {
+ this.requestParams = requestParams;
+ this.queryWrapper = new QueryWrapper();
+ this.evn = SpringUtil.getBean(Environment.class);
+ }
+
+ public CrudOption fieldEq(QueryColumn... fields) {
+ this.fieldEq = fields;
+ return this;
+ }
+
+ public QueryWrapper getQueryWrapper(Class entityClass) {
+ return build(this.queryWrapper, entityClass);
+ }
+
+ public CrudOption queryWrapper(QueryWrapper queryWrapper) {
+ this.queryWrapper = queryWrapper;
+ return this;
+ }
+
+ public CrudOption keyWordLikeFields(QueryColumn... fields) {
+ this.keyWordLikeFields = fields;
+ return this;
+ }
+
+ public CrudOption select(QueryColumn... selects) {
+ this.select = selects;
+ return this;
+ }
+
+
+ /**
+ * 构建查询条件
+ *
+ * @return QueryWrapper
+ */
+ private QueryWrapper build(QueryWrapper queryWrapper, Class entityClass) {
+ if (ObjectUtil.isNotEmpty(fieldEq)) {
+ Arrays.stream(fieldEq).toList().forEach(filed -> queryWrapper.and(
+ filed.eq(requestParams.get(StrUtil.toCamelCase(filed.getName())))));
+ }
+ Object keyWord = requestParams.get("keyWord");
+ if (ObjectUtil.isNotEmpty(this.keyWordLikeFields) && ObjectUtil.isNotEmpty(keyWord)) {
+ QueryCondition queryCondition = new QueryCondition();
+ Arrays.stream(keyWordLikeFields).toList().forEach(likeQueryColumn -> {
+ if (ObjectUtil.isEmpty(queryCondition.getColumn())) {
+ queryCondition.setColumn(likeQueryColumn);
+ queryCondition.setLogic(" LIKE ");
+ queryCondition.setValue("%" + keyWord + "%");
+ } else {
+ queryCondition.or(likeQueryColumn.like(keyWord));
+ }
+ });
+ queryWrapper.and(queryCondition);
+ }
+ 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 order = requestParams.getStr("order",
+ tableAnnotation.camelToUnderline() ? "create_time" : "createTime");
+ String sort = requestParams.getStr("sort", "desc");
+ if (StrUtil.isNotEmpty(order) && StrUtil.isNotEmpty(sort)) {
+ queryWrapper.orderBy(
+ tableAnnotation.camelToUnderline() ? StrUtil.toUnderlineCase(order) : order,
+ sort.equals("asc"));
+ }
+ }
+}
diff --git a/src/main/java/com/cool/core/request/R.java b/src/main/java/com/cool/core/request/R.java
new file mode 100644
index 0000000..fe86175
--- /dev/null
+++ b/src/main/java/com/cool/core/request/R.java
@@ -0,0 +1,49 @@
+package com.cool.core.request;
+
+import java.util.HashMap;
+
+/**
+ * 返回信息
+ */
+public class R extends HashMap {
+ private static final long serialVersionUID = 1L;
+
+ public R() {
+ put("code", 1000);
+ put("message", "success");
+ }
+
+ 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.put("code", code);
+ r.put("message", msg);
+ return r;
+ }
+
+ public static R okMsg(String msg) {
+ R r = new R();
+ r.put("message", msg);
+ return r;
+ }
+
+ public static R ok() {
+ return new R();
+ }
+
+ public static R ok(Object data) {
+ return new R().put("data", data);
+ }
+
+ public R put(String key, Object value) {
+ super.put(key, value);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/request/RequestParamsFilter.java b/src/main/java/com/cool/core/request/RequestParamsFilter.java
new file mode 100644
index 0000000..70c14a6
--- /dev/null
+++ b/src/main/java/com/cool/core/request/RequestParamsFilter.java
@@ -0,0 +1,85 @@
+package com.cool.core.request;
+
+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.util.BodyReaderHttpServletRequestWrapper;
+import jakarta.servlet.*;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 封装请求参数 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();
+ if (StrUtil.isNotEmpty(request.getContentType()) && request.getContentType().contains("multipart/form-data")) {
+ servletRequest.setAttribute("requestParams", requestParams);
+ filterChain.doFilter(servletRequest, servletResponse);
+ } else {
+ BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
+ String body = requestWrapper.getBodyString(requestWrapper);
+ if (StrUtil.isNotEmpty(body) && JSONUtil.isJson(body) && !JSONUtil.isJsonArray(body)) {
+ requestParams = JSONUtil.parseObj(body);
+ }
+ requestParams.set("body", body);
+ requestParams.putAll(getAllRequestParam(request));
+
+ Object jwtObj = request.getAttribute("tokenInfo");
+ if (jwtObj != null) {
+ requestParams.set("tokenInfo", ((JWT) jwtObj).getPayload().getClaimsJson());
+ }
+ requestWrapper.setAttribute("requestParams", requestParams);
+
+ filterChain.doFilter(requestWrapper, servletResponse);
+ }
+ }
+
+ /**
+ * 获取客户端请求参数中所有的信息
+ *
+ * @param request
+ * @return
+ */
+ 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/src/main/java/com/cool/core/request/RestInterceptor.java b/src/main/java/com/cool/core/request/RestInterceptor.java
new file mode 100644
index 0000000..21a85cb
--- /dev/null
+++ b/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/src/main/java/com/cool/core/request/prefix/AutoPrefixConfiguration.java b/src/main/java/com/cool/core/request/prefix/AutoPrefixConfiguration.java
new file mode 100644
index 0000000..c699a80
--- /dev/null
+++ b/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/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java b/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java
new file mode 100644
index 0000000..2d1b300
--- /dev/null
+++ b/src/main/java/com/cool/core/request/prefix/AutoPrefixUrlMapping.java
@@ -0,0 +1,100 @@
+package com.cool.core.request.prefix;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import com.cool.core.annotation.CoolRestController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 自动配置模块的路由
+ */
+@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(handlerType, packageName);
+ String cName = getCName(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 = new String[] { "add", "delete", "update", "page", "list", "info" };
+ 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(Class> handlerType, String prefix) {
+ String name = handlerType.getName();
+ String[] names = name.split("[.]");
+ name = names[names.length - 1];
+ return name.toLowerCase().replace("controller", "").replace(prefix.replace("/", ""), "");
+ }
+
+ /**
+ * 构建路由前缀
+ *
+ * @param handlerType 类
+ * @param packageName 包名
+ * @return 返回路由前缀
+ */
+ private String getPrefix(Class> handlerType, 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/src/main/java/com/cool/core/security/EntryPointUnauthorizedHandler.java b/src/main/java/com/cool/core/security/EntryPointUnauthorizedHandler.java
new file mode 100644
index 0000000..1d86f8a
--- /dev/null
+++ b/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/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java b/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java
new file mode 100644
index 0000000..21cf905
--- /dev/null
+++ b/src/main/java/com/cool/core/security/IgnoredUrlsProperties.java
@@ -0,0 +1,19 @@
+package com.cool.core.security;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 忽略token地址配置
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "ignored")
+public class IgnoredUrlsProperties {
+ // 忽略权限列表
+ private List urls = new ArrayList<>();
+}
diff --git a/src/main/java/com/cool/core/security/JwtSecurityConfig.java b/src/main/java/com/cool/core/security/JwtSecurityConfig.java
new file mode 100644
index 0000000..b5dbc68
--- /dev/null
+++ b/src/main/java/com/cool/core/security/JwtSecurityConfig.java
@@ -0,0 +1,88 @@
+package com.cool.core.security;
+
+import com.cool.modules.base.security.JwtAuthenticationTokenFilter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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;
+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;
+
+@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;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ return httpSecurity
+ .authorizeHttpRequests(
+ conf -> conf.requestMatchers(
+ ignoredUrlsProperties.getUrls().toArray(String[]::new))
+ .permitAll().anyRequest().authenticated())
+ .headers(config -> config.frameOptions(HeadersConfigurer.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();
+ }
+
+ @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/src/main/java/com/cool/core/security/MyAccessDecisionManager.java b/src/main/java/com/cool/core/security/MyAccessDecisionManager.java
new file mode 100644
index 0000000..6542033
--- /dev/null
+++ b/src/main/java/com/cool/core/security/MyAccessDecisionManager.java
@@ -0,0 +1,50 @@
+package com.cool.core.security;
+
+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.stereotype.Component;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * 权限管理决断器 判断用户拥有的权限或角色是否有资源访问权限
+ */
+@Slf4j
+@Component
+public class MyAccessDecisionManager implements AccessDecisionManager {
+ @Override
+ public void decide(Authentication authentication, Object o, Collection configAttributes)
+ throws AccessDeniedException, InsufficientAuthenticationException {
+ if (configAttributes == null) {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/security/MyFilterSecurityInterceptor.java b/src/main/java/com/cool/core/security/MyFilterSecurityInterceptor.java
new file mode 100644
index 0000000..f22165b
--- /dev/null
+++ b/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/src/main/java/com/cool/core/security/RestAccessDeniedHandler.java b/src/main/java/com/cool/core/security/RestAccessDeniedHandler.java
new file mode 100644
index 0000000..7954d05
--- /dev/null
+++ b/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/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java b/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java
new file mode 100644
index 0000000..127b0f7
--- /dev/null
+++ b/src/main/java/com/cool/core/security/jwt/JwtTokenUtil.java
@@ -0,0 +1,124 @@
+package com.cool.core.security.jwt;
+
+import cn.hutool.core.date.DateUtil;
+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 java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+import com.cool.modules.base.service.sys.BaseSysConfService;
+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 key = "JWT_SECRET";
+
+ public long getExpire() {
+ return this.coolProperties.getToken().getExpire();
+ }
+
+ public long getRefreshExpire() {
+ return this.coolProperties.getToken().getRefreshExpire();
+ }
+
+ public String getSecret() {
+ String secret = baseSysConfService.getValueWithCache(key);
+ if (StrUtil.isBlank(secret)) {
+ secret = StrUtil.uuid().replaceAll("-", "");
+ baseSysConfService.setValue(key, 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(getSecret().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(getSecret().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) {
+ String tokenUsername = getUsernameFromToken(token);
+ String secret = getSecret();
+ boolean isValidSignature = JWTUtil.verify(token, secret.getBytes());
+ return (tokenUsername.equals(username) && !isTokenExpired(token) && isValidSignature);
+ }
+}
diff --git a/src/main/java/com/cool/core/security/jwt/JwtUser.java b/src/main/java/com/cool/core/security/jwt/JwtUser.java
new file mode 100644
index 0000000..bddcfa7
--- /dev/null
+++ b/src/main/java/com/cool/core/security/jwt/JwtUser.java
@@ -0,0 +1,62 @@
+package com.cool.core.security.jwt;
+
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 用户信息
+ */
+@Data
+public class JwtUser implements UserDetails {
+
+ public JwtUser(String username, String password, List perms, Boolean status) {
+ this.username = username;
+ this.password = password;
+ this.perms = perms;
+ this.status = status;
+ }
+
+ private String username;
+ private String password;
+ private Boolean status;
+ private List perms;
+
+ @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/src/main/java/com/cool/core/util/AnnotationUtils.java b/src/main/java/com/cool/core/util/AnnotationUtils.java
new file mode 100644
index 0000000..2ab2c84
--- /dev/null
+++ b/src/main/java/com/cool/core/util/AnnotationUtils.java
@@ -0,0 +1,77 @@
+package com.cool.core.util;
+
+import com.cool.core.annotation.CoolPlugin;
+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;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+
+@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 {
+// if (clazz.getAnnotation(CoolPlugin.class) != null) {
+// return true;
+// }
+ 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());
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/core/util/BodyReaderHttpServletRequestWrapper.java b/src/main/java/com/cool/core/util/BodyReaderHttpServletRequestWrapper.java
new file mode 100644
index 0000000..2c783d2
--- /dev/null
+++ b/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/src/main/java/com/cool/core/util/CompilerUtils.java b/src/main/java/com/cool/core/util/CompilerUtils.java
new file mode 100644
index 0000000..2f6f2d9
--- /dev/null
+++ b/src/main/java/com/cool/core/util/CompilerUtils.java
@@ -0,0 +1,86 @@
+package com.cool.core.util;
+
+import cn.hutool.core.io.FileUtil;
+import com.mybatisflex.processor.MybatisFlexProcessor;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.processing.Processor;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+public class CompilerUtils {
+
+ /**
+ * 创建文件, 先删除在创建
+ */
+ public static void createFile(String content, String filePathStr) {
+ FileUtil.del(filePathStr);
+ File file = FileUtil.touch(filePathStr);
+ FileUtil.appendString(content, file, StandardCharsets.UTF_8.name());
+ }
+
+ public static void createMapper(String actModulePath, String fileName, String mapper) {
+ String pathStr = actModulePath + "/mapper/";
+ String filePathStr = pathStr + fileName + "Mapper.java";
+ createFile(mapper, filePathStr);
+ }
+
+ public static void createServiceImpl(String actModulePath, String fileName,
+ String serviceImpl) {
+ String pathStr = actModulePath + "/service/impl/";
+ String filePathStr = pathStr + fileName + "ServiceImpl.java";
+ createFile(serviceImpl, filePathStr);
+ }
+
+ public static void createService(String actModulePath, String fileName, String service) {
+ String pathStr = actModulePath + "/service/";
+ String filePathStr = pathStr + fileName + "Service.java";
+ createFile(service, filePathStr);
+ }
+
+ public static String createEntity(String actModulePath, String fileName, String entity) {
+ String pathStr = actModulePath + "/entity/";
+ String filePathStr = pathStr + fileName + "Entity.java";
+ createFile(entity, filePathStr);
+ return filePathStr;
+ }
+
+ public static void createController(String actModulePath, String fileName, String controller) {
+ String pathStr = actModulePath + "/controller/admin/";
+ String filePathStr = pathStr + "Admin" + fileName + "Controller.java";
+ createFile(controller, filePathStr);
+ }
+
+ public static String createModule(String modulesPath, String module) {
+ String pathStr = modulesPath + "/" + module;
+ PathUtils.noExistsMk(pathStr);
+ return pathStr;
+ }
+
+ public static void compilerEntityTableDef(String path) {
+// JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+// StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+//
+// Iterable extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
+// path);
+//
+// // 设置注解处理器
+// Iterable extends Processor> processors = Arrays.asList(new MybatisFlexProcessor());
+// // 添加 -proc:only 选项
+// List options = Arrays.asList("-proc:only");
+// JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options,
+// null, compilationUnits);
+// task.setProcessors(processors);
+//
+// boolean success = task.call();
+// if (success) {
+// System.out.println("Compilation and annotation processing completed successfully.");
+// } else {
+// System.out.println("Compilation and annotation processing failed.");
+// }
+ }
+}
diff --git a/src/main/java/com/cool/core/util/ConvertUtil.java b/src/main/java/com/cool/core/util/ConvertUtil.java
new file mode 100644
index 0000000..9836a19
--- /dev/null
+++ b/src/main/java/com/cool/core/util/ConvertUtil.java
@@ -0,0 +1,152 @@
+package com.cool.core.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+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();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/cool/core/util/CoolPluginInvokers.java b/src/main/java/com/cool/core/util/CoolPluginInvokers.java
new file mode 100644
index 0000000..6adfb88
--- /dev/null
+++ b/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/src/main/java/com/cool/core/util/EntityUtils.java b/src/main/java/com/cool/core/util/EntityUtils.java
new file mode 100644
index 0000000..4bd47ef
--- /dev/null
+++ b/src/main/java/com/cool/core/util/EntityUtils.java
@@ -0,0 +1,95 @@
+package com.cool.core.util;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.util.ObjUtil;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.query.QueryColumn;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+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/modules/**/entity/**/*.class");
+ for (Resource r : resources) {
+ String path = r.getURL().getPath();
+ String className = path.substring(path.indexOf("com/cool/modules"),
+ 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) {
+ return Arrays.stream(queryColumns).toList().stream()
+ .filter(o -> ObjUtil.equals(o.getName(), excludeNames)).toList();
+ }
+
+ /**
+ * 检查字段名是否在排除列表中
+ *
+ * @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/src/main/java/com/cool/core/util/IPUtils.java b/src/main/java/com/cool/core/util/IPUtils.java
new file mode 100644
index 0000000..ed33d80
--- /dev/null
+++ b/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/src/main/java/com/cool/core/util/MapExtUtil.java b/src/main/java/com/cool/core/util/MapExtUtil.java
new file mode 100644
index 0000000..3179ea2
--- /dev/null
+++ b/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/src/main/java/com/cool/core/util/PathUtils.java b/src/main/java/com/cool/core/util/PathUtils.java
new file mode 100644
index 0000000..72bed3e
--- /dev/null
+++ b/src/main/java/com/cool/core/util/PathUtils.java
@@ -0,0 +1,33 @@
+package com.cool.core.util;
+
+import cn.hutool.core.io.file.PathUtil;
+import com.cool.CoolApplication;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class PathUtils {
+
+ 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() + "/src/main/java/" + CoolApplication.class.getPackageName()
+ .replace(".", "/") + "/modules";
+ }
+
+ /**
+ * 路径不存在创建
+ */
+ public static void noExistsMk(String pathStr) {
+ Path path = Paths.get(pathStr);
+ if (PathUtil.exists(path, false)) {
+ PathUtil.mkParentDirs(path);
+ }
+ }
+}
diff --git a/src/main/java/com/cool/core/util/SpringContextUtils.java b/src/main/java/com/cool/core/util/SpringContextUtils.java
new file mode 100644
index 0000000..f026531
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java
new file mode 100644
index 0000000..0fe42ff
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java
@@ -0,0 +1,98 @@
+package com.cool.modules.base.controller.admin;
+
+import cn.hutool.core.lang.Dict;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.file.FileUploadStrategyFactory;
+import com.cool.core.request.R;
+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 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;
+
+ @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) {
+ return R.ok(baseSysPermsService.permmenu(adminUserId));
+ }
+
+ @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();
+ }
+
+ @Operation(summary = "编程")
+ @GetMapping("/program")
+ public R program() {
+ return R.ok("Java");
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java
new file mode 100644
index 0000000..9f44739
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java
@@ -0,0 +1,77 @@
+package com.cool.modules.base.controller.admin;
+
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.file.FileUploadStrategyFactory;
+import com.cool.core.request.R;
+import com.cool.modules.base.dto.sys.BaseSysLoginDto;
+import com.cool.modules.base.service.sys.BaseSysLoginService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 系统开放接口,无需权限校验
+ */
+@RequiredArgsConstructor
+@Tag(name = "系统开放", description = "系统开放")
+@CoolRestController()
+public class AdminBaseOpenController {
+
+ final private BaseSysLoginService baseSysLoginService;
+
+ final private CoolEps coolEps;
+
+ final private FileUploadStrategyFactory fileUploadStrategyFactory;
+
+ @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(type, width, height));
+ }
+
+ @Operation(summary = "刷新token")
+ @GetMapping("/refreshToken")
+ public R refreshToken(String refreshToken) {
+ return R.ok(baseSysLoginService.refreshToken(refreshToken));
+ }
+
+ @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/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysDepartmentController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysDepartmentController.java
new file mode 100644
index 0000000..b969055
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysLogController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysLogController.java
new file mode 100644
index 0000000..7b1ff35
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java
new file mode 100644
index 0000000..d1abeee
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java
@@ -0,0 +1,56 @@
+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.R;
+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) {
+ }
+
+ @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/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java
new file mode 100644
index 0000000..00fd851
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysParamController.java
@@ -0,0 +1,30 @@
+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.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 org.springframework.web.bind.annotation.GetMapping;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 系统参数配置
+ */
+@Tag(name = "系统参数配置", description = "系统参数配置")
+@CoolRestController(api = { "add", "delete", "update", "page", "info" })
+public class AdminBaseSysParamController extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ }
+
+ @Operation(summary = "根据键返回网页的参数值")
+ @GetMapping("/html")
+ public String html(String key) {
+ return service.htmlByKey(key);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java
new file mode 100644
index 0000000..86d6b58
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysRoleController.java
@@ -0,0 +1,32 @@
+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.modules.base.entity.sys.BaseSysRoleEntity;
+import static com.cool.modules.base.entity.sys.table.BaseSysRoleEntityTableDef.BASE_SYS_ROLE_ENTITY;
+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.get("userId")).or(w -> {
+ w.in(BASE_SYS_ROLE_ENTITY.ID.getName(), tokenInfo.get("roleIds"));
+ });
+ }, !isAdmin).and(BASE_SYS_ROLE_ENTITY.LABEL.ne("admin"))));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysUserController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysUserController.java
new file mode 100644
index 0000000..aa15e18
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java b/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java
new file mode 100644
index 0000000..447207f
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/controller/app/AppBaseCommController.java
@@ -0,0 +1,26 @@
+package com.cool.modules.base.controller.app;
+
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.eps.CoolEps;
+import com.cool.core.request.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * app通用接口
+ */
+@RequiredArgsConstructor
+@Tag(name = "应用通用", description = "应用通用")
+@CoolRestController
+public class AppBaseCommController {
+
+ final private CoolEps coolEps;
+
+ @Operation(summary = "实体信息与路径", description = "系统所有的实体信息与路径,供前端自动生成代码与服务")
+ @GetMapping("/eps")
+ public R eps() {
+ return R.ok(coolEps.getApp());
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/dto/sys/BaseSysLoginDto.java b/src/main/java/com/cool/modules/base/dto/sys/BaseSysLoginDto.java
new file mode 100644
index 0000000..20f3149
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java
new file mode 100644
index 0000000..50343bf
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysConfEntity.java
@@ -0,0 +1,25 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.tangzc.autotable.annotation.enums.IndexTypeEnum;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.autotable.annotation.Index;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 系统配置
+ */
+@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/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java
new file mode 100644
index 0000000..200a126
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java
new file mode 100644
index 0000000..32a2f4a
--- /dev/null
+++ b/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.core.handler.Fastjson2TypeHandler;
+import com.tangzc.autotable.annotation.Index;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+@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/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java
new file mode 100644
index 0000000..3473135
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysMenuEntity.java
@@ -0,0 +1,54 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.tangzc.autotable.annotation.Index;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@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 = "权限")
+ private String perms;
+
+ @ColumnDefine(comment = "类型 0:目录 1:菜单 2:按钮", type = "tinyint", 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/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java
new file mode 100644
index 0000000..97db9e7
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysParamEntity.java
@@ -0,0 +1,30 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.tangzc.autotable.annotation.Index;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+@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", type = "tinyint")
+ private Integer dataType;
+
+ @ColumnDefine(comment = "备注")
+ private String remark;
+}
diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleDepartmentEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleDepartmentEntity.java
new file mode 100644
index 0000000..f7a48ff
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java
new file mode 100644
index 0000000..2623fce
--- /dev/null
+++ b/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.mybatisflex.core.handler.Fastjson2TypeHandler;
+import com.tangzc.autotable.annotation.Index;
+import com.tangzc.autotable.annotation.enums.IndexTypeEnum;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+
+@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/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleMenuEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleMenuEntity.java
new file mode 100644
index 0000000..0886904
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java
new file mode 100644
index 0000000..8098af9
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserEntity.java
@@ -0,0 +1,63 @@
+package com.cool.modules.base.entity.sys;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.tangzc.autotable.annotation.Index;
+import com.tangzc.autotable.annotation.enums.IndexTypeEnum;
+
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.mybatisflex.annotation.Table;
+import com.tangzc.autotable.annotation.Index;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Table(value = "base_sys_user", comment = "系统用户表")
+public class BaseSysUserEntity extends BaseEntity {
+ @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", type = "tinyint")
+ private Integer status;
+
+ // 部门名称
+ @Column(ignore = true)
+ private String departmentName;
+
+ // 角色名称
+ @Column(ignore = true)
+ private String roleName;
+
+ @ColumnDefine(comment = "socketId")
+ private String socketId;
+}
diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.java
new file mode 100644
index 0000000..17dbbc2
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysUserRoleEntity.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_user_role", comment = "系统用户角色表")
+public class BaseSysUserRoleEntity extends BaseEntity {
+ @ColumnDefine(comment = "用户ID", type = "bigint")
+ private Long userId;
+
+ @ColumnDefine(comment = "角色ID", type = "bigint")
+ private Long roleId;
+}
diff --git a/src/main/java/com/cool/modules/base/filter/BaseLogFilter.java b/src/main/java/com/cool/modules/base/filter/BaseLogFilter.java
new file mode 100644
index 0000000..9e10474
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysConfMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysConfMapper.java
new file mode 100644
index 0000000..298448c
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysDepartmentMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysDepartmentMapper.java
new file mode 100644
index 0000000..cc3148e
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysLogMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysLogMapper.java
new file mode 100644
index 0000000..5bd6f09
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysMenuMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysMenuMapper.java
new file mode 100644
index 0000000..ab39f68
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysParamMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysParamMapper.java
new file mode 100644
index 0000000..49ce1bd
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleDepartmentMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleDepartmentMapper.java
new file mode 100644
index 0000000..9f3f027
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMapper.java
new file mode 100644
index 0000000..6264504
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMenuMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysRoleMenuMapper.java
new file mode 100644
index 0000000..4d488f0
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserMapper.java
new file mode 100644
index 0000000..37f4c90
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserRoleMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysUserRoleMapper.java
new file mode 100644
index 0000000..51b8105
--- /dev/null
+++ b/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/src/main/java/com/cool/modules/base/security/CoolSecurityUtil.java b/src/main/java/com/cool/modules/base/security/CoolSecurityUtil.java
new file mode 100644
index 0000000..8997937
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/security/CoolSecurityUtil.java
@@ -0,0 +1,63 @@
+package com.cool.modules.base.security;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.cache.CoolCache;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+/**
+ * Security 工具类
+ */
+@Component
+@RequiredArgsConstructor
+public class CoolSecurityUtil {
+
+ final private CoolCache coolCache;
+
+ /**
+ * 登录的用户名
+ *
+ * @return 用户名
+ */
+ public String username() {
+ return SecurityContextHolder.getContext().getAuthentication().getName();
+ }
+
+ /**
+ * 获得jwt中的信息
+ *
+ * @param requestParams 请求参数
+ * @return jwt
+ */
+ public JSONObject userInfo(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;
+ }
+
+ /**
+ * 退出登录
+ *
+ * @param adminUserId 用户ID
+ * @param username 用户名
+ */
+ public void logout(Long adminUserId, String username) {
+ coolCache.del("admin:department:" + adminUserId, "admin:passwordVersion:" + adminUserId,
+ "admin:userInfo:" + adminUserId, "admin:userDetails:" + username);
+ }
+
+ /**
+ * 退出登录
+ *
+ * @param userEntity 用户
+ */
+ public void logout(BaseSysUserEntity userEntity) {
+ logout(userEntity.getId(), userEntity.getUsername());
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/security/JwtAuthenticationTokenFilter.java b/src/main/java/com/cool/modules/base/security/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..11b08ca
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/security/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,65 @@
+package com.cool.modules.base.security;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.jwt.JWT;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.security.jwt.JwtTokenUtil;
+import com.cool.core.security.jwt.JwtUser;
+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;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain chain)
+ throws ServletException, IOException {
+ String authToken = request.getHeader("Authorization");
+ if (!StrUtil.isEmpty(authToken)) {
+ JWT jwt = jwtTokenUtil.getTokenInfo(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);
+ }
+ }
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/security/JwtUserDetailsServiceImpl.java b/src/main/java/com/cool/modules/base/security/JwtUserDetailsServiceImpl.java
new file mode 100644
index 0000000..99e5853
--- /dev/null
+++ b/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.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/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java b/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java
new file mode 100644
index 0000000..7322d1d
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java
@@ -0,0 +1,74 @@
+package com.cool.modules.base.security;
+
+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 {
+ if (map == null) {
+ loadResourceDefine();
+ }
+ // Object中包含用户请求request
+ String url = ((FilterInvocation) o).getRequestUrl();
+ return map.get(url.replace("/admin/", "").split("[?]")[0]);
+
+ }
+
+ @Override
+ public Collection getAllConfigAttributes() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean supports(Class> aClass) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysConfService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysConfService.java
new file mode 100644
index 0000000..3117a6f
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysConfService.java
@@ -0,0 +1,41 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysConfEntity;
+
+/**
+ * 系统配置
+ */
+public interface BaseSysConfService extends BaseService {
+ /**
+ * 更新配置
+ *
+ * @param key 键
+ * @param value 值
+ */
+ void updateValue(String key, String value);
+
+ /**
+ * 获得值
+ *
+ * @param key 键
+ * @return 值
+ */
+ String getValue(String key);
+
+ /**
+ * 获得值(带缓存)
+ *
+ * @param key 键
+ * @return 值
+ */
+ String getValueWithCache(String key);
+
+ /**
+ * 设置值
+ *
+ * @param key 键
+ * @param value 值
+ */
+ void setValue(String key, String value);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysDepartmentService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysDepartmentService.java
new file mode 100644
index 0000000..502862d
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysDepartmentService.java
@@ -0,0 +1,18 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity;
+
+import java.util.List;
+
+/**
+ * 系统部门
+ */
+public interface BaseSysDepartmentService extends BaseService {
+ /**
+ * 排序
+ *
+ * @param list 新的排序
+ */
+ void order(List list);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysLogService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysLogService.java
new file mode 100644
index 0000000..4ca596d
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysLogService.java
@@ -0,0 +1,27 @@
+package com.cool.modules.base.service.sys;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysLogEntity;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 系统日志
+ */
+public interface BaseSysLogService extends BaseService {
+ /**
+ * 清理日志
+ *
+ * @param isAll 是否全部清除
+ */
+ void clear(boolean isAll);
+
+ /**
+ * 日志记录
+ *
+ * @param requestParams 请求参数
+ * @param request 请求
+ */
+ void record(HttpServletRequest request, JSONObject requestParams);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java
new file mode 100644
index 0000000..731e45f
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java
@@ -0,0 +1,42 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.modules.base.dto.sys.BaseSysLoginDto;
+
+/**
+ * 系统登录
+ */
+public interface BaseSysLoginService {
+ /**
+ * 验证码
+ *
+ * @param type 类型 svg base64 svg是node版本的, java版本用base64, svg未实现
+ * @param width 宽度
+ * @param height 高度
+ * @return base64 验证码与ID
+ */
+ Object captcha(String type, Integer width, Integer height);
+
+ /**
+ * 登录
+ *
+ * @param baseSysLoginDto 登录必要信息
+ * @return token与相关的过期信息
+ */
+ Object login(BaseSysLoginDto baseSysLoginDto);
+
+ /**
+ * 退出登录
+ *
+ * @param adminUserId 用户ID
+ * @param username 用户名称
+ */
+ void logout(Long adminUserId, String username);
+
+ /**
+ * 刷新token
+ *
+ * @param refreshToken 刷新token
+ * @return 新的token
+ */
+ Object refreshToken(String refreshToken);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysMenuService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysMenuService.java
new file mode 100644
index 0000000..e7753d9
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysMenuService.java
@@ -0,0 +1,18 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 系统菜单
+ */
+public interface BaseSysMenuService extends BaseService {
+
+ Object export(List ids);
+
+ boolean importMenu(List menus);
+
+ void create(Map params);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysParamService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysParamService.java
new file mode 100644
index 0000000..7926140
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysParamService.java
@@ -0,0 +1,25 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysParamEntity;
+
+/**
+ * 系统参数配置
+ */
+public interface BaseSysParamService extends BaseService {
+ /**
+ * 根据key获得网页内容
+ *
+ * @param key 键
+ * @return 网页内容
+ */
+ String htmlByKey(String key);
+
+ /**
+ * 根据key获得数据
+ *
+ * @param key 键
+ * @return 数据
+ */
+ String dataByKey(String key);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java
new file mode 100644
index 0000000..2333b66
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java
@@ -0,0 +1,160 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+
+import java.util.List;
+
+/**
+ * 权限菜单
+ */
+public interface BaseSysPermsService {
+ /**
+ * 获得权限缓存
+ *
+ * @param userId 用户ID
+ * @return 返回用户相关的权限信息
+ */
+ String[] getPermsCache(Long userId);
+
+ /**
+ * 获得权限
+ *
+ * @param userId 用户ID
+ * @return 返回用户相关的权限信息
+ */
+ String[] getPerms(Long userId);
+
+ /**
+ * 获得权限
+ *
+ * @param roleIds 用户角色数组
+ * @return 返回用户相关的权限信息
+ */
+ String[] getPerms(Long[] roleIds);
+
+ /**
+ * 获得菜单
+ *
+ * @param roleIds 角色
+ * @return 返回菜单
+ */
+ List getMenus(Long[] roleIds);
+
+ /**
+ * 获得菜单
+ *
+ * @param userId 用户ID
+ * @return 返回菜单
+ */
+ List getMenus(Long userId);
+
+ /**
+ * 获得菜单
+ *
+ * @param username 用户名
+ * @return 返回菜单
+ */
+ List getMenus(String username);
+
+ /**
+ * 获得角色数组
+ *
+ * @param userId 用户ID
+ * @return 返回角色数组
+ */
+ Long[] getRoles(Long userId);
+
+ /**
+ * 获得角色数组
+ *
+ * @param username 用户名
+ * @return 返回角色数组
+ */
+ Long[] getRoles(String username);
+
+ /**
+ * 获得登录用户的部门权限
+ *
+ * @return 部门ID集合
+ */
+ Long[] loginDepartmentIds();
+
+ /**
+ * 根据角色获得部门ID
+ *
+ * @param roleIds 角色ID数组
+ * @return 部门ID数组
+ */
+ Long[] getDepartmentIdsByRoleIds(Long[] roleIds);
+
+ /**
+ * 根据用户ID获得部门ID
+ *
+ * @param userId 角色ID数组
+ * @return 部门ID数组
+ */
+ Long[] getDepartmentIdsByRoleIds(Long userId);
+
+ /**
+ * 获得角色数组
+ *
+ * @param userEntity 用户
+ * @return 返回角色数组
+ */
+ Long[] getRoles(BaseSysUserEntity userEntity);
+
+ /**
+ * 所有的操作权限
+ *
+ * @return 返回所有的操作权限
+ */
+ String[] getAllPerms();
+
+ /**
+ * 用户的权限菜单
+ *
+ * @param adminUserId 登录的用户
+ * @return 权限菜单
+ */
+ Object permmenu(Long adminUserId);
+
+ /**
+ * 更新角色权限
+ *
+ * @param roleId 角色ID
+ * @param menuIdList 菜单ID
+ * @param departmentIds 部门ID
+ */
+ void updatePerms(Long roleId, Long[] menuIdList, Long[] departmentIds);
+
+ /**
+ * 更新用户角色
+ *
+ * @param userId 用户ID
+ * @param roleIdList 角色集合
+ */
+ void updateUserRole(Long userId, Long[] roleIdList);
+
+ /**
+ * 刷新权限
+ *
+ * @param userId 用户ID
+ */
+ void refreshPerms(Long userId);
+
+ /**
+ * 刷新权限
+ *
+ * @param menuId 用户ID
+ */
+ void refreshPermsByMenuId(Long menuId);
+
+ /**
+ * 刷新权限
+ *
+ * @param roleId 角色ID
+ */
+ void refreshPermsByRoleId(Long roleId);
+
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysRoleService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysRoleService.java
new file mode 100644
index 0000000..f9e4546
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysRoleService.java
@@ -0,0 +1,10 @@
+package com.cool.modules.base.service.sys;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysRoleEntity;
+
+/**
+ * 系统角色
+ */
+public interface BaseSysRoleService extends BaseService {
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysUserService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysUserService.java
new file mode 100644
index 0000000..0a6197a
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysUserService.java
@@ -0,0 +1,25 @@
+package com.cool.modules.base.service.sys;
+
+import cn.hutool.core.lang.Dict;
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+
+/**
+ * 系统用户
+ */
+public interface BaseSysUserService extends BaseService {
+ /**
+ * 修改用户信息
+ *
+ * @param body 用户信息
+ */
+ void personUpdate(Long userId, Dict body);
+
+ /**
+ * 移动部门
+ *
+ * @param departmentId 部门ID
+ * @param userIds 用户ID集合
+ */
+ void move(Long departmentId, Long[] userIds);
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysConfServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysConfServiceImpl.java
new file mode 100644
index 0000000..db130a9
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysConfServiceImpl.java
@@ -0,0 +1,58 @@
+package com.cool.modules.base.service.sys.impl;
+
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.cache.CoolCache;
+import com.cool.modules.base.entity.sys.BaseSysConfEntity;
+import com.cool.modules.base.mapper.sys.BaseSysConfMapper;
+import com.cool.modules.base.service.sys.BaseSysConfService;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.update.UpdateChain;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统配置
+ */
+@Service
+@RequiredArgsConstructor
+public class BaseSysConfServiceImpl extends BaseServiceImpl
+ implements BaseSysConfService {
+
+ private final CoolCache coolCache;
+
+ @Override
+ public void updateValue(String key, String value) {
+ UpdateChain.of(BaseSysConfEntity.class).set(BaseSysConfEntity::getCValue, value)
+ .eq(BaseSysConfEntity::getCKey, key).update();
+ }
+
+ @Override
+ public String getValue(String key) {
+ BaseSysConfEntity baseSysConfEntity = getOne(QueryWrapper.create().eq(BaseSysConfEntity::getCKey, key));
+ if (baseSysConfEntity != null) {
+ return baseSysConfEntity.getCValue();
+ }
+ return null;
+ }
+
+ @Override
+ public String getValueWithCache(String key) {
+ String value = coolCache.get(key, String.class);
+ if (value != null) {
+ return value;
+ }
+ value = getValue(key);
+ if (value != null) {
+ coolCache.set(key, value);
+ }
+ return value;
+ }
+
+ @Override
+ public void setValue(String key, String value) {
+ BaseSysConfEntity baseSysConfEntity = new BaseSysConfEntity();
+ baseSysConfEntity.setCKey(key);
+ baseSysConfEntity.setCValue(value);
+ save(baseSysConfEntity);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysDepartmentServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysDepartmentServiceImpl.java
new file mode 100644
index 0000000..c666b16
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysDepartmentServiceImpl.java
@@ -0,0 +1,85 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.mapper.sys.BaseSysDepartmentMapper;
+import com.cool.modules.base.mapper.sys.BaseSysUserMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysDepartmentService;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.update.UpdateChain;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统部门
+ */
+@RequiredArgsConstructor
+@Service
+public class BaseSysDepartmentServiceImpl extends
+ BaseServiceImpl
+ implements BaseSysDepartmentService {
+
+ final private BaseSysUserMapper baseSysUserMapper;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ @Override
+ public void order(List list) {
+ list.forEach(baseSysDepartmentEntity -> {
+ UpdateChain.of(BaseSysDepartmentEntity.class)
+ .set(BaseSysDepartmentEntity::getOrderNum, baseSysDepartmentEntity.getOrderNum())
+ .eq(BaseSysDepartmentEntity::getId, baseSysDepartmentEntity.getId()).update();
+ });
+ }
+
+ @Override
+ public List list(JSONObject requestParams, QueryWrapper queryWrapper) {
+ String username = coolSecurityUtil.username();
+ Long[] loginDepartmentIds = baseSysPermsService.loginDepartmentIds();
+ if (loginDepartmentIds != null && loginDepartmentIds.length == 0) {
+ return new ArrayList<>();
+ }
+ List list = this.list(
+ QueryWrapper.create()
+ .in(BaseSysDepartmentEntity::getId, loginDepartmentIds, !username.equals("admin"))
+ .orderBy(BaseSysDepartmentEntity::getOrderNum, false));
+ list.forEach(e -> {
+ List parentDepartment = list.stream()
+ .filter(sysDepartmentEntity -> e.getParentId() != null
+ && e.getParentId().equals(sysDepartmentEntity.getId()))
+ .toList();
+ if (!parentDepartment.isEmpty()) {
+ e.setParentName(parentDepartment.get(0).getName());
+ }
+ });
+ return list;
+ }
+
+ @Override
+ public boolean delete(JSONObject requestParams, Long... ids) {
+ super.delete(ids);
+ // 是否删除对应用户 否则移动到顶层部门
+ if (requestParams.getBool("deleteUser")) {
+ return baseSysUserMapper
+ .deleteByQuery(
+ QueryWrapper.create().in(BaseSysUserEntity::getDepartmentId, (Object) ids)) > 0;
+ } else {
+ BaseSysDepartmentEntity topDepartment = getOne(
+ QueryWrapper.create().isNull(BaseSysDepartmentEntity::getParentId));
+ if (topDepartment != null) {
+ UpdateChain.of(BaseSysUserEntity.class)
+ .set(BaseSysUserEntity::getDepartmentId, topDepartment.getId())
+ .in(BaseSysUserEntity::getDepartmentId, (Object) ids).update();
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLogServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLogServiceImpl.java
new file mode 100644
index 0000000..9e4796c
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLogServiceImpl.java
@@ -0,0 +1,95 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.util.IPUtils;
+import com.cool.modules.base.entity.sys.BaseSysLogEntity;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.entity.sys.table.BaseSysLogEntityTableDef;
+import com.cool.modules.base.entity.sys.table.BaseSysUserEntityTableDef;
+import com.cool.modules.base.mapper.sys.BaseSysLogMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysConfService;
+import com.cool.modules.base.service.sys.BaseSysLogService;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.Date;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统日志
+ */
+@RequiredArgsConstructor
+@Service
+public class BaseSysLogServiceImpl extends BaseServiceImpl
+ implements BaseSysLogService {
+
+ private final BaseSysConfService baseSysConfService;
+
+ private final CoolSecurityUtil coolSecurityUtil;
+
+ private final IPUtils ipUtils;
+
+ @Override
+ public Object page(
+ JSONObject requestParams, Page page, QueryWrapper queryWrapper) {
+ queryWrapper
+ .select(
+ BaseSysLogEntityTableDef.BASE_SYS_LOG_ENTITY.ALL_COLUMNS,
+ BaseSysUserEntityTableDef.BASE_SYS_USER_ENTITY.NAME)
+ .from(BaseSysLogEntityTableDef.BASE_SYS_LOG_ENTITY)
+ .leftJoin(BaseSysUserEntityTableDef.BASE_SYS_USER_ENTITY)
+ .on(BaseSysLogEntity::getUserId, BaseSysUserEntity::getId);
+ return mapper.paginate(page, queryWrapper);
+ }
+
+ @Override
+ public void clear(boolean isAll) {
+ if (isAll) {
+ this.remove(QueryWrapper.create().ge(BaseSysLogEntity::getId, 0));
+ } else {
+ String keepDay = baseSysConfService.getValue("logKeep");
+ int keepDays = Integer.parseInt(StrUtil.isNotEmpty(keepDay) ? keepDay : "30");
+ Date beforeDate = DateUtil.offsetDay(new Date(), -keepDays);
+ this.remove(QueryWrapper.create().lt(BaseSysLogEntity::getCreateTime, beforeDate));
+ }
+ }
+
+ @Override
+ public void record(HttpServletRequest request, JSONObject requestParams) {
+ String requestURI = request.getRequestURI();
+ String ipAddr = ipUtils.getIpAddr(request);
+ JSONObject userInfo = coolSecurityUtil.userInfo(requestParams);
+
+ Long userId = null;
+ if (userInfo != null) {
+ userId = userInfo.getLong("userId");
+ }
+
+ JSONObject newJSONObject = JSONUtil.parseObj(JSONUtil.toJsonStr(requestParams));
+ newJSONObject.remove("tokenInfo");
+ newJSONObject.remove("refreshToken");
+ newJSONObject.remove("body");
+
+ recordAsync(requestURI, ipAddr, userId, newJSONObject);
+ }
+
+
+ @Async
+ public void recordAsync(String requestURI, String ip, Long userId, JSONObject params) {
+ BaseSysLogEntity logEntity = new BaseSysLogEntity();
+ logEntity.setAction(requestURI);
+ logEntity.setIp(ip);
+ if (userId != null) {
+ logEntity.setUserId(userId);
+ }
+ logEntity.setParams(params);
+ save(logEntity);
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java
new file mode 100644
index 0000000..41df62a
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java
@@ -0,0 +1,137 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.GifCaptcha;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+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.exception.CoolException;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.core.security.jwt.JwtTokenUtil;
+import com.cool.modules.base.dto.sys.BaseSysLoginDto;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.mapper.sys.BaseSysUserMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysLoginService;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class BaseSysLoginServiceImpl implements BaseSysLoginService {
+
+ private final CoolCache coolCache;
+
+ private final AuthenticationManager authenticationManager;
+
+ private final CoolSecurityUtil coolSecurityUtil;
+
+ private final JwtTokenUtil jwtTokenUtil;
+
+ private final BaseSysUserMapper baseSysUserMapper;
+
+ private final BaseSysPermsService baseSysPermsService;
+
+ @Override
+ public Object captcha(String type, Integer width, Integer height) {
+ // 1、生成验证码 2、生成对应的ID并设置在缓存中,验证码过期时间30分钟;
+ Map result = new HashMap<>();
+ String captchaId = StrUtil.uuid();
+ result.put("captchaId", captchaId);
+ RandomGenerator randomGenerator = new RandomGenerator(4);
+ GifCaptcha gifCaptcha = CaptchaUtil.createGifCaptcha(width, height);
+ gifCaptcha.setGenerator(randomGenerator);
+ gifCaptcha.setBackground(new Color(248, 248, 248));
+ gifCaptcha.setMaxColor(60);
+ gifCaptcha.setMinColor(55);
+ result.put("data", "data:image/png;base64," + gifCaptcha.getImageBase64());
+ coolCache.set("verify:img:" + captchaId, gifCaptcha.getCode(), 1800);
+ return result;
+ }
+
+ @Override
+ public Object login(BaseSysLoginDto baseSysLoginDto) {
+ // 1、检查验证码是否正确 2、执行登录操作
+ String verifyCode = coolCache.get("verify:img:" + baseSysLoginDto.getCaptchaId(),
+ String.class);
+ if (StrUtil.isNotEmpty(verifyCode)
+ && verifyCode.equalsIgnoreCase(baseSysLoginDto.getVerifyCode())) {
+ UsernamePasswordAuthenticationToken upToken =
+ new UsernamePasswordAuthenticationToken(
+ baseSysLoginDto.getUsername(), baseSysLoginDto.getPassword());
+ Authentication authentication = authenticationManager.authenticate(upToken);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ // 查询用户信息并生成token
+ BaseSysUserEntity sysUserEntity =
+ baseSysUserMapper.selectOneByQuery(
+ QueryWrapper.create()
+ .eq(BaseSysUserEntity::getUsername, baseSysLoginDto.getUsername()));
+ CoolPreconditions.check(
+ ObjectUtil.isEmpty(sysUserEntity) || sysUserEntity.getStatus() == 0, "用户已禁用");
+ Long[] roleIds = baseSysPermsService.getRoles(sysUserEntity);
+ Dict tokenInfo =
+ Dict.create()
+ .set("roleIds", roleIds)
+ .set("username", baseSysLoginDto.getUsername())
+ .set("userId", sysUserEntity.getId())
+ .set("passwordVersion", sysUserEntity.getPasswordV());
+ String token = jwtTokenUtil.generateToken(tokenInfo);
+ String refreshToken = jwtTokenUtil.generateRefreshToken(tokenInfo);
+ coolCache.del("verify:img:" + baseSysLoginDto.getCaptchaId());
+ return Dict.create()
+ .set("token", token)
+ .set("expire", jwtTokenUtil.getExpire())
+ .set("refreshToken", refreshToken)
+ .set("refreshExpire", jwtTokenUtil.getRefreshExpire());
+ } else {
+ coolCache.del("verify:img:" + baseSysLoginDto.getCaptchaId());
+ throw new CoolException("验证码不正确");
+ }
+ }
+
+ @Override
+ public void logout(Long adminUserId, String username) {
+ coolSecurityUtil.logout(adminUserId, username);
+ }
+
+ @Override
+ public Object refreshToken(String refreshToken) {
+ JWT jwt = jwtTokenUtil.getTokenInfo(refreshToken);
+ try {
+ CoolPreconditions.check(jwt == null || !(Boolean) jwt.getPayload("isRefresh"),
+ "错误的token");
+
+ BaseSysUserEntity baseSysUserEntity =
+ baseSysUserMapper.selectOneById(Convert.toLong(jwt.getPayload("userId")));
+ Long[] roleIds = baseSysPermsService.getRoles(baseSysUserEntity);
+ Dict tokenInfo =
+ Dict.create()
+ .set("roleIds", roleIds)
+ .set("username", baseSysUserEntity.getUsername())
+ .set("userId", baseSysUserEntity.getId())
+ .set("passwordVersion", baseSysUserEntity.getPasswordV());
+ String token = jwtTokenUtil.generateToken(tokenInfo);
+ refreshToken = jwtTokenUtil.generateRefreshToken(tokenInfo);
+ return Dict.create()
+ .set("token", token)
+ .set("expire", jwtTokenUtil.getExpire())
+ .set("refreshToken", refreshToken)
+ .set("refreshExpire", jwtTokenUtil.getRefreshExpire());
+ } catch (Exception e) {
+ throw new CoolException("错误的token", e);
+ }
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java
new file mode 100644
index 0000000..fb07917
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java
@@ -0,0 +1,170 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.base.ModifyEnum;
+import com.cool.core.util.CompilerUtils;
+import com.cool.core.util.PathUtils;
+import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
+import com.cool.modules.base.mapper.sys.BaseSysMenuMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysMenuService;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统菜单
+ */
+@Service
+@RequiredArgsConstructor
+public class BaseSysMenuServiceImpl extends BaseServiceImpl
+ implements BaseSysMenuService {
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ @Override
+ public Object list(JSONObject requestParams, QueryWrapper queryWrapper) {
+ List list = baseSysPermsService.getMenus(coolSecurityUtil.username());
+ list.forEach(e -> {
+ List parent = list.stream()
+ .filter(sysMenuEntity -> e.getParentId() != null && e.getParentId()
+ .equals(sysMenuEntity.getId()))
+ .toList();
+ if (!parent.isEmpty()) {
+ e.setParentName(parent.get(0).getName());
+ }
+ });
+ return list;
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, BaseSysMenuEntity sysMenuEntity,
+ ModifyEnum type) {
+ if (sysMenuEntity != null && sysMenuEntity.getId() != null) {
+ baseSysPermsService.refreshPermsByMenuId(requestParams.getLong("id"));
+ }
+ if (requestParams.get("ids") != null) {
+ Long[] ids = requestParams.get("ids", Long[].class);
+ for (Long id : ids) {
+ baseSysPermsService.refreshPermsByMenuId(id);
+ }
+ }
+ }
+
+ @Override
+ public boolean delete(Long... ids) {
+ super.delete(ids);
+ for (Long id : ids) {
+ this.delChildMenu(id);
+ }
+ return true;
+ }
+
+ /**
+ * 删除子菜单
+ *
+ * @param id 删除的菜单ID
+ */
+ private void delChildMenu(Long id) {
+ List delMenu = list(
+ QueryWrapper.create().eq(BaseSysMenuEntity::getParentId, id));
+ if (CollectionUtil.isEmpty(delMenu)) {
+ return;
+ }
+ Long[] ids = delMenu.stream().map(BaseSysMenuEntity::getId).toArray(Long[]::new);
+ if (ArrayUtil.isNotEmpty(ids)) {
+ delete(ids);
+ for (Long delId : ids) {
+ this.delChildMenu(delId);
+ }
+ }
+ }
+
+ @Override
+ public Object export(List ids) {
+ List list = list(
+ QueryWrapper.create().in(BaseSysMenuEntity::getId, ids));
+ List parentList = list.stream()
+ .filter(o -> ObjUtil.isEmpty(o.getParentId())).toList();
+ Map> map = list.stream()
+ .filter(o -> ObjUtil.isNotEmpty(o.getParentId()))
+ .collect(Collectors.groupingBy(BaseSysMenuEntity::getParentId));
+ parentList.forEach(o -> handler(o, map));
+ return parentList;
+ }
+
+ private void handler(BaseSysMenuEntity parentBaseSysMenuEntity,
+ Map> map) {
+ parentBaseSysMenuEntity.setChildMenus(
+ map.getOrDefault(parentBaseSysMenuEntity.getId(), new ArrayList<>()));
+ parentBaseSysMenuEntity.getChildMenus().forEach(o -> {
+ handler(o, map);
+ o.setId(null);
+ o.setParentId(null);
+ o.setCreateTime(null);
+ o.setUpdateTime(null);
+ });
+ parentBaseSysMenuEntity.setId(null);
+ parentBaseSysMenuEntity.setParentId(null);
+ parentBaseSysMenuEntity.setCreateTime(null);
+ parentBaseSysMenuEntity.setUpdateTime(null);
+ }
+
+ @Override
+ public boolean importMenu(List menus) {
+ menus.forEach(this::importMenu);
+ return true;
+ }
+
+ private void importMenu(BaseSysMenuEntity sysMenuEntity) {
+ sysMenuEntity.save();
+ if (ObjUtil.isNotEmpty(sysMenuEntity.getChildMenus())) {
+ sysMenuEntity.getChildMenus().forEach(o -> {
+ o.setParentId(sysMenuEntity.getId());
+ importMenu(o);
+ });
+ }
+ }
+
+ @Override
+ public void create(Map params) {
+ String module = (String) params.get("module");
+ String controller = (String) params.get("controller");
+ String entity = (String) params.get("entity");
+ String service = (String) params.get("service");
+ String serviceImpl = (String) params.get("service-impl");
+ String mapper = (String) params.get("mapper");
+
+ String fileName = (String) params.get("fileName");
+ String modulesPath = PathUtils.getModulesPath();
+ // 创建的模块地址
+ String actModulePath = CompilerUtils.createModule(modulesPath, module);
+ // 创建 controller
+ CompilerUtils.createController(actModulePath, fileName, controller);
+ // 创建 entity
+ String entityPath = CompilerUtils.createEntity(actModulePath, fileName, entity);
+ // 创建 service
+ CompilerUtils.createService(actModulePath, fileName, service);
+ // 创建 serviceImpl
+ CompilerUtils.createServiceImpl(actModulePath, fileName, serviceImpl);
+ // 创建 mapper
+ CompilerUtils.createMapper(actModulePath, fileName, mapper);
+ // 关闭springboot
+ System.exit(0);
+ // 构建TableDef
+ // CompilerUtils.compilerEntityTableDef(entityPath);
+ // 重启
+ // CoolApplication.restart();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysParamServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysParamServiceImpl.java
new file mode 100644
index 0000000..30607c6
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysParamServiceImpl.java
@@ -0,0 +1,53 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.cache.CoolCache;
+import com.cool.modules.base.entity.sys.BaseSysParamEntity;
+import com.cool.modules.base.mapper.sys.BaseSysParamMapper;
+import com.cool.modules.base.service.sys.BaseSysParamService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统参数配置
+ */
+@Service
+@RequiredArgsConstructor
+public class BaseSysParamServiceImpl extends BaseServiceImpl
+ implements BaseSysParamService {
+
+ final private CoolCache coolCache;
+
+ @Override
+ public String htmlByKey(String key) {
+ String data = dataByKey(key);
+ return "" + (StrUtil.isNotEmpty(data) ? data : "key notfound")
+ + "";
+ }
+
+ @Override
+ public String dataByKey(String key) {
+ BaseSysParamEntity baseSysParamEntity = coolCache.get(key, BaseSysParamEntity.class);
+ if (baseSysParamEntity == null) {
+ baseSysParamEntity = getOne(
+ QueryWrapper.create().eq(BaseSysParamEntity::getKeyName, key));
+ }
+ if (baseSysParamEntity != null) {
+ coolCache.set("param:" + baseSysParamEntity.getKeyName(), baseSysParamEntity);
+ return baseSysParamEntity.getData();
+ }
+ return null;
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, BaseSysParamEntity baseSysParamEntity) {
+ List list = this.list();
+ list.forEach(e -> {
+ coolCache.set("param:" + e.getKeyName(), e);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java
new file mode 100644
index 0000000..208550f
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java
@@ -0,0 +1,246 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.util.SpringContextUtils;
+import com.cool.modules.base.entity.sys.*;
+import com.cool.modules.base.mapper.sys.*;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.row.Row;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+import static com.cool.modules.base.entity.sys.table.BaseSysMenuEntityTableDef.BASE_SYS_MENU_ENTITY;
+import static com.cool.modules.base.entity.sys.table.BaseSysRoleMenuEntityTableDef.BASE_SYS_ROLE_MENU_ENTITY;
+import static com.cool.modules.base.entity.sys.table.BaseSysUserRoleEntityTableDef.BASE_SYS_USER_ROLE_ENTITY;
+
+@Service
+@RequiredArgsConstructor
+public class BaseSysPermsServiceImpl implements BaseSysPermsService {
+ final private CoolCache coolCache;
+
+ final private BaseSysUserMapper baseSysUserMapper;
+
+ final private BaseSysUserRoleMapper baseSysUserRoleMapper;
+
+ final private BaseSysMenuMapper baseSysMenuMapper;
+
+ final private BaseSysRoleMenuMapper baseSysRoleMenuMapper;
+
+ final private BaseSysRoleDepartmentMapper baseSysRoleDepartmentMapper;
+
+ final private BaseSysDepartmentMapper baseSysDepartmentMapper;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ @Override
+ public Long[] loginDepartmentIds() {
+ String username = coolSecurityUtil.username();
+ if (username.equals("admin")) {
+ return baseSysDepartmentMapper.selectAll().stream().map(BaseSysDepartmentEntity::getId)
+ .toArray(Long[]::new);
+ } else {
+ Long[] roleIds = getRoles(username);
+ return baseSysRoleDepartmentMapper
+ .selectListByQuery(
+ QueryWrapper.create().in(BaseSysRoleDepartmentEntity::getRoleId, (Object) roleIds))
+ .stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).toArray(Long[]::new);
+ }
+ }
+
+ @Override
+ public Long[] getDepartmentIdsByRoleIds(Long[] roleIds) {
+ return getLongs(roleIds);
+ }
+
+ private Long[] getLongs(Long[] roleIds) {
+ return baseSysRoleDepartmentMapper
+ .selectListByQuery(QueryWrapper.create().in(BaseSysRoleDepartmentEntity::getRoleId, (Object) roleIds,
+ roleIds != null && !CollUtil.toList(roleIds).contains(1L)))
+ .stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).toArray(Long[]::new);
+ }
+
+ @Override
+ public Long[] getDepartmentIdsByRoleIds(Long userId) {
+ Long[] roleIds = getRoles(userId);
+ return getLongs(roleIds);
+ }
+
+ @Override
+ public String[] getPermsCache(Long userId) {
+ Object result = coolCache.get("admin:perms:" + userId);
+ if (ObjectUtil.isNotEmpty(result)) {
+ return Convert.toStrArray(result);
+ }
+ return getPerms(userId);
+ }
+
+ @Override
+ public Long[] getRoles(Long userId) {
+ return getRoles(baseSysUserMapper.selectOneById(userId));
+ }
+
+ @Override
+ public Long[] getRoles(String username) {
+ return getRoles(
+ baseSysUserMapper.selectOneByQuery(QueryWrapper.create().eq(BaseSysUserEntity::getUsername, username)));
+ }
+
+ @Override
+ public Long[] getRoles(BaseSysUserEntity userEntity) {
+ Long[] roleIds = null;
+ if (!userEntity.getUsername().equals("admin")) {
+ List list = baseSysUserRoleMapper
+ .selectListByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userEntity.getId()));
+ roleIds = list.stream().map(BaseSysUserRoleEntity::getRoleId).toArray(Long[]::new);
+ if (Arrays.asList(roleIds).contains(1L)) {
+ roleIds = null;
+ }
+ }
+ return roleIds;
+ }
+
+ @Override
+ public String[] getPerms(Long userId) {
+ return getPerms(getRoles(userId));
+ }
+
+ @Override
+ public String[] getPerms(Long[] roleIds) {
+ List menus = getMenus(roleIds);
+ Set perms = new HashSet<>();
+ String[] permsData = menus.stream().map(BaseSysMenuEntity::getPerms)
+ .filter(itemPerms -> !StrUtil.isEmpty(itemPerms)).toArray(String[]::new);
+ for (String permData : permsData) {
+ perms.addAll(Arrays.asList(permData.split(",")));
+ }
+ return ArrayUtil.toArray(perms, String.class);
+ }
+
+ @Override
+ public List getMenus(Long[] roleIds) {
+ if (CollUtil.toList(roleIds).contains(1L)) {
+ roleIds = null;
+ }
+ if (roleIds != null && roleIds.length == 0) {
+ return new ArrayList<>();
+ }
+
+ QueryWrapper queryWrapper = QueryWrapper.create().select(BASE_SYS_MENU_ENTITY.ALL_COLUMNS).from(BASE_SYS_MENU_ENTITY);
+ if (ObjectUtil.isNotEmpty(roleIds)) {
+ queryWrapper.join(BASE_SYS_ROLE_MENU_ENTITY).on(BASE_SYS_MENU_ENTITY.ID.eq(BASE_SYS_ROLE_MENU_ENTITY.MENU_ID)).and(BASE_SYS_ROLE_MENU_ENTITY.ROLE_ID.in((Object) roleIds));
+ }
+ return baseSysMenuMapper.selectListByQuery(queryWrapper.groupBy(BASE_SYS_MENU_ENTITY.ID).orderBy(BASE_SYS_MENU_ENTITY.ORDER_NUM, false));
+ }
+
+ @Override
+ public List getMenus(Long userId) {
+ return getMenus(getRoles(userId));
+ }
+
+ @Override
+ public List getMenus(String username) {
+ BaseSysUserEntity sysUserEntity = baseSysUserMapper
+ .selectOneByQuery(QueryWrapper.create().eq(BaseSysUserEntity::getUsername, username));
+ return getMenus(sysUserEntity.getId());
+ }
+
+ @Override
+ public String[] getAllPerms() {
+ return getPerms((Long[]) null);
+ }
+
+ @Override
+ public Object permmenu(Long adminUserId) {
+ return Dict.create().set("menus", getMenus(adminUserId)).set("perms", getPerms(adminUserId));
+ }
+
+ @Override
+ public void updatePerms(Long roleId, Long[] menuIdList, Long[] departmentIds) {
+ // 更新菜单权限
+ baseSysRoleMenuMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysRoleMenuEntity::getRoleId, roleId));
+ for (Long menuId : menuIdList) {
+ BaseSysRoleMenuEntity roleMenuEntity = new BaseSysRoleMenuEntity();
+ roleMenuEntity.setRoleId(roleId);
+ roleMenuEntity.setMenuId(menuId);
+ baseSysRoleMenuMapper.insert(roleMenuEntity);
+ }
+ // 更新部门权限
+ baseSysRoleDepartmentMapper
+ .deleteByQuery(QueryWrapper.create().eq(BaseSysRoleDepartmentEntity::getRoleId, roleId));
+ for (Long departmentId : departmentIds) {
+ BaseSysRoleDepartmentEntity roleDepartmentEntity = new BaseSysRoleDepartmentEntity();
+ roleDepartmentEntity.setRoleId(roleId);
+ roleDepartmentEntity.setDepartmentId(departmentId);
+ baseSysRoleDepartmentMapper.insert(roleDepartmentEntity);
+ }
+ // 刷新对应角色用户的权限
+ List userRoles = baseSysUserRoleMapper
+ .selectListByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getRoleId, roleId));
+ for (BaseSysUserRoleEntity userRole : userRoles) {
+ refreshPerms(userRole.getUserId());
+ }
+ }
+
+ @Override
+ public void updateUserRole(Long userId, Long[] roleIdList) {
+ baseSysUserRoleMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId));
+ if (roleIdList == null) {
+ roleIdList = new Long[0];
+ }
+ for (Long roleId : roleIdList) {
+ BaseSysUserRoleEntity sysUserRoleEntity = new BaseSysUserRoleEntity();
+ sysUserRoleEntity.setRoleId(roleId);
+ sysUserRoleEntity.setUserId(userId);
+ baseSysUserRoleMapper.insert(sysUserRoleEntity);
+ }
+ refreshPerms(userId);
+ }
+
+ @Override
+ public void refreshPerms(Long userId) {
+ BaseSysUserEntity baseSysUserEntity = baseSysUserMapper.selectOneById(userId);
+ if (baseSysUserEntity != null && baseSysUserEntity.getStatus() != 0) {
+ SpringContextUtils.getBean(UserDetailsService.class).loadUserByUsername(baseSysUserEntity.getUsername());
+ }
+ if (baseSysUserEntity != null && baseSysUserEntity.getStatus() == 0) {
+ coolSecurityUtil.logout(baseSysUserEntity.getId(), baseSysUserEntity.getUsername());
+ }
+ }
+
+ @Async
+ @Override
+ public void refreshPermsByMenuId(Long menuId) {
+ // 刷新超管权限、 找出这个菜单的所有用户、 刷新用户权限
+ BaseSysUserEntity admin = baseSysUserMapper
+ .selectOneByQuery(QueryWrapper.create().eq(BaseSysUserEntity::getUsername, "admin"));
+ refreshPerms(admin.getId());
+ List list = baseSysRoleMenuMapper.selectRowsByQuery(QueryWrapper.create().select(BASE_SYS_USER_ROLE_ENTITY.USER_ID)
+ .from(BASE_SYS_ROLE_MENU_ENTITY).leftJoin(BASE_SYS_USER_ROLE_ENTITY)
+ .on(BASE_SYS_ROLE_MENU_ENTITY.ROLE_ID.eq(BASE_SYS_USER_ROLE_ENTITY.ROLE_ID)).and(BASE_SYS_ROLE_MENU_ENTITY.MENU_ID.eq(menuId, ObjectUtil.isNotEmpty(menuId))).groupBy(BASE_SYS_USER_ROLE_ENTITY.USER_ID));
+ for (Row row : list) {
+ refreshPerms(row.getLong("userId"));
+ }
+ }
+
+ @Override
+ public void refreshPermsByRoleId(Long roleId) {
+ // 找出角色对应的所有用户
+ List list = baseSysUserRoleMapper
+ .selectListByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getRoleId, roleId));
+ list.forEach(e -> {
+ refreshPerms(e.getUserId());
+ });
+ }
+}
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysRoleServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysRoleServiceImpl.java
new file mode 100644
index 0000000..8c5a762
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysRoleServiceImpl.java
@@ -0,0 +1,92 @@
+package com.cool.modules.base.service.sys.impl;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.base.ModifyEnum;
+import com.cool.core.exception.CoolException;
+import com.cool.modules.base.entity.sys.BaseSysRoleDepartmentEntity;
+import com.cool.modules.base.entity.sys.BaseSysRoleEntity;
+import com.cool.modules.base.entity.sys.BaseSysRoleMenuEntity;
+import com.cool.modules.base.mapper.sys.BaseSysRoleDepartmentMapper;
+import com.cool.modules.base.mapper.sys.BaseSysRoleMapper;
+import com.cool.modules.base.mapper.sys.BaseSysRoleMenuMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.cool.modules.base.service.sys.BaseSysRoleService;
+import com.mybatisflex.core.query.QueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 系统角色
+ */
+@RequiredArgsConstructor
+@Service
+public class BaseSysRoleServiceImpl extends BaseServiceImpl
+ implements BaseSysRoleService {
+
+ final private BaseSysRoleMapper baseSysRoleMapper;
+
+ final private BaseSysRoleMenuMapper baseSysRoleMenuMapper;
+
+ final private BaseSysRoleDepartmentMapper baseSysRoleDepartmentMapper;
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ @Override
+ public Object add(JSONObject requestParams, BaseSysRoleEntity entity) {
+ BaseSysRoleEntity checkLabel = getOne(QueryWrapper.create().eq(BaseSysRoleEntity::getLabel, entity.getLabel()));
+ if (checkLabel != null) {
+ throw new CoolException("标识已存在");
+ }
+ entity.setUserId((coolSecurityUtil.userInfo(requestParams).getLong("userId")));
+ return super.add(requestParams, entity);
+ }
+
+ @Override
+ public Object info(Long id) {
+ BaseSysRoleEntity roleEntity = getById(id);
+ Long[] menuIdList = new Long[0];
+ Long[] departmentIdList = new Long[0];
+ if (roleEntity != null) {
+ List list = baseSysRoleMenuMapper
+ .selectListByQuery(QueryWrapper.create().eq(BaseSysRoleMenuEntity::getRoleId, id, !id.equals(1L)));
+ menuIdList = list.stream().map(BaseSysRoleMenuEntity::getMenuId).toArray(Long[]::new);
+
+ List departmentEntities = baseSysRoleDepartmentMapper.selectListByQuery(
+ QueryWrapper.create().eq(BaseSysRoleDepartmentEntity::getRoleId, id, !id.equals(1L)));
+
+ departmentIdList = departmentEntities.stream().map(BaseSysRoleDepartmentEntity::getDepartmentId)
+ .toArray(Long[]::new);
+ }
+ return Dict.parse(roleEntity).set("menuIdList", menuIdList).set("departmentIdList", departmentIdList);
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, BaseSysRoleEntity baseSysRoleEntity, ModifyEnum type) {
+ if (type == ModifyEnum.DELETE) {
+ Long[] ids = requestParams.get("ids", Long[].class);
+ for (Long id : ids) {
+ baseSysPermsService.refreshPermsByRoleId(id);
+ }
+ } else {
+ baseSysPermsService.updatePerms(baseSysRoleEntity.getId(), requestParams.get("menuIdList", Long[].class),
+ requestParams.get("departmentIdList", Long[].class));
+ }
+ }
+
+ @Override
+ public Object list(JSONObject requestParams, QueryWrapper queryWrapper) {
+ return baseSysRoleMapper.selectListByQuery(queryWrapper.ne(BaseSysRoleEntity::getId, 1L).and(qw -> {
+ qw.eq(BaseSysRoleEntity::getUserId, coolSecurityUtil.userInfo(requestParams).get("userId")).or(w -> {
+ w.in(BaseSysRoleEntity::getId,
+ (Object) coolSecurityUtil.userInfo(requestParams).get("roleIds", Long[].class));
+ });
+ }, !coolSecurityUtil.username().equals("admin")));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java
new file mode 100644
index 0000000..7cac719
--- /dev/null
+++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java
@@ -0,0 +1,161 @@
+package com.cool.modules.base.service.sys.impl;
+
+import static com.cool.modules.base.entity.sys.table.BaseSysDepartmentEntityTableDef.BASE_SYS_DEPARTMENT_ENTITY;
+import static com.cool.modules.base.entity.sys.table.BaseSysRoleEntityTableDef.BASE_SYS_ROLE_ENTITY;
+import static com.cool.modules.base.entity.sys.table.BaseSysUserEntityTableDef.BASE_SYS_USER_ENTITY;
+import static com.cool.modules.base.entity.sys.table.BaseSysUserRoleEntityTableDef.BASE_SYS_USER_ROLE_ENTITY;
+import static com.mybatisflex.core.query.QueryMethods.groupConcat;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.MD5;
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.base.ModifyEnum;
+import com.cool.core.cache.CoolCache;
+import com.cool.core.exception.CoolPreconditions;
+import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.mapper.sys.BaseSysDepartmentMapper;
+import com.cool.modules.base.mapper.sys.BaseSysUserMapper;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.base.service.sys.BaseSysPermsService;
+import com.cool.modules.base.service.sys.BaseSysUserService;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.update.UpdateChain;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统用户
+ */
+@Service
+@RequiredArgsConstructor
+public class BaseSysUserServiceImpl extends BaseServiceImpl
+ implements BaseSysUserService {
+
+ final private CoolCache coolCache;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ final private BaseSysPermsService baseSysPermsService;
+
+ final private BaseSysDepartmentMapper baseSysDepartmentMapper;
+
+ @Override
+ public Object page(JSONObject requestParams, Page page, QueryWrapper qw) {
+ String keyWord = requestParams.getStr("keyWord");
+ Integer status = requestParams.getInt("status");
+ Long[] departmentIds = requestParams.get("departmentIds", Long[].class);
+ JSONObject tokenInfo = coolSecurityUtil.userInfo(requestParams);
+ // 用户的部门权限
+ Long[] permsDepartmentArr = coolCache.get("admin:department:" + tokenInfo.get("userId"),
+ Long[].class);
+
+ qw.select(BASE_SYS_USER_ENTITY.ALL_COLUMNS,
+ groupConcat(BASE_SYS_ROLE_ENTITY.NAME).as("roleName"),
+ BASE_SYS_DEPARTMENT_ENTITY.NAME.as("departmentName"))
+ .from(BASE_SYS_USER_ENTITY).leftJoin(BASE_SYS_USER_ROLE_ENTITY)
+ .on(BASE_SYS_USER_ENTITY.ID.eq(BASE_SYS_USER_ROLE_ENTITY.USER_ID))
+ .leftJoin(BASE_SYS_ROLE_ENTITY)
+ .on(BASE_SYS_USER_ROLE_ENTITY.USER_ID.eq(BASE_SYS_ROLE_ENTITY.ID))
+ .leftJoin(BASE_SYS_DEPARTMENT_ENTITY)
+ .on(BASE_SYS_USER_ENTITY.DEPARTMENT_ID.eq(BASE_SYS_DEPARTMENT_ENTITY.ID));
+
+ // 不显示admin用户
+ qw.and(BASE_SYS_USER_ENTITY.USERNAME.ne("admin"));
+ // 筛选部门
+ qw.and(BASE_SYS_USER_ENTITY.DEPARTMENT_ID.in(departmentIds,
+ ArrayUtil.isNotEmpty(departmentIds)));
+ // 筛选状态
+ qw.and(BASE_SYS_USER_ENTITY.STATUS.eq(status, status != null));
+ // 搜索关键字
+ if (StrUtil.isNotEmpty(keyWord)) {
+ qw.and(BASE_SYS_USER_ENTITY.NAME.like(keyWord)
+ .or(BASE_SYS_USER_ENTITY.USERNAME.like(keyWord)));
+ }
+ // 过滤部门权限
+ qw.and(BASE_SYS_USER_ENTITY.DEPARTMENT_ID.in(
+ permsDepartmentArr == null || permsDepartmentArr.length == 0 ? new Long[]{null}
+ : permsDepartmentArr,
+ !coolSecurityUtil.username().equals("admin")));
+
+ qw.groupBy(BASE_SYS_USER_ENTITY.ID);
+ return mapper.paginateWithRelations(page, qw);
+ }
+
+ @Override
+ public void personUpdate(Long userId, Dict body) {
+ BaseSysUserEntity userEntity = getById(userId);
+ CoolPreconditions.checkEmpty(userEntity, "用户不存在");
+ userEntity.setNickName(body.getStr("nickName"));
+ // 修改密码
+ if (StrUtil.isNotEmpty(body.getStr("password"))) {
+ userEntity.setPassword(MD5.create().digestHex(body.getStr("password")));
+ userEntity.setPasswordV(userEntity.getPasswordV() + 1);
+ coolCache.set("admin:passwordVersion:" + userId, userEntity.getPasswordV());
+ }
+ updateById(userEntity);
+ }
+
+ @Override
+ public void move(Long departmentId, Long[] userIds) {
+ UpdateChain.of(BaseSysUserEntity.class)
+ .set(BaseSysUserEntity::getDepartmentId, departmentId)
+ .in(BaseSysUserEntity::getId, (Object) userIds).update();
+ }
+
+ @Override
+ public Long add(JSONObject requestParams, BaseSysUserEntity entity) {
+ BaseSysUserEntity check = getOne(
+ QueryWrapper.create().eq(BaseSysUserEntity::getUsername, entity.getUsername()));
+ CoolPreconditions.check(check != null, "用户名已存在");
+ entity.setPassword(MD5.create().digestHex(entity.getPassword()));
+ super.add(requestParams, entity);
+ return entity.getId();
+ }
+
+ @Override
+ public boolean update(JSONObject requestParams, BaseSysUserEntity entity) {
+ CoolPreconditions.check(
+ StrUtil.isNotEmpty(entity.getUsername()) && entity.getUsername().equals("admin"),
+ "非法操作");
+ BaseSysUserEntity userEntity = getById(entity.getId());
+ if (StrUtil.isNotEmpty(entity.getPassword())) {
+ entity.setPasswordV(entity.getPasswordV() + 1);
+ entity.setPassword(MD5.create().digestHex(entity.getPassword()));
+ coolCache.set("admin:passwordVersion:" + entity.getId(), entity.getPasswordV());
+ } else {
+ entity.setPassword(userEntity.getPassword());
+ entity.setPasswordV(userEntity.getPasswordV());
+ }
+ // 被禁用
+ if (entity.getStatus() == 0) {
+ coolSecurityUtil.logout(entity);
+ }
+ return super.update(requestParams, entity);
+ }
+
+ @Override
+ public void modifyAfter(JSONObject requestParams, BaseSysUserEntity baseSysUserEntity,
+ ModifyEnum type) {
+ if (type != ModifyEnum.DELETE && requestParams.get("roleIdList", Long[].class) != null) {
+ // 刷新权限
+ baseSysPermsService.updateUserRole(baseSysUserEntity.getId(),
+ requestParams.get("roleIdList", Long[].class));
+ }
+ }
+
+ @Override
+ public Object info(Long id) {
+ BaseSysUserEntity userEntity = getById(id);
+ Long[] roleIdList = baseSysPermsService.getRoles(id);
+ BaseSysDepartmentEntity departmentEntity = baseSysDepartmentMapper.selectOneById(
+ userEntity.getDepartmentId());
+ userEntity.setPassword(null);
+ return Dict.parse(userEntity).set("roleIdList", roleIdList).set("departmentName",
+ departmentEntity != null ? departmentEntity.getName() : null);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java
new file mode 100644
index 0000000..6ad3483
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java
@@ -0,0 +1,37 @@
+package com.cool.modules.dict.controller.admin;
+
+import cn.hutool.core.lang.Dict;
+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.dict.entity.DictInfoEntity;
+import com.cool.modules.dict.entity.table.DictInfoEntityTableDef;
+import com.cool.modules.dict.service.DictInfoService;
+import com.mybatisflex.core.query.QueryWrapper;
+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;
+
+/**
+ * 字典信息
+ */
+@Tag(name = "字典信息", description = "字典信息")
+@CoolRestController(api = { "add", "delete", "update", "page", "list", "info" })
+public class AdminDictInfoController extends BaseController {
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ setListOption(createOp().fieldEq(DictInfoEntityTableDef.DICT_INFO_ENTITY.TYPE_ID)
+ .keyWordLikeFields(DictInfoEntityTableDef.DICT_INFO_ENTITY.NAME)
+ .queryWrapper(QueryWrapper.create().orderBy(DictInfoEntityTableDef.DICT_INFO_ENTITY.CREATE_TIME, false)));
+ }
+
+ @Operation(summary = "获得字典数据", description = "获得字典数据信息")
+ @PostMapping("/data")
+ public R data(@RequestBody Dict body) {
+ return R.ok(this.service.data(body.get("types", null)));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java
new file mode 100644
index 0000000..0c9f791
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java
@@ -0,0 +1,25 @@
+package com.cool.modules.dict.controller.admin;
+
+import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.base.BaseController;
+import com.cool.modules.dict.entity.DictTypeEntity;
+import com.cool.modules.dict.service.DictTypeService;
+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 AdminDictTypeController extends BaseController {
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ setPageOption(
+ createOp().select(DICT_TYPE_ENTITY.ID, DICT_TYPE_ENTITY.KEY, DICT_TYPE_ENTITY.NAME));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/dict/controller/app/AppDictInfoController.java b/src/main/java/com/cool/modules/dict/controller/app/AppDictInfoController.java
new file mode 100644
index 0000000..bad4ba8
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/controller/app/AppDictInfoController.java
@@ -0,0 +1,34 @@
+package com.cool.modules.dict.controller.app;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+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.dict.entity.DictInfoEntity;
+import com.cool.modules.dict.service.DictInfoService;
+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;
+
+/**
+ * 字典信息
+ */
+@Tag(name = "字典信息", description = "字典信息")
+@CoolRestController(api = {})
+public class AppDictInfoController extends BaseController {
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+
+ }
+
+ @Operation(summary = "获得字典数据", description = "获得字典数据信息")
+ @PostMapping("/data")
+ public R data(@RequestBody Dict body) {
+ return R.ok(this.service.data(Convert.toList(String.class, body.get("types"))));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/dict/entity/DictInfoEntity.java b/src/main/java/com/cool/modules/dict/entity/DictInfoEntity.java
new file mode 100644
index 0000000..ff0c944
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/entity/DictInfoEntity.java
@@ -0,0 +1,32 @@
+package com.cool.modules.dict.entity;
+
+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 = "dict_info", comment = "字典信息")
+public class DictInfoEntity extends BaseEntity {
+
+ @ColumnDefine(comment = "类型ID", notNull = true)
+ private Long typeId;
+
+ @ColumnDefine(comment = "父ID")
+ private Long parentId;
+
+ @ColumnDefine(comment = "名称", notNull = true)
+ private String name;
+
+ @ColumnDefine(comment = "值")
+ private String value;
+
+ @ColumnDefine(comment = "排序", defaultValue = "0")
+ private Integer orderNum;
+
+ @ColumnDefine(comment = "备注")
+ private String remark;
+
+}
diff --git a/src/main/java/com/cool/modules/dict/entity/DictTypeEntity.java b/src/main/java/com/cool/modules/dict/entity/DictTypeEntity.java
new file mode 100644
index 0000000..35a2fd2
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/entity/DictTypeEntity.java
@@ -0,0 +1,21 @@
+package com.cool.modules.dict.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 = "dict_type", comment = "字典类型")
+public class DictTypeEntity extends BaseEntity {
+
+ @ColumnDefine(comment = "名称", notNull = true)
+ private String name;
+
+ @ColumnDefine(comment = "标识", notNull = true)
+ @UniIndex
+ private String key;
+}
diff --git a/src/main/java/com/cool/modules/dict/mapper/DictInfoMapper.java b/src/main/java/com/cool/modules/dict/mapper/DictInfoMapper.java
new file mode 100644
index 0000000..6c79be5
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/mapper/DictInfoMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.dict.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.dict.entity.DictInfoEntity;
+
+/**
+ * 字典信息
+ */
+public interface DictInfoMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/cool/modules/dict/mapper/DictTypeMapper.java b/src/main/java/com/cool/modules/dict/mapper/DictTypeMapper.java
new file mode 100644
index 0000000..1c01714
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/mapper/DictTypeMapper.java
@@ -0,0 +1,10 @@
+package com.cool.modules.dict.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.dict.entity.DictTypeEntity;
+
+/**
+ * 字典类型
+ */
+public interface DictTypeMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/cool/modules/dict/service/DictInfoService.java b/src/main/java/com/cool/modules/dict/service/DictInfoService.java
new file mode 100644
index 0000000..7b5db8a
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/service/DictInfoService.java
@@ -0,0 +1,19 @@
+package com.cool.modules.dict.service;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.dict.entity.DictInfoEntity;
+
+import java.util.List;
+
+/**
+ * 字典信息
+ */
+public interface DictInfoService extends BaseService {
+ /**
+ * 字典数据
+ *
+ * @param types 字典类型
+ * @return
+ */
+ Object data(List types);
+}
diff --git a/src/main/java/com/cool/modules/dict/service/DictTypeService.java b/src/main/java/com/cool/modules/dict/service/DictTypeService.java
new file mode 100644
index 0000000..0c17f82
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/service/DictTypeService.java
@@ -0,0 +1,10 @@
+package com.cool.modules.dict.service;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.dict.entity.DictTypeEntity;
+
+/**
+ * 字典类型
+ */
+public interface DictTypeService extends BaseService {
+}
diff --git a/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java b/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java
new file mode 100644
index 0000000..11caced
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java
@@ -0,0 +1,100 @@
+package com.cool.modules.dict.service.impl;
+
+import static com.cool.modules.dict.entity.table.DictInfoEntityTableDef.DICT_INFO_ENTITY;
+import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.modules.dict.entity.DictInfoEntity;
+import com.cool.modules.dict.entity.DictTypeEntity;
+import com.cool.modules.dict.mapper.DictInfoMapper;
+import com.cool.modules.dict.mapper.DictTypeMapper;
+import com.cool.modules.dict.service.DictInfoService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 字典信息
+ */
+@Service
+@RequiredArgsConstructor
+public class DictInfoServiceImpl extends BaseServiceImpl implements
+ DictInfoService {
+
+ final private DictTypeMapper dictTypeMapper;
+
+ @Override
+ public Object data(List types) {
+ Dict result = Dict.create();
+ QueryWrapper find = QueryWrapper.create();
+ find.select(DICT_TYPE_ENTITY.ID, DICT_TYPE_ENTITY.KEY,
+ DICT_TYPE_ENTITY.NAME);
+ if (CollectionUtil.isNotEmpty(types)) {
+ find.and(DICT_TYPE_ENTITY.KEY.in(types));
+ }
+ List typeData = dictTypeMapper.selectListByQuery(find);
+ if (typeData.isEmpty()) {
+ return result;
+ }
+ List infos = this.list(QueryWrapper.create()
+ .select(DictInfoEntity::getId, DictInfoEntity::getName, DictInfoEntity::getTypeId,
+ DictInfoEntity::getParentId, DictInfoEntity::getValue)
+ .in(DictInfoEntity::getTypeId,
+ typeData.stream().map(DictTypeEntity::getId).collect(Collectors.toList()))
+ .orderBy(DICT_INFO_ENTITY.ORDER_NUM.getName(), DICT_INFO_ENTITY.CREATE_TIME.getName()));
+ typeData.forEach(item -> {
+ List datas = new ArrayList<>();
+ infos.stream().filter(d -> d.getTypeId().equals(item.getId())).toList().forEach(d -> {
+ Dict data = Dict.create();
+ data.set("typeId", d.getTypeId());
+ data.set("parentId", d.getParentId());
+ data.set("name", d.getName());
+ data.set("id", d.getId());
+ data.set("value", StrUtil.isEmpty(d.getValue()) ? null : d.getValue());
+ try {
+ data.set("value", Integer.parseInt(d.getValue()));
+ } catch (Exception ignored) {
+ }
+ datas.add(data);
+ });
+ result.set(item.getKey(), datas);
+ });
+ return result;
+ }
+
+ @Override
+ public boolean delete(Long... ids) {
+ super.delete(ids);
+ for (Long id : ids) {
+ this.delDictChild(id);
+ }
+ return true;
+ }
+
+ /**
+ * 删除子菜单
+ *
+ * @param id 删除的菜单ID
+ */
+ private void delDictChild(Long id) {
+ List delDict = list(
+ QueryWrapper.create().eq(DictInfoEntity::getParentId, id));
+ if (CollectionUtil.isEmpty(delDict)) {
+ return;
+ }
+ Long[] ids = delDict.stream().map(DictInfoEntity::getId).toArray(Long[]::new);
+ if (ArrayUtil.isNotEmpty(ids)) {
+ delete(ids);
+ for (Long delId : ids) {
+ this.delDictChild(delId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/dict/service/impl/DictTypeServiceImpl.java b/src/main/java/com/cool/modules/dict/service/impl/DictTypeServiceImpl.java
new file mode 100644
index 0000000..3655b2b
--- /dev/null
+++ b/src/main/java/com/cool/modules/dict/service/impl/DictTypeServiceImpl.java
@@ -0,0 +1,39 @@
+package com.cool.modules.dict.service.impl;
+
+import static com.cool.modules.dict.entity.table.DictInfoEntityTableDef.DICT_INFO_ENTITY;
+import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY;
+
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.modules.dict.entity.DictTypeEntity;
+import com.cool.modules.dict.mapper.DictInfoMapper;
+import com.cool.modules.dict.mapper.DictTypeMapper;
+import com.cool.modules.dict.service.DictTypeService;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 字典类型
+ */
+@Service
+@RequiredArgsConstructor
+public class DictTypeServiceImpl extends BaseServiceImpl implements
+ DictTypeService {
+
+ final private DictInfoMapper dictInfoMapper;
+
+ @Override
+ public List list(QueryWrapper queryWrapper) {
+ return super.list(
+ queryWrapper.select(DICT_TYPE_ENTITY.ID, DICT_TYPE_ENTITY.KEY,
+ DICT_TYPE_ENTITY.NAME));
+ }
+
+ @Override
+ public boolean delete(Long... ids) {
+ super.delete(ids);
+ return dictInfoMapper.deleteByQuery(
+ QueryWrapper.create().and(DICT_INFO_ENTITY.TYPE_ID.in((Object) ids))) > 0;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java
new file mode 100644
index 0000000..7ca7b42
--- /dev/null
+++ b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java
@@ -0,0 +1,72 @@
+package com.cool.modules.plugin.controller.admin;
+
+import static com.cool.modules.plugin.entity.table.PluginInfoEntityTableDef.PLUGIN_INFO_ENTITY;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.CoolRestController;
+import com.cool.core.annotation.IgnoreRecycleData;
+import com.cool.core.base.BaseController;
+import com.cool.core.plugin.service.CoolPluginService;
+import com.cool.core.request.R;
+import com.cool.core.util.EntityUtils;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import com.cool.modules.plugin.service.PluginInfoService;
+import com.mybatisflex.core.query.QueryWrapper;
+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.Map;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+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.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+@Tag(name = "插件信息", description = "插件信息")
+@CoolRestController(api = {"add", "delete", "update", "page", "list", "info"})
+@RequiredArgsConstructor
+public class AdminPluginInfoController extends BaseController {
+
+ final private CoolPluginService coolPluginService;
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+
+ setPageOption(createOp().queryWrapper(
+ QueryWrapper.create().orderBy(PLUGIN_INFO_ENTITY.UPDATE_TIME, false))
+ .select(EntityUtils.getFieldNamesWithSuperClass(PLUGIN_INFO_ENTITY.DEFAULT_COLUMNS,
+ PLUGIN_INFO_ENTITY.JAR_FILE.getName())));
+ }
+
+ @Override
+ @Operation(summary = "修改", description = "根据ID修改")
+ @PostMapping("/update")
+ protected R update(@RequestBody PluginInfoEntity t,
+ @RequestAttribute() JSONObject requestParams) {
+ coolPluginService.updatePlugin(t);
+ return R.ok();
+ }
+
+ @Operation(summary = "安装插件")
+ @PostMapping(value = "/install", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R install(
+ @RequestParam(value = "files") @Parameter(description = "文件") MultipartFile[] files,
+ @RequestParam(value = "force") @Parameter(description = "是否强制安装") boolean force) {
+ coolPluginService.install(files[0], force);
+ return R.ok();
+ }
+
+ @Override
+ @Operation(summary = "卸载插件")
+ @PostMapping("/delete")
+ @IgnoreRecycleData()
+ public R delete(HttpServletRequest request, @RequestBody Map params,
+ @RequestAttribute() JSONObject requestParams) {
+ coolPluginService.uninstall(Convert.toLongArray(getIds(params))[0]);
+ return R.ok();
+ }
+}
diff --git a/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java b/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java
new file mode 100644
index 0000000..5a9cbcb
--- /dev/null
+++ b/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java
@@ -0,0 +1,68 @@
+package com.cool.modules.plugin.entity;
+
+import com.cool.core.base.BaseEntity;
+import com.cool.core.config.PluginJson;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.handler.Fastjson2TypeHandler;
+import com.tangzc.autotable.annotation.Ignore;
+import com.tangzc.autotable.annotation.Index;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import com.tangzc.mybatisflex.autotable.annotation.UniIndex;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Table(value = "plugin_info", comment = "插件信息")
+public class PluginInfoEntity extends BaseEntity {
+
+ @ColumnDefine(comment = "名称")
+ private String name;
+
+ @ColumnDefine(comment = "简介")
+ private String description;
+
+ @UniIndex
+ @ColumnDefine(comment = "实例对象")
+ private String key;
+
+ @Index
+ @ColumnDefine(comment = "Hook", length = 50)
+ private String hook;
+
+ @ColumnDefine(comment = "描述", type = "text")
+ private String readme;
+
+ @ColumnDefine(comment = "版本")
+ private String version;
+
+ @ColumnDefine(comment = "Logo(base64)", type = "text", notNull = true)
+ private String logo;
+
+ @ColumnDefine(comment = "作者")
+ private String author;
+
+ @ColumnDefine(comment = "状态 0-禁用 1-启用", defaultValue = "1")
+ private Integer status;
+
+ @ColumnDefine(comment = "插件的plugin.json", type = "json", notNull = true)
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private PluginJson pluginJson;
+
+ @ColumnDefine(comment = "jar二进制文件", type = "longblob", notNull = true)
+ private byte[] jarFile;
+
+ @ColumnDefine(comment = "配置", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private Map config;
+
+ @Ignore
+ @Column(ignore = true)
+ public String keyName;
+
+ public String getKeyName() {
+ return key;
+ }
+}
diff --git a/src/main/java/com/cool/modules/plugin/mapper/PluginInfoMapper.java b/src/main/java/com/cool/modules/plugin/mapper/PluginInfoMapper.java
new file mode 100644
index 0000000..c38e8d7
--- /dev/null
+++ b/src/main/java/com/cool/modules/plugin/mapper/PluginInfoMapper.java
@@ -0,0 +1,8 @@
+package com.cool.modules.plugin.mapper;
+
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import com.mybatisflex.core.BaseMapper;
+
+public interface PluginInfoMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java b/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java
new file mode 100644
index 0000000..a8291d7
--- /dev/null
+++ b/src/main/java/com/cool/modules/plugin/service/PluginInfoService.java
@@ -0,0 +1,12 @@
+package com.cool.modules.plugin.service;
+
+import com.cool.core.base.BaseService;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+
+public interface PluginInfoService extends BaseService {
+ PluginInfoEntity getByKeyNoJarFile(String key);
+
+ PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook);
+
+ PluginInfoEntity getPluginInfoEntityByIdNoJarFile(Long id);
+}
diff --git a/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java b/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java
new file mode 100644
index 0000000..a18bcc8
--- /dev/null
+++ b/src/main/java/com/cool/modules/plugin/service/impl/PluginInfoServiceImpl.java
@@ -0,0 +1,55 @@
+package com.cool.modules.plugin.service.impl;
+
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.util.EntityUtils;
+import com.cool.modules.plugin.entity.PluginInfoEntity;
+import com.cool.modules.plugin.mapper.PluginInfoMapper;
+import com.cool.modules.plugin.service.PluginInfoService;
+import com.mybatisflex.core.query.QueryWrapper;
+import org.springframework.stereotype.Service;
+
+import static com.cool.modules.plugin.entity.table.PluginInfoEntityTableDef.PLUGIN_INFO_ENTITY;
+
+/**
+ * 插件信息服务类
+ */
+@Service
+public class PluginInfoServiceImpl extends BaseServiceImpl
+ implements PluginInfoService {
+
+ /**
+ * 通过key获取插件信息,不带jar二进制
+ */
+ @Override
+ public PluginInfoEntity getByKeyNoJarFile(String key) {
+ QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper();
+ queryWrapper.and(PLUGIN_INFO_ENTITY.KEY.eq(key));
+ return getOne(queryWrapper);
+ }
+
+ /**
+ * 通过hook获取插件信息,不带jar二进制
+ */
+ @Override
+ public PluginInfoEntity getPluginInfoEntityByHookNoJarFile(String hook) {
+ QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper().and(PLUGIN_INFO_ENTITY.HOOK.eq(hook))
+ .and(PLUGIN_INFO_ENTITY.STATUS.eq(1)).limit(1);
+ return getOne(queryWrapper);
+ }
+
+ /**
+ * 通过id获取插件信息,不带jar二进制
+ */
+ @Override
+ public PluginInfoEntity getPluginInfoEntityByIdNoJarFile(Long id) {
+ QueryWrapper queryWrapper = getPluginInfoEntityQueryWrapper().and(PLUGIN_INFO_ENTITY.ID.eq(id));
+ return getOne(queryWrapper);
+ }
+
+ /**
+ * 获取查询对象,排除掉 jar二进制
+ */
+ private QueryWrapper getPluginInfoEntityQueryWrapper() {
+ return QueryWrapper.create().select(EntityUtils.getFieldNamesWithSuperClass(PLUGIN_INFO_ENTITY.DEFAULT_COLUMNS, PLUGIN_INFO_ENTITY.JAR_FILE.getName()));
+ }
+}
diff --git a/src/main/java/com/cool/modules/recycle/aop/AopConfig.java b/src/main/java/com/cool/modules/recycle/aop/AopConfig.java
new file mode 100644
index 0000000..e44708d
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/aop/AopConfig.java
@@ -0,0 +1,15 @@
+//package com.cool.modules.recycle.aop;
+//
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.context.annotation.EnableAspectJAutoProxy;
+//
+//@Configuration
+//@EnableAspectJAutoProxy
+//public class AopConfig {
+//
+// @Bean
+// public DeleteAspect deleteAspect() {
+// return new DeleteAspect();
+// }
+//}
diff --git a/src/main/java/com/cool/modules/recycle/aop/DeleteAspect.java b/src/main/java/com/cool/modules/recycle/aop/DeleteAspect.java
new file mode 100644
index 0000000..ad9f10f
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/aop/DeleteAspect.java
@@ -0,0 +1,114 @@
+package com.cool.modules.recycle.aop;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.json.JSONObject;
+import com.cool.core.annotation.IgnoreRecycleData;
+import com.cool.core.base.BaseController;
+import com.cool.core.base.BaseService;
+import com.cool.modules.base.security.CoolSecurityUtil;
+import com.cool.modules.recycle.entity.RecycleDataEntity;
+import com.cool.modules.recycle.service.RecycleDataService;
+import com.mybatisflex.core.query.QueryWrapper;
+import jakarta.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+/**
+ * 数据删除前拦截
+ */
+@Aspect
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class DeleteAspect {
+
+ final private RecycleDataService recycleDataService;
+
+ final private CoolSecurityUtil coolSecurityUtil;
+
+ @Around(value = "execution(* com.cool.core.base.BaseController.delete*(..)) && args(request, params, requestParams)", argNames = "joinPoint,request,params,requestParams")
+ public Object aroundAdvice(ProceedingJoinPoint joinPoint, HttpServletRequest request,
+ Map params,
+ JSONObject requestParams) throws Throwable {
+ Method currentMethod = getCurrentMethod(joinPoint);
+ if (Objects.nonNull(currentMethod) && currentMethod.isAnnotationPresent(
+ IgnoreRecycleData.class)) {
+ // 忽略回收站记录
+ return joinPoint.proceed();
+ }
+ List list = null;
+ String className = null;
+ try {
+ log.info("数据删除前拦截");
+ // 可以在目标方法执行前进行一些操作
+ BaseController baseController = (BaseController) joinPoint.getTarget();
+ BaseService service = baseController.getService();
+ className = (baseController.currentEntityClass()).getName();
+ QueryWrapper queryWrapper = new QueryWrapper();
+ Object ids = params.get("ids");
+ if (!(ids instanceof ArrayList)) {
+ ids = ids.toString().split(",");
+ }
+ List idList = Convert.toList(Long.class, ids);
+ queryWrapper.in("id", (Object) Convert.toLongArray(idList));
+ list = service.list(queryWrapper);
+ } catch (Exception e) {
+ log.error("数据删除前拦截获取数据详情信息失败", e);
+ }
+
+ Object result = joinPoint.proceed();
+ if (ObjUtil.isNotEmpty(list)) {
+ RecycleDataEntity recycleDataEntity = new RecycleDataEntity();
+ recycleDataEntity.setUrl(request.getRequestURI());
+ recycleDataEntity.setUserName(coolSecurityUtil.username());
+ recycleDataEntity
+ .setUserId(Long.parseLong(
+ String.valueOf(coolSecurityUtil.userInfo(requestParams).get("userId"))));
+ recycleDataEntity.setParams(params);
+ recycleDataEntity.setData(list);
+ recycleDataEntity.setParams(params);
+ RecycleDataEntity.EntityInfo entityInfo = new RecycleDataEntity.EntityInfo();
+ entityInfo.setEntityClassName(className);
+ recycleDataEntity.setEntityInfo(entityInfo);
+ recycleDataEntity.setCount(recycleDataEntity.getData().size());
+
+ log.info("数据进入回收站 {}", recycleDataService.add(recycleDataEntity));
+ }
+ return result;
+ }
+
+ private Method getCurrentMethod(JoinPoint joinPoint) {
+ String methodName = joinPoint.getSignature().getName();
+ Object[] args = joinPoint.getArgs();
+ Method[] methods = joinPoint.getTarget().getClass().getMethods();
+
+ for (Method method : methods) {
+ if (method.getName().equals(methodName) && method.getParameterCount() == args.length) {
+ boolean isSameMethod = true;
+ Class>[] parameterTypes = method.getParameterTypes();
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) {
+ isSameMethod = false;
+ break;
+ }
+ }
+ if (isSameMethod) {
+ return method;
+ }
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/cool/modules/recycle/controller/admin/AdminRecycleDataController.java b/src/main/java/com/cool/modules/recycle/controller/admin/AdminRecycleDataController.java
new file mode 100644
index 0000000..9e428e5
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/controller/admin/AdminRecycleDataController.java
@@ -0,0 +1,40 @@
+package com.cool.modules.recycle.controller.admin;
+
+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.recycle.entity.RecycleDataEntity;
+import com.cool.modules.recycle.entity.table.RecycleDataEntityTableDef;
+import com.cool.modules.recycle.service.RecycleDataService;
+import com.mybatisflex.core.query.QueryWrapper;
+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.RequestBody;
+
+import java.util.Map;
+
+
+/**
+ * 数据回收站
+ */
+@Tag(name = "数据回收站", description = "数据回收站")
+@CoolRestController(api = { "add", "delete", "update", "page", "list", "info" })
+@RequiredArgsConstructor
+public class AdminRecycleDataController extends BaseController {
+ final private RecycleDataService recycleDataService;
+
+ @Override
+ protected void init(HttpServletRequest request, JSONObject requestParams) {
+ setListOption(createOp().queryWrapper(QueryWrapper.create().orderBy(RecycleDataEntityTableDef.RECYCLE_DATA_ENTITY.CREATE_TIME, false)));
+ }
+
+ @Operation(summary = "恢复数据", description = "恢复数据")
+ @PostMapping("/restore")
+ public R restore(@RequestBody Map params) {
+ return R.ok(this.service.restore(getIds(params)));
+ }
+}
diff --git a/src/main/java/com/cool/modules/recycle/entity/RecycleDataEntity.java b/src/main/java/com/cool/modules/recycle/entity/RecycleDataEntity.java
new file mode 100644
index 0000000..0704e01
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/entity/RecycleDataEntity.java
@@ -0,0 +1,56 @@
+package com.cool.modules.recycle.entity;
+
+import com.cool.core.base.BaseEntity;
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.handler.Fastjson2TypeHandler;
+import com.tangzc.autotable.annotation.Ignore;
+import com.tangzc.autotable.annotation.Index;
+import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 数据回收站 软删除的时候数据会回收到该表
+ */
+@Getter
+@Setter
+@Table(value = "recycle_data", comment = "数据回收站表")
+public class RecycleDataEntity extends BaseEntity {
+
+ @ColumnDefine(comment = "表信息", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private EntityInfo entityInfo;
+
+ @Index()
+ @ColumnDefine(comment = "操作人", notNull = true)
+ private Long userId;
+
+ @ColumnDefine(comment = "被删除的数据", type = "json")
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private List data;
+
+ @ColumnDefine(comment = "请求的接口", notNull = true)
+ private String url;
+
+ @ColumnDefine(comment = "请求参数", type = "json", notNull = true)
+ @Column(typeHandler = Fastjson2TypeHandler.class)
+ private Map params;
+
+ @ColumnDefine(comment = "删除数据条数", defaultValue = "1")
+ private Integer count;
+
+ @Setter
+ @Getter
+ public static class EntityInfo {
+
+ // entityClassName
+ public String entityClassName;
+ }
+
+ @Ignore
+ @Column(ignore = true) // 操作人名称
+ public String userName;
+}
diff --git a/src/main/java/com/cool/modules/recycle/mapper/RecycleDataMapper.java b/src/main/java/com/cool/modules/recycle/mapper/RecycleDataMapper.java
new file mode 100644
index 0000000..7fef289
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/mapper/RecycleDataMapper.java
@@ -0,0 +1,7 @@
+package com.cool.modules.recycle.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cool.modules.recycle.entity.RecycleDataEntity;
+
+public interface RecycleDataMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/cool/modules/recycle/service/RecycleDataService.java b/src/main/java/com/cool/modules/recycle/service/RecycleDataService.java
new file mode 100644
index 0000000..612584e
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/service/RecycleDataService.java
@@ -0,0 +1,21 @@
+package com.cool.modules.recycle.service;
+
+import cn.hutool.json.JSONObject;
+import com.cool.core.base.BaseService;
+import com.cool.modules.recycle.entity.RecycleDataEntity;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+
+import java.util.List;
+
+public interface RecycleDataService extends BaseService {
+ Object page(JSONObject requestParams, Page page, QueryWrapper queryWrapper);
+
+ /**
+ * 恢复数据
+ *
+ * @param ids
+ * @return
+ */
+ Boolean restore(List ids);
+}
diff --git a/src/main/java/com/cool/modules/recycle/service/impl/RecycleDataServiceImpl.java b/src/main/java/com/cool/modules/recycle/service/impl/RecycleDataServiceImpl.java
new file mode 100644
index 0000000..4af9a84
--- /dev/null
+++ b/src/main/java/com/cool/modules/recycle/service/impl/RecycleDataServiceImpl.java
@@ -0,0 +1,118 @@
+package com.cool.modules.recycle.service.impl;
+
+import cn.hutool.core.util.ClassUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cool.core.base.BaseServiceImpl;
+import com.cool.core.base.service.MapperProviderService;
+import com.cool.modules.base.entity.sys.BaseSysUserEntity;
+import com.cool.modules.base.service.sys.BaseSysUserService;
+import com.cool.modules.recycle.entity.RecycleDataEntity;
+import com.cool.modules.recycle.mapper.RecycleDataMapper;
+import com.cool.modules.recycle.service.RecycleDataService;
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 数据回收站
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class RecycleDataServiceImpl extends BaseServiceImpl
+ implements RecycleDataService {
+
+ final private BaseSysUserService baseSysUserService;
+
+ final private MapperProviderService mapperProviderService;
+
+ @Override
+ public Object page(JSONObject requestParams, Page page,
+ QueryWrapper queryWrapper) {
+ String keyWord = requestParams.getStr("keyWord");
+ if (ObjUtil.isNotEmpty(keyWord)) {
+ List list = baseSysUserService
+ .list(queryWrapper.select(BaseSysUserEntity::getId)
+ .like(BaseSysUserEntity::getName, keyWord))
+ .stream().map(BaseSysUserEntity::getId).toList();
+ queryWrapper.like(RecycleDataEntity::getUrl, keyWord).or(w -> {
+ w.in(RecycleDataEntity::getUserId, list, ObjUtil.isNotEmpty(list));
+ });
+ }
+ Page iPage = page(page, queryWrapper);
+ List records = iPage.getRecords();
+ List