Initial commit

This commit is contained in:
wangyuxuan 2025-12-09 23:47:14 +08:00
commit 65d34df792
314 changed files with 14366 additions and 0 deletions

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

32
.idea/compiler.xml Normal file
View File

@ -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>

7
.idea/encodings.xml Normal file
View File

@ -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>

40
.idea/jarRepositories.xml Normal file
View File

@ -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>

12
.idea/misc.xml Normal file
View File

@ -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>

124
.idea/uiDesigner.xml Normal file
View File

@ -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>

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

134
README.md Normal file
View File

@ -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 连接失败:核对主机、端口、用户名密码与防火墙/容器网络。
### 版权与声明
本项目仅用于教学/演示目的,涉及的账号、密钥配置请在实际部署前更换为安全的私有配置。

337
pom.xml Normal file
View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
package com.CUST.brain.common.api;
public interface IErrorCode {
Integer getCode();
String getMessage();
}

View File

@ -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;
}
}

View File

@ -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且不缓存nullkey={}", 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且不缓存nullkey={}", 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());
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}

View File

@ -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; }
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,21 @@
package com.CUST.brain.common.enums;
/**
* 缓存更新策略枚举
*/
public enum CacheUpdateStrategy {
/**
* 更新缓存
*/
UPDATE,
/**
* 删除缓存
*/
DELETE,
/**
* 忽略缓存
*/
IGNORE
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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("系统异常,请联系管理员");
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1 @@

View File

@ -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;
}
}

View File

@ -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必须大于0millis={}", 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成功直接setkey={}, 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必须大于0millis={}", 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);
}
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
/**
* 是否高新技术企业认证01
*/
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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
/**
* 是否负责人01
*/
@TableField("is_principal")
private Boolean isPrincipal;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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略可自动生成
}

View File

@ -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略可自动生成
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -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