release v7.1

This commit is contained in:
xiaopeng
2024-07-14 21:17:26 +08:00
parent 9425517d6e
commit 14f9897569
195 changed files with 11269 additions and 2 deletions
+37
View File
@@ -0,0 +1,37 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
assets/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
.DS_Store
lib
plugin
+14
View File
@@ -0,0 +1,14 @@
# 使用 GraalVM 17 作为基础镜像
FROM ghcr.io/graalvm/graalvm-ce:latest
# 设置容器内的工作目录
WORKDIR /app
# 将可执行的jar文件复制到容器内
COPY target/cool-admin-7.1.0.jar /app/cool-admin-7.1.0.jar
# 暴露Spring Boot应用程序运行的端口
EXPOSE 8001
# 运行Spring Boot应用程序的命令
ENTRYPOINT ["java", "-jar", "/app/cool-admin-7.1.0.jar", "--spring.profiles.active=prod"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 cool-team-official
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+92 -2
View File
@@ -1,2 +1,92 @@
# cool-admin-java
🔥 cool-admin(java版)一个很酷的后台权限管理框架,Ai编码、流程编排、模块化、插件化、CRUD极速开发,永久开源免费,基于springboot3、typescript、vue3、vite、element-ui等构建 <p align="center">
<a href="https://midwayjs.org/" target="blank"><img src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/logo.png" width="200" alt="Midway Logo" /></a>
</p>
<p align="center">cool-admin(java版)后台权限管理系统,开源免费,Ai编码、流程编排、模块化、插件化,用于快速构建后台应用程序,详情可到<a href="https://cool-admin.com" target="_blank">官网</a> 进一步了解。
<p align="center">
<a href="https://github.com/cool-team-official/cool-admin-midway/blob/master/LICENSE" target="_blank"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="GitHub license" />
<a href=""><img src="https://img.shields.io/github/package-json/v/cool-team-official/cool-admin-midway?style=flat-square" alt="GitHub tag"></a>
<img src="https://img.shields.io/github/last-commit/cool-team-official/cool-admin-midway?style=flat-square" alt="GitHub tag"></a>
</p>
## 技术栈
- 后端:**`Springboot3` `Mybatis-Flex`**
- 前端:**`Vue3` `Vite` `Element-Ui` `Typescript`**
- 数据库:**`Mysql` `Postgresql(适配中)` `Sqlite(适配中)` `...`**
## 特性
Ai时代,很多老旧的框架已经无法满足现代化的开发需求,Cool-Admin开发了一系列的功能,让开发变得更简单、更快速、更高效。
- **Ai编码**:通过微调大模型学习框架特有写法,实现简单功能从Api接口到前端页面的一键生成
- **流程编排**:通过拖拽编排方式,即可实现类似像智能客服这样的功能
- **模块化**:代码是模块化的,清晰明了,方便维护
- **插件化**:插件化的设计,可以通过安装插件的方式扩展如:支付、短信、邮件等功能
![](https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/flow.png)
## 地址
- 官网:[https://cool-admin.com](https://cool-admin.com)
- 文档:[https://java.cool-admin.com](https://java.cool-admin.com)
## 演示
[https://show.cool-admin.com](https://show.cool-admin.com)
- 账户:admin
- 密码:123456
![](https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/home-mini.png)
#### 项目前端
系统是前后端分离的,启动完成后,还需要启动前端项目,前端项目地址:
[https://github.com/cool-team-official/cool-admin-vue](https://github.com/cool-team-official/cool-admin-vue)
或
[https://gitee.com/cool-team-official/cool-admin-vue](https://gitee.com/cool-team-official/cool-admin-vue)
## 微信群
<img width="260" src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/wechat.jpeg?v=1" alt="Admin Wechat"></a>
## 运行
### 环境要求
- Java Graalvm 17+
- Maven 3.6+
### 配置
修改数据库配置,配置文件位于`src/resources/application-local.yml`
以 Mysql 为例,其他数据库请参考[数据库配置文档](https://cool-js.com/admin/node/quick.html#%E6%95%B0%E6%8D%AE%E5%BA%93%E9%85%8D%E7%BD%AE)
Mysql(`>=5.7版本`),建议 8.0,首次启动会自动初始化并导入数据
```yaml
# mysql,驱动已经内置,无需安装
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cool?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
```
### 启动
注:项目使用到了[Mybatis-Flex 的Apt功能](https://mybatis-flex.com/zh/others/apt.html),如果启动报错,请先执行`mvn compile`编译
1、启动文件:`src/main/java/com/cool/CoolApplication.java`
2、启动完成后,访问:[http://localhost:8001](http://localhost:8001)
3、如果看到以下界面,说明启动成功。这时候再启动前端项目即可,数据库会自动初始化,默认账号:admin,密码:123456
![](https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/run.png)
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations/src/main/java" isTestSource="false" />
</content>
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" />
</content>
</component>
</module>
+40
View File
@@ -0,0 +1,40 @@
# 本地数据库环境
# 数据存放在当前目录下的 data里
# 推荐使用安装了docker扩展的vscode打开目录 在本文件上右键可以快速启动,停止
# 如不需要相关容器开机自启动,可注释掉 restart: always
# 如遇端口冲突 可调整ports下 :前面的端口号
version: "3.1"
services:
coolDB:
image: mysql
command:
--default-authentication-plugin=mysql_native_password
--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
--group_concat_max_len=102400
restart: always
volumes:
- ./data/mysql/:/var/lib/mysql/
environment:
TZ: Asia/Shanghai # 指定时区
MYSQL_ROOT_PASSWORD: "123456" # 配置root用户密码
MYSQL_DATABASE: "cool" # 业务库名
MYSQL_USER: "root" # 业务库用户名
MYSQL_PASSWORD: "123456" # 业务库密码
networks:
- cool
ports:
- 3306:3306
coolRedis:
image: redis
#command: --requirepass "12345678" # redis库密码,不需要密码注释本行
restart: always
environment:
TZ: Asia/Shanghai # 指定时区
volumes:
- ./data/redis/:/data/
networks:
- cool
ports:
- 6379:6379
+189
View File
@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.cool</groupId>
<artifactId>cool-admin</artifactId>
<version>7.1.0</version>
<name>cool-admin</name>
<description>cool admin for java</description>
<properties>
<java.version>17</java.version>
<lombok.version>1.18.34</lombok.version>
<mybatis-flex.version>1.9.3</mybatis-flex.version>
<mybatis-flex.ext.version>1.9.3.021-beta</mybatis-flex.ext.version>
<hutool.version>5.8.26</hutool.version>
<ognl.version>3.3.2</ognl.version>
<fastjson2.version>2.0.51</fastjson2.version>
<springdoc-openapi.version>2.5.0</springdoc-openapi.version>
</properties>
<dependencies>
<!-- springframework start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Boot Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springframework end -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>${ognl.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- mybatis-flex-ext -->
<dependency>
<groupId>com.tangzc</groupId>
<artifactId>mybatis-flex-ext-spring-boot3-starter</artifactId>
<version>${mybatis-flex.ext.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>${mybatis-flex.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.active>local</spring.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.active>prod</spring.active>
</properties>
</profile>
</profiles>
</project>
@@ -0,0 +1,25 @@
package com.cool;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import com.tangzc.autotable.springboot.EnableAutoTable;
/**
* CoolApplication - 应用程序的主类
* 该类配置并运行应用程序。
*/
@EnableAutoTable // 开启自动建表
@EnableAsync // 开启异步处理
@EnableCaching // 开启缓存
@SpringBootApplication
@MapperScan("com.cool.modules.*.mapper") // 扫描指定包中的MyBatis映射器
public class CoolApplication {
public static void main(String[] args) {
SpringApplication.run(CoolApplication.class, args);
}
}
@@ -0,0 +1,12 @@
package com.cool.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CoolPlugin {
String value() default "";
}
@@ -0,0 +1,26 @@
package com.cool.core.annotation;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.annotation.*;
/**
* 自定义路由注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@RequestMapping
public @interface CoolRestController {
@AliasFor(annotation = RequestMapping.class)
String name() default "";
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
String[] api() default {};
}
@@ -0,0 +1,12 @@
package com.cool.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreRecycleData {
String value() default "";
}
@@ -0,0 +1,226 @@
package com.cool.core.base;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.TypeUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cool.core.exception.CoolPreconditions;
import com.cool.core.request.CrudOption;
import com.cool.core.request.R;
import com.cool.core.util.SpringContextUtils;
import com.mybatisflex.core.paginate.Page;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
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;
/**
* 控制层基类
*
* @param <S>
* @param <T>
*/
public abstract class BaseController<S extends BaseService<T>, T extends BaseEntity<T>> {
@Getter
protected S service;
protected Class<T> entityClass;
@PostConstruct
private void init() {
if (entityClass == null) {
this.entityClass = currentEntityClass();
}
if (service == null) {
this.service = (S) SpringContextUtils.getBean(currentServiceClass());
}
}
private final String COOL_PAGE_OP = "COOL_PAGE_OP";
private final String COOL_LIST_OP = "COOL_LIST_OP";
private final ThreadLocal<CrudOption<T>> pageOption = new ThreadLocal<>();
private final ThreadLocal<CrudOption<T>> listOption = new ThreadLocal<>();
private final ThreadLocal<JSONObject> requestParams = new ThreadLocal<>();
@ModelAttribute
protected void preHandle(HttpServletRequest request,
@RequestAttribute JSONObject requestParams) {
this.pageOption.set(new CrudOption<>(requestParams));
this.listOption.set(new CrudOption<>(requestParams));
this.requestParams.set(requestParams);
init(request, requestParams);
request.setAttribute(COOL_PAGE_OP, this.pageOption.get());
request.setAttribute(COOL_LIST_OP, this.listOption.get());
removeThreadLocal();
}
/**
* 手动移除变量
*/
private void removeThreadLocal() {
this.listOption.remove();
this.pageOption.remove();
this.requestParams.remove();
}
public CrudOption<T> createOp() {
return new CrudOption<>(this.requestParams.get());
}
public void setListOption(CrudOption<T> listOption) {
this.listOption.set(listOption);
}
public void setPageOption(CrudOption<T> pageOption) {
this.pageOption.set(pageOption);
}
protected abstract void init(HttpServletRequest request, JSONObject requestParams);
/**
* 新增
* <p>
* // * @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<String, Object> 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<T> 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<T> option) {
Integer page = requestParams.getInt("page", 1);
Integer size = requestParams.getInt("size", 20);
return R.ok(
pageResult((Page<T>) service.page(requestParams, new Page<>(page, size),
option.getQueryWrapper(entityClass))));
}
/**
* 分页结果
*
* @param page 分页返回数据
*/
protected Map<String, Object> pageResult(Page<T> page) {
Map<String, Object> result = new HashMap<>();
Map<String, Object> 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<T> currentEntityClass() {
// 使用 获取泛型参数类型
Type type = TypeUtil.getTypeArgument(this.getClass(), 1); // 获取第二个泛型参数
if (type instanceof Class<?>) {
return (Class<T>) type;
}
throw new IllegalStateException("Unable to determine entity class type");
}
public Class<T> currentServiceClass() {
// 使用 获取泛型参数类型
Type type = TypeUtil.getTypeArgument(this.getClass(), 0); // 获取第一个泛型参数
if (type instanceof Class<?>) {
return (Class<T>) type;
}
throw new IllegalStateException("Unable to determine entity class type");
}
protected List<Long> getIds(Map<String, Object> params) {
Object ids = params.get("ids");
CoolPreconditions.checkEmpty(ids, "ids 参数错误");
if (!(ids instanceof ArrayList)) {
ids = ids.toString().split(",");
}
return Convert.toList(Long.class, ids);
}
}
@@ -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<T extends Model<T>> extends Model<T> 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;
}
@@ -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 <T> 实体
*/
public interface BaseService<T> extends IService<T> {
/**
* 新增
*
* @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<T> 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<T> 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);
}
@@ -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 <M> Mapper 类
* @param <T> 实体
*/
public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity<T>> extends
ServiceImpl<M, T>
implements BaseService<T> {
@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<T> entitys) {
List<Long> 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<T> 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);
}
}
@@ -0,0 +1,13 @@
package com.cool.core.base;
/**
* 修改枚举
*/
public enum ModifyEnum {
// 新增
ADD,
// 修改
UPDATE,
// 删除
DELETE
}
@@ -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";
}
}
@@ -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<Class<?>, BaseMapper<?>> mapperMap;
/**
* 初始化mapperMap,key 为entityClass,value 为 mapper
*/
private void init() {
// 获取所有BaseMapper类型的Bean
Map<String, BaseMapper> 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 <T> BaseMapper<T> getMapperByEntityClass(Class<T> entityClass) {
if (ObjUtil.isEmpty(mapperMap)) {
init();
}
return (BaseMapper<T>) 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;
}
}
+159
View File
@@ -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> T get(String key, Class<T> 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));
}
}
}
@@ -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);
}
}
@@ -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", "");
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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<OpenAPIService> 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()));
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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;
}
}
@@ -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) {
// 我们可以在这里进行一些列的初始化配置
}
}
@@ -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;
}
@@ -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<String, Object> config;
/**
* jar包存放路径
*/
private String jarPath;
/**
* 同名hook id
*/
@JsonIgnore
private Long sameHookId;
}
@@ -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 {
}
@@ -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;
}
@@ -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<Object, Object> caffeine() {
return Caffeine.newBuilder().maximumSize(10000);
}
@Bean
public CaffeineCacheManager cacheManager(Caffeine<Object, Object> 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<Object, Object> cacheMap = (Map<Object, Object>) inputStream.readObject();
com.github.benmanes.caffeine.cache.Cache<Object, Object> 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<Object, Object> cacheMap = ((com.github.benmanes.caffeine.cache.Cache<Object, Object>) cache
.getNativeCache()).asMap();
try (ObjectOutputStream outputStream = new ObjectOutputStream(
new FileOutputStream(cacheFile))) {
outputStream.writeObject(new HashMap<>(cacheMap));
} catch (IOException e) {
log.error("persistCacheErr", e);
}
}
}
}
}
@@ -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<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.create(redisConnectionFactory);
}
}
@@ -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<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> 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<Dict>());
}
List<Dict> 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<Dict>());
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<Dict> apis(String prefix, String methodPath, List<Dict> 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<String, Object> 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<String> 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<Class<?>> classes = ClassUtil.scanPackageByAnnotation("", Table.class);
classes.forEach(e -> {
// 获得属性
Field[] fields = ClassUtil.getDeclaredFields(e);
List<Dict> columns = columns(fields);
entityInfo.set(e.getSimpleName(), columns);
});
}
/**
* 获得所有的列
*
* @param fields 字段名
* @return 所有的列
*/
private List<Dict> columns(Field[] fields) {
List<Dict> 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";
};
}
}
@@ -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信息");
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
}
@@ -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());
}
}
@@ -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();
}
}
@@ -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 "";
}
}
@@ -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<String> 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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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<String, Class<?>> 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);
}
}
}
}
@@ -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<String, Class<?>> 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<String, Class<?>> 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);
}
}
}
@@ -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";
}
@@ -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<PluginInfoEntity> 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);
}
}
@@ -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<String, DynamicJarClassLoader> dynamicJarClassLoaderMap = new ConcurrentHashMap<>();
private final Map<String, Object> 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<Class<?>> plugins = new ArrayList<>();
try (JarFile jarFile = ((JarURLConnection) jarUrl.openConnection()).getJarFile()) {
Enumeration<JarEntry> 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<Class<?>> 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);
}
}
@@ -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 <T>
*/
@Data
public class CrudOption<T> {
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<T> fieldEq(QueryColumn... fields) {
this.fieldEq = fields;
return this;
}
public QueryWrapper getQueryWrapper(Class<T> entityClass) {
return build(this.queryWrapper, entityClass);
}
public CrudOption<T> queryWrapper(QueryWrapper queryWrapper) {
this.queryWrapper = queryWrapper;
return this;
}
public CrudOption<T> keyWordLikeFields(QueryColumn... fields) {
this.keyWordLikeFields = fields;
return this;
}
public CrudOption<T> select(QueryColumn... selects) {
this.select = selects;
return this;
}
/**
* 构建查询条件
*
* @return QueryWrapper
*/
private QueryWrapper build(QueryWrapper queryWrapper, Class<T> 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<T> 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"));
}
}
}
@@ -0,0 +1,49 @@
package com.cool.core.request;
import java.util.HashMap;
/**
* 返回信息
*/
public class R extends HashMap<String, Object> {
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;
}
}
@@ -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<String, Object> getAllRequestParam(final HttpServletRequest request) {
Map<String, Object> 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();
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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<String> setApis;
if (ArrayUtil.isNotEmpty(annotations)) {
CoolRestController coolRestController = annotations[0];
setApis = CollUtil.toList(coolRestController.api());
Set<String> 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<String> 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;
}
}
@@ -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<String, Object>() {
{
put("code", "401");
put("message", "未登录");
}
}));
response.setStatus(401);
}
}
@@ -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<String> urls = new ArrayList<>();
}
@@ -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();
}
}
@@ -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<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if (configAttributes == null) {
return;
}
Iterator<ConfigAttribute> 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;
}
}
@@ -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;
}
}
@@ -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<String, Object>() {
{
put("code", "403");
put("message", "无权限");
}
}));
response.setStatus(403);
}
}
@@ -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<String, Object> 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<String, Object> 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);
}
}
@@ -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<GrantedAuthority> 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<GrantedAuthority> 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;
}
}
@@ -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;
}
}
@@ -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: 复制输入流</br>
*
* @param inputStream
* @return</br>
*/
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) {
}
};
}
}
@@ -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<String> 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.");
// }
}
}
@@ -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();
}
}
}
}
}
@@ -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;
}
}
}
@@ -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<String, Class<?>> TABLE_MAP;
public static Set<String> findEntityClassName() {
Set<String> 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<String, Class<?>> findTableMap() {
if (ObjUtil.isEmpty(TABLE_MAP)) {
init();
}
return TABLE_MAP;
}
private static void init() {
Set<String> 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<QueryColumn> 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;
}
}
@@ -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地址
* <p>
* 使用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;
}
}
@@ -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<String, Object> map1, Map<String, Object> map2) {
if (ObjectUtil.isEmpty(map1) || ObjectUtil.isEmpty(map2)) {
return true;
}
if (map1.size() != map2.size()) {
return false;
}
for (Map.Entry<String, Object> entry : map1.entrySet()) {
if (!map2.containsKey(entry.getKey())) {
return false;
}
if (!ObjectUtil.equal(entry.getValue(), map2.get(entry.getKey()))) {
return false;
}
}
return true;
}
}
@@ -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);
}
}
}
@@ -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> T getBean(Class<T> 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 <T> Map<String, T> getBeansOfType(Class<T> requiredType) {
return applicationContext.getBeansOfType(requiredType);
}
}
@@ -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");
}
}
@@ -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());
}
}
@@ -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<BaseSysDepartmentService, BaseSysDepartmentEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
}
@Operation(summary = "排序")
@PostMapping("/order")
public R order(@RequestBody List<BaseSysDepartmentEntity> list) {
this.service.order(list);
return R.ok();
}
}
@@ -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<BaseSysLogService, BaseSysLogEntity> {
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"));
}
}
@@ -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<BaseSysMenuService, BaseSysMenuEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
}
@Operation(summary = "创建代码", description = "创建代码")
@PostMapping("/create")
public R create(@RequestBody() Map<String, Object> 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<String, Object> params) {
return R.ok(this.service.export(getIds(params)));
}
@Operation(summary = "导入", description = "导入")
@PostMapping("/import")
public R importMenu(@RequestBody Map<String, List<BaseSysMenuEntity>> params) {
CoolPreconditions.checkEmpty(params.get("menus"), "参数不能为空");
return R.ok(this.service.importMenu(params.get("menus")));
}
}
@@ -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<BaseSysParamService, BaseSysParamEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
}
@Operation(summary = "根据键返回网页的参数值")
@GetMapping("/html")
public String html(String key) {
return service.htmlByKey(key);
}
}
@@ -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<BaseSysRoleService, BaseSysRoleEntity> {
@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"))));
}
}
@@ -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<BaseSysUserService, BaseSysUserEntity> {
@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();
}
}
@@ -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());
}
}
@@ -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;
}
@@ -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<BaseSysConfEntity> {
@Index(type = IndexTypeEnum.UNIQUE)
@ColumnDefine(comment = "配置键", notNull = true)
private String cKey;
@ColumnDefine(comment = "值", notNull = true, type = "text")
private String cValue;
}
@@ -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<BaseSysDepartmentEntity> {
@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;
}
@@ -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<BaseSysLogEntity> {
@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;
}
@@ -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<BaseSysMenuEntity> {
@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<BaseSysMenuEntity> childMenus;
}
@@ -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<BaseSysParamEntity> {
@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;
}
@@ -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<BaseSysRoleDepartmentEntity> {
@ColumnDefine(comment = "角色ID", type = "bigint")
private Long roleId;
@ColumnDefine(comment = "部门ID", type = "bigint")
private Long departmentId;
}
@@ -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<BaseSysRoleEntity> {
@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<Long> menuIdList;
@ColumnDefine(comment = "部门权限", type = "json")
@Column(typeHandler = Fastjson2TypeHandler.class)
private List<Long> departmentIdList;
}
@@ -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<BaseSysRoleMenuEntity> {
@ColumnDefine(comment = "菜单", type = "bigint")
private Long menuId;
@ColumnDefine(comment = "角色ID", type = "bigint")
private Long roleId;
}
@@ -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<BaseSysUserEntity> {
@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;
}
@@ -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<BaseSysUserRoleEntity> {
@ColumnDefine(comment = "用户ID", type = "bigint")
private Long userId;
@ColumnDefine(comment = "角色ID", type = "bigint")
private Long roleId;
}
@@ -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);
}
}
@@ -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<BaseSysConfEntity> {
}
@@ -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<BaseSysDepartmentEntity> {
}
@@ -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<BaseSysLogEntity> {
}
@@ -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<BaseSysMenuEntity> {
/**
* 根据角色ID获得所有菜单
*
* @param roleIds 角色ID集合
* @return SysMenuEntity
*/
List<BaseSysMenuEntity> getMenus(@Param("roleIds") Long[] roleIds);
}
@@ -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<BaseSysParamEntity> {
}

Some files were not shown because too many files have changed in this diff Show More