Initial commit
This commit is contained in:
commit
65d34df792
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
</profile>
|
||||
<profile name="Annotation profile for brain" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mapstruct/mapstruct-processor/1.5.5.Final/mapstruct-processor-1.5.5.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mapstruct/mapstruct/1.5.5.Final/mapstruct-1.5.5.Final.jar" />
|
||||
</processorPath>
|
||||
<module name="brain" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="technological-brain" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="brain" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="spring-milestones" />
|
||||
<option name="name" value="Spring Milestones" />
|
||||
<option name="url" value="https://repo.spring.io/milestone" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="spring-snapshots" />
|
||||
<option name="name" value="Spring Snapshots" />
|
||||
<option name="url" value="https://repo.spring.io/snapshot" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="aliyun" />
|
||||
<option name="name" value="aliyun" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="gitee-snapshots" />
|
||||
<option name="name" value="Gitee Snapshots" />
|
||||
<option name="url" value="https://gitee.com/xiaoym/knife4j/raw/master/repository" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
## 科技大脑展示平台(technological-brain)
|
||||
|
||||
面向高校/企业的人才与科研信息管理与展示后端服务,基于 Spring Boot 构建,集成权限认证、参数配置、科研与成果管理等功能模块,并提供在线 API 文档。
|
||||
|
||||
### 技术栈
|
||||
- **后端框架**: Spring Boot 2.5.14, Spring MVC, Spring Security (JWT)
|
||||
- **持久层**: MyBatis-Plus, MyBatis XML 映射
|
||||
- **数据库/连接池**: MySQL 8.x, Druid
|
||||
- **缓存**: Redis (Lettuce)
|
||||
- **对象映射**: MapStruct
|
||||
- **工具库**: Hutool, Guava, Apache Commons
|
||||
- **文档**: Springfox Swagger2 2.9.2, Knife4j
|
||||
- **云存储**: 阿里云 OSS SDK
|
||||
|
||||
### 目录结构(关键路径)
|
||||
```text
|
||||
src/
|
||||
main/
|
||||
java/com/CUST/brain/
|
||||
BrainApplication.java # 应用入口
|
||||
common/ # 通用配置、拦截器、异常、工具
|
||||
config/ # Security / Swagger / Redis 等配置
|
||||
security/ # JWT 过滤器、异常处理器
|
||||
dto/ enums/ utils/ ...
|
||||
dao/
|
||||
domain/ # 实体对象
|
||||
mapper/ # MyBatis-Plus Mapper 接口
|
||||
service/ # 业务接口与实现
|
||||
web/controller/ # REST 控制器
|
||||
resources/
|
||||
application.yml # 应用配置
|
||||
mapper/*.xml # MyBatis 映射 XML
|
||||
db/schema.sql # 库表结构初始化
|
||||
db/data.sql # 基础数据(菜单/用户/角色/参数等)
|
||||
```
|
||||
|
||||
### 功能模块
|
||||
- **系统管理**: 用户、角色、权限、参数(类型/值)
|
||||
- **人才管理**: 人才基本信息、工作单位(高校/企业)、履历、工作领域
|
||||
- **科研管理**: 科研项目、立项项目、科技报告
|
||||
- **成果管理**: 论文专著、专利、学术奖励、荣誉称号
|
||||
|
||||
以上模块的导航/菜单初始化见 `src/main/resources/db/data.sql`。
|
||||
|
||||
### 环境要求
|
||||
- JDK 1.8
|
||||
- Maven 3.6+
|
||||
- MySQL 8.x
|
||||
- Redis 5+
|
||||
|
||||
### 快速开始
|
||||
1) 克隆并进入项目目录
|
||||
```bash
|
||||
git clone <your-repo-url>
|
||||
cd technological_brain
|
||||
```
|
||||
|
||||
2) 配置数据库与中间件
|
||||
- 新建数据库 `technological_brain`(或使用自定义库名)
|
||||
- 启动 Redis 服务
|
||||
- 根据实际环境修改 `src/main/resources/application.yml` 中的以下配置项(建议改为环境变量或外部化配置):
|
||||
- spring.datasource.url / username / password
|
||||
- spring.redis.host / port / password
|
||||
- jwt.secret / expiration
|
||||
- oss.endpoint / access-key-id / access-key-secret / bucket-name / domain
|
||||
|
||||
3) 初始化数据库
|
||||
```sql
|
||||
-- 执行库表结构
|
||||
src/main/resources/db/schema.sql
|
||||
|
||||
-- 初始化菜单/用户/角色/参数等数据
|
||||
src/main/resources/db/data.sql
|
||||
```
|
||||
|
||||
4) 启动服务
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/brain-1.0.0.jar
|
||||
```
|
||||
|
||||
默认服务端口与上下文路径见 `application.yml`:
|
||||
- 端口: 8090
|
||||
- 上下文: /brain
|
||||
|
||||
因此应用根地址为: `http://localhost:8090/brain`
|
||||
|
||||
### API 文档与调试
|
||||
- 文档入口: `http://localhost:8090/brain/doc.html`
|
||||
- Swagger/Knife4j 可通过 `swagger.enable` 与 `knife4j.enable` 开关控制
|
||||
- 文档扫描基础包: `com.CUST.brain.web.controller`
|
||||
|
||||
### 认证与授权
|
||||
- 统一认证接口(放行): `POST /api/system/auth/login`
|
||||
- 其他接口默认需要认证,携带 JWT:
|
||||
- Header: `Authorization: Bearer <token>`
|
||||
- 安全配置参考 `common/config/SecurityConfig.java`
|
||||
|
||||
### 配置说明(节选)
|
||||
`src/main/resources/application.yml` 包含以下关键配置:
|
||||
- **server**: `port`, `servlet.context-path`
|
||||
- **spring.datasource**: Druid 数据源与连接池参数
|
||||
- **spring.redis**: Redis 连接与池参数
|
||||
- **mybatis-plus**: `mapper-locations`, `type-aliases-package`, 驼峰映射、逻辑删除
|
||||
- **logging**: 各包日志级别
|
||||
- **swagger/knife4j**: 文档开关与显示项
|
||||
- **jwt**: `secret`, `expiration`, `tokenHeader`, `tokenHead`
|
||||
- **oss**: 阿里云 OSS 访问配置(请勿在仓库中明文提交敏感信息)
|
||||
|
||||
> 安全提示:强烈建议将数据库、Redis、JWT、OSS 等敏感配置改为环境变量或外部化配置文件,避免明文泄露。
|
||||
|
||||
### 构建与部署
|
||||
- 构建命令:`mvn clean package -DskipTests`
|
||||
- 运行命令:`java -jar target/brain-1.0.0.jar`
|
||||
- 常用 JVM/运行参数:
|
||||
```bash
|
||||
java -Xms512m -Xmx1024m \
|
||||
-Dspring.profiles.active=prod \
|
||||
-Dserver.port=8090 \
|
||||
-jar target/brain-1.0.0.jar
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
- 文档页无法访问:确认 `swagger.enable=true`、`knife4j.enable=true`,并使用带有上下文路径的地址 `/brain/doc.html`。
|
||||
- 登录失败:检查初始化数据是否执行、用户密码是否匹配、JWT `secret` 是否与生成 token 的配置一致。
|
||||
- 数据源/Redis 连接失败:核对主机、端口、用户名密码与防火墙/容器网络。
|
||||
|
||||
### 版权与声明
|
||||
本项目仅用于教学/演示目的,涉及的账号、密钥配置请在实际部署前更换为安全的私有配置。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.CUST</groupId>
|
||||
<artifactId>brain</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>brain</name>
|
||||
<description>科技大脑展示平台</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
||||
<!-- 依赖版本管理 -->
|
||||
<spring-cloud.version>2021.0.8</spring-cloud.version>
|
||||
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
|
||||
<druid.version>1.2.20</druid.version>
|
||||
<knife4j.version>2.0.9</knife4j.version>
|
||||
<hutool.version>5.8.25</hutool.version>
|
||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||
<commons-collections4.version>4.4</commons-collections4.version>
|
||||
<commons-io.version>2.15.1</commons-io.version>
|
||||
<guava.version>32.1.3-jre</guava.version>
|
||||
<jjwt.version>0.12.5</jjwt.version>
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<validation.version>6.2.5.Final</validation.version>
|
||||
<fastjson2.version>2.0.45</fastjson2.version>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<springfox.version>2.9.2</springfox.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- Spring Cloud 依赖管理 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot 基础依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!-- Redis 连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
<!-- Redis 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.20</version>
|
||||
</dependency>
|
||||
|
||||
<!-- API文档 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>${springfox.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>${springfox.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-ui</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons-collections4.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 对象映射 -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 参数校验 -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Java 8 Time Support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云OSS -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>3.16.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<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>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>aliyun</id>
|
||||
<name>aliyun</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>gitee-snapshots</id>
|
||||
<name>Gitee Snapshots</name>
|
||||
<url>https://gitee.com/xiaoym/knife4j/raw/master/repository</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>aliyun-plugin</id>
|
||||
<name>aliyun-plugin</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.CUST.brain;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* 应用程序入口
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableTransactionManagement
|
||||
@MapperScan("com.CUST.brain.dao.mapper")
|
||||
public class BrainApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BrainApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.CUST.brain.common.annotation;
|
||||
|
||||
import com.CUST.brain.common.enums.CacheUpdateStrategy;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Redis缓存注解
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RedisCache {
|
||||
/**
|
||||
* 缓存key前缀
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* 缓存key
|
||||
*/
|
||||
String key() default "";
|
||||
|
||||
/**
|
||||
* 缓存字段(用于Hash结构)
|
||||
*/
|
||||
String field() default "";
|
||||
|
||||
/**
|
||||
* 过期时间(秒)
|
||||
*/
|
||||
int expire() default 3600;
|
||||
|
||||
/**
|
||||
* 是否使用Hash结构存储
|
||||
*/
|
||||
boolean isHash() default false;
|
||||
|
||||
/**
|
||||
* 是否缓存null值
|
||||
*/
|
||||
boolean cacheNull() default false;
|
||||
|
||||
/**
|
||||
* 缓存条件(SpEL表达式)
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* 不缓存条件(SpEL表达式)
|
||||
*/
|
||||
String unless() default "";
|
||||
|
||||
/**
|
||||
* 缓存更新策略
|
||||
*/
|
||||
CacheUpdateStrategy updateStrategy() default CacheUpdateStrategy.UPDATE;
|
||||
|
||||
/**
|
||||
* 是否自动续期
|
||||
*/
|
||||
boolean autoRenew() default false;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.CUST.brain.common.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通用返回结果
|
||||
*/
|
||||
@Data
|
||||
public class CommonResult<T> {
|
||||
private long code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
protected CommonResult() {
|
||||
}
|
||||
|
||||
protected CommonResult(long code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> success(T data, String message) {
|
||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> failed(String message) {
|
||||
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> failed() {
|
||||
return failed(ResultCode.FAILED.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证失败返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> validateFailed(String message) {
|
||||
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> unauthorized(T data) {
|
||||
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> forbidden(T data) {
|
||||
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义返回结果
|
||||
*/
|
||||
public static <T> CommonResult<T> custom(long code, String message, T data) {
|
||||
return new CommonResult<T>(code, message, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.CUST.brain.common.api;
|
||||
|
||||
public interface IErrorCode {
|
||||
Integer getCode();
|
||||
String getMessage();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.common.api;
|
||||
|
||||
/**
|
||||
* 返回码枚举类
|
||||
*/
|
||||
public enum ResultCode {
|
||||
SUCCESS(200, "操作成功"),
|
||||
FAILED(500, "操作失败"),
|
||||
VALIDATE_FAILED(404, "参数检验失败"),
|
||||
UNAUTHORIZED(401, "暂未登录或token已经过期"),
|
||||
FORBIDDEN(403, "没有相关权限");
|
||||
|
||||
private long code;
|
||||
private String message;
|
||||
|
||||
private ResultCode(long code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
package com.CUST.brain.common.aspect;
|
||||
|
||||
import com.CUST.brain.common.annotation.RedisCache;
|
||||
import com.CUST.brain.common.enums.CacheUpdateStrategy;
|
||||
import com.CUST.brain.common.constant.RedisKeyConstants;
|
||||
import com.CUST.brain.common.exception.BusinessException;
|
||||
import com.CUST.brain.common.utils.RedisUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.lang.reflect.Type;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
/**
|
||||
* Redis缓存切面
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class RedisCacheAspect {
|
||||
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private static final ExpressionParser PARSER = new SpelExpressionParser();
|
||||
private static final String NULL_VALUE = "NULL_VALUE";
|
||||
private static final long DEFAULT_EXPIRE = 3600L;
|
||||
private static final long LOCK_TIMEOUT = 10000L;
|
||||
private static final String LOCK_PREFIX = "lock:";
|
||||
|
||||
@Around("@annotation(redisCache)")
|
||||
public Object around(ProceedingJoinPoint pjp, RedisCache redisCache) throws Throwable {
|
||||
// 1. 解析缓存key
|
||||
String key = parseKey(pjp, redisCache.key());
|
||||
log.info("【RedisCache】开始处理缓存,原始key表达式={}, 解析后key={}", redisCache.key(), key);
|
||||
|
||||
if (!StringUtils.hasText(key) || "cache:error".equals(key) || "cache:empty".equals(key)) {
|
||||
log.warn("【RedisCache】无效的缓存key,直接执行目标方法,key={}", key);
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
||||
// 2. 从 Redis 中获取缓存数据
|
||||
Object cachedValue = redisUtils.get(key);
|
||||
log.info("【RedisCache】尝试获取缓存,key={}, 缓存值={}", key, cachedValue);
|
||||
|
||||
if (cachedValue != null) {
|
||||
log.info("【RedisCache】缓存命中,key={}, value={}", key, cachedValue);
|
||||
// 自动反序列化为目标方法返回类型
|
||||
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||
Class<?> returnType = signature.getReturnType();
|
||||
Type genericReturnType = signature.getMethod().getGenericReturnType();
|
||||
try {
|
||||
if (returnType == String.class) {
|
||||
return cachedValue.toString();
|
||||
} else if (returnType == java.util.List.class || (cachedValue.toString().startsWith("[") && cachedValue.toString().endsWith("]"))) {
|
||||
// 针对List类型自动反序列化
|
||||
return objectMapper.readValue(cachedValue.toString(), objectMapper.getTypeFactory().constructType(genericReturnType));
|
||||
} else if (cachedValue instanceof String) {
|
||||
return objectMapper.readValue((String) cachedValue, returnType);
|
||||
} else {
|
||||
return cachedValue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("【RedisCache】自动反序列化缓存失败,key={}, type={}, error={}", key, returnType, e.getMessage());
|
||||
throw new RuntimeException("缓存反序列化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 缓存未命中,使用分布式锁
|
||||
String lockKey = LOCK_PREFIX + key.replace("cache:", "");
|
||||
String lockValue = UUID.randomUUID().toString();
|
||||
|
||||
log.info("【RedisCache】缓存未命中,尝试获取分布式锁,lockKey={}, lockValue={}, timeout={}ms",
|
||||
lockKey, lockValue, LOCK_TIMEOUT);
|
||||
boolean lockAcquired = redisUtils.tryLock(lockKey, lockValue, LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (lockAcquired) {
|
||||
try {
|
||||
// 3.1 二次检查,避免缓存穿透
|
||||
cachedValue = redisUtils.get(key);
|
||||
log.info("【RedisCache】获取锁后二次检查缓存,key={}, 缓存值={}", key, cachedValue);
|
||||
|
||||
if (cachedValue != null) {
|
||||
log.info("【RedisCache】二次检查缓存命中,key={}, value={}", key, cachedValue);
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
// 3.2 执行目标方法,获取新数据
|
||||
log.info("【RedisCache】开始执行目标方法,key={}", key);
|
||||
Object newValue = pjp.proceed();
|
||||
log.info("【RedisCache】目标方法执行完成,key={}, 新值={}", key, newValue);
|
||||
|
||||
// 3.3 将新数据存入 Redis
|
||||
if (newValue != null || redisCache.cacheNull()) {
|
||||
redisUtils.set(key, newValue, redisCache.expire());
|
||||
log.info("【RedisCache】更新缓存成功,key={}, value={}, expire={}", key, newValue, redisCache.expire());
|
||||
} else {
|
||||
log.info("【RedisCache】值为null且不缓存null,key={}", key);
|
||||
}
|
||||
|
||||
return newValue;
|
||||
} finally {
|
||||
// 3.4 释放分布式锁
|
||||
boolean unlocked = redisUtils.unlock(lockKey, lockValue);
|
||||
log.info("【RedisCache】释放分布式锁,lockKey={}, lockValue={}, result={}", lockKey, lockValue, unlocked);
|
||||
}
|
||||
} else {
|
||||
// 3.5 未获取到锁,采用自旋策略
|
||||
log.info("【RedisCache】未获取到锁,开始自旋重试,key={}", key);
|
||||
int retryCount = 0;
|
||||
int maxRetry = 3;
|
||||
long retryInterval = 100; // 重试间隔,单位毫秒
|
||||
|
||||
while (retryCount < maxRetry) {
|
||||
cachedValue = redisUtils.get(key);
|
||||
log.info("【RedisCache】自旋重试获取缓存,key={}, 重试次数={}, 缓存值={}", key, retryCount, cachedValue);
|
||||
|
||||
if (cachedValue != null) {
|
||||
log.info("【RedisCache】自旋重试后缓存命中,key={}, value={}, retryCount={}", key, cachedValue, retryCount);
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(retryInterval);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("【RedisCache】自旋等待被中断,key={}, retryCount={}", key, retryCount);
|
||||
throw new BusinessException(500, "缓存操作被中断,请稍后重试");
|
||||
}
|
||||
|
||||
retryCount++;
|
||||
}
|
||||
|
||||
// 3.6 自旋重试后仍未命中,执行目标方法
|
||||
log.info("【RedisCache】自旋重试后仍未命中,开始执行目标方法,key={}", key);
|
||||
Object newValue = pjp.proceed();
|
||||
log.info("【RedisCache】目标方法执行完成,key={}, 新值={}", key, newValue);
|
||||
|
||||
if (newValue != null || redisCache.cacheNull()) {
|
||||
redisUtils.set(key, newValue, redisCache.expire());
|
||||
log.info("【RedisCache】更新缓存成功,key={}, value={}, expire={}", key, newValue, redisCache.expire());
|
||||
} else {
|
||||
log.info("【RedisCache】值为null且不缓存null,key={}", key);
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析缓存key
|
||||
*/
|
||||
private String parseKey(ProceedingJoinPoint pjp, String keyExpression) {
|
||||
if (!StringUtils.hasText(keyExpression)) {
|
||||
log.warn("【RedisCache】key表达式为空");
|
||||
return "cache:empty";
|
||||
}
|
||||
|
||||
// 如果keyExpression不包含SpEL表达式,直接返回
|
||||
if (!keyExpression.contains("#")) {
|
||||
return "cache:" + keyExpression;
|
||||
}
|
||||
|
||||
// 使用SpEL解析
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
Object[] args = pjp.getArgs();
|
||||
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||
String[] paramNames = signature.getParameterNames();
|
||||
|
||||
// 支持参数名和#p0两种方式
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
Object arg = args[i];
|
||||
if (arg != null) {
|
||||
// 如果是复杂对象,转换为JSON字符串
|
||||
if (!(arg instanceof String) && !(arg instanceof Number) && !(arg instanceof Boolean)) {
|
||||
try {
|
||||
arg = objectMapper.writeValueAsString(arg);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("【RedisCache】参数序列化失败,使用toString(),arg={}", arg);
|
||||
arg = String.valueOf(arg);
|
||||
}
|
||||
}
|
||||
// 同时支持参数名和#p0两种方式
|
||||
if (paramNames != null && i < paramNames.length) {
|
||||
context.setVariable(paramNames[i], arg);
|
||||
}
|
||||
context.setVariable("p" + i, arg);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug("【RedisCache】开始解析SpEL表达式,原始表达式={}", keyExpression);
|
||||
// 直接使用SpEL解析,不使用TemplateParserContext
|
||||
String parsedKey = parser.parseExpression(keyExpression).getValue(context, String.class);
|
||||
if (parsedKey == null) {
|
||||
log.warn("【RedisCache】SpEL表达式解析结果为空,keyExpression={}", keyExpression);
|
||||
return "cache:null";
|
||||
}
|
||||
|
||||
// 清理和规范化key
|
||||
parsedKey = parsedKey.trim();
|
||||
// 移除多余的下划线
|
||||
parsedKey = parsedKey.replaceAll("_+", "_");
|
||||
// 移除开头和结尾的下划线
|
||||
parsedKey = parsedKey.replaceAll("^_+|_+$", "");
|
||||
// 确保key不包含特殊字符
|
||||
parsedKey = parsedKey.replaceAll("[^a-zA-Z0-9_:.-]", "_");
|
||||
|
||||
// 如果key为空,返回默认值
|
||||
if (!StringUtils.hasText(parsedKey)) {
|
||||
log.warn("【RedisCache】SpEL表达式解析结果为空字符串,keyExpression={}", keyExpression);
|
||||
return "cache:empty";
|
||||
}
|
||||
|
||||
log.debug("【RedisCache】SpEL表达式解析成功,keyExpression={},解析结果={}", keyExpression, parsedKey);
|
||||
return "cache:" + parsedKey;
|
||||
} catch (Exception e) {
|
||||
log.error("【RedisCache】SpEL表达式解析失败,keyExpression={},错误信息={}", keyExpression, e.getMessage());
|
||||
return "cache:error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件
|
||||
*/
|
||||
private boolean checkCondition(ProceedingJoinPoint point, String condition) {
|
||||
if (!StringUtils.hasText(condition)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
EvaluationContext context = new StandardEvaluationContext(point.getArgs());
|
||||
return Boolean.TRUE.equals(PARSER.parseExpression(condition).getValue(context, Boolean.class));
|
||||
} catch (Exception e) {
|
||||
log.error("解析条件表达式失败: {}", condition, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存值
|
||||
*/
|
||||
private Object getCacheValue(String key, RedisCache redisCache) {
|
||||
try {
|
||||
if (redisCache.isHash() && StringUtils.hasText(redisCache.field())) {
|
||||
Object value = redisUtils.hGet(key, redisCache.field(), String.class);
|
||||
return parseValue(value);
|
||||
} else {
|
||||
Object value = redisUtils.get(key);
|
||||
return parseValue(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取缓存值失败: {}", key, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析缓存值
|
||||
*/
|
||||
private Object parseValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String && NULL_VALUE.equals(value)) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存
|
||||
*/
|
||||
private void updateCache(String key, Object value, RedisCache redisCache) {
|
||||
try {
|
||||
if (value == null && !redisCache.cacheNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long expire = redisCache.expire() > 0 ? redisCache.expire() : DEFAULT_EXPIRE;
|
||||
|
||||
// 确保key是字符串类型
|
||||
String safeKey = String.valueOf(key);
|
||||
// 确保field是字符串类型
|
||||
String safeField = StringUtils.hasText(redisCache.field()) ? String.valueOf(redisCache.field()) : null;
|
||||
// 确保value是字符串类型
|
||||
String safeValue;
|
||||
if (value == null) {
|
||||
safeValue = NULL_VALUE;
|
||||
} else if (value instanceof String) {
|
||||
safeValue = (String) value;
|
||||
} else {
|
||||
try {
|
||||
safeValue = objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("【RedisCache】值序列化失败,使用toString(),value={}", value);
|
||||
safeValue = String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("【RedisCache】更新缓存,key={}, field={}, value={}, expire={}", safeKey, safeField, safeValue, expire);
|
||||
|
||||
switch (redisCache.updateStrategy()) {
|
||||
case UPDATE:
|
||||
if (redisCache.isHash() && safeField != null) {
|
||||
redisUtils.hSet(safeKey, safeField, safeValue, expire, TimeUnit.SECONDS);
|
||||
} else {
|
||||
redisUtils.set(safeKey, safeValue, expire);
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
if (redisCache.isHash() && safeField != null) {
|
||||
redisUtils.hDelete(safeKey, safeField);
|
||||
} else {
|
||||
redisUtils.delete(safeKey);
|
||||
}
|
||||
break;
|
||||
case IGNORE:
|
||||
log.debug("【RedisCache】忽略缓存更新,key={}", safeKey);
|
||||
break;
|
||||
}
|
||||
|
||||
// 自动续期
|
||||
if (redisCache.autoRenew() && value != null) {
|
||||
scheduleRenew(safeKey, redisCache);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("【RedisCache】更新缓存失败,key={}, error={}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时续期
|
||||
*/
|
||||
private void scheduleRenew(String key, RedisCache redisCache) {
|
||||
// 这里可以使用定时任务框架(如Quartz)来实现续期
|
||||
// 为了简单起见,这里只是记录日志
|
||||
log.info("缓存自动续期: {}, expire: {}", key, redisCache.expire());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.CUST.brain.common.cache;
|
||||
|
||||
import com.CUST.brain.common.enums.ParamTypeEnum;
|
||||
import com.CUST.brain.dao.domain.system.SysParam;
|
||||
import com.CUST.brain.service.SysParamService;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 参数缓存管理器
|
||||
*/
|
||||
@Component
|
||||
public class ParamCacheManager {
|
||||
|
||||
@Resource(name = "paramRedisTemplate")
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private SysParamService sysParamService;
|
||||
|
||||
private static final String PARAM_CACHE_KEY = "sys:param:type:";
|
||||
private static final String PARAM_LIST_CACHE_KEY = "sys:param:list:";
|
||||
|
||||
/**
|
||||
* 服务启动时初始化参数缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 获取所有参数类型
|
||||
List<String> typeCodes = Arrays.stream(ParamTypeEnum.values())
|
||||
.map(ParamTypeEnum::getCode)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 批量获取所有参数
|
||||
Map<String, List<SysParam>> paramMap = sysParamService.getParamsByTypes(typeCodes);
|
||||
|
||||
// 更新缓存
|
||||
paramMap.forEach(this::cacheParamList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存参数列表
|
||||
*/
|
||||
public void cacheParamList(String typeCode, List<SysParam> params) {
|
||||
if (params != null) {
|
||||
redisTemplate.opsForValue().set(PARAM_LIST_CACHE_KEY + typeCode, params);
|
||||
|
||||
// 缓存参数Map
|
||||
Map<String, String> paramValueMap = params.stream()
|
||||
.collect(Collectors.toMap(
|
||||
SysParam::getParamName,
|
||||
SysParam::getParamValue,
|
||||
(v1, v2) -> v1
|
||||
));
|
||||
redisTemplate.opsForValue().set(PARAM_CACHE_KEY + typeCode, paramValueMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<SysParam> getParamList(String typeCode) {
|
||||
Object cached;
|
||||
try {
|
||||
cached = redisTemplate.opsForValue().get(PARAM_LIST_CACHE_KEY + typeCode);
|
||||
} catch (RuntimeException e) {
|
||||
// 兼容历史序列化格式不一致导致的反序列化失败
|
||||
removeParamCache(typeCode);
|
||||
return null;
|
||||
}
|
||||
if (cached == null) {
|
||||
return null;
|
||||
}
|
||||
if (cached instanceof List) {
|
||||
List<?> rawList = (List<?>) cached;
|
||||
if (rawList.isEmpty() || rawList.get(0) instanceof SysParam) {
|
||||
return (List<SysParam>) cached;
|
||||
}
|
||||
if (rawList.get(0) instanceof Map) {
|
||||
// 兼容历史缓存中以 LinkedHashMap 形式存储的对象
|
||||
List<SysParam> converted = new ArrayList<>(rawList.size());
|
||||
for (Object item : rawList) {
|
||||
if (!(item instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> map = (Map<?, ?>) item;
|
||||
SysParam p = new SysParam();
|
||||
Object v;
|
||||
if ((v = map.get("paramType")) != null) p.setParamType(String.valueOf(v));
|
||||
if ((v = map.get("paramName")) != null) p.setParamName(String.valueOf(v));
|
||||
if ((v = map.get("paramValue")) != null) p.setParamValue(String.valueOf(v));
|
||||
if ((v = map.get("dataType")) != null) p.setDataType(String.valueOf(v));
|
||||
if ((v = map.get("sortOrder")) != null) p.setSortOrder(v instanceof Number ? ((Number) v).intValue() : null);
|
||||
if ((v = map.get("status")) != null) p.setStatus(v instanceof Number ? ((Number) v).intValue() : null);
|
||||
if ((v = map.get("remark")) != null) p.setRemark(String.valueOf(v));
|
||||
converted.add(p);
|
||||
}
|
||||
// 回写成强类型,避免后续再转换
|
||||
cacheParamList(typeCode, converted);
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
// 类型不匹配时直接清理,避免 ClassCastException 重现
|
||||
removeParamCache(typeCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数Map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, String> getParamMap(String typeCode) {
|
||||
return (Map<String, String>) redisTemplate.opsForValue().get(PARAM_CACHE_KEY + typeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除参数缓存
|
||||
*/
|
||||
public void removeParamCache(String typeCode) {
|
||||
redisTemplate.delete(PARAM_LIST_CACHE_KEY + typeCode);
|
||||
redisTemplate.delete(PARAM_CACHE_KEY + typeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除所有参数缓存
|
||||
*/
|
||||
public void removeAllParamCache() {
|
||||
Arrays.stream(ParamTypeEnum.values())
|
||||
.map(ParamTypeEnum::getCode)
|
||||
.forEach(this::removeParamCache);
|
||||
// 兜底:直接删除所有匹配 key,防止新增类型遗漏
|
||||
Set<String> keys1 = redisTemplate.keys(PARAM_LIST_CACHE_KEY + "*");
|
||||
if (keys1 != null && !keys1.isEmpty()) {
|
||||
redisTemplate.delete(keys1);
|
||||
}
|
||||
Set<String> keys2 = redisTemplate.keys(PARAM_CACHE_KEY + "*");
|
||||
if (keys2 != null && !keys2.isEmpty()) {
|
||||
redisTemplate.delete(keys2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import com.CUST.brain.common.utils.SecurityUtils;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Component
|
||||
public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
String userName = SecurityUtils.getCurrentUsername();
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, userName);
|
||||
this.strictInsertFill(metaObject, "updateBy", String.class, userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
String userName = SecurityUtils.getCurrentUsername();
|
||||
this.strictUpdateFill(metaObject, "updateBy", String.class, userName);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "oss")
|
||||
public class OssProperties {
|
||||
private String endpoint;
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String bucketName;
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
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.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig {
|
||||
|
||||
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
public static final LocalDateTimeSerializer LOCAL_DATE_TIME_SERIALIZER =
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule()); // 支持Java8时间类型
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
|
||||
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory factory) {
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
// 统一时间格式
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, LOCAL_DATE_TIME_SERIALIZER);
|
||||
mapper.registerModule(javaTimeModule);
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofDays(1))
|
||||
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
|
||||
.disableCachingNullValues();
|
||||
|
||||
return RedisCacheManager.builder(factory)
|
||||
.cacheDefaults(config)
|
||||
.transactionAware()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean("paramRedisTemplate")
|
||||
public RedisTemplate<String, Object> paramRedisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 使用带 JavaTimeModule 的 GenericJackson2JsonRedisSerializer 兼容 LocalDateTime
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, LOCAL_DATE_TIME_SERIALIZER);
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
|
||||
mapper.registerModule(javaTimeModule);
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
GenericJackson2JsonRedisSerializer genericSerializer = new GenericJackson2JsonRedisSerializer(mapper);
|
||||
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(genericSerializer);
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(genericSerializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import java.time.Duration;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule()); // 支持Java8时间类型
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
|
||||
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
// 配置序列化
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
|
||||
// 缓存有效期1天
|
||||
.entryTtl(Duration.ofDays(1))
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
|
||||
// 禁用空值
|
||||
.disableCachingNullValues();
|
||||
|
||||
return RedisCacheManager.builder(factory)
|
||||
.cacheDefaults(config)
|
||||
.transactionAware()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean("paramRedisTemplate")
|
||||
public RedisTemplate<String, Object> paramRedisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// 不加activateDefaultTyping
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import com.CUST.brain.common.security.filter.JwtAuthenticationTokenFilter;
|
||||
import com.CUST.brain.common.security.handler.RestAuthenticationEntryPoint;
|
||||
import com.CUST.brain.common.security.handler.RestfulAccessDeniedHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* Spring Security配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig {
|
||||
|
||||
@Resource
|
||||
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
|
||||
|
||||
@Resource
|
||||
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
|
||||
|
||||
@Resource
|
||||
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/system/auth/login").permitAll()
|
||||
.antMatchers("/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs").permitAll()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
http.exceptionHandling()
|
||||
.accessDeniedHandler(restfulAccessDeniedHandler)
|
||||
.authenticationEntryPoint(restAuthenticationEntryPoint);
|
||||
|
||||
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.CUST.brain.common.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
@Value("${swagger.enable}")
|
||||
private boolean enableSwagger;
|
||||
|
||||
@Value("${swagger.title}")
|
||||
private String title;
|
||||
|
||||
@Value("${swagger.description}")
|
||||
private String description;
|
||||
|
||||
@Value("${swagger.version}")
|
||||
private String version;
|
||||
|
||||
@Value("${swagger.base-package}")
|
||||
private String basePackage;
|
||||
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(enableSwagger)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(basePackage))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title(title)
|
||||
.description(description)
|
||||
.version(version)
|
||||
.contact(new Contact("CUST", "http://www.cust.edu.cn", "support@cust.edu.cn"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package com.CUST.brain.common.constant;
|
||||
|
||||
/**
|
||||
* Redis缓存key常量
|
||||
*/
|
||||
public class RedisKeyConstants {
|
||||
|
||||
/**
|
||||
* 用户相关缓存key前缀
|
||||
*/
|
||||
public static final String USER_PREFIX = "user:";
|
||||
|
||||
/**
|
||||
* 用户token缓存key
|
||||
*/
|
||||
public static final String USER_TOKEN = USER_PREFIX + "token:%s";
|
||||
|
||||
/**
|
||||
* 用户信息缓存key
|
||||
*/
|
||||
public static final String USER_INFO = USER_PREFIX + "info:";
|
||||
|
||||
/**
|
||||
* 用户角色缓存key
|
||||
*/
|
||||
public static final String USER_ROLES = USER_PREFIX + "roles:";
|
||||
|
||||
/**
|
||||
* 用户权限缓存key
|
||||
*/
|
||||
public static final String USER_PERMISSIONS = USER_PREFIX + "permissions:";
|
||||
|
||||
/**
|
||||
* 系统相关缓存key前缀
|
||||
*/
|
||||
public static final String SYS_PREFIX = "sys:";
|
||||
|
||||
/**
|
||||
* 系统用户缓存key
|
||||
*/
|
||||
public static final String SYS_USER = SYS_PREFIX + "user:";
|
||||
|
||||
/**
|
||||
* 系统角色缓存key
|
||||
*/
|
||||
public static final String SYS_ROLE = SYS_PREFIX + "role:";
|
||||
|
||||
/**
|
||||
* 系统权限缓存key
|
||||
*/
|
||||
public static final String SYS_PERMISSION = SYS_PREFIX + "permission:";
|
||||
|
||||
/**
|
||||
* 分布式锁key前缀
|
||||
*/
|
||||
public static final String LOCK_PREFIX = "lock:";
|
||||
|
||||
/**
|
||||
* 验证码缓存key前缀
|
||||
*/
|
||||
public static final String CAPTCHA_PREFIX = "captcha:";
|
||||
|
||||
/**
|
||||
* 限流缓存key前缀
|
||||
*/
|
||||
public static final String RATE_LIMIT_PREFIX = "rate:limit:";
|
||||
|
||||
/**
|
||||
* 业务相关缓存key前缀
|
||||
*/
|
||||
public static final String BUSINESS_PREFIX = "business:";
|
||||
|
||||
/**
|
||||
* 人才信息缓存key
|
||||
*/
|
||||
public static final String TALENT_INFO = BUSINESS_PREFIX + "talent:info:";
|
||||
|
||||
/**
|
||||
* 企业信息缓存key
|
||||
*/
|
||||
public static final String ENTERPRISE_INFO = BUSINESS_PREFIX + "enterprise:info:";
|
||||
|
||||
/**
|
||||
* 项目信息缓存key
|
||||
*/
|
||||
public static final String PROJECT_INFO = BUSINESS_PREFIX + "project:info:";
|
||||
|
||||
/**
|
||||
* 系统参数相关
|
||||
*/
|
||||
public static final String PARAM_TYPE = SYS_PREFIX + "param:type:";
|
||||
public static final String PARAM_VALUES = SYS_PREFIX + "param:values:";
|
||||
public static final String PARAM_LIST = SYS_PREFIX + "param:list:";
|
||||
|
||||
/**
|
||||
* 角色权限相关
|
||||
*/
|
||||
public static final String ROLE_PERMISSIONS = SYS_ROLE + "permissions:%s";
|
||||
public static final String ROLE_INFO = SYS_ROLE + "info:%s";
|
||||
public static final String PERMISSION_INFO = SYS_PERMISSION + "info:%s";
|
||||
|
||||
/**
|
||||
* 人才信息相关
|
||||
*/
|
||||
public static final String TALENT_BASIC = BUSINESS_PREFIX + "basic:%s";
|
||||
public static final String TALENT_LIST = BUSINESS_PREFIX + "list:%s";
|
||||
public static final String TALENT_UNIT = BUSINESS_PREFIX + "unit:%s";
|
||||
|
||||
/**
|
||||
* 项目相关
|
||||
*/
|
||||
public static final String PROJECT_LIST = BUSINESS_PREFIX + "list:%s";
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*/
|
||||
public static final String SYS_CONFIG = SYS_PREFIX + "config:%s";
|
||||
|
||||
/**
|
||||
* 用户菜单缓存前缀
|
||||
*/
|
||||
public static final String USER_MENUS = USER_PREFIX + "menus:";
|
||||
|
||||
/**
|
||||
* Token黑名单前缀
|
||||
*/
|
||||
public static final String TOKEN_BLACKLIST = USER_PREFIX + "token:blacklist:";
|
||||
|
||||
private RedisKeyConstants() {
|
||||
throw new IllegalStateException("Constant class");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AssignRoleRequestDTO {
|
||||
|
||||
private String userId;
|
||||
private List<String> roleIds;
|
||||
|
||||
// getter/setter
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
public List<String> getRoleIds() { return roleIds; }
|
||||
public void setRoleIds(List<String> roleIds) { this.roleIds = roleIds; }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 登录请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginRequestDTO {
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@ApiModelProperty(value = "登录类型:1-展示端,2-后台", required = true, example = "2")
|
||||
private Integer loginType;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginResponseDTO {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private List<String> roles;
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private List<String> permissions;
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 菜单响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class MenuResponseDTO {
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 菜单编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 菜单路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 菜单图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 菜单类型(1-目录,2-菜单,3-按钮)
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 子菜单
|
||||
*/
|
||||
private List<MenuResponseDTO> children;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色对应权限列表DTO
|
||||
*/
|
||||
@Data
|
||||
public class PermissionTreeVO {
|
||||
|
||||
private String id;
|
||||
private String permissionName;
|
||||
private String permissionCode;
|
||||
private Integer permissionType;
|
||||
private String parentId;
|
||||
private String path;
|
||||
private String component;
|
||||
private String icon;
|
||||
private Integer sort;
|
||||
private Integer status; // 角色下该权限的启用状态(1-启用,0-禁用,null-未分配)
|
||||
private List<PermissionTreeVO> children;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 系统角色请求DTO
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "系统角色请求参数")
|
||||
public class SysRoleRequestDTO {
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
@ApiModelProperty(value = "角色ID", example = "1")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
@NotBlank(message = "角色名称不能为空")
|
||||
@Size(max = 50, message = "角色名称长度不能超过50个字符")
|
||||
@ApiModelProperty(value = "角色名称", required = true, example = "管理员")
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
@NotBlank(message = "角色编码不能为空")
|
||||
@Size(max = 50, message = "角色编码长度不能超过50个字符")
|
||||
@ApiModelProperty(value = "角色编码", required = true, example = "ADMIN")
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
@Size(max = 200, message = "角色描述长度不能超过200个字符")
|
||||
@ApiModelProperty(value = "角色描述", example = "系统管理员角色")
|
||||
private String roleDesc;
|
||||
|
||||
/**
|
||||
* 登录类型:1-仅展示端,2-展示端和后台
|
||||
*/
|
||||
@NotNull(message = "登录类型不能为空")
|
||||
@ApiModelProperty(value = "登录类型:1-仅展示端,2-展示端和后台", required = true, example = "2")
|
||||
private Integer loginType;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
@ApiModelProperty(value = "排序", example = "1")
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用,1-启用
|
||||
*/
|
||||
@NotNull(message = "状态不能为空")
|
||||
@ApiModelProperty(value = "状态:0-禁用,1-启用", required = true, example = "1")
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统角色响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class SysRoleResponseDTO {
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String roleDesc;
|
||||
|
||||
/**
|
||||
* 登录类型:1-仅展示端,2-展示端和后台
|
||||
*/
|
||||
private Integer loginType;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用,1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新角色权限请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class UpdateRolePermissionsRequestDTO {
|
||||
|
||||
@NotBlank(message = "角色ID不能为空")
|
||||
private String roleId;
|
||||
|
||||
private List<String> permissionIds;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.CUST.brain.common.dto.system;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户信息响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class UserInfoResponseDTO {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
private String lastLoginIp;
|
||||
|
||||
/**
|
||||
* 用户角色列表
|
||||
*/
|
||||
private List<String> roles;
|
||||
|
||||
/**
|
||||
* 用户权限列表
|
||||
*/
|
||||
private List<String> permissions;
|
||||
|
||||
/**
|
||||
* 用户菜单列表
|
||||
*/
|
||||
private List<MenuResponseDTO> menus;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.CUST.brain.common.enums;
|
||||
|
||||
/**
|
||||
* 缓存更新策略枚举
|
||||
*/
|
||||
public enum CacheUpdateStrategy {
|
||||
/**
|
||||
* 更新缓存
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*/
|
||||
DELETE,
|
||||
|
||||
/**
|
||||
* 忽略缓存
|
||||
*/
|
||||
IGNORE
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.CUST.brain.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 学历枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum EducationEnum {
|
||||
|
||||
/**
|
||||
* 博士
|
||||
*/
|
||||
DOCTOR("DOCTOR", "博士", "1", 1),
|
||||
|
||||
/**
|
||||
* 硕士
|
||||
*/
|
||||
MASTER("MASTER", "硕士", "2", 2),
|
||||
|
||||
/**
|
||||
* 本科
|
||||
*/
|
||||
BACHELOR("BACHELOR", "本科", "3", 3),
|
||||
|
||||
/**
|
||||
* 专科
|
||||
*/
|
||||
COLLEGE("COLLEGE", "专科", "4", 4),
|
||||
|
||||
/**
|
||||
* 其他
|
||||
*/
|
||||
OTHER("OTHER", "其他", "5", 5);
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final Integer sort;
|
||||
|
||||
EducationEnum(String code, String name, String value, Integer sort) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public static EducationEnum getByCode(String code) {
|
||||
for (EducationEnum education : values()) {
|
||||
if (education.getCode().equals(code)) {
|
||||
return education;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EducationEnum getByValue(String value) {
|
||||
for (EducationEnum education : values()) {
|
||||
if (education.getValue().equals(value)) {
|
||||
return education;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package com.CUST.brain.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 参数类型枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum ParamTypeEnum {
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
SEX("SEX", "性别"),
|
||||
|
||||
/**
|
||||
* 学历
|
||||
*/
|
||||
EDUCATION("EDUCATION", "学历"),
|
||||
|
||||
/**
|
||||
* 职称
|
||||
*/
|
||||
TITLE("TITLE", "职称"),
|
||||
|
||||
/**
|
||||
* 政治面貌
|
||||
*/
|
||||
POLITICAL_STATUS("POLITICAL_STATUS", "政治面貌"),
|
||||
|
||||
/**
|
||||
* 民族
|
||||
*/
|
||||
ETHNICITY("ETHNICITY", "民族"),
|
||||
|
||||
/**
|
||||
* 婚姻状况
|
||||
*/
|
||||
MARITAL_STATUS("MARITAL_STATUS", "婚姻状况"),
|
||||
|
||||
/**
|
||||
* 健康状况
|
||||
*/
|
||||
HEALTH_STATUS("HEALTH_STATUS", "健康状况"),
|
||||
|
||||
/**
|
||||
* 工作状态
|
||||
*/
|
||||
WORK_STATUS("WORK_STATUS", "工作状态"),
|
||||
|
||||
/**
|
||||
* 项目类型
|
||||
*/
|
||||
PROJECT_TYPE("PROJECT_TYPE", "项目类型"),
|
||||
|
||||
/**
|
||||
* 项目级别
|
||||
*/
|
||||
PROJECT_LEVEL("PROJECT_LEVEL", "项目级别"),
|
||||
|
||||
/**
|
||||
* 项目状态
|
||||
*/
|
||||
PROJECT_STATUS("PROJECT_STATUS", "项目状态"),
|
||||
|
||||
/**
|
||||
* 获奖级别
|
||||
*/
|
||||
AWARD_LEVEL("AWARD_LEVEL", "获奖级别"),
|
||||
|
||||
/**
|
||||
* 获奖类型
|
||||
*/
|
||||
AWARD_TYPE("AWARD_TYPE", "获奖类型"),
|
||||
|
||||
/**
|
||||
* 专利类型
|
||||
*/
|
||||
PATENT_TYPE("PATENT_TYPE", "专利类型"),
|
||||
|
||||
/**
|
||||
* 专利状态
|
||||
*/
|
||||
PATENT_STATUS("PATENT_STATUS", "专利状态"),
|
||||
|
||||
/**
|
||||
* 论文类型
|
||||
*/
|
||||
PAPER_TYPE("PAPER_TYPE", "论文类型"),
|
||||
|
||||
/**
|
||||
* 论文级别
|
||||
*/
|
||||
PAPER_LEVEL("PAPER_LEVEL", "论文级别"),
|
||||
|
||||
/**
|
||||
* 著作类型
|
||||
*/
|
||||
MONOGRAPH_TYPE("MONOGRAPH_TYPE", "著作类型"),
|
||||
|
||||
/**
|
||||
* 单位类型
|
||||
*/
|
||||
UNIT_TYPE("UNIT_TYPE", "单位类型"),
|
||||
|
||||
/**
|
||||
* 单位性质
|
||||
*/
|
||||
UNIT_NATURE("UNIT_NATURE", "单位性质"),
|
||||
|
||||
/**
|
||||
* 单位规模
|
||||
*/
|
||||
UNIT_SCALE("UNIT_SCALE", "单位规模"),
|
||||
|
||||
/**
|
||||
* 单位行业
|
||||
*/
|
||||
UNIT_INDUSTRY("UNIT_INDUSTRY", "单位行业"),
|
||||
|
||||
/**
|
||||
* 单位地区
|
||||
*/
|
||||
UNIT_REGION("UNIT_REGION", "单位地区"),
|
||||
|
||||
/**
|
||||
* 单位状态
|
||||
*/
|
||||
UNIT_STATUS("UNIT_STATUS", "单位状态");
|
||||
|
||||
/**
|
||||
* 参数类型编码
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 参数类型名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
ParamTypeEnum(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取枚举
|
||||
*/
|
||||
public static ParamTypeEnum getByCode(String code) {
|
||||
for (ParamTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.CUST.brain.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum SexEnum {
|
||||
|
||||
/**
|
||||
* 男
|
||||
*/
|
||||
MALE("MALE", "男", "0", 1),
|
||||
|
||||
/**
|
||||
* 女
|
||||
*/
|
||||
FEMALE("FEMALE", "女", "1", 2);
|
||||
|
||||
/**
|
||||
* 参数编码
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 参数名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 参数值
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
private final Integer sort;
|
||||
|
||||
SexEnum(String code, String name, String value, Integer sort) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取枚举
|
||||
*/
|
||||
public static SexEnum getByCode(String code) {
|
||||
for (SexEnum sex : values()) {
|
||||
if (sex.getCode().equals(code)) {
|
||||
return sex;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值获取枚举
|
||||
*/
|
||||
public static SexEnum getByValue(String value) {
|
||||
for (SexEnum sex : values()) {
|
||||
if (sex.getValue().equals(value)) {
|
||||
return sex;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.CUST.brain.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 职称枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum TitleEnum {
|
||||
|
||||
/**
|
||||
* 教授
|
||||
*/
|
||||
PROFESSOR("PROFESSOR", "教授", "1", 1),
|
||||
|
||||
/**
|
||||
* 副教授
|
||||
*/
|
||||
ASSOCIATE_PROFESSOR("ASSOCIATE_PROFESSOR", "副教授", "2", 2),
|
||||
|
||||
/**
|
||||
* 讲师
|
||||
*/
|
||||
LECTURER("LECTURER", "讲师", "3", 3),
|
||||
|
||||
/**
|
||||
* 助教
|
||||
*/
|
||||
ASSISTANT("ASSISTANT", "助教", "4", 4),
|
||||
|
||||
/**
|
||||
* 研究员
|
||||
*/
|
||||
RESEARCHER("RESEARCHER", "研究员", "5", 5),
|
||||
|
||||
/**
|
||||
* 副研究员
|
||||
*/
|
||||
ASSOCIATE_RESEARCHER("ASSOCIATE_RESEARCHER", "副研究员", "6", 6),
|
||||
|
||||
/**
|
||||
* 助理研究员
|
||||
*/
|
||||
ASSISTANT_RESEARCHER("ASSISTANT_RESEARCHER", "助理研究员", "7", 7),
|
||||
|
||||
/**
|
||||
* 其他
|
||||
*/
|
||||
OTHER("OTHER", "其他", "8", 8);
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final Integer sort;
|
||||
|
||||
TitleEnum(String code, String name, String value, Integer sort) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public static TitleEnum getByCode(String code) {
|
||||
for (TitleEnum title : values()) {
|
||||
if (title.getCode().equals(code)) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TitleEnum getByValue(String value) {
|
||||
for (TitleEnum title : values()) {
|
||||
if (title.getValue().equals(value)) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.CUST.brain.common.exception;
|
||||
|
||||
import com.CUST.brain.common.api.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
private final long code;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.FAILED.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode, String message) {
|
||||
super(message);
|
||||
this.code = resultCode.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(long code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.CUST.brain.common.exception;
|
||||
|
||||
import com.CUST.brain.common.api.CommonResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public CommonResult<Object> handleBusinessException(BusinessException e) {
|
||||
log.warn("业务异常:{}", e.getMessage());
|
||||
return CommonResult.custom(e.getCode(), e.getMessage(), null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public CommonResult<Object> handleAccessDeniedException(AccessDeniedException e) {
|
||||
log.error("权限不足:{}", e.getMessage(), e);
|
||||
return CommonResult.forbidden(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public CommonResult<Object> handleValidException(MethodArgumentNotValidException e) {
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
String message = null;
|
||||
if (bindingResult.hasErrors()) {
|
||||
FieldError fieldError = bindingResult.getFieldError();
|
||||
if (fieldError != null) {
|
||||
message = fieldError.getDefaultMessage();
|
||||
}
|
||||
}
|
||||
return CommonResult.validateFailed(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
public CommonResult<Object> handleValidException(BindException e) {
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
String message = null;
|
||||
if (bindingResult.hasErrors()) {
|
||||
FieldError fieldError = bindingResult.getFieldError();
|
||||
if (fieldError != null) {
|
||||
message = fieldError.getDefaultMessage();
|
||||
}
|
||||
}
|
||||
return CommonResult.validateFailed(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public CommonResult<Object> handleException(Exception e) {
|
||||
log.error("系统异常:{}", e.getMessage(), e);
|
||||
return CommonResult.failed("系统异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.CUST.brain.common.security;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义登录用户信息,扩展UserDetails
|
||||
*/
|
||||
@Data
|
||||
public class LoginUser implements UserDetails {
|
||||
private String userId;
|
||||
private String username;
|
||||
private String password;
|
||||
private String realName;
|
||||
private String phone;
|
||||
private String email;
|
||||
private Integer status;
|
||||
private LocalDateTime lastLoginTime;
|
||||
private String lastLoginIp;
|
||||
private List<String> roles;
|
||||
private List<String> permissions;
|
||||
private Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@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 != null && status == 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.CUST.brain.common.security.filter;
|
||||
|
||||
import com.CUST.brain.common.utils.JwtTokenUtil;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JWT登录授权过滤器
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
@Resource
|
||||
private UserDetailsService userDetailsService;
|
||||
@Resource
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain chain) throws ServletException, IOException {
|
||||
String authHeader = request.getHeader(jwtTokenUtil.getTokenHeader());
|
||||
if (authHeader != null && authHeader.startsWith(jwtTokenUtil.getTokenHead())) {
|
||||
// The part after "Bearer "
|
||||
String authToken = authHeader.substring(jwtTokenUtil.getTokenHead().length()).trim();
|
||||
String username = jwtTokenUtil.getUsernameFromToken(authToken);
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||
if (jwtTokenUtil.validateToken(authToken)) {
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.CUST.brain.common.security.handler;
|
||||
|
||||
import com.CUST.brain.common.api.CommonResult;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义未登录或token失效时的返回结果
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("未登录或token已过期: {}", authException.getMessage());
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(new ObjectMapper().writeValueAsString(CommonResult.unauthorized(authException.getMessage())));
|
||||
response.getWriter().flush();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.common.security.handler;
|
||||
|
||||
import com.CUST.brain.common.api.CommonResult;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义无权限访问的返回结果
|
||||
*/
|
||||
@Component
|
||||
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
|
||||
@Override
|
||||
public void handle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AccessDeniedException e) throws IOException, ServletException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(new ObjectMapper().writeValueAsString(CommonResult.forbidden(e.getMessage())));
|
||||
response.getWriter().flush();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.CUST.brain.common.security.service;
|
||||
|
||||
import com.CUST.brain.dao.domain.system.SysPermission;
|
||||
import com.CUST.brain.dao.domain.system.SysRole;
|
||||
import com.CUST.brain.dao.domain.system.SysUser;
|
||||
import com.CUST.brain.service.SysPermissionService;
|
||||
import com.CUST.brain.service.SysRoleService;
|
||||
import com.CUST.brain.service.SysUserService;
|
||||
import com.CUST.brain.common.security.LoginUser;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户详情服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Resource
|
||||
private SysUserService userService;
|
||||
|
||||
@Resource
|
||||
private SysRoleService roleService;
|
||||
|
||||
@Resource
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 1. 查询用户
|
||||
SysUser user = userService.getByUsername(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 2. 查询用户角色
|
||||
List<SysRole> roles = roleService.getRolesByUserId(user.getUserId());
|
||||
List<String> roleCodes = roles.stream()
|
||||
.map(SysRole::getRoleCode)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 查询用户权限
|
||||
List<SysPermission> permissions = permissionService.getPermissionsByUserId(user.getUserId());
|
||||
List<String> permissionCodes = permissions.stream()
|
||||
.map(SysPermission::getPermissionCode)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 4. 合并角色和权限
|
||||
List<SimpleGrantedAuthority> authorities = roleCodes.stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
.collect(Collectors.toList());
|
||||
authorities.addAll(permissionCodes.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
// 5. 返回LoginUser对象
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setUsername(user.getUsername());
|
||||
loginUser.setPassword(user.getPassword());
|
||||
loginUser.setRealName(user.getRealName());
|
||||
loginUser.setPhone(user.getPhone());
|
||||
loginUser.setEmail(user.getEmail());
|
||||
loginUser.setStatus(user.getStatus());
|
||||
loginUser.setLastLoginTime(user.getLastLoginTime());
|
||||
loginUser.setLastLoginIp(user.getLastLoginIp());
|
||||
loginUser.setRoles(roleCodes);
|
||||
loginUser.setPermissions(permissionCodes);
|
||||
loginUser.setAuthorities(authorities);
|
||||
return loginUser;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import com.CUST.brain.dao.mapper.SysUserRoleMapper;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class CacheCleanUtils {
|
||||
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
@Resource
|
||||
private SysUserRoleMapper sysUserRoleMapper;
|
||||
|
||||
@Resource
|
||||
private CacheManager cacheManager;
|
||||
|
||||
/**
|
||||
* 清理角色相关缓存
|
||||
* key: cache:sys:role:detail:{roleId}
|
||||
* cache:sys:role:select*
|
||||
*/
|
||||
public void clearRoleCache(String roleId) {
|
||||
if (roleId != null) {
|
||||
redisUtils.delete("cache:sys:role:detail:" + roleId);
|
||||
}
|
||||
redisUtils.deleteByPattern("cache:sys:role:select*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理用户相关缓存
|
||||
* key: cache:sys:user:info:{userId}
|
||||
* cache:user:roles:{userId}
|
||||
* cache:user:permissions:{userId}
|
||||
*/
|
||||
public void clearUserCache(String userId) {
|
||||
if (userId != null) {
|
||||
redisUtils.delete("cache:sys:user:info:" + userId);
|
||||
redisUtils.delete("cache:user:roles:" + userId);
|
||||
redisUtils.delete("cache:user:permissions:" + userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理用户信息缓存(通过用户名)
|
||||
* key: cache:sys:user:{username}
|
||||
*/
|
||||
public void clearUserCacheByUserName(String username) {
|
||||
if (username != null) {
|
||||
redisUtils.delete("cache:sys:user:" + username);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理拥有某角色的所有用户的相关缓存
|
||||
*/
|
||||
public void clearUsersByRole(String roleId) {
|
||||
if (roleId != null) {
|
||||
List<String> userIds = sysUserRoleMapper.selectUserIdsByRoleId(roleId);
|
||||
for (String userId : userIds) {
|
||||
clearUserCache(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理角色权限分配列表缓存
|
||||
* key: cache:permission:listForRole:{roleCode}
|
||||
*/
|
||||
public void clearPermissionListCacheByRoleCode(String roleCode) {
|
||||
if (roleCode != null) {
|
||||
redisUtils.delete("cache:permission:listForRole:" + roleCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有角色权限分配列表缓存(如有需要)
|
||||
*/
|
||||
public void clearAllPermissionListCache() {
|
||||
redisUtils.deleteByPattern("cache:permission:listForRole*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理参数缓存
|
||||
*
|
||||
* @param paramType 参数类型
|
||||
* @param paramName 参数名称,如果为null则清理该类型的所有参数缓存
|
||||
*/
|
||||
public void clearParamCache(String paramType, String paramName) {
|
||||
if (paramType != null) {
|
||||
redisUtils.delete("sys:param:type:" + paramType);
|
||||
redisUtils.delete("sys:param:list:" + paramType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import com.CUST.brain.common.constant.RedisKeyConstants;
|
||||
import com.CUST.brain.common.exception.BusinessException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* JWT Token工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtTokenUtil {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
@Value("${jwt.tokenHeader}")
|
||||
private String tokenHeader;
|
||||
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param username 用户名
|
||||
* @return token
|
||||
*/
|
||||
public String generateToken(String userId, String username) {
|
||||
log.info("生成token用的密钥片段: {}...{}", secret.substring(0, 8), secret.substring(secret.length() - 8));
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
return generateToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户ID
|
||||
*
|
||||
* @param token token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public String getUserIdFromToken(String token) {
|
||||
String userId;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
userId = claims.get("userId", String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("从token中获取用户ID失败: {}", e.getMessage());
|
||||
throw new BusinessException(401, "无效的token");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户名
|
||||
*
|
||||
* @param token token
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
String username;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
username = claims.get("username", String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("从token中获取用户名失败: {}", e.getMessage());
|
||||
throw new BusinessException(401, "无效的token");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token是否有效
|
||||
*
|
||||
* @param token token
|
||||
* @return 是否有效
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
// 1. 检查token是否在黑名单中
|
||||
String blacklistKey = RedisKeyConstants.TOKEN_BLACKLIST + token;
|
||||
if (redisUtils.hasKey(blacklistKey)) {
|
||||
log.warn("token在黑名单中: {}", token);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查token是否过期
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
Date expiration = claims.getExpiration();
|
||||
if (expiration.before(new Date())) {
|
||||
log.warn("token已过期: {}", token);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查token是否在Redis中存在
|
||||
String userId = claims.get("userId", String.class);
|
||||
String tokenKey = String.format(RedisKeyConstants.USER_TOKEN, userId);
|
||||
Object cachedTokenObj = redisUtils.get(tokenKey);
|
||||
if (cachedTokenObj == null) {
|
||||
log.warn("token不存在: {}", token);
|
||||
return false;
|
||||
}
|
||||
String cachedToken = cachedTokenObj.toString();
|
||||
log.info("校验token: 传入token={}, Redis中token={}", token, cachedToken);
|
||||
if (!cachedToken.equals(token)) {
|
||||
log.warn("token已更新: {}", token);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("验证token失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*
|
||||
* @param token 原token
|
||||
* @return 新token
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
claims.put("created", new Date());
|
||||
String newToken = generateToken(claims);
|
||||
|
||||
// 更新Redis中的token
|
||||
String userId = claims.get("userId", String.class);
|
||||
String tokenKey = String.format(RedisKeyConstants.USER_TOKEN, userId);
|
||||
// 将毫秒转换为秒
|
||||
long expireSeconds = TimeUnit.MILLISECONDS.toSeconds(expiration);
|
||||
redisUtils.set(tokenKey, newToken, expireSeconds);
|
||||
|
||||
return newToken;
|
||||
} catch (Exception e) {
|
||||
log.error("刷新token失败: {}", e.getMessage());
|
||||
throw new BusinessException(401, "刷新token失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Base64解码后的密钥字节数组
|
||||
*/
|
||||
private byte[] getSecretKeyBytes() {
|
||||
log.info("JWT密钥片段: {}...{}", secret.substring(0, 8), secret.substring(secret.length() - 8));
|
||||
return Base64.getDecoder().decode(secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取JWT中的负载
|
||||
*/
|
||||
private Claims getClaimsFromToken(String token) {
|
||||
Claims claims = null;
|
||||
try {
|
||||
log.info("校验token: {}", token);
|
||||
log.info("校验token用的密钥片段: {}...{}", secret.substring(0, 8), secret.substring(secret.length() - 8));
|
||||
claims = Jwts.parser()
|
||||
.setSigningKey(getSecretKeyBytes())
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
} catch (Exception e) {
|
||||
log.error("JWT格式验证失败: {}", e.getMessage());
|
||||
throw new BusinessException(401, "无效的token");
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据负载生成JWT token
|
||||
*/
|
||||
private String generateToken(Map<String, Object> claims) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setExpiration(generateExpirationDate())
|
||||
.signWith(SignatureAlgorithm.HS512, getSecretKeyBytes())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token的过期时间
|
||||
*/
|
||||
private Date generateExpirationDate() {
|
||||
return new Date(System.currentTimeMillis() + expiration * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token的完整格式(带前缀)
|
||||
*/
|
||||
public String getTokenHeader() {
|
||||
return tokenHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token的前缀
|
||||
*/
|
||||
public String getTokenHead() {
|
||||
return tokenHead;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import com.CUST.brain.common.config.OssProperties;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Component
|
||||
public class OssUtil {
|
||||
|
||||
@Autowired
|
||||
private OssProperties ossProperties;
|
||||
|
||||
public String upload(MultipartFile file, String objectName) {
|
||||
OSS ossClient = new OSSClientBuilder().build(
|
||||
ossProperties.getEndpoint(),
|
||||
ossProperties.getAccessKeyId(),
|
||||
ossProperties.getAccessKeySecret()
|
||||
);
|
||||
try {
|
||||
ossClient.putObject(ossProperties.getBucketName(), objectName, file.getInputStream());
|
||||
// 拼接文件公网访问地址
|
||||
return "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint().replace("https://", "") + "/" + objectName;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
*/
|
||||
@Component
|
||||
public class PasswordEncoder extends BCryptPasswordEncoder {
|
||||
private static final Logger log = LoggerFactory.getLogger(PasswordEncoder.class);
|
||||
|
||||
public PasswordEncoder() {
|
||||
super(10); // 使用强度为10的BCrypt加密
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void testPasswordEncoder() {
|
||||
// 测试1:使用当前实例
|
||||
String rawPassword = "admin123";
|
||||
String encodedPassword = encode(rawPassword);
|
||||
log.info("测试1 - 当前实例 - 原始密码: {}, 加密后: {}", rawPassword, encodedPassword);
|
||||
boolean matches = matches(rawPassword, encodedPassword);
|
||||
log.info("测试1 - 当前实例 - 匹配结果: {}", matches);
|
||||
|
||||
// 测试2:使用数据库中的密码
|
||||
String dbPassword = "$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi";
|
||||
boolean dbMatches = matches(rawPassword, dbPassword);
|
||||
log.info("测试2 - 当前实例 - 数据库密码匹配结果: {}", dbMatches);
|
||||
|
||||
// 测试3:使用新的 BCryptPasswordEncoder 实例
|
||||
BCryptPasswordEncoder newEncoder = new BCryptPasswordEncoder(10);
|
||||
String newEncoded = newEncoder.encode(rawPassword);
|
||||
log.info("测试3 - 新实例 - 原始密码: {}, 加密后: {}", rawPassword, newEncoded);
|
||||
boolean newMatches = newEncoder.matches(rawPassword, newEncoded);
|
||||
log.info("测试3 - 新实例 - 匹配结果: {}", newMatches);
|
||||
|
||||
// 测试4:使用新实例匹配数据库密码
|
||||
boolean newDbMatches = newEncoder.matches(rawPassword, dbPassword);
|
||||
log.info("测试4 - 新实例 - 数据库密码匹配结果: {}", newDbMatches);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String encoded = super.encode(rawPassword);
|
||||
log.debug("密码加密 - 原始密码: {}, 加密后: {}", rawPassword, encoded);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
boolean matches = super.matches(rawPassword, encodedPassword);
|
||||
log.debug("密码匹配 - 原始密码: {}, 加密密码: {}, 匹配结果: {}", rawPassword, encodedPassword, matches);
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,625 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import com.CUST.brain.common.constant.RedisKeyConstants;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedisUtils {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private static ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
RedisUtils.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
private static final String LOCK_SCRIPT =
|
||||
"local expire = tonumber(ARGV[2]) " +
|
||||
"if not expire then redis.log(redis.LOG_WARNING, 'expire is nil or not a number: ' .. tostring(ARGV[2])) return 0 end " +
|
||||
"if expire <= 0 then redis.log(redis.LOG_WARNING, 'expire <= 0: ' .. tostring(expire)) return 0 end " +
|
||||
"local set_result = redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', expire) " +
|
||||
"redis.log(redis.LOG_DEBUG, 'set result: ' .. tostring(set_result)) " +
|
||||
"return set_result == 'OK' and 1 or 0";
|
||||
|
||||
private static final String UNLOCK_SCRIPT =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
" return redis.call('del', KEYS[1]) " +
|
||||
"else " +
|
||||
" return 0 " +
|
||||
"end";
|
||||
|
||||
private static final String RENEW_SCRIPT =
|
||||
"local expire = tonumber(ARGV[2]) " +
|
||||
"if not expire then redis.log(redis.LOG_WARNING, 'expire is nil or not a number: ' .. tostring(ARGV[2])) return 0 end " +
|
||||
"if expire <= 0 then redis.log(redis.LOG_WARNING, 'expire <= 0: ' .. tostring(expire)) return 0 end " +
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
" local pexpire_result = redis.call('pexpire', KEYS[1], expire) " +
|
||||
" redis.log(redis.LOG_DEBUG, 'pexpire result: ' .. tostring(pexpire_result)) " +
|
||||
" return pexpire_result == 1 and 1 or 0 " +
|
||||
"else " +
|
||||
" return 0 " +
|
||||
"end";
|
||||
|
||||
/**
|
||||
* 设置缓存(对象自动转为JSON字符串)
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param timeout 过期时间(秒)
|
||||
*/
|
||||
public void set(String key, Object value, long timeout) {
|
||||
try {
|
||||
if (value instanceof String) {
|
||||
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
|
||||
} else {
|
||||
String json = objectMapper.writeValueAsString(value);
|
||||
redisTemplate.opsForValue().set(key, json, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("[RedisUtils] set序列化异常", e);
|
||||
throw new RuntimeException("Redis序列化异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存并反序列化为指定类型(JSON字符串)
|
||||
*/
|
||||
public <T> T get(String key, Class<T> clazz) {
|
||||
Object obj = redisTemplate.opsForValue().get(key);
|
||||
if (obj == null) return null;
|
||||
if (clazz == String.class) {
|
||||
return clazz.cast(obj);
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(obj.toString(), clazz);
|
||||
} catch (Exception e) {
|
||||
log.error("[RedisUtils] get反序列化异常", e);
|
||||
throw new RuntimeException("Redis反序列化异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存(原始String)
|
||||
*/
|
||||
public String get(String key) {
|
||||
Object obj = redisTemplate.opsForValue().get(key);
|
||||
return obj == null ? null : obj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
public void delete(String key) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断key是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @return 是否存在
|
||||
*/
|
||||
public Boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置过期时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param timeout 过期时间(秒)
|
||||
* @return 是否成功
|
||||
*/
|
||||
public Boolean expire(String key, long timeout) {
|
||||
return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @param key 键
|
||||
* @return 过期时间(秒)
|
||||
*/
|
||||
public Long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除缓存
|
||||
*/
|
||||
public Long delete(Collection<String> keys) {
|
||||
return redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增
|
||||
*/
|
||||
public Long increment(String key, long delta) {
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减
|
||||
*/
|
||||
public Long decrement(String key, long delta) {
|
||||
return redisTemplate.opsForValue().decrement(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Hash中的属性并反序列化为指定类型
|
||||
*/
|
||||
public <T> T hGet(String key, String hashKey, Class<T> clazz) {
|
||||
Object obj = redisTemplate.opsForHash().get(key, hashKey);
|
||||
return objectMapper.convertValue(obj, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向Hash中放入一个属性
|
||||
*/
|
||||
public void hSet(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向Hash中放入一个属性并设置过期时间
|
||||
*/
|
||||
public void hSet(String key, String hashKey, Object value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接设置整个Hash
|
||||
*/
|
||||
public void hSetAll(String key, Map<String, Object> map) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接设置整个Hash并设置过期时间
|
||||
*/
|
||||
public void hSetAll(String key, Map<String, Object> map, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取整个Hash并反序列化为指定类型Map
|
||||
*/
|
||||
public <T> Map<String, T> hGetAll(String key, Class<T> clazz) {
|
||||
Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
|
||||
Map<String, T> result = new HashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : map.entrySet()) {
|
||||
result.put(String.valueOf(entry.getKey()), objectMapper.convertValue(entry.getValue(), clazz));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的属性
|
||||
*/
|
||||
public Long hDelete(String key, Object... hashKeys) {
|
||||
return redisTemplate.opsForHash().delete(key, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Hash中是否存在该属性
|
||||
*/
|
||||
public Boolean hHasKey(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().hasKey(key, hashKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash递增
|
||||
*/
|
||||
public Long hIncrement(String key, String hashKey, long delta) {
|
||||
return redisTemplate.opsForHash().increment(key, hashKey, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash递减
|
||||
*/
|
||||
public Long hDecrement(String key, String hashKey, long delta) {
|
||||
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Set中的所有值
|
||||
*/
|
||||
public Set<Object> sMembers(String key) {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Set中是否存在value
|
||||
*/
|
||||
public Boolean sIsMember(String key, Object value) {
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向Set中放入数据
|
||||
*/
|
||||
public Long sAdd(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向Set中放入数据并设置过期时间
|
||||
*/
|
||||
public Long sAdd(String key, long timeout, TimeUnit unit, Object... values) {
|
||||
Long count = redisTemplate.opsForSet().add(key, values);
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Set的长度
|
||||
*/
|
||||
public Long sSize(String key) {
|
||||
return redisTemplate.opsForSet().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Set中的值
|
||||
*/
|
||||
public Long sRemove(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().remove(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取List缓存并反序列化为指定类型List
|
||||
*/
|
||||
public <T> List<T> getList(String key, Class<T> clazz) {
|
||||
Object obj = redisTemplate.opsForValue().get(key);
|
||||
if (obj instanceof List) {
|
||||
List<?> list = (List<?>) obj;
|
||||
List<T> result = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
result.add(objectMapper.convertValue(item, clazz));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取List中的值并反序列化为指定类型
|
||||
*/
|
||||
public <T> List<T> lRange(String key, long start, long end, Class<T> clazz) {
|
||||
List<Object> list = redisTemplate.opsForList().range(key, start, end);
|
||||
List<T> result = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
result.add(objectMapper.convertValue(item, clazz));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取List的长度
|
||||
*/
|
||||
public Long lSize(String key) {
|
||||
return redisTemplate.opsForList().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取List中指定索引的值
|
||||
*/
|
||||
public Object lIndex(String key, long index) {
|
||||
return redisTemplate.opsForList().index(key, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向List中放入数据
|
||||
*/
|
||||
public Long lRightPush(String key, Object value) {
|
||||
return redisTemplate.opsForList().rightPush(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向List中放入数据并设置过期时间
|
||||
*/
|
||||
public Long lRightPush(String key, Object value, long timeout, TimeUnit unit) {
|
||||
Long size = redisTemplate.opsForList().rightPush(key, value);
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向List中批量放入数据
|
||||
*/
|
||||
public Long lRightPushAll(String key, Object... values) {
|
||||
return redisTemplate.opsForList().rightPushAll(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向List中批量放入数据并设置过期时间
|
||||
*/
|
||||
public Long lRightPushAll(String key, long timeout, TimeUnit unit, Object... values) {
|
||||
Long size = redisTemplate.opsForList().rightPushAll(key, values);
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从List中移除数据
|
||||
*/
|
||||
public Long lRemove(String key, long count, Object value) {
|
||||
return redisTemplate.opsForList().remove(key, count, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁
|
||||
*/
|
||||
public boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(key)) {
|
||||
log.warn("【分布式锁】tryLock失败,key为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保key是有效的字符串
|
||||
String safeKey = key.trim().replaceAll("[^a-zA-Z0-9_:.-]", "_");
|
||||
if (!StringUtils.hasText(safeKey)) {
|
||||
log.warn("【分布式锁】tryLock失败,key无效,原始key={}", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保value是有效的字符串
|
||||
String safeValue = value == null ? UUID.randomUUID().toString() : value.trim();
|
||||
if (!StringUtils.hasText(safeValue)) {
|
||||
log.warn("【分布式锁】tryLock失败,value无效,key={}", safeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 参数修正
|
||||
if (timeout <= 0) {
|
||||
timeout = 10; // 默认10秒
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
}
|
||||
long millis = unit.toMillis(timeout);
|
||||
if (millis <= 0) {
|
||||
millis = 10000; // 默认10秒
|
||||
}
|
||||
|
||||
log.info("【分布式锁】tryLock开始,key={}, value={}, timeout={}, unit={}, millis={}, thread={}",
|
||||
safeKey, safeValue, timeout, unit, millis, Thread.currentThread().getName());
|
||||
|
||||
try {
|
||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
|
||||
List<String> keys = Collections.singletonList(safeKey);
|
||||
// 确保 millis 是有效的正整数
|
||||
if (millis <= 0) {
|
||||
log.warn("【分布式锁】tryLock失败,timeout必须大于0,millis={}", millis);
|
||||
return false;
|
||||
}
|
||||
String millisStr = String.valueOf(millis);
|
||||
log.debug("【分布式锁】tryLock参数,key={}, value={}, millisStr={}", safeKey, safeValue, millisStr);
|
||||
String[] args = new String[]{safeValue, millisStr};
|
||||
|
||||
// 先检查 key 是否存在
|
||||
Boolean exists = redisTemplate.hasKey(safeKey);
|
||||
log.debug("【分布式锁】tryLock前检查key是否存在,key={}, exists={}", safeKey, exists);
|
||||
|
||||
// 尝试直接使用 set 命令
|
||||
Boolean setResult = redisTemplate.opsForValue().setIfAbsent(safeKey, safeValue, millis, TimeUnit.MILLISECONDS);
|
||||
log.debug("【分布式锁】直接set命令结果,key={}, result={}", safeKey, setResult);
|
||||
|
||||
if (Boolean.TRUE.equals(setResult)) {
|
||||
log.info("【分布式锁】tryLock成功(直接set),key={}, value={}", safeKey, safeValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果直接set失败,尝试使用脚本
|
||||
Long result = redisTemplate.execute(script, keys, args);
|
||||
boolean lockResult = (result != null && result == 1L);
|
||||
|
||||
// 检查执行后的状态
|
||||
if (!lockResult) {
|
||||
String currentValue = (String) redisTemplate.opsForValue().get(safeKey);
|
||||
Long ttl = redisTemplate.getExpire(safeKey, TimeUnit.MILLISECONDS);
|
||||
log.debug("【分布式锁】tryLock失败后的状态,key={}, currentValue={}, ttl={}", safeKey, currentValue, ttl);
|
||||
}
|
||||
|
||||
log.info("【分布式锁】tryLock结束,key={}, value={}, result={}, thread={}",
|
||||
safeKey, safeValue, lockResult, Thread.currentThread().getName());
|
||||
return lockResult;
|
||||
} catch (Exception e) {
|
||||
log.error("【分布式锁】tryLock异常,key={}, value={}, error={}", safeKey, safeValue, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放分布式锁
|
||||
*/
|
||||
public boolean unlock(String key, String value) {
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(key)) {
|
||||
log.warn("【分布式锁】unlock失败,key为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保key是有效的字符串
|
||||
String safeKey = key.trim().replaceAll("[^a-zA-Z0-9_:.-]", "_");
|
||||
if (!StringUtils.hasText(safeKey)) {
|
||||
log.warn("【分布式锁】unlock失败,key无效,原始key={}", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保value是有效的字符串
|
||||
String safeValue = value == null ? "" : value.trim();
|
||||
if (!StringUtils.hasText(safeValue)) {
|
||||
log.warn("【分布式锁】unlock失败,value无效,key={}", safeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("【分布式锁】unlock开始,key={}, value={}, thread={}",
|
||||
safeKey, safeValue, Thread.currentThread().getName());
|
||||
|
||||
try {
|
||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
|
||||
List<String> keys = Collections.singletonList(safeKey);
|
||||
String[] args = new String[]{safeValue};
|
||||
|
||||
Long result = redisTemplate.execute(script, keys, args);
|
||||
boolean unlockResult = (result != null && result == 1L);
|
||||
|
||||
log.info("【分布式锁】unlock结束,key={}, value={}, result={}, thread={}",
|
||||
safeKey, safeValue, unlockResult, Thread.currentThread().getName());
|
||||
return unlockResult;
|
||||
} catch (Exception e) {
|
||||
log.error("【分布式锁】unlock异常,key={}, value={}, error={}", safeKey, safeValue, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 续期分布式锁
|
||||
*/
|
||||
public boolean renewLock(String key, String value, long timeout, TimeUnit unit) {
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(key)) {
|
||||
log.warn("【分布式锁】renewLock失败,key为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保key是有效的字符串
|
||||
String safeKey = key.trim().replaceAll("[^a-zA-Z0-9_:.-]", "_");
|
||||
if (!StringUtils.hasText(safeKey)) {
|
||||
log.warn("【分布式锁】renewLock失败,key无效,原始key={}", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保value是有效的字符串
|
||||
String safeValue = value == null ? "" : value.trim();
|
||||
if (!StringUtils.hasText(safeValue)) {
|
||||
log.warn("【分布式锁】renewLock失败,value无效,key={}", safeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 参数修正
|
||||
if (timeout <= 0) {
|
||||
timeout = 10; // 默认10秒
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
}
|
||||
long millis = unit.toMillis(timeout);
|
||||
if (millis <= 0) {
|
||||
millis = 10000; // 默认10秒
|
||||
}
|
||||
|
||||
log.info("【分布式锁】renewLock开始,key={}, value={}, timeout={}, unit={}, millis={}, thread={}",
|
||||
safeKey, safeValue, timeout, unit, millis, Thread.currentThread().getName());
|
||||
|
||||
try {
|
||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>(RENEW_SCRIPT, Long.class);
|
||||
List<String> keys = Collections.singletonList(safeKey);
|
||||
// 确保 millis 是有效的正整数
|
||||
if (millis <= 0) {
|
||||
log.warn("【分布式锁】renewLock失败,timeout必须大于0,millis={}", millis);
|
||||
return false;
|
||||
}
|
||||
String millisStr = String.valueOf(millis);
|
||||
log.debug("【分布式锁】renewLock参数,key={}, value={}, millisStr={}", safeKey, safeValue, millisStr);
|
||||
String[] args = new String[]{safeValue, millisStr};
|
||||
|
||||
Long result = redisTemplate.execute(script, keys, args);
|
||||
boolean renewResult = (result != null && result == 1L);
|
||||
|
||||
log.info("【分布式锁】renewLock结束,key={}, value={}, result={}, thread={}",
|
||||
safeKey, safeValue, renewResult, Thread.currentThread().getName());
|
||||
return renewResult;
|
||||
} catch (Exception e) {
|
||||
log.error("【分布式锁】renewLock异常,key={}, value={}, error={}", safeKey, safeValue, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除指定前缀的key
|
||||
*/
|
||||
public void deleteByPattern(String pattern) {
|
||||
Set<String> keys = redisTemplate.keys(pattern);
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
*/
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
try {
|
||||
Properties info = redisTemplate.getConnectionFactory().getConnection().info();
|
||||
stats.put("usedMemory", info.getProperty("used_memory"));
|
||||
stats.put("connectedClients", info.getProperty("connected_clients"));
|
||||
stats.put("totalConnectionsReceived", info.getProperty("total_connections_received"));
|
||||
stats.put("totalCommandsProcessed", info.getProperty("total_commands_processed"));
|
||||
stats.put("instantaneousOpsPerSec", info.getProperty("instantaneous_ops_per_sec"));
|
||||
stats.put("keyspaceHits", info.getProperty("keyspace_hits"));
|
||||
stats.put("keyspaceMisses", info.getProperty("keyspace_misses"));
|
||||
} catch (Exception e) {
|
||||
log.error("获取Redis统计信息失败", e);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存命中率
|
||||
*/
|
||||
public double getHitRate() {
|
||||
try {
|
||||
Properties info = redisTemplate.getConnectionFactory().getConnection().info();
|
||||
long hits = Long.parseLong(info.getProperty("keyspace_hits", "0"));
|
||||
long misses = Long.parseLong(info.getProperty("keyspace_misses", "0"));
|
||||
long total = hits + misses;
|
||||
return total > 0 ? (double) hits / total : 0;
|
||||
} catch (Exception e) {
|
||||
log.error("获取Redis命中率失败", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定期清理TTL为-1的死锁key(建议用定时任务调用)
|
||||
*/
|
||||
public void cleanDeadLocks(String lockPrefix) {
|
||||
Set<String> keys = redisTemplate.keys(lockPrefix + "*");
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
for (String key : keys) {
|
||||
Long ttl = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
|
||||
if (ttl == null || ttl == -1) {
|
||||
redisTemplate.delete(key);
|
||||
log.warn("【分布式锁】发现并清理死锁key:{}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.CUST.brain.common.utils;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import com.CUST.brain.common.security.LoginUser;
|
||||
import com.CUST.brain.common.exception.BusinessException;
|
||||
|
||||
/**
|
||||
* 安全工具类
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
private SecurityUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户ID
|
||||
*/
|
||||
public static String getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new BusinessException(401, "用户未登录");
|
||||
}
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof LoginUser) {
|
||||
return ((LoginUser) principal).getUserId();
|
||||
} else if (principal instanceof UserDetails) {
|
||||
// 兼容旧逻辑
|
||||
return ((UserDetails) principal).getUsername();
|
||||
}
|
||||
return principal.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
*/
|
||||
public static String getCurrentUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new BusinessException(401, "用户未登录");
|
||||
}
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof LoginUser) {
|
||||
return ((LoginUser) principal).getUsername();
|
||||
} else if (principal instanceof UserDetails) {
|
||||
return ((UserDetails) principal).getUsername();
|
||||
}
|
||||
return principal.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前用户是否已登录
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && authentication.isAuthenticated();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.CUST.brain.dao.domain.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体类
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 删除标志(0-未删除,1-已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer flag;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.CUST.brain.dao.domain.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 关系实体基类
|
||||
*/
|
||||
@Data
|
||||
public class BaseRelationEntity{
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 学术奖励信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("academic_award")
|
||||
public class AcademicAward extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 奖项id
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 奖励名称
|
||||
*/
|
||||
private String awardName;
|
||||
|
||||
/**
|
||||
* 奖励级别
|
||||
*/
|
||||
private String awardLevel;
|
||||
|
||||
/**
|
||||
* 授予时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate awardTime;
|
||||
|
||||
/**
|
||||
* 授予机构
|
||||
*/
|
||||
private String awardingAgency;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 工作单位信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("employment_unit")
|
||||
public class EmploymentUnit extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 工作单位id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 工作单位名称
|
||||
*/
|
||||
private String unitName;
|
||||
|
||||
/**
|
||||
* 工作单位地址
|
||||
*/
|
||||
private String unitAddress;
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 企业信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("enterprise_info")
|
||||
public class EnterpriseInfo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 企业id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 纳税人识别号
|
||||
*/
|
||||
private String taxpayerId;
|
||||
|
||||
/**
|
||||
* 企业名称
|
||||
*/
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* 企业类型
|
||||
*/
|
||||
private String companyType;
|
||||
|
||||
/**
|
||||
* 是否高新技术企业认证(0:否,1:是)
|
||||
*/
|
||||
private Integer isHighTechCertified;
|
||||
|
||||
/**
|
||||
* 资质证书
|
||||
*/
|
||||
private String qualificationCertificate;
|
||||
|
||||
/**
|
||||
* 企业状态
|
||||
*/
|
||||
private String companyStatus;
|
||||
|
||||
/**
|
||||
* 法定代表人id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 注册资本
|
||||
*/
|
||||
private String registeredCapital;
|
||||
|
||||
/**
|
||||
* 企业地址
|
||||
*/
|
||||
private String companyAddress;
|
||||
|
||||
/**
|
||||
* 所属省份
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 所属城市
|
||||
*/
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 所属区域
|
||||
*/
|
||||
private String district;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
private String industry;
|
||||
|
||||
/**
|
||||
* 成立日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate establishmentDate;
|
||||
|
||||
/**
|
||||
* 员工人数
|
||||
*/
|
||||
private Integer employeeCount;
|
||||
|
||||
/**
|
||||
* 网址
|
||||
*/
|
||||
private String website;
|
||||
|
||||
/**
|
||||
* 经营范围
|
||||
*/
|
||||
private String businessScope;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String contactPhone;
|
||||
|
||||
/**
|
||||
* 联系邮箱
|
||||
*/
|
||||
private String contactEmail;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 荣誉称号信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("honorary_title")
|
||||
public class HonoraryTitle extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 荣誉ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 荣誉称号
|
||||
*/
|
||||
private String honorTitle;
|
||||
|
||||
/**
|
||||
* 授予时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate awardTime;
|
||||
|
||||
/**
|
||||
* 授予机构
|
||||
*/
|
||||
private String awardingAgency;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.common.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 荣誉称号信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("honorary_title")
|
||||
public class HonoraryTitle extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 荣誉ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 荣誉称号
|
||||
*/
|
||||
private String honorTitle;
|
||||
|
||||
/**
|
||||
* 授予时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate awardTime;
|
||||
|
||||
/**
|
||||
* 授予机构
|
||||
*/
|
||||
private String awardingAgency;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 论文专著信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("papers_and_monographs")
|
||||
public class PapersAndMonographs extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 论文专著ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 发表时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime publicationTime;
|
||||
|
||||
/**
|
||||
* 刊物名称
|
||||
*/
|
||||
private String journalName;
|
||||
|
||||
/**
|
||||
* 卷号
|
||||
*/
|
||||
private String volumeNumber;
|
||||
|
||||
/**
|
||||
* 期号
|
||||
*/
|
||||
private String issueNumber;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
private String pageRange;
|
||||
|
||||
/**
|
||||
* DOI号
|
||||
*/
|
||||
private String doi;
|
||||
|
||||
/**
|
||||
* 著作类型(参数表WORK_TYPE)
|
||||
*/
|
||||
private String workType;
|
||||
|
||||
/**
|
||||
* 状态:1-草稿,2-已发布
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 专利信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("patent")
|
||||
public class Patent extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 专利id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 专利名称
|
||||
*/
|
||||
private String patentName;
|
||||
|
||||
/**
|
||||
* 专利编号
|
||||
*/
|
||||
private String patentNumber;
|
||||
|
||||
/**
|
||||
* 申请时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate applicationTime;
|
||||
|
||||
/**
|
||||
* 授权时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate authorizationTime;
|
||||
|
||||
/**
|
||||
* 状态:1-草稿,2-已发布
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 立项项目信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("project_initiation")
|
||||
public class ProjectInitiation extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 立项项目id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 立项项目编号
|
||||
*/
|
||||
private String projectProposalNumber;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 项目来源
|
||||
*/
|
||||
private String projectFrom;
|
||||
|
||||
/**
|
||||
* 项目类型
|
||||
*/
|
||||
private String projectType;
|
||||
|
||||
/**
|
||||
* 资金来源组织
|
||||
*/
|
||||
private String fundingFrom;
|
||||
|
||||
/**
|
||||
* 承担单位id
|
||||
*/
|
||||
private String undertakingUnitId;
|
||||
|
||||
/**
|
||||
* 项目金额
|
||||
*/
|
||||
private BigDecimal projectAccount;
|
||||
|
||||
/**
|
||||
* 项目启动时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate startTime;
|
||||
|
||||
/**
|
||||
* 项目终止时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate endTime;
|
||||
|
||||
/**
|
||||
* 项目当前状态(PROJECT_STATUS)
|
||||
*/
|
||||
private String projectStatus;
|
||||
|
||||
/**
|
||||
* 状态:1-草稿,2-已发布
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 科研项目信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("research_project")
|
||||
public class ResearchProject extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 项目名称英文版
|
||||
*/
|
||||
private String titleEn;
|
||||
|
||||
/**
|
||||
* 项目类型
|
||||
*/
|
||||
private String projectType;
|
||||
|
||||
/**
|
||||
* 检索关键词
|
||||
*/
|
||||
private String keywordOriginal;
|
||||
|
||||
/**
|
||||
* 检索关键词英文
|
||||
*/
|
||||
private String keywordEn;
|
||||
|
||||
/**
|
||||
* 摘要
|
||||
*/
|
||||
private String summaryCn;
|
||||
|
||||
/**
|
||||
* 摘要英文
|
||||
*/
|
||||
private String summaryEn;
|
||||
|
||||
/**
|
||||
* 预算/财政拨款
|
||||
*/
|
||||
private BigDecimal funding;
|
||||
|
||||
/**
|
||||
* 货币代码
|
||||
*/
|
||||
private String currencyCode;
|
||||
|
||||
/**
|
||||
* 项目金额(美元)
|
||||
*/
|
||||
private BigDecimal fundingDollar;
|
||||
|
||||
/**
|
||||
* 申请年份
|
||||
*/
|
||||
private Integer applyYear;
|
||||
|
||||
/**
|
||||
* 项目启动时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate planStartDate;
|
||||
|
||||
/**
|
||||
* 项目终止时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate planEndDate;
|
||||
|
||||
/**
|
||||
* 承担单位id
|
||||
*/
|
||||
private String undertakeUnitId;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
private String undertakeUnitState;
|
||||
|
||||
/**
|
||||
* 国家
|
||||
*/
|
||||
private String undertakeUnitCountry;
|
||||
|
||||
/**
|
||||
* 资金名称
|
||||
*/
|
||||
private String funderName;
|
||||
|
||||
/**
|
||||
* 资金来源组织
|
||||
*/
|
||||
private String funderGroup;
|
||||
|
||||
/**
|
||||
* 资金国家
|
||||
*/
|
||||
private String funderCountry;
|
||||
|
||||
/**
|
||||
* 学科大类(第一学科)
|
||||
*/
|
||||
private String primarySubject;
|
||||
|
||||
/**
|
||||
* 学科小类
|
||||
*/
|
||||
private String subSubject;
|
||||
|
||||
/**
|
||||
* 状态:1-草稿,2-已发布
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 履历信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("resume")
|
||||
public class Resume extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 履历ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 起始时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 所在单位id
|
||||
*/
|
||||
private String unitId;
|
||||
|
||||
/**
|
||||
* 工作内容
|
||||
*/
|
||||
private String workContent;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 评审经历信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("review_experience")
|
||||
public class ReviewExperience extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 评审id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 评审项目
|
||||
*/
|
||||
private String reviewProject;
|
||||
|
||||
/**
|
||||
* 评审时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime reviewTime;
|
||||
|
||||
/**
|
||||
* 评审机构
|
||||
*/
|
||||
private String reviewingAgency;
|
||||
|
||||
/**
|
||||
* 评审角色
|
||||
*/
|
||||
private String reviewRole;
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 人才基本信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("talent_basic")
|
||||
public class TalentBasic extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 性别(0-男,1-女)
|
||||
*/
|
||||
private Integer sex;
|
||||
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 单位id
|
||||
*/
|
||||
private String unitId;
|
||||
|
||||
/**
|
||||
* 单位类型:school-高校,company-企业
|
||||
*/
|
||||
private String unitType;
|
||||
|
||||
/**
|
||||
* 最高学历
|
||||
*/
|
||||
private String highestEdu;
|
||||
|
||||
/**
|
||||
* 最高学历毕业学校
|
||||
*/
|
||||
private String highestSchool;
|
||||
|
||||
/**
|
||||
* 分类定级人才
|
||||
*/
|
||||
private String classifyGrade;
|
||||
|
||||
/**
|
||||
* 职称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 地区编码
|
||||
*/
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* 照片
|
||||
*/
|
||||
private String image;
|
||||
|
||||
/**
|
||||
* 最高技术职务资格
|
||||
*/
|
||||
private String highestPosition;
|
||||
|
||||
/**
|
||||
* 入职时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate entryDate;
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 人才基本信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("talent_basic")
|
||||
public class TalentBasic extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 性别(0-男,1-女)
|
||||
*/
|
||||
private Integer sex;
|
||||
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 单位id
|
||||
*/
|
||||
private String unitId;
|
||||
|
||||
/**
|
||||
* 单位id
|
||||
*/
|
||||
private String unitType;
|
||||
|
||||
/**
|
||||
* 最高学历
|
||||
*/
|
||||
private String highestEdu;
|
||||
|
||||
/**
|
||||
* 最高学历毕业学校
|
||||
*/
|
||||
private String highestSchool;
|
||||
|
||||
/**
|
||||
* 分类定级人才
|
||||
*/
|
||||
private String classifyGrade;
|
||||
|
||||
/**
|
||||
* 职称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 地区编码
|
||||
*/
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* 照片
|
||||
*/
|
||||
private String image;
|
||||
|
||||
/**
|
||||
* 最高技术职务资格
|
||||
*/
|
||||
private String highestPosition;
|
||||
|
||||
/**
|
||||
* 入职时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate entryDate;
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 科技报告信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("tech_report")
|
||||
public class TechReport extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 报告id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 项目id
|
||||
*/
|
||||
private String projectId;
|
||||
|
||||
/**
|
||||
* 报告名称(中文)
|
||||
*/
|
||||
private String reportNameCn;
|
||||
|
||||
/**
|
||||
* 报告名称(英文)
|
||||
*/
|
||||
private String reportName;
|
||||
|
||||
/**
|
||||
* 作者(中文)
|
||||
*/
|
||||
private String authorCn;
|
||||
|
||||
/**
|
||||
* 作者(英文)
|
||||
*/
|
||||
private String authorEn;
|
||||
|
||||
/**
|
||||
* 作者单位(中文)
|
||||
*/
|
||||
private String workplaceCn;
|
||||
|
||||
/**
|
||||
* 作者单位(英文)
|
||||
*/
|
||||
private String workplaceEn;
|
||||
|
||||
/**
|
||||
* 关键词(中文)
|
||||
*/
|
||||
private String keywordCn;
|
||||
|
||||
/**
|
||||
* 关键词(英文)
|
||||
*/
|
||||
private String keywordEn;
|
||||
|
||||
/**
|
||||
* 摘要(中文)
|
||||
*/
|
||||
private String summaryCn;
|
||||
|
||||
/**
|
||||
* 摘要(英文)
|
||||
*/
|
||||
private String summaryEn;
|
||||
|
||||
/**
|
||||
* 编撰时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime writingTime;
|
||||
|
||||
/**
|
||||
* 支持机构
|
||||
*/
|
||||
private String institution;
|
||||
|
||||
/**
|
||||
* 报告类型
|
||||
*/
|
||||
private String reportType;
|
||||
|
||||
/**
|
||||
* 支持渠道
|
||||
*/
|
||||
private String channel;
|
||||
|
||||
/**
|
||||
* 是否公开
|
||||
*/
|
||||
private String isPublic;
|
||||
|
||||
/**
|
||||
* 报告状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 报告全文路径
|
||||
*/
|
||||
private String reportPath;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.CUST.brain.dao.domain.business;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 工作领域信息实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("work_domain")
|
||||
public class WorkDomain extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 领域id
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 学科项目
|
||||
*/
|
||||
private String disciplineDomain;
|
||||
|
||||
/**
|
||||
* 行业领域
|
||||
*/
|
||||
private String industryDomain;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 论文专著关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("papers_and_monographs_talent")
|
||||
public class PapersAndMonographsTalent extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
@TableField("talent_id")
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 论文专著ID
|
||||
*/
|
||||
@TableField("papers_and_monographs_id")
|
||||
private String papersAndMonographsId;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 专利关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("patent_talent")
|
||||
public class PatentTalent extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
@TableField("talent_id")
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 专利ID
|
||||
*/
|
||||
@TableField("patent_id")
|
||||
private String patentId;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 科研项目人才关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("research_project_talent")
|
||||
public class ResearchProjectTalent extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 项目编号
|
||||
*/
|
||||
private String projectId;
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 角色权限关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_role_permission")
|
||||
public class SysRolePermission extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
@TableField("role_id")
|
||||
private String roleId;
|
||||
|
||||
/**
|
||||
* 权限ID
|
||||
*/
|
||||
@TableField("permission_id")
|
||||
private String permissionId;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 用户角色关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_user_role")
|
||||
public class SysUserRole extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@TableField("user_id")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
@TableField("role_id")
|
||||
private String roleId;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 人才立项关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("talent_project_initiation")
|
||||
public class TalentProjectInitiation extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 人才ID
|
||||
*/
|
||||
@TableField("talent_id")
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 立项项目ID
|
||||
*/
|
||||
@TableField("project_initiation_id")
|
||||
private String projectInitiationId;
|
||||
|
||||
/**
|
||||
* 是否负责人(0:否,1:是)
|
||||
*/
|
||||
@TableField("is_principal")
|
||||
private Boolean isPrincipal;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.CUST.brain.dao.domain.relation;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseRelationEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 人才报告关联实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("talent_report")
|
||||
public class TalentReport extends BaseRelationEntity {
|
||||
|
||||
/**
|
||||
* 人才id
|
||||
*/
|
||||
private String talentId;
|
||||
|
||||
/**
|
||||
* 报告id
|
||||
*/
|
||||
private String reportId;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_param")
|
||||
public class SysParam extends BaseEntity {
|
||||
private String paramType;
|
||||
private String paramName;
|
||||
private String paramValue;
|
||||
private String dataType;
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_param")
|
||||
public class SysParam extends BaseEntity {
|
||||
private String paramType;
|
||||
private String paramCode;
|
||||
private String paramName;
|
||||
private String paramValue;
|
||||
private String dataType;
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
private
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 系统参数类型实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_param_type")
|
||||
public class SysParamType extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 类型代码
|
||||
*/
|
||||
private String typeCode;
|
||||
|
||||
/**
|
||||
* 类型名称
|
||||
*/
|
||||
private String typeName;
|
||||
|
||||
/**
|
||||
* 类型描述
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
private Integer status;
|
||||
|
||||
// getter和setter略,可自动生成
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 系统参数类型实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_param_type")
|
||||
public class SysParamType extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 类型代码
|
||||
*/
|
||||
private String typeCode;
|
||||
|
||||
/**
|
||||
* 类型名称
|
||||
*/
|
||||
private String typeName;
|
||||
|
||||
/**
|
||||
* 类型描述
|
||||
*/
|
||||
private String typeDesc;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
private Integer status;
|
||||
|
||||
// getter和setter略,可自动生成
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 系统权限实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_permission")
|
||||
public class SysPermission extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
private String permissionName;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
private String permissionCode;
|
||||
|
||||
/**
|
||||
* 权限类型:1-菜单,2-按钮,3-接口
|
||||
*/
|
||||
private Integer permissionType;
|
||||
|
||||
/**
|
||||
* 父权限ID
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 前端组件
|
||||
*/
|
||||
private String component;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用,1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.CUST.brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 系统角色实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_role")
|
||||
public class SysRole extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String roleDesc;
|
||||
|
||||
/**
|
||||
* 登录类型:1-仅展示端,2-展示端和后台
|
||||
*/
|
||||
private Integer loginType;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用,1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.axtk.technological_brain.dao.domain.system;
|
||||
|
||||
import com.axtk.technological_brain.dao.domain.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 系统角色实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_role")
|
||||
public class SysRole extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用,1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.CUST.brain.dao.domain.system;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统用户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_user")
|
||||
@ApiModel("系统用户")
|
||||
public class SysUser {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@ApiModelProperty("用户ID")
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty("用户名")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty("密码")
|
||||
private String password;
|
||||
|
||||
@ApiModelProperty("真实姓名")
|
||||
private String realName;
|
||||
|
||||
@ApiModelProperty("手机号")
|
||||
private String phone;
|
||||
|
||||
@ApiModelProperty("邮箱")
|
||||
private String email;
|
||||
|
||||
@ApiModelProperty("状态:0-禁用,1-启用")
|
||||
private Integer status;
|
||||
|
||||
@ApiModelProperty("最后登录时间")
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
@ApiModelProperty("最后登录IP")
|
||||
private String lastLoginIp;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@ApiModelProperty("更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@ApiModelProperty("删除标志:0-已删除,1-未删除")
|
||||
private Integer flag;
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getUserId() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.AcademicAward;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学术奖励Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface AcademicAwardMapper extends BaseMapper<AcademicAward> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.EmploymentUnit;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 就业单位Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface EmploymentUnitMapper extends BaseMapper<EmploymentUnit> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.EnterpriseInfo;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 企业信息Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface EnterpriseInfoMapper extends BaseMapper<EnterpriseInfo> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.HonoraryTitle;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 荣誉称号Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HonoraryTitleMapper extends BaseMapper<HonoraryTitle> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.PapersAndMonographs;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 论文专著Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface PapersAndMonographsMapper extends BaseMapper<PapersAndMonographs> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.relation.PapersAndMonographsTalent;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 论文专著人才关联Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface PapersAndMonographsTalentMapper extends BaseMapper<PapersAndMonographsTalent> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.Patent;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 专利Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface PatentMapper extends BaseMapper<Patent> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.relation.PatentTalent;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 专利人才关联Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface PatentTalentMapper extends BaseMapper<PatentTalent> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.ProjectInitiation;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目立项Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectInitiationMapper extends BaseMapper<ProjectInitiation> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.ResearchProject;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 科研项目Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResearchProjectMapper extends BaseMapper<ResearchProject> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.relation.ResearchProjectTalent;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 科研项目人才关联Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResearchProjectTalentMapper extends BaseMapper<ResearchProjectTalent> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.Resume;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 简历Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResumeMapper extends BaseMapper<Resume> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.business.ReviewExperience;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 评审经历Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReviewExperienceMapper extends BaseMapper<ReviewExperience> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.system.SysParam;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 系统参数Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface SysParamMapper extends BaseMapper<SysParam> {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.system.SysParamType;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 系统参数类型Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface SysParamTypeMapper extends BaseMapper<SysParamType> {
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.common.dto.system.PermissionTreeVO;
|
||||
import com.CUST.brain.dao.domain.system.SysPermission;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统权限Mapper接口
|
||||
*/
|
||||
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
|
||||
|
||||
/**
|
||||
* 根据用户ID查询权限列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 权限列表
|
||||
*/
|
||||
|
||||
List<SysPermission> selectPermissionsByUserId(@Param("userId") String userId);
|
||||
|
||||
List<PermissionTreeVO> selectPermissionsWithRoleStatus(@Param("roleId") String roleId, @Param("isSuperAdmin") boolean isSuperAdmin);
|
||||
|
||||
List<SysPermission> selectPermissionsByRoleId(@Param("roleId") String roleId);
|
||||
|
||||
void deleteRolePermissionsByRoleId(@Param("roleId") String roleId);
|
||||
|
||||
void insertRolePermissionsBatch(@Param("list") List<com.CUST.brain.dao.domain.relation.SysRolePermission> list);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.CUST.brain.dao.mapper;
|
||||
|
||||
import com.CUST.brain.dao.domain.system.SysRole;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统角色Mapper接口
|
||||
*/
|
||||
public interface SysRoleMapper extends BaseMapper<SysRole> {
|
||||
|
||||
/**
|
||||
* 根据用户ID查询角色列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 角色列表
|
||||
*/
|
||||
List<SysRole> selectRolesByUserId(@Param("userId") String userId);
|
||||
|
||||
@Update("UPDATE sys_role SET status = 0 WHERE id = #{roleId} AND status = 1")
|
||||
int disableRole(@Param("roleId") String roleId);
|
||||
|
||||
@Update("UPDATE sys_role SET status = 1 WHERE id = #{roleId} AND status = 0")
|
||||
int restoreRole(@Param("roleId") String roleId);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue