diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..93449cc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,59 @@
+# Build artifacts and dependencies
+target/
+.mvn/
+*.iml
+*.ipr
+*.iws
+
+# IDE files
+.idea/
+.vscode/
+*.sublime-*
+.eclipse
+.project
+.classpath
+.settings/
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# Version control
+.git/
+.gitignore
+.gitattributes
+
+# Documentation and scripts (comment out if needed in container)
+README.md
+*.md
+deploy.sh
+rollback.sh
+fetch-log.sh
+
+# Docker files (avoid recursion)
+docker/
+Dockerfile*
+docker-compose*
+
+# Logs and temp files
+log/
+logs/
+*.log
+*.tmp
+
+# Backup files
+backup/
+*.backup
+*.bak
+
+# Environment files
+.env
+.env.*
+
+# Test files
+**/src/test/
+
+# Maven wrapper (we install Maven in container)
+.mvn/
+mvnw
+mvnw.cmd
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0716199..e2ab988 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,7 @@ nbproject/
/storage/
/var/log/*
*.factorypath
-/storage/
\ No newline at end of file
+/storage/
+
+# Docker buildx cache
+.buildx-cache/
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..6003b54
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,119 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a Spring Boot multi-module Java backend application called "peipei-backend" that appears to be a social platform/gaming system with WeChat Mini Program integration. The system supports clerk management, customer interactions, orders, gifts, and various social features.
+
+## Project Structure
+
+The project is organized as a Maven multi-module application:
+
+- **play-admin**: Main backend API module containing REST controllers and business logic
+- **play-common**: Shared utilities, configurations, and common components
+- **play-generator**: Code generation tool for creating CRUD operations from database tables
+
+## Technology Stack
+
+- **Java 11** with Spring Boot 2.5.4
+- **MyBatis Plus 3.5.3** with join support for database operations
+- **MySQL 8** with Flyway migrations
+- **Redis** for caching and session management
+- **JWT** for authentication
+- **WeChat Mini Program SDK** for WeChat integration
+- **Aliyun OSS** for file storage
+- **Lombok** for reducing boilerplate code
+- **Knife4j/Swagger** for API documentation
+
+## Development Commands
+
+### Building and Running
+```bash
+# Build the entire project
+mvn clean compile
+
+# Package the application
+mvn clean package
+
+# Run the main application (play-admin module)
+cd play-admin
+mvn spring-boot:run
+
+# Or run the packaged jar
+java -jar play-admin/target/play-admin-1.0.jar
+```
+
+### Database Migrations
+```bash
+# Run Flyway migrations
+mvn flyway:migrate
+
+# Check migration status
+mvn flyway:info
+```
+
+### Code Generation
+```bash
+# Generate CRUD code from database tables
+cd play-generator
+mvn clean compile exec:java
+# Or run directly: ./run.sh (Linux/Mac) or run.bat (Windows)
+```
+
+## Configuration
+
+The application uses Spring profiles with separate configuration files:
+- `application.yml`: Main configuration
+- `application-dev.yml`: Development environment
+- `application-test.yml`: Test environment
+- `application-prod.yml`: Production environment
+
+Default active profile is `test`. Change via `spring.profiles.active` property.
+
+## Architecture
+
+### Module Structure
+- **Controllers**: Located in `modules/{domain}/controller/` - Handle HTTP requests and responses
+- **Services**: Located in `modules/{domain}/service/` - Business logic layer
+- **Mappers**: Located in `modules/{domain}/mapper/` - Database access layer using MyBatis Plus
+- **Entities**: Domain objects representing database tables
+
+### Key Domains
+- **clerk**: Clerk/staff management and operations
+- **custom**: Customer management and interactions
+- **order**: Order processing and management
+- **shop**: Product catalog and commerce features
+- **system**: System administration and user management
+- **weichat**: WeChat Mini Program integration
+
+### Authentication & Security
+- JWT-based authentication with Spring Security
+- Multi-tenant architecture support
+- Role-based access control
+- XSS protection and input validation
+
+### Database
+- Uses MyBatis Plus for ORM with automatic CRUD generation
+- Flyway for database migration management
+- Logical deletion support (soft delete)
+- Multi-tenant data isolation
+
+## Code Generation Tool
+
+The project includes a powerful code generator (`play-generator`) that can:
+- Read MySQL table structures
+- Generate Entity, Mapper, Service, Controller classes
+- Create MyBatis XML mapping files
+- Support batch generation for multiple tables
+
+Configure database connection in `play-generator/src/main/resources/config.properties` and specify table names to generate complete CRUD operations.
+
+## Deployment
+
+The project includes a deployment script (`deploy.sh`) that:
+- Builds and packages the application
+- Deploys to remote server via SCP
+- Restarts the application service
+
+Server runs on port 7002 with context path `/api`.
\ No newline at end of file
diff --git a/README.md b/README.md
index d5dd317..c6fd0f6 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,76 @@ mvn spotless:apply compile
mvn spotless:apply checkstyle:check compile
```
+## 部署说明
+
+### Docker 构建和推送
+
+项目支持多架构 Docker 构建,特别适合在 Apple Silicon Mac 上为 Linux 服务器构建镜像。
+
+#### 构建镜像
+
+```bash
+# 构建服务器部署镜像 (Linux amd64)
+./build-docker.sh amd64
+
+# 构建本地开发镜像 (Apple Silicon arm64)
+./build-docker.sh arm64
+
+# 自动检测架构构建
+./build-docker.sh
+
+# 查看帮助
+./build-docker.sh -h
+```
+
+#### 推送到私有仓库
+
+```bash
+# 推送 amd64 镜像到私有仓库 (用于服务器部署)
+./push-docker.sh
+```
+
+### 服务器部署
+
+#### 部署环境
+- **服务器**: CentOS Linux
+- **架构**: amd64
+- **容器**: Docker + Docker Compose
+
+#### 部署步骤
+
+1. **服务器上的配置文件**
+ ```bash
+ # 服务器主目录有专门的 docker-compose 文件
+ ~/docker-compose.yml # 为 CentOS 环境优化的配置
+ ```
+
+2. **启动服务**
+ ```bash
+ # 在服务器主目录执行
+ cd ~
+ docker-compose up -d
+ ```
+
+3. **查看日志**
+ ```bash
+ # 应用日志位置
+ ~/log/ # 应用日志目录
+
+ # 查看实时日志
+ tail -f ~/log/detail.log
+ tail -f ~/log/error.log
+
+ # 查看容器日志
+ docker-compose logs -f
+ ```
+
+#### 部署文件说明
+
+- **~/docker-compose.yml**: 为 CentOS 环境定制的 Docker Compose 配置
+- **~/log/**: 应用日志输出目录
+- 配置文件已针对服务器环境进行优化,可直接使用
+
## 模块介绍
### play-admin
diff --git a/backend.txt b/backend.txt
deleted file mode 100644
index 555cff4..0000000
--- a/backend.txt
+++ /dev/null
@@ -1,67364 +0,0 @@
---- File: .gitignore ---
-# Compiled class file
-*.class
-
-# Log file
-*.log
-
-# BlueJ files
-*.ctxt
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-target
-.idea
-*.iml
-nb-configuration.xml
-nbactions.xml
-.project
-.settings
-*/.settings
-*/.project
-.classpath
-*/.idea
-.idea/
-.vscode/
-.settings/
-target/
-ideaproj/
-*/bin
-.DS_Store
-*.ipr
-*.iws
-bin/
-nbproject/
-.springBeans
-/log/
-/logs/
-/storage/
-/var/log/*
-*.factorypath
-/storage/
-
---- File: README.md ---
-# PeiPei 后端项目
-
-基于 Spring Boot 的多模块项目,为 PeiPei 应用提供后端服务。
-
-## 项目结构
-
-- **play-admin**: 主要的 Spring Boot 应用模块
-- **play-common**: 公共工具类和共享代码
-- **play-generator**: 代码生成工具
-
-## 构建要求
-
-- **Java 11** (必须)
-- **Maven 3.6+**
-
-## 快速开始
-
-### 1. 安装 Java 11
-
-在 macOS 上使用 Homebrew:
-```bash
-brew install --cask zulu@11
-```
-
-设置 Java 11 为默认版本:
-
-#### 临时设置 (当前会话):
-```bash
-export JAVA_HOME=$(/usr/libexec/java_home -v 11)
-```
-
-#### 永久设置方法:
-
-**方法1: Shell 配置 (推荐)**
-添加到 `~/.zshrc` 或 `~/.bash_profile`:
-```bash
-export JAVA_HOME=$(/usr/libexec/java_home -v 11)
-export PATH="$JAVA_HOME/bin:$PATH"
-```
-
-**方法2: Maven 专用**
-创建 `~/.mavenrc`:
-```bash
-export JAVA_HOME=$(/usr/libexec/java_home -v 11)
-```
-
-**方法3: VS Code 项目配置**
-项目已配置 `.vscode/settings.json` 使用 Java 11:
-```json
-{
- "java.configuration.runtimes": [
- {
- "name": "JavaSE-11",
- "path": "/usr/libexec/java_home -v 11"
- }
- ],
- "java.jdt.ls.java.home": "/usr/libexec/java_home -v 11"
-}
-```
-
-### 2. 构建项目
-
-```bash
-# 清理并构建所有模块
-mvn clean install
-
-# 或者仅编译
-mvn clean compile
-```
-
-### 3. 运行应用
-
-```bash
-# 运行主应用
-java -jar play-admin/target/play-admin-1.0.jar
-
-# 或使用 Maven
-cd play-admin
-mvn spring-boot:run
-```
-
-## 配置说明
-
-项目在所有模块中统一使用 Java 11:
-- 所有模块都配置为 Java 11 源码和目标版本
-- Lombok 注解自动处理
-- 无需显式配置注解处理器
-
-## 开发说明
-
-- 项目已更新为所有模块统一使用 Java 11
-- Lombok 依赖使用 `scope=provided` 启用自动注解处理
-- Maven 编译插件继承 Spring Boot 父 POM 配置
-
-### 代码格式化和质量检查
-
-项目集成了 Spotless 和 Checkstyle 插件:
-
-#### Spotless (代码格式化)
-- 基于空格的缩进 (4个空格)
-- 自动清理尾随空白字符
-- 文件末尾添加换行符
-- 基本的导入组织
-
-常用命令:
-```bash
-# 检查代码格式
-mvn spotless:check
-
-# 自动格式化代码
-mvn spotless:apply
-```
-
-#### Checkstyle (代码规范检查)
-- 使用 Sun Java 编码规范 (比 Google 规范更宽松)
-- 在编译时自动检查代码规范
-- 与 Java 11 和 Lombok 兼容
-
-常用命令:
-```bash
-# 检查代码规范
-mvn checkstyle:check
-
-# 生成规范检查报告
-mvn checkstyle:checkstyle
-```
-
-#### 集成命令
-```bash
-# 格式化代码并编译
-mvn spotless:apply compile
-
-# 完整检查 (格式化 + 规范检查 + 编译)
-mvn spotless:apply checkstyle:check compile
-```
-
-## 模块介绍
-
-### play-admin
-主要的 Spring Boot 应用,包含:
-- REST API 接口
-- 安全配置
-- 数据库集成
-- 微信集成
-
-### play-common
-共享工具库,包含:
-- 公共域对象
-- 工具类
-- Redis 配置
-- 安全工具
-
-### play-generator
-代码生成工具,包含:
-- MyBatis Plus 代码生成
-- 基于模板的代码生成
-
-## 故障排除
-
-### 常见问题
-
-**编译失败: "cannot find symbol" 错误**
-- 确保使用 Java 11: `java -version` 应显示 Java 11
-- 设置正确的 JAVA_HOME: `export JAVA_HOME=$(/usr/libexec/java_home -v 11)`
-- 清理并重新编译: `mvn clean compile`
-
-**Maven 使用错误的 Java 版本**
-- 检查 Maven 版本: `mvn -version`
-- 创建 `~/.mavenrc` 文件设置 JAVA_HOME
-- 或在命令前加环境变量: `JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn clean compile`
-
-**VS Code Java 支持问题**
-- 确保安装了 Extension Pack for Java
-- 检查 `.vscode/settings.json` 中的 Java 配置
-- 重新加载窗口: Cmd+Shift+P → "Developer: Reload Window"
-
-**Spotless 格式化问题**
-- 修复格式问题: `mvn spotless:apply`
-- 跳过格式检查: `mvn compile -Dspotless.check.skip=true`
-
-### 验证配置
-```bash
-# 验证 Java 版本
-java -version
-
-# 验证 Maven Java 版本
-mvn -version
-
-# 验证编译
-mvn clean compile
-
-# 验证完整构建
-mvn clean install
-```
-
-## 构建状态
-
-✅ 所有模块使用 Java 11 编译成功
-✅ Lombok 注解自动处理
-✅ 模块间配置一致
-✅ Spotless 代码格式化已配置
-✅ Checkstyle 代码规范检查已配置
-✅ VS Code Java 配置已设置
-
---- File: deploy.sh ---
-#!/bin/sh
-# 发包脚本
-set -e
-echo "发布开始,当前时间是:$current_time"
-#mvn clean install
-scp ./play-admin/target/play-admin-1.0.jar root@122.51.20.105:/www/wwwroot/july.hucs.top
-ssh root@122.51.20.105 "source /etc/profile;cd /www/wwwroot/july.hucs.top;sh start.sh restart"
-# 获取当前时间并格式化为指定格式
-current_time=$(date +"%Y-%m-%d %H:%M:%S")
-
-echo "发布完成,当前时间是:$current_time"
-
---- File: docker/Dockerfile ---
-FROM openjdk:11-jre-slim
-
-# 设置工作目录
-WORKDIR /app
-
-# 只复制应用程序JAR包,不复制lib目录
-COPY ./*.jar app.jar
-
-# 设置环境变量
-ENV APP_NAME=app.jar
-
-# 暴露应用端口
-EXPOSE 8080
-
-# 设置启动命令
-CMD ["sh", "-c", "java -Dloader.path=./lib/ -Xms2g -Xmx2g -jar $APP_NAME --spring.profiles.active=test"]
-
-
---- File: docker/docker-compose.yml ---
-version: '3'
-
-services:
- spring-boot-app:
- build: .
- container_name: spring-boot-app
- ports:
- - "7003:7002"
- volumes:
- - ./lib:/app/lib # 挂载主机的lib目录到容器内的lib目录
- - ./log:/app/log # 挂载日志目录到主机
- restart: always
-
-
---- File: fetch-log.sh ---
-#!/bin/sh
-# 拉取日志脚本
-set -e
-
-# 获取当前时间并格式化为指定格式
-current_time=$(date +"%Y-%m-%d %H:%M:%S")
-echo "开始拉取日志,当前时间是:$current_time"
-
-# 创建本地log目录(如果不存在)
-mkdir -p ./log
-
-# 从远程服务器拉取整个log文件夹
-echo "正在从远程服务器拉取日志文件..."
-scp -r root@122.51.20.105:/www/wwwroot/july.hucs.top/log/* ./log/
-
-# 获取当前时间并格式化为指定格式
-current_time=$(date +"%Y-%m-%d %H:%M:%S")
-echo "日志拉取完成,当前时间是:$current_time"
-
---- File: play-admin/pom.xml ---
-
-
- 4.0.0
-
- com.starry
- play-with
- 1.0
-
-
- play-admin
-
-
- 11
- 11
- 11
- UTF-8
-
-
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.flywaydb
- flyway-core
-
-
-
- com.starry
- play-common
- 1.0
-
-
-
- com.starry
- play-generator
-
-
-
-
-
- io.jsonwebtoken
- jjwt
-
-
-
- mysql
- mysql-connector-java
- 8.0.26
-
-
- com.github.yulichang
- mybatis-plus-join-boot-starter
-
-
-
- com.alibaba
- easyexcel
-
-
-
- org.springframework.boot
- spring-boot-starter-aop
-
-
-
- org.springframework.boot
- spring-boot-starter-data-redis
-
-
-
- com.aliyun.oss
- aliyun-sdk-oss
-
-
-
- com.github.binarywang
- wx-java-mp-spring-boot-starter
-
-
- com.github.wxpay
- wxpay-sdk
-
-
-
-
- ws.schild
- jave-core
-
-
-
- ws.schild
- jave-nativebin-linux64
-
-
-
- com.github.binarywang
- weixin-java-pay
- 4.5.0
-
-
-
- com.squareup.okio
- okio
-
-
-
- com.tencentcloudapi
- tencentcloud-sdk-java-dnspod
- 3.1.322
-
-
- okio
- com.squareup.okio
-
-
-
-
-
-
- org.projectlombok
- lombok
- provided
-
-
-
- org.projectlombok
- lombok-mapstruct-binding
- 0.2.0
-
-
-
-
-
- ruoyi-admin-mrwho
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.projectlombok
- lombok
- 1.18.30
-
-
-
-
-
- org.flywaydb
- flyway-maven-plugin
- 7.15.0
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 2.7.9
-
- ZIP
-
-
- nothing
- nothing
-
-
-
-
-
-
- repackage
-
-
-
-
-
-
-
-
---- File: play-admin/src/main/java/com/starry/admin/Application.java ---
-package com.starry.admin;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.scheduling.annotation.EnableScheduling;
-import org.springframework.transaction.annotation.EnableTransactionManagement;
-
-/**
- * @author admin
- */
-@EnableTransactionManagement
-@SpringBootApplication
-@EnableScheduling
-@ComponentScan("com.starry")
-public class Application {
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/ClerkUserLogin.java ---
-package com.starry.admin.common.aspect;
-
-import java.lang.annotation.*;
-
-/**
- * 陪聊登录注解
- *
- * @author ruoyi
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface ClerkUserLogin {
-
- boolean manage() default false;
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/ClerkUserLoginAspect.java ---
-package com.starry.admin.common.aspect;
-
-import com.starry.admin.common.conf.ThreadLocalRequestDetail;
-import com.starry.admin.common.exception.ServiceException;
-import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
-import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
-import com.starry.admin.modules.weichat.service.WxTokenService;
-import com.starry.common.constant.Constants;
-import com.starry.common.constant.HttpStatus;
-import com.starry.common.utils.StringUtils;
-import java.util.Objects;
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.springframework.stereotype.Component;
-
-/**
- * 限流处理
- *
- * @author ruoyi
- */
-@Slf4j
-@Aspect
-@Component
-public class ClerkUserLoginAspect {
-
- @Resource
- private PlayClerkUserInfoServiceImpl clerkUserInfoService;
-
- @Resource
- private WxTokenService tokenService;
- @Resource
- private HttpServletRequest request;
-
- @Before("@annotation(clerkUserLogin)")
- public void doBefore(JoinPoint point, ClerkUserLogin clerkUserLogin) {
- String userToken = request.getHeader(Constants.CLERK_USER_LOGIN_TOKEN);
- if (StringUtils.isEmpty(userToken)) {
- throw new ServiceException("token为空", HttpStatus.UNAUTHORIZED);
- }
- if (userToken.startsWith(Constants.TOKEN_PREFIX)) {
- userToken = userToken.replace(Constants.TOKEN_PREFIX, "");
- }
- // 解析token
- String userId;
- try {
- userId = tokenService.getWxUserIdByToken(userToken);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
- }
- PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
- if (Objects.isNull(entity)) {
- throw new ServiceException("未查询到有效用户", HttpStatus.UNAUTHORIZED);
- }
- if (!userToken.equals(entity.getToken())) {
- throw new ServiceException("token异常", HttpStatus.UNAUTHORIZED);
- }
- ThreadLocalRequestDetail.setRequestDetail(entity);
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/CustomUserLogin.java ---
-package com.starry.admin.common.aspect;
-
-import java.lang.annotation.*;
-
-/**
- * 客户登录注解
- *
- * @author ruoyi
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface CustomUserLogin {
-
- boolean manage() default false;
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/CustomUserLoginAspect.java ---
-package com.starry.admin.common.aspect;
-
-import com.starry.admin.common.conf.ThreadLocalRequestDetail;
-import com.starry.admin.common.exception.ServiceException;
-import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
-import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
-import com.starry.admin.modules.weichat.service.WxTokenService;
-import com.starry.common.constant.Constants;
-import com.starry.common.constant.HttpStatus;
-import com.starry.common.utils.StringUtils;
-import java.util.Objects;
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.springframework.stereotype.Component;
-
-/**
- * 限流处理
- *
- * @author ruoyi
- */
-@Slf4j
-@Aspect
-@Component
-public class CustomUserLoginAspect {
-
- @Resource
- private PlayCustomUserInfoServiceImpl customUserInfoService;
-
- @Resource
- private WxTokenService tokenService;
- @Resource
- private HttpServletRequest request;
-
- @Before("@annotation(customUserLogin)")
- public void doBefore(JoinPoint point, CustomUserLogin customUserLogin) {
- String userToken = request.getHeader(Constants.CUSTOM_USER_LOGIN_TOKEN);
- if (StringUtils.isEmpty(userToken)) {
- throw new ServiceException("token为空", HttpStatus.UNAUTHORIZED);
- }
- if (userToken.startsWith(Constants.TOKEN_PREFIX)) {
- userToken = userToken.replace(Constants.TOKEN_PREFIX, "");
- }
- // 解析token
- String userId;
- try {
- userId = tokenService.getWxUserIdByToken(userToken);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
- }
- PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
- if (Objects.isNull(entity)) {
- throw new ServiceException("未查询到有效用户", HttpStatus.UNAUTHORIZED);
- }
- if (!userToken.equals(entity.getToken())) {
- throw new ServiceException("token异常", HttpStatus.UNAUTHORIZED);
- }
- ThreadLocalRequestDetail.setRequestDetail(entity);
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/DataScopeAspect.java ---
-package com.starry.admin.common.aspect;
-
-import cn.hutool.core.convert.Convert;
-import cn.hutool.core.util.StrUtil;
-import com.starry.admin.common.domain.LoginUser;
-import com.starry.admin.modules.system.module.entity.SysRoleEntity;
-import com.starry.admin.modules.system.module.entity.SysUserEntity;
-import com.starry.admin.utils.SecurityUtils;
-import com.starry.common.annotation.DataScope;
-import com.starry.common.context.CustomSecurityContextHolder;
-import com.starry.common.domain.BaseEntity;
-import com.starry.common.utils.StringUtils;
-import java.util.ArrayList;
-import java.util.List;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.springframework.stereotype.Component;
-
-/**
- * 数据过滤处理
- *
- * @author vctgo
- */
-@Aspect
-@Component
-public class DataScopeAspect {
- /**
- * 全部数据权限
- */
- public static final String DATA_SCOPE_ALL = "1";
-
- /**
- * 自定数据权限
- */
- public static final String DATA_SCOPE_CUSTOM = "2";
-
- /**
- * 部门数据权限
- */
- public static final String DATA_SCOPE_DEPT = "3";
-
- /**
- * 部门及以下数据权限
- */
- public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
-
- /**
- * 仅本人数据权限
- */
- public static final String DATA_SCOPE_SELF = "5";
-
- /**
- * 数据权限过滤关键字
- */
- public static final String DATA_SCOPE = "dataScope";
-
- /**
- * 数据范围过滤
- *
- * @param joinPoint
- * 切点
- * @param user
- * 用户
- * @param deptAlias
- * 部门别名
- * @param userAlias
- * 用户别名
- * @param permission
- * 权限字符
- */
- public static void dataScopeFilter(JoinPoint joinPoint, SysUserEntity user, String deptAlias, String userAlias,
- String permission) {
- StringBuilder sqlString = new StringBuilder();
- List conditions = new ArrayList<>();
- for (SysRoleEntity role : user.getRoles()) {
- String dataScope = role.getDataScope();
- if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
- continue;
- }
- if (StrUtil.isNotBlank(permission) && StringUtils.isNotEmpty(role.getPermissions())
- && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
- continue;
- }
- if (DATA_SCOPE_ALL.equals(dataScope)) {
- sqlString = new StringBuilder();
- break;
- } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
- sqlString.append(StringUtils.format(
- " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
- role.getRoleId()));
- } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
- sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
- } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
- sqlString.append(StringUtils.format(
- " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
- deptAlias, user.getDeptId(), user.getDeptId()));
- } else if (DATA_SCOPE_SELF.equals(dataScope)) {
- if (StringUtils.isNotBlank(userAlias)) {
- sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
- } else {
- // 数据权限为仅本人且没有userAlias别名不查询任何数据
- sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
- }
- }
- conditions.add(dataScope);
- }
-
- if (StringUtils.isNotBlank(sqlString.toString())) {
- Object params = joinPoint.getArgs()[0];
- if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
- BaseEntity baseEntity = (BaseEntity) params;
- baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
- }
- }
- }
-
- @Before("@annotation(controllerDataScope)")
- public void doBefore(JoinPoint point, DataScope controllerDataScope) {
- clearDataScope(point);
- handleDataScope(point, controllerDataScope);
- }
-
- protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
- if (controllerDataScope == null) {
- return;
- }
- // 获取当前的用户
- LoginUser loginUser = SecurityUtils.getLoginUser();
- if (StringUtils.isNotNull(loginUser)) {
- SysUserEntity currentUser = loginUser.getUser();
- // 如果是超级管理员,则不过滤数据
- if (StringUtils.isNotNull(currentUser) && SysUserEntity.isAdmin(currentUser)) {
- String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(),
- CustomSecurityContextHolder.getPermission());
- dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
- controllerDataScope.userAlias(), permission);
- }
- }
- }
-
- /**
- * 拼接权限sql前先清空params.dataScope参数防止注入
- */
- private void clearDataScope(final JoinPoint joinPoint) {
- Object params = joinPoint.getArgs()[0];
- if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
- BaseEntity baseEntity = (BaseEntity) params;
- baseEntity.getParams().put(DATA_SCOPE, "");
- }
- }
-
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/LogAspect.java ---
-package com.starry.admin.common.aspect;
-
-import cn.hutool.extra.servlet.ServletUtil;
-import com.alibaba.fastjson2.JSON;
-import com.starry.admin.common.domain.LoginUser;
-import com.starry.admin.modules.system.module.entity.SysOperationLogEntity;
-import com.starry.admin.modules.system.service.ISysOperationLogService;
-import com.starry.admin.utils.SecurityUtils;
-import com.starry.common.annotation.Log;
-import com.starry.common.utils.ServletUtils;
-import com.starry.common.utils.StringUtils;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.AfterReturning;
-import org.aspectj.lang.annotation.Aspect;
-import org.springframework.http.HttpMethod;
-import org.springframework.stereotype.Component;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.servlet.HandlerMapping;
-
-/**
- * @author admin
- */
-@Aspect
-@Component
-@Slf4j
-public class LogAspect {
-
- @Resource
- private ISysOperationLogService operLogService;
-
- /**
- * 处理完请求后执行
- *
- * @param joinPoint
- * 切点
- */
- @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
- public void doAfterReturn(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
- handleLog(joinPoint, controllerLog, jsonResult);
- }
-
- protected void handleLog(final JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
- try {
- // 获取当前的用户
- LoginUser loginUser = SecurityUtils.getLoginUser();
-
- // 日志记录
- SysOperationLogEntity operLog = new SysOperationLogEntity();
- operLog.setStatus(0);
- // 请求的IP地址
- String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
- if ("0:0:0:0:0:0:0:1".equals(iP)) {
- iP = "127.0.0.1";
- }
- operLog.setOperIp(iP);
- operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
- if (loginUser != null) {
- operLog.setOperName(loginUser.getUsername());
- }
- if (null != null) {
- operLog.setStatus(1);
- operLog.setErrorMsg(StringUtils.substring(((Exception) null).getMessage(), 0, 2000));
- }
- // 设置方法名称
- String className = joinPoint.getTarget().getClass().getName();
- String methodName = joinPoint.getSignature().getName();
- operLog.setMethod(className + "." + methodName + "()");
- operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
- operLog.setOperTime(new Date());
- // 处理设置注解上的参数
- getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
- // 保存数据库
- operLogService.save(operLog);
-
- } catch (Exception exp) {
- log.error("异常信息:{}", exp.getMessage());
- }
- }
-
- /**
- * 获取注解中对方法的描述信息 用于Controller层注解
- *
- * @param log
- * 日志
- * @param operLog
- * 操作日志
- */
- public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperationLogEntity operLog,
- Object jsonResult) {
- // 设置操作业务类型
- operLog.setBusinessType(log.businessType().ordinal());
- // 设置标题
- operLog.setTitle(log.title());
- // 是否需要保存request,参数和值
- if (log.isSaveRequestData()) {
- // 设置参数的信息
- setRequestValue(joinPoint, operLog);
- }
- // 是否需要保存response,参数和值
- if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
- operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
- }
- }
-
- /**
- * 获取请求的参数,放到log中
- *
- * @param operLog
- * 操作日志
- */
- private void setRequestValue(JoinPoint joinPoint, SysOperationLogEntity operLog) {
- String requsetMethod = operLog.getRequestMethod();
- if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
- String params = argsArrayToString(joinPoint.getArgs());
- operLog.setOperParam(StringUtils.substring(params, 0, 2000));
- } else {
- Map, ?> paramsMap = (Map, ?>) ServletUtils.getRequest()
- .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
- operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
- }
- }
-
- /**
- * 参数拼装
- */
- private String argsArrayToString(Object[] paramsArray) {
- StringBuilder params = new StringBuilder();
- if (paramsArray != null) {
- for (Object object : paramsArray) {
- // 不为空 并且是不需要过滤的 对象
- if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
- Object jsonObj = JSON.toJSON(object);
- params.append(jsonObj.toString()).append(" ");
- }
- }
- }
- return params.toString().trim();
- }
-
- /**
- * 判断是否需要过滤的对象。
- *
- * @param object
- * 对象信息。
- * @return 如果是需要过滤的对象,则返回true;否则返回false。
- */
- @SuppressWarnings("rawtypes")
- public boolean isFilterObject(final Object object) {
- Class> clazz = object.getClass();
- if (clazz.isArray()) {
- return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
- } else if (Collection.class.isAssignableFrom(clazz)) {
- Collection collection = (Collection) object;
- for (Object value : collection) {
- return value instanceof MultipartFile;
- }
- } else if (Map.class.isAssignableFrom(clazz)) {
- Map map = (Map) object;
- for (Object value : map.entrySet()) {
- Map.Entry entry = (Map.Entry) value;
- return entry.getValue() instanceof MultipartFile;
- }
- }
- return object instanceof MultipartFile || object instanceof HttpServletRequest
- || object instanceof HttpServletResponse || object instanceof BindingResult;
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/aspect/MybatisAspectj.java ---
-package com.starry.admin.common.aspect;
-
-import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.aspectj.lang.annotation.Pointcut;
-import org.springframework.stereotype.Component;
-
-/**
- * @Author: huchuansai
- * @Date: 2023/8/2 4:38 PM
- * @Description:
- */
-@Aspect
-@Component
-public class MybatisAspectj {
-
- // 配置织入点
- @Pointcut("execution(public * com.baomidou.mybatisplus.core.mapper.BaseMapper.selectOne(..))")
- public void selectOneAspect() {
- }
-
- @Before("selectOneAspect()")
- public void beforeSelect(JoinPoint point) {
- Object arg = point.getArgs()[0];
- if (arg instanceof AbstractWrapper) {
- arg = (AbstractWrapper) arg;
- ((AbstractWrapper) arg).last("limit 1");
- }
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/component/JwtToken.java ---
-package com.starry.admin.common.component;
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.extra.servlet.ServletUtil;
-import cn.hutool.http.useragent.UserAgent;
-import cn.hutool.http.useragent.UserAgentUtil;
-import com.starry.admin.common.domain.LoginUser;
-import com.starry.admin.common.security.entity.JwtUser;
-import com.starry.common.constant.CacheConstants;
-import com.starry.common.constant.Constants;
-import com.starry.common.constant.SecurityConstants;
-import com.starry.common.context.CustomSecurityContextHolder;
-import com.starry.common.redis.RedisCache;
-import com.starry.common.utils.IdUtils;
-import com.starry.common.utils.ServletUtils;
-import com.starry.common.utils.ip.AddressUtils;
-import com.starry.common.utils.ip.IpUtils;
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Component;
-
-/**
- * @author admin token 组件
- * @since 2021/9/6
- */
-@Slf4j
-@Component
-public class JwtToken {
-
- private static final String CLAIM_KEY_USERNAME = "sub";
- private static final String CLAIM_KEY_CREATED = "created";
- private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
-
- @Value("${jwt.secret}")
- private String secret;
- @Value("${jwt.expiration}")
- private Long expire;
- @Value("${jwt.tokenHeader}")
- private String tokenHeader;
- @Value("${jwt.tokenHead}")
- private String tokenHead;
-
- @Resource
- private RedisCache redisCache;
-
- /**
- * 从token中获取登录用户名
- */
- public String getUserNameFromToken(String token) {
- String username;
- try {
- Claims claims = getClaimsFromToken(token);
- username = claims.getSubject();
- } catch (Exception e) {
- username = null;
- }
- return username;
- }
-
- /**
- * 校验token
- */
- public boolean validateToken(String token, UserDetails userDetails) {
- String username = getUserNameFromToken(token);
- return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
- }
-
- /**
- * 根据用户信息生成token
- */
- public String generateToken(UserDetails userDetails) {
- Map claims = new HashMap<>();
- claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
- claims.put(CLAIM_KEY_CREATED, new Date());
- return generateToken(claims);
- }
-
- /**
- * 创建令牌
- *
- * @param jwtUser
- * 用户信息
- * @return 令牌
- */
- public String createToken(JwtUser jwtUser) {
- String token = IdUtils.getUuid();
- jwtUser.setToken(token);
- setUserAgent(jwtUser);
- refersToken(jwtUser);
-
- Map claims = new HashMap<>();
- claims.put(Constants.LOGIN_USER_KEY, token);
- return createToken(claims);
- }
-
- /**
- * 从数据声明生成令牌
- *
- * @param claims
- * 数据声明
- * @return 令牌
- */
- private String createToken(Map claims) {
- String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
- return token;
- }
-
- /**
- * 判断token是否已经失效
- */
- private boolean isTokenExpired(String token) {
- Date expiredDate = getClaimsFromToken(token).getExpiration();
- return expiredDate.before(new Date());
- }
-
- private String generateToken(Map claims) {
- return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
- // 签名算法
- .signWith(SignatureAlgorithm.HS512, secret).compact();
- }
-
- /**
- * 生成token的过期时间
- */
- private Date generateExpirationDate() {
- return new Date(System.currentTimeMillis() + expire * 1000);
- }
-
- /**
- * 从令牌中获取数据声明
- *
- * @param token
- * 令牌
- * @return 数据声明
- */
- private Claims getClaimsFromToken(String token) {
- Claims claims = null;
- try {
- claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
- } catch (Exception e) {
- log.info("JWT格式验证失败:{}", token);
- }
- return claims;
- }
-
- /**
- * 设置用户代理信息
- *
- * @param jwtUser
- * 登录信息
- */
- public void setUserAgent(JwtUser jwtUser) {
- UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
- String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
- jwtUser.setIpaddr(ip);
- jwtUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
- jwtUser.setBrowser(userAgent.getBrowser().getName());
- jwtUser.setOs(userAgent.getOs().getName());
- }
-
- public void refersToken(JwtUser jwtUser) {
- jwtUser.setLoginTime(System.currentTimeMillis());
- jwtUser.setExpireTime(jwtUser.getLoginTime() + expire * 1000);
- String userKey = getTokenKey(jwtUser.getToken());
- redisCache.setCacheObject(userKey, jwtUser, expire, TimeUnit.SECONDS);
- String key = "login:resource:" + jwtUser.getUserId();
- redisCache.setCacheObject(key, userKey, expire, TimeUnit.SECONDS);
- }
-
- private String getTokenKey(String uuid) {
- return CacheConstants.LOGIN_TOKEN_KEY + uuid;
- }
-
- /**
- * 获取登录用户身份信息
- *
- * @return 用户信息
- */
- public JwtUser getLoginUser(HttpServletRequest request) {
- String token = getToken(request);
- if (StrUtil.isNotBlank(token)) {
- try {
- Claims claims = getClaimsFromToken(token);
- String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
- String userKey = getTokenKey(uuid);
- JwtUser user = redisCache.getCacheObject(userKey);
- return user;
- } catch (Exception e) {
-
- }
- }
- return null;
- }
-
- /**
- * 获取请求token
- *
- * @param request
- * @return token
- */
- private String getToken(HttpServletRequest request) {
- // 获取请求头
- String token = request.getHeader(tokenHeader);
- if (StrUtil.isNotBlank(token) && token.startsWith(tokenHead)) {
- token = token.replace(tokenHead, "");
- }
- return token;
- }
-
- /**
- * 验证令牌有效期,相差不足20分钟,自动刷新缓存
- *
- * @param jwtUser
- * @return 令牌
- */
-
- /**
- * 删除用户身份信息
- */
- public void removeJwtUser(String token) {
- if (StrUtil.isNotBlank(token)) {
- String userKey = getTokenKey(token);
- redisCache.deleteObject(userKey);
- }
- }
-
- /**
- * 创建令牌
- */
- public Map createToken(LoginUser loginUser) {
- String token = IdUtils.getUuid();
- String userId = loginUser.getUser().getUserId();
- String userName = loginUser.getUser().getUserCode();
- String tenantId = loginUser.getUser().getTenantId();
- Long deptId = loginUser.getUser().getDeptId();
- loginUser.setToken(token);
- loginUser.setUserId(userId);
- loginUser.setUserName(userName);
- loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
- // 添加地址信息
- setUserAgent(loginUser);
- String userRedisKey = refreshToken(loginUser);
-
- String key = "login:resource:" + loginUser.getUserId();
- redisCache.setCacheObject(key, userRedisKey, expire, TimeUnit.SECONDS);
-
- // Jwt存储信息
- Map claimsMap = new HashMap<>(8);
- claimsMap.put(SecurityConstants.USER_KEY, token);
- claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
- claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
- // 租户id
- claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId);
- // 部门id
- claimsMap.put(SecurityConstants.DETAILS_DEPT_ID, deptId);
- // 接口返回信息
- Map rspMap = new HashMap<>();
- rspMap.put("token", this.createToken(claimsMap));
- rspMap.put("expires_in", expire);
- rspMap.put("tenant_id", tenantId);
- rspMap.put("tokenHead", tokenHead);
- return rspMap;
- }
-
- /**
- * 设置用户代理信息
- *
- * @param loginUser
- * 登录信息
- */
- public void setUserAgent(LoginUser loginUser) {
- UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
- String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
- loginUser.setIpaddr(ip);
- loginUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
- loginUser.setBrowser(userAgent.getBrowser().getName());
- loginUser.setOs(userAgent.getOs().getName());
- }
-
- /**
- * 刷新令牌有效期
- *
- * @param loginUser
- * 登录信息
- * @return
- */
- public String refreshToken(LoginUser loginUser) {
- loginUser.setLoginTime(System.currentTimeMillis());
- loginUser.setExpireTime(loginUser.getLoginTime() + expire * 1000);
- // 根据uuid将loginUser缓存
- String userKey = getTokenKey(loginUser.getToken());
- redisCache.setCacheObject(userKey, loginUser, expire, TimeUnit.MINUTES);
- return userKey;
- }
-
- /**
- * 获取登录用户身份信息
- *
- * @return 用户信息
- */
- public LoginUser getNewLoginUser(HttpServletRequest request) {
- String token = getToken(request);
- if (StrUtil.isNotBlank(token)) {
- try {
- Claims claims = getClaimsFromToken(token);
- String uuid = (String) claims.get(SecurityConstants.USER_KEY);
- String userKey = getTokenKey(uuid);
- LoginUser loginUser = redisCache.getCacheObject(userKey);
- CustomSecurityContextHolder.set(SecurityConstants.DETAILS_TENANT_ID, loginUser.getUser().getTenantId());
- CustomSecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
- return loginUser;
- } catch (Exception e) {
- log.error("getNewLoginUser error", e);
- }
- }
- return null;
- }
-
- /**
- * 验证令牌有效期,相差不足20分钟,自动刷新缓存
- *
- * @param loginUser
- * @return 令牌
- */
- public void verifyToken(LoginUser loginUser) {
- long expireTime = loginUser.getExpireTime();
- long currentTime = System.currentTimeMillis();
- if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
- refreshToken(loginUser);
- }
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/component/PermissionService.java ---
-package com.starry.admin.common.component;
-
-import com.starry.admin.common.domain.LoginUser;
-import com.starry.admin.modules.system.module.entity.SysRoleEntity;
-import com.starry.admin.utils.SecurityUtils;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
-
-/**
- * 自定义权限实现,ss取自SpringSecurity首字母
- *
- * @author admin
- */
-@Service("customSs")
-public class PermissionService {
- /**
- * 所有权限标识
- */
- private static final String ALL_PERMISSION = "*:*:*";
-
- /**
- * 管理员角色权限标识
- */
- private static final String SUPER_ADMIN = "admin";
-
- private static final String ROLE_DELIMITER = ",";
-
- private static final String PERMISSION_DELIMETER = ",";
-
- /**
- * 验证用户是否具备某权限
- *
- * @param permission
- * 权限字符串
- * @return 用户是否具备某权限
- */
- public boolean hasPermission(String permission) {
- if (StringUtils.isEmpty(permission)) {
- return false;
- }
- LoginUser loginUser = SecurityUtils.getLoginUser();
- if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
- return false;
- }
- return hasPermissions(loginUser.getPermissions(), permission);
- }
-
- /**
- * 验证用户是否不具备某权限,与 hasPermission逻辑相反
- *
- * @param permission
- * 权限字符串
- * @return 用户是否不具备某权限
- */
- public boolean lacksPermission(String permission) {
- return hasPermission(permission) != true;
- }
-
- /**
- * 验证用户是否具有以下任意一个权限
- *
- * @param permissions
- * 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
- * @return 用户是否具有以下任意一个权限
- */
- public boolean hasAnyPermission(String permissions) {
- if (StringUtils.isEmpty(permissions)) {
- return false;
- }
- LoginUser loginUser = SecurityUtils.getLoginUser();
- if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
- return false;
- }
- Set authorities = loginUser.getPermissions();
- for (String permission : permissions.split(PERMISSION_DELIMETER)) {
- if (permission != null && hasPermissions(authorities, permission)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 判断用户是否拥有某个角色
- *
- * @param role
- * 角色字符串
- * @return 用户是否具备某角色
- */
- public boolean hasRole(String role) {
- if (StringUtils.isEmpty(role)) {
- return false;
- }
- LoginUser loginUser = SecurityUtils.getLoginUser();
- if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
- return false;
- }
- for (SysRoleEntity sysRoleEntity : loginUser.getUser().getRoles()) {
- String roleKey = sysRoleEntity.getRoleKey();
- if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 验证用户是否不具备某角色,与 isRole逻辑相反。
- *
- * @param role
- * 角色名称
- * @return 用户是否不具备某角色
- */
- public boolean lacksRole(String role) {
- return !hasRole(role);
- }
-
- /**
- * 验证用户是否具有以下任意一个角色
- *
- * @param roles
- * 以 ROLE_NAMES_DELIMITER 为分隔符的角色列表
- * @return 用户是否具有以下任意一个角色
- */
- public boolean hasAnyRoles(String roles) {
- if (StringUtils.isEmpty(roles)) {
- return false;
- }
- LoginUser loginUser = SecurityUtils.getLoginUser();
- if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
- return false;
- }
- for (String role : roles.split(ROLE_DELIMITER)) {
- if (hasRole(role)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 判断是否包含权限
- *
- * @param permissions
- * 权限列表
- * @param permission
- * 权限字符串
- * @return 用户是否具备某权限
- */
- private boolean hasPermissions(Set permissions, String permission) {
- return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/conf/AbstractListTypeHandler.java ---
-package com.starry.admin.common.conf;
-
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.TypeReference;
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.apache.ibatis.type.MappedJdbcTypes;
-import org.apache.ibatis.type.MappedTypes;
-
-/**
- * 数据库数据-String 和 List 自动转换
- *
- * @author admin
- */
-@Slf4j
-@MappedJdbcTypes(JdbcType.VARCHAR)
-@MappedTypes({List.class})
-public abstract class AbstractListTypeHandler extends BaseTypeHandler> {
- @Override
- public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType)
- throws SQLException {
- String content = StrUtil.isEmptyIfStr(parameter) ? null : JSON.toJSONString(parameter);
- ps.setString(i, content);
- }
-
- @Override
- public List getNullableResult(ResultSet rs, String columnName) throws SQLException {
- return this.getListByJsonArrayString(rs.getString(columnName));
- }
-
- @Override
- public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
- return this.getListByJsonArrayString(rs.getString(columnIndex));
- }
-
- @Override
- public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
- return this.getListByJsonArrayString(cs.getString(columnIndex));
- }
-
- private List getListByJsonArrayString(String content) {
- return StrUtil.isEmptyIfStr(content) ? new ArrayList<>() : JSON.parseObject(content, this.specificType());
- }
-
- /**
- * 具体类型,由子类提供
- *
- * @return 具体类型
- */
- protected abstract TypeReference> specificType();
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/conf/DataSourceConfig.java ---
-package com.starry.admin.common.conf;
-
-import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
-import javax.sql.DataSource;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-
-@Configuration
-public class DataSourceConfig {
-
- // For flyway only
- @Bean(name = "primaryDataSource")
- @Primary
- public DataSource dataSource() {
- return DruidDataSourceBuilder.create().build();
- }
-
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/conf/StringTypeHandler.java ---
-package com.starry.admin.common.conf;
-
-import com.alibaba.fastjson2.TypeReference;
-import java.util.List;
-
-/**
- * @author admin
- */
-public class StringTypeHandler extends AbstractListTypeHandler {
- @Override
- protected TypeReference> specificType() {
- return new TypeReference>() {
- };
- }
-}
-
-
---- File: play-admin/src/main/java/com/starry/admin/common/conf/ThreadLocalRequestDetail.java ---
-package com.starry.admin.common.conf;
-
-import com.alibaba.ttl.TransmittableThreadLocal;
-import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
-import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
-
-/**
- * @author : huchuansai
- * @since : 2024/4/2 12:10 AM
- */
-public class ThreadLocalRequestDetail {
- private static final TransmittableThreadLocal