初始化

This commit is contained in:
2026-05-08 17:07:33 +08:00
commit 92839e2d67
187 changed files with 19265 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target/

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

6
.idea/ApifoxUploaderProjectSetting.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ApifoxUploaderProjectSetting">
<option name="apiAccessToken" value="APS-FUPYJMd51p4Ju7gxBgHIxEZtFWfYmCvt" />
</component>
</project>

9
.idea/app-java.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

18
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<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" />
<module name="paopao-api" />
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="paopao-api" options="-parameters" />
</option>
</component>
</project>

8
.idea/dictionaries/project.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>websoft</w>
<w>websopy</w>
</words>
</dictionary>
</component>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>

25
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,25 @@
<?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="aliYunMaven" />
<option name="name" value="aliYunMaven" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</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="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>

14
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?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_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

12
.idea/paopao-java.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,73 @@
# 2026-05-05 工作日志
## 修复编译错误 - 恢复被误删的文件
### 问题
用户报告编译错误:`CloudStorageProvider.java:3:32 - package com.gxwebsoft.app.entity does not exist`
原因:`AppResource.java` 实体类被删除,但 `CloudStorageProvider.java` 仍依赖它。
### 解决方案
从 git 历史中恢复了多个被误删的文件:
#### 恢复的实体类 (entity)
- `AppResource.java` - 云资源实体(最初报告缺失)
- `AppProduct.java` - 应用产品实体
- `ResourceAccessLevel.java` - 资源访问级别
- `AppCloudCredential.java` - 云凭证
- `AppPermissionRequest.java` - 权限请求
- `AppRechargeCode.java` - 充值码
- `AppRechargeRecord.java` - 充值记录
#### 恢复的控制器 (controller)
- `AppResourceController.java`
- `AppProductController.java`
#### 恢复的服务接口 (service)
- `AppResourceService.java`
- `AppProductService.java`
- `AppCloudCredentialService.java`
- `AppPermissionRequestService.java`
- `AppRechargeCodeService.java`
- `AppRechargeRecordService.java`
#### 恢复的服务实现 (service/impl)
- `AppResourceServiceImpl.java`
- `AppProductServiceImpl.java`
- `AppCloudCredentialServiceImpl.java`
- `AppPermissionRequestServiceImpl.java`
- `AppRechargeCodeServiceImpl.java`
- `AppRechargeRecordServiceImpl.java`
#### 恢复的 Mapper 接口
- `AppResourceMapper.java`
- `AppProductMapper.java`
- `AppCloudCredentialMapper.java`
- `AppPermissionRequestMapper.java`
- `AppRechargeCodeMapper.java`
- `AppRechargeRecordMapper.java`
#### 恢复的参数类 (param)
- `AppResourceParam.java`
- `AppProductParam.java`
- `AppCloudCredentialParam.java`
#### 恢复的云存储接口
- `CloudStorageProvider.java`
### 恢复方法
使用 git 命令从删除前的提交中恢复文件:
```bash
git show HEAD~1:src/main/java/com/gxwebsoft/app/entity/AppResource.java > AppResource.java
```
对于在更早提交中删除的文件(如 `AppResourceController.java`),找到删除前的提交(如 `2202f1f`)进行恢复。
### 后续步骤
1. 用户需要在 IDE 中刷新/重建项目IntelliJ: Build → Rebuild Project
2. 如果还有编译错误,提供文件名、行号、错误信息,继续修复
### 注意事项
- 这些文件在提交 `8e30f34` 中被删除(该提交旨在移除与项目无关的功能)
- 但其中一些文件仍被其他代码引用,导致编译错误
- 需要权衡:是继续恢复文件,还是修改引用代码以移除依赖

View File

@@ -0,0 +1,19 @@
# 2026-05-06 工作日志
## 清理孤立实体类
- **任务**:对比 `docs/db_paopao.sql`38张表`entity/` 目录26个实体删除 SQL 中不存在的表对应的实体及其全套关联文件
- **删除了 21 个孤立实体**AppApiKey, AppArticleCategory, AppBuild, AppCloudCredential, AppConfig, AppContract, AppCredential, AppEvent, AppGitAccount, AppInviteToken, AppPermissionRequest, AppPipeline, AppRechargeCode, AppResource, AppSetting, AppSubscription, AppTicket, AppTicketReply, AppUserCache, AppVersion, ResourceAccessLevel
- **共删除 107 个文件**Entity(21) + Mapper(21) + Param(16) + Service接口(18) + ServiceImpl(22) + Controller(20) + 其他被牵连文件(9)
- **保留的 5 个有效实体**AppUser, AppArticle, AppNotification, AppProduct, AppRechargeRecord
- 被牵连删除的文件包括云存储Provider全家桶4个、UserController、WxLoginController、SyncMessageConsumer、WxMiniprogramUtil、UserCacheRefreshTask 等
## 重新组织目录结构:子模块合并到顶层
- **目标**:将 user/、product/、order/、finance/、marketing/、distribution/、activity/、cms/ 子模块全部合并到顶层统一按层entity/mapper/controller/param/service/service/impl组织
- **删除了 8 个子模块目录**user/、product/、order/、finance/、marketing/、distribution/、activity/、cms/
- **迁移了独有文件到顶层**(修正 package 和 import
- **Service 接口(4)**: AppMemberLevelService, AppUserAddressService, AppUserMemberOrderService, AppProductCategoryService, AppOrderService
- **Service 实现(5)**: AppMemberLevelServiceImpl, AppUserAddressServiceImpl, AppUserMemberOrderServiceImpl, AppUserServiceImpl, AppProductCategoryServiceImpl, AppOrderServiceImpl
- **Mapper(10)**: AppCartMapper, AppOrderMapper, AppOrderItemMapper, AppOrderRefundMapper, AppMemberLevelMapper, AppUserAddressMapper, AppUserMemberOrderMapper, AppProductBrandMapper, AppProductCategoryMapper, AppProductSkuMapper, AppProductSpecMapper, AppProductSpecValueMapper
- **Param(4)**: AppMemberLevelParam, AppUserAddressParam, AppUserMemberOrderParam, AppOrderParam
- **最终结构**纯扁平按层分entity/(38个) + mapper/ + param/ + controller/ + service/ + service/impl/
- 剩余 129 个 Java 文件,无子模块残留

51
Dockerfile Normal file
View File

@@ -0,0 +1,51 @@
# 使用更小的 Alpine Linux + OpenJDK 17 镜像
FROM openjdk:17-jdk-alpine
# 设置工作目录
WORKDIR /app
# 创建日志目录
RUN mkdir -p /app/logs
# 创建上传文件目录
RUN mkdir -p /app/uploads
# 安装必要工具和中文字体支持
# fontconfig: 字体配置库
# ttf-dejavu: 包含DejaVu字体支持中文显示
# wqy-zenhei: 文泉驿正黑字体,开源中文字体
RUN apk add --no-cache wget fontconfig ttf-dejavu && \
# 下载并安装文泉驿微米黑字体(更好的中文支持)
wget -O /tmp/wqy-microhei.ttc https://github.com/anthonyfok/fonts-wqy-microhei/raw/master/wqy-microhei.ttc && \
mkdir -p /usr/share/fonts/truetype/wqy && \
mv /tmp/wqy-microhei.ttc /usr/share/fonts/truetype/wqy/ && \
# 刷新字体缓存
fc-cache -fv && \
# 创建应用用户(安全考虑)
addgroup -g 1000 appgroup && \
adduser -D -u 1000 -G appgroup appuser
# 复制jar包到容器
COPY target/*.jar app.jar
# 设置目录权限
RUN chown -R appuser:appgroup /app
# 切换到应用用户
USER appuser
# 暴露端口
EXPOSE 9200
# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom"
# 设置Spring Profile
ENV SPRING_PROFILES_ACTIVE=prod
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:9200/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

218
README.md Normal file
View File

@@ -0,0 +1,218 @@
# WebSoft API
WebSoft API 是一个基于 `Spring Boot + Spring Security + MyBatis-Plus` 的后端 API 项目,覆盖 CMS、商城、支付、系统配置、消息同步等业务场景。
## 当前技术栈
以下信息以仓库当前代码为准:
| 技术 | 当前版本 | 说明 |
|------|------|------|
| Java | 17 | 运行时与编译版本 |
| Spring Boot | 2.7.18 | 应用基础框架 |
| Spring Security | 5.7.x随 Boot 2.7.18 | 认证与授权 |
| MyBatis-Plus | 3.4.3.3 | ORM 与通用 CRUD |
| MyBatis-Plus-Join | 1.4.5 | 连表查询扩展 |
| Druid | 1.2.20 | 数据源连接池 |
| Redis | Starter 管理 | 缓存 |
| RabbitMQ | Starter 管理 | 消息队列 |
| SpringDoc OpenAPI | 1.7.0 | OpenAPI 文档 |
| Knife4j | 4.3.0 | 接口文档增强 |
| Maven | 3.6+ | 构建工具 |
## 主要能力
- 用户认证与权限控制
- CMS 内容管理
- 商城商品、订单、优惠券
- 微信支付、支付宝支付
- Redis 缓存
- RabbitMQ 消息同步
- WebSocket 实时通信
- Excel 导入导出
- 多云对象存储接入
## 项目结构
```text
src/main/java/com/gxwebsoft/
├── WebSoftApplication.java
├── app/ # 应用配置、任务、通用业务模块
├── cms/ # 内容管理
├── common/ # 公共能力、核心配置、MQ、系统模块
├── payment/ # 支付领域
└── shop/ # 商城领域
```
资源与配置文件位于 `src/main/resources/`
- `application.yml`
- `application-dev.yml`
- `application-test.yml`
- `application-prod.yml`
- `application-websopy.yml`
## 环境要求
- JDK 17
- Maven 3.6+
- MySQL 8.x
- Redis
- RabbitMQ
## 本地开发
### 1. 安装依赖环境
确保本地已经启动:
- MySQL
- Redis
- RabbitMQ
### 2. 配置开发环境
默认激活的 Spring Profile 在 [src/main/resources/application.yml](/Users/gxwebsoft/JAVA/websopy-java/src/main/resources/application.yml:5) 中配置为 `dev`
开发环境配置文件为:
- [src/main/resources/application-dev.yml](/Users/gxwebsoft/JAVA/websopy-java/src/main/resources/application-dev.yml)
建议优先使用环境变量或本地未提交配置覆盖以下信息:
- 数据库连接
- Redis 连接
- RabbitMQ 连接
- 邮件账号
- OSS / 支付 / 第三方平台密钥
### 3. 启动项目
```bash
mvn spring-boot:run
```
或在 IDE 中直接运行启动类:
- [WebSoftApplication.java](/Users/gxwebsoft/JAVA/websopy-java/src/main/java/com/gxwebsoft/WebSoftApplication.java:25)
### 4. 访问地址
开发环境默认端口是 `9200`
- 接口根地址:`http://localhost:9200`
- Swagger UI`http://localhost:9200/swagger-ui/index.html`
- Knife4j`http://localhost:9200/doc.html`
测试环境默认端口是 `9300`,生产环境默认端口是 `9500`
## 配置说明
### 多环境配置
- `dev`:本地开发
- `test`:测试环境
- `prod`:生产环境
- `websopy`:额外业务环境配置
### 文件上传
主配置中定义了上传目录相关参数,生产部署前请根据服务器目录调整:
- `config.upload-path`
- `config.local-upload-path`
### 证书与支付
项目内包含微信支付、支付宝证书目录和密钥相关配置。建议:
- 不要在仓库中保存真实密钥
- 使用环境变量、挂载文件或配置中心注入
- 区分开发和生产证书
## 打包与运行
### Maven 打包
```bash
mvn clean package -Dmaven.test.skip=true
```
打包后运行:
```bash
java -jar target/websopy-api-1.5.0.jar --spring.profiles.active=prod
```
## Docker 部署
仓库包含以下文件:
- [Dockerfile](/Users/gxwebsoft/JAVA/websopy-java/Dockerfile)
- [docker-compose.yml](/Users/gxwebsoft/JAVA/websopy-java/docker-compose.yml)
### Dockerfile 当前行为
- 基础镜像:`openjdk:17-jdk-alpine`
- 容器内默认激活:`SPRING_PROFILES_ACTIVE=prod`
- 暴露端口:`9200`
### 注意
当前仓库内存在一个端口差异:
- `application-prod.yml` 默认端口是 `9500`
- `Dockerfile``docker-compose.yml` 仍按 `9200` 编排
如果直接按当前 Docker 配置部署,建议先统一端口设置后再上线,避免健康检查和端口映射不一致。
### 构建镜像
```bash
docker build -t websopy-api .
```
### 运行容器
以下命令仅适用于你已经把应用监听端口与 Docker 映射端口统一为 `9200` 的情况:
```bash
docker run -d \
--name websopy-api \
-p 9200:9200 \
-e SPRING_PROFILES_ACTIVE=prod \
websopy-api
```
### 使用 Compose
`docker-compose.yml` 同样建议在统一端口后再直接使用:
```bash
docker compose up -d --build
```
## 接口文档
项目当前使用 `SpringDoc OpenAPI + Knife4j`
- Swagger UI`/swagger-ui/index.html`
- Knife4j`/doc.html`
## 开发说明
### 代码分层
- `controller`:接口层
- `service`:业务层
- `mapper`:数据访问层
- `entity / dto / vo / param`:模型层
### 构建说明
- `src/main/java` 下同时存放了 Java 代码和 `*Mapper.xml`
- Maven 构建已显式把 `*Mapper.xml` 作为资源文件打包
## 文档维护说明
历史 README 中的 `Java 1.8``Spring Boot 2.5.4`、部分目录结构与当前代码不一致,本次已按现有仓库状态更新。后续如果升级到 `Spring Boot 3.x` 或调整 Docker 端口,建议同步更新本文档。

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
version: '3.8'
services:
# 应用服务
cms-api:
build: .
container_name: cms-api
ports:
- "9200:9200"
environment:
- SPRING_PROFILES_ACTIVE=prod
- JAVA_OPTS=-Xms512m -Xmx1024m
volumes:
# 证书挂载卷 - 将宿主机证书目录挂载到容器
- ./certs:/app/certs:ro
# 日志挂载卷
- ./logs:/app/logs
# 上传文件挂载卷
- ./uploads:/app/uploads
networks:
- cms-network
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9200/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
cms-network:
driver: bridge
volumes:
mysql_data:
driver: local
redis_data:
driver: local

188
docker-deploy-guide.md Normal file
View File

@@ -0,0 +1,188 @@
# Docker容器化部署指南
## 支付证书问题解决方案
本项目已经解决了Docker容器中支付证书路径失效的问题支持多种证书加载方式。
## 目录结构
```
project-root/
├── Dockerfile
├── docker-compose.yml
├── certs/ # 证书目录(需要手动创建)
│ ├── wechat/ # 微信支付证书
│ │ ├── apiclient_key.pem
│ │ ├── apiclient_cert.pem
│ │ └── wechatpay_cert.pem
│ └── alipay/ # 支付宝证书
│ ├── app_private_key.pem
│ ├── appCertPublicKey.crt
│ ├── alipayCertPublicKey.crt
│ └── alipayRootCert.crt
├── logs/ # 日志目录
├── uploads/ # 上传文件目录
└── src/
```
## 部署步骤
### 1. 准备证书文件
创建证书目录并放置证书文件:
```bash
# 创建证书目录
mkdir -p certs/wechat
mkdir -p certs/alipay
# 复制微信支付证书到对应目录
cp /path/to/your/apiclient_key.pem certs/wechat/
cp /path/to/your/apiclient_cert.pem certs/wechat/
cp /path/to/your/wechatpay_cert.pem certs/wechat/
# 复制支付宝证书到对应目录
cp /path/to/your/app_private_key.pem certs/alipay/
cp /path/to/your/appCertPublicKey.crt certs/alipay/
cp /path/to/your/alipayCertPublicKey.crt certs/alipay/
cp /path/to/your/alipayRootCert.crt certs/alipay/
# 设置证书文件权限(只读)
chmod -R 444 certs/
```
### 2. 配置环境变量
创建 `.env` 文件(可选):
```bash
# 应用配置
SPRING_PROFILES_ACTIVE=prod
JAVA_OPTS=-Xms512m -Xmx1024m
# 数据库配置
MYSQL_ROOT_PASSWORD=root123456
MYSQL_DATABASE=modules
MYSQL_USER=modules
MYSQL_PASSWORD=8YdLnk7KsPAyDXGA
# Redis配置
REDIS_PASSWORD=redis_WSDb88
```
### 3. 构建和启动
```bash
# 构建应用
mvn clean package -DskipTests
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看应用日志
docker-compose logs -f cms-app
```
### 4. 验证部署
```bash
# 检查应用健康状态
curl http://localhost:9200/actuator/health
# 检查证书是否正确加载
docker exec cms-java-app ls -la /app/certs/
```
## 证书加载模式
### 开发环境 (CLASSPATH)
- 证书文件放在 `src/main/resources/certs/` 目录下
- 打包时会包含在jar包中
- 适合开发和测试环境
### 生产环境 (VOLUME)
- 证书文件通过Docker挂载卷加载
- 证书文件在宿主机上,挂载到容器的 `/app/certs` 目录
- 支持证书文件的动态更新(重启容器后生效)
### 文件系统模式 (FILESYSTEM)
- 直接从文件系统路径加载证书
- 适合传统部署方式
## 配置说明
### application.yml 配置
```yaml
certificate:
load-mode: VOLUME # 证书加载模式
cert-root-path: /app/certs # 证书根目录
wechat-pay:
dev:
api-v3-key: "your-api-v3-key"
private-key-file: "apiclient_key.pem"
apiclient-cert-file: "apiclient_cert.pem"
wechatpay-cert-file: "wechatpay_cert.pem"
```
### 环境特定配置
- **开发环境**: `application-dev.yml` - 使用CLASSPATH模式
- **生产环境**: `application-prod.yml` - 使用VOLUME模式
## 故障排除
### 1. 证书文件找不到
```bash
# 检查证书文件是否存在
docker exec cms-java-app ls -la /app/certs/
# 检查文件权限
docker exec cms-java-app ls -la /app/certs/wechat/
```
### 2. 支付接口调用失败
```bash
# 查看应用日志
docker-compose logs cms-app | grep -i cert
# 检查证书配置
docker exec cms-java-app cat /app/application.yml | grep -A 10 certificate
```
### 3. 容器启动失败
```bash
# 查看详细错误信息
docker-compose logs cms-app
# 检查容器状态
docker-compose ps
```
## 安全建议
1. **证书文件权限**: 设置为只读权限 (444)
2. **证书目录权限**: 限制访问权限
3. **敏感信息**: 使用环境变量或Docker secrets管理敏感配置
4. **网络安全**: 使用内部网络,限制端口暴露
## 更新证书
1. 停止应用容器:`docker-compose stop cms-app`
2. 更新证书文件到 `certs/` 目录
3. 重启应用容器:`docker-compose start cms-app`
## 监控和日志
- 应用日志:`./logs/` 目录
- 容器日志:`docker-compose logs`
- 健康检查:访问 `/actuator/health` 端点
通过以上配置你的应用在Docker容器中就能正确加载支付证书了

1026
docs/db_paopao_v2.sql Normal file

File diff suppressed because it is too large Load Diff

1866
docs/db_system.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,366 @@
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font, Border, Side, Alignment
from openpyxl.utils import get_column_letter
wb = Workbook()
# ====== Sheet 1: 总览 ======
ws_summary = wb.active
ws_summary.title = "开发计划总览"
ws_summary.sheet_view.showGridLines = False
# 颜色定义
header_fill = PatternFill(start_color="1F4E79", end_color="1F4E79", fill_type="solid")
subheader_fill = PatternFill(start_color="2E75B6", end_color="2E75B6", fill_type="solid")
p1_fill = PatternFill(start_color="C00000", end_color="C00000", fill_type="solid")
p2_fill = PatternFill(start_color="FF7C00", end_color="FF7C00", fill_type="solid")
p3_fill = PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid")
white_font = Font(color="FFFFFF", bold=True, size=11)
normal_font = Font(size=10)
thin_border = Border(
left=Side(style='thin', color='CCCCCC'),
right=Side(style='thin', color='CCCCCC'),
top=Side(style='thin', color='CCCCCC'),
bottom=Side(style='thin', color='CCCCCC')
)
# 标题
ws_summary['B2'] = "paopao 电商系统开发计划表"
ws_summary['B2'].font = Font(size=18, bold=True, color="1F4E79")
ws_summary['B2'].alignment = Alignment(horizontal='left', vertical='center')
ws_summary.row_dimensions[2].height = 36
# 统计信息
stats = [
("总功能点", "64"),
("小程序端", "24"),
("后台管理端", "40"),
("核心流程", "9"),
]
for i, (label, val) in enumerate(stats):
col = get_column_letter(2 + i)
ws_summary[f'{col}4'] = label
ws_summary[f'{col}4'].font = Font(bold=True, size=10, color="1F4E79")
ws_summary[f'{col}4'].alignment = Alignment(horizontal='center')
ws_summary[f'{col}5'] = val
ws_summary[f'{col}5'].font = Font(size=24, bold=True, color="1F4E79")
ws_summary[f'{col}5'].alignment = Alignment(horizontal='center')
ws_summary.row_dimensions[5].height = 30
# 模块列表
modules = [
("序号", "模块名称", "", "功能点数", "优先级", "预计工时(h)", "状态"),
]
row = 7
for m in modules:
for col_idx, val in enumerate(m, 2):
cell = ws_summary.cell(row=row, column=col_idx, value=val)
cell.fill = header_fill
cell.font = white_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
ws_summary.row_dimensions[row].height = 24
module_data = [
(1, "登录/注册", "小程序端", 3, "P1", 16, "待开发"),
(2, "首页模块", "小程序端", 11, "P1", 40, "待开发"),
(3, "会员中心", "小程序端", 4, "P1", 32, "待开发"),
(4, "商城模块", "小程序端", 3, "P1", 24, "待开发"),
(5, "购物车", "小程序端", 3, "P1", 16, "待开发"),
(6, "我的页面", "小程序端", 10, "P1", 32, "待开发"),
(7, "首页统计", "后台管理端", 2, "P1", 8, "待开发"),
(8, "会员管理", "后台管理端", 2, "P1", 16, "待开发"),
(9, "商品管理", "后台管理端", 5, "P1", 40, "待开发"),
(10, "比赛活动", "后台管理端", 2, "P1", 24, "待开发"),
(11, "预约管理", "后台管理端", 1, "P2", 8, "待开发"),
(12, "营销模块", "后台管理端", 5, "P2", 32, "待开发"),
(13, "系统管理", "后台管理端", 3, "P2", 16, "待开发"),
(14, "财务模块", "后台管理端", 6, "P1", 32, "待开发"),
]
for i, row_data in enumerate(module_data):
row = 8 + i
for col_idx, val in enumerate(row_data, 2):
cell = ws_summary.cell(row=row, column=col_idx, value=val)
cell.font = normal_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
# 优先级着色
if col_idx == 6: # 优先级列
if val == "P1":
cell.fill = p1_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
elif val == "P2":
cell.fill = p2_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
ws_summary.row_dimensions[row].height = 20
# 列宽
widths = [4, 18, 14, 10, 10, 12, 10]
for i, w in enumerate(widths, 2):
ws_summary.column_dimensions[get_column_letter(i)].width = w
# ====== Sheet 2: 小程序端详细计划 ======
ws_mini = wb.create_sheet("小程序端详细计划")
ws_mini.sheet_view.showGridLines = False
ws_mini['B2'] = "小程序端(用户端)功能开发计划"
ws_mini['B2'].font = Font(size=16, bold=True, color="1F4E79")
headers = ["序号", "功能模块", "功能名称", "功能描述", "优先级", "预计工时(h)", "关联流程", "备注"]
for col_idx, h in enumerate(headers, 2):
cell = ws_mini.cell(row=4, column=col_idx, value=h)
cell.fill = header_fill
cell.font = white_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
ws_mini.row_dimensions[4].height = 24
mini_data = [
# 登录注册
(1, "登录注册", "手机短信验证码登录/注册", "可通过手机短信验证方式注册,修改密码", "P1", 8, "用户注册与登录流程", ""),
(2, "登录注册", "微信授权登录/注册", "可直接通过微信账号注册进入小程序", "P1", 4, "用户注册与登录流程", ""),
(3, "登录注册", "隐私/政策协议", "可自由上传修改文案内容", "P3", 4, "用户注册与登录流程", ""),
# 首页
(4, "首页", "LOGO+小程序名称", "可自由修改", "P1", 2, "", ""),
(5, "首页", "搜索", "关键字搜索:可通过搜索栏搜索关键字查找到商城内相应商品(模糊查询)", "P1", 8, "商品购物与支付流程", ""),
(6, "首页", "消息通知", "系统通知:所有小程序内订单状态变化及支付记录,佣金收入等通知", "P1", 6, "", ""),
(7, "首页", "轮播图", "自定义替换图片:可自由设置图片尺寸规格", "P1", 4, "", ""),
(8, "首页", "精选商城入口", "入口跳转至商城页面", "P2", 2, "", ""),
(9, "首页", "活动报名入口", "活动列表:展示活动名称,活动时间等概要信息", "P1", 6, "活动报名流程", ""),
(10, "首页", "穿线预约入口", "显示门店名称、地址、营业时间,距离;点击定位跳转地图;点击电话拨打", "P1", 8, "穿线预约流程", ""),
(11, "首页", "积分商城入口", "显示总积分,可积分兑换的商品、查询兑换订单进度", "P1", 8, "积分商城与使用流程", ""),
(12, "首页", "热销商品展示", "展示购买率高的商品列表", "P2", 4, "", ""),
(13, "首页", "会员登录入口", "点击跳转会员中心;点商品会员价跳转注册界面", "P1", 4, "会员开通与推广流程", ""),
(14, "首页", "热门活动展示", "后台设置的热门活动展示", "P2", 4, "", ""),
(15, "首页", "领券中心入口", "优惠券领取入口", "P2", 4, "优惠券领取与使用流程", ""),
(16, "首页", "积分签到", "每天签到固定可获取X积分", "P2", 6, "积分商城与使用流程", ""),
# 会员中心
(17, "会员中心", "购买会员/会员升级", "会员升级成为会员获取推广权益,线下收取下级会员费后录入注册", "P1", 12, "会员开通与推广流程", ""),
(18, "会员中心", "分佣查看", "自己下级在平台产生消费,会员可获得相应比例的分佣", "P1", 8, "佣金结算与提现流程", ""),
(19, "会员中心", "分佣结算", "订单完成货款到账后(过了售后期),佣金才能结算到上级账户", "P1", 6, "佣金结算与提现流程", ""),
(20, "会员中心", "可提现佣金", "申请提现至微信设置提现时间和上限单次200以上每天不超过2000", "P1", 6, "佣金结算与提现流程", ""),
# 商城
(21, "商城", "商品搜索", "关键字搜索查找到相应商品(模糊查询)", "P1", 6, "商品购物与支付流程", ""),
(22, "商城", "商品分类", "三级分类导航", "P1", 6, "", ""),
(23, "商城", "商品详情", "商品主图/视频、规格、价格等详情展示", "P1", 12, "商品购物与支付流程", ""),
# 购物车
(24, "购物车", "商品列表展示", "显示已添加的商品列表", "P1", 4, "", ""),
(25, "购物车", "购物车管理", "删除选购商品", "P1", 4, "", ""),
(26, "购物车", "下单结算", "多选商品下单支付,同一订单只收一笔快递费", "P1", 8, "商品购物与支付流程", ""),
# 我的
(27, "我的", "头像昵称设置", "设置头像、昵称、性别、手机号、显示ID、注册时间", "P1", 6, "", ""),
(28, "我的", "消息中心", "系统通知、公告消息", "P2", 4, "", ""),
(29, "我的", "设置页面", "服务协议、隐私政策、版权信息、关于我们", "P2", 4, "", ""),
(30, "我的", "会员等级显示", "展示当前会员等级", "P1", 2, "", ""),
(31, "我的", "我的钱包", "余额充值功能", "P1", 6, "", ""),
(32, "我的", "我的积分", "积分查询", "P1", 2, "", ""),
(33, "我的", "我的优惠券", "已使用、未使用、已过期;显示已领取优惠券", "P1", 6, "优惠券领取与使用流程", ""),
(34, "我的", "我的订单", "全部订单:待支付、待发货、待收货、商品评价、申请售后(部分退款)", "P1", 12, "商品购物与支付流程", ""),
(35, "我的", "我的活动", "已报名活动列表,可取消报名", "P1", 6, "活动报名流程", ""),
(36, "我的", "分销推广入口", "我的邀请人查看", "P1", 4, "", ""),
]
for i, row_data in enumerate(mini_data):
row = 5 + i
for col_idx, val in enumerate(row_data, 2):
cell = ws_mini.cell(row=row, column=col_idx, value=val)
cell.font = normal_font
cell.border = thin_border
if col_idx == 2:
cell.alignment = Alignment(horizontal='center', vertical='center')
elif col_idx == 6 or col_idx == 7:
cell.alignment = Alignment(horizontal='center', vertical='center')
else:
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
# 优先级着色
if col_idx == 6:
if val == "P1":
cell.fill = p1_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
elif val == "P2":
cell.fill = p2_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
elif val == "P3":
cell.fill = p3_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
ws_mini.row_dimensions[row].height = 30
# 列宽
mini_widths = [4, 12, 16, 50, 8, 10, 18, 10]
for i, w in enumerate(mini_widths, 2):
ws_mini.column_dimensions[get_column_letter(i)].width = w
# ====== Sheet 3: 后台管理端详细计划 ======
ws_admin = wb.create_sheet("后台管理端详细计划")
ws_admin.sheet_view.showGridLines = False
ws_admin['B2'] = "后台管理端Web端功能开发计划"
ws_admin['B2'].font = Font(size=16, bold=True, color="1F4E79")
for col_idx, h in enumerate(headers, 2):
cell = ws_admin.cell(row=4, column=col_idx, value=h)
cell.fill = header_fill
cell.font = white_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
ws_admin.row_dimensions[4].height = 24
admin_data = [
# 首页统计
(1, "首页统计", "普通会员统计", "普通会员人数统计", "P1", 2, "", ""),
(2, "首页统计", "趋势图", "注册人数、会员人数趋势", "P1", 6, "", ""),
# 会员管理
(3, "会员管理", "会员列表", "普通会员:登录注册的用户信息,手机号、昵称、余额、积分等", "P1", 8, "后台管理核心流程", ""),
(4, "会员管理", "会员权限设置", "VIP会员开通价格、会员时间、享受权限等", "P1", 8, "", ""),
# 商城管理
(5, "商城管理", "商品管理", "商品添加、上下架、商品规格设置(如服装码数,颜色等商品参数)", "P1", 16, "后台管理核心流程", ""),
(6, "商城管理", "批量导入导出", "能实现线上导出导入数据,实现批量修改导入数据", "P1", 8, "", ""),
(7, "商城管理", "商品分销设置", "商品单独设置分销佣金", "P1", 6, "", ""),
(8, "商城管理", "订单管理", "商品订单:待支付,待发货,待收货,商品评价,退款/售后", "P1", 12, "后台管理核心流程", ""),
(9, "商城管理", "商品品牌管理", "品牌列表管理", "P2", 4, "", ""),
(10, "商城管理", "商品类型管理", "三级分类管理", "P1", 6, "", ""),
# 比赛活动
(11, "比赛活动", "活动列表", "添加比赛信息,限制报名人数,可设置热门;后台自定义参赛人员必填项;衣服尺码后台设置;限制报名人数或队伍数", "P1", 16, "后台管理核心流程", ""),
(12, "比赛活动", "预约报名管理", "活动报名人员管理", "P1", 8, "", ""),
# 预约管理
(13, "预约管理", "预约订单查询", "用户预约穿线订单,分门店查询", "P2", 8, "穿线预约流程", ""),
# 营销
(14, "营销", "优惠券管理", "场地使用券、无门槛、满减券、折扣券;可设置优惠券指定发放给会员/非会员/单独某个账户", "P1", 12, "优惠券领取与使用流程", ""),
(15, "营销", "优惠券使用记录", "优惠券使用记录,核销门店等信息", "P1", 6, "", ""),
(16, "营销", "积分抵扣设置", "积分抵扣规则配置", "P2", 6, "", ""),
(17, "营销", "积分商城管理", "积分兑换商品管理", "P2", 8, "积分商城与使用流程", ""),
(18, "营销", "轮播图管理", "广告图替换", "P2", 4, "", ""),
(19, "营销", "系统公告发布", "发布系统公告通知", "P2", 4, "", ""),
# 系统管理
(20, "系统管理", "用户管理", "添加系统用户账户", "P2", 8, "后台管理核心流程", ""),
(21, "系统管理", "角色权限管理", "用户角色权限设置", "P2", 8, "", ""),
(22, "系统管理", "门店管理", "添加门店信息、账号、密码", "P2", 8, "", ""),
# 财务
(23, "财务", "账务概况", "订单、会员、分销分别进行数据统计,可按日期查询,可导出表格", "P1", 12, "后台管理核心流程", ""),
(24, "财务", "充值记录查询", "查询所有充值记录,可按账号、日期查询,可导出表格", "P1", 6, "", ""),
(25, "财务", "资金记录查询", "查询所有账号资金账单,可按账号、日期查询,可导出表格", "P1", 6, "", ""),
(26, "财务", "积分记录查询", "查询所有账号积分情况,可按账号、日期查询,可导出表格", "P1", 6, "", ""),
(27, "财务", "佣金提现审核", "审核所有佣金提现申请", "P1", 8, "佣金结算与提现流程", ""),
(28, "财务", "佣金记录查询", "所有佣金账单,可按账号、日期查询,可导出表格", "P1", 6, "", ""),
]
for i, row_data in enumerate(admin_data):
row = 5 + i
for col_idx, val in enumerate(row_data, 2):
cell = ws_admin.cell(row=row, column=col_idx, value=val)
cell.font = normal_font
cell.border = thin_border
if col_idx == 2:
cell.alignment = Alignment(horizontal='center', vertical='center')
elif col_idx == 6 or col_idx == 7:
cell.alignment = Alignment(horizontal='center', vertical='center')
else:
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
if col_idx == 6:
if val == "P1":
cell.fill = p1_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
elif val == "P2":
cell.fill = p2_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
elif val == "P3":
cell.fill = p3_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
ws_admin.row_dimensions[row].height = 30
# 列宽
admin_widths = [4, 12, 16, 50, 8, 10, 18, 10]
for i, w in enumerate(admin_widths, 2):
ws_admin.column_dimensions[get_column_letter(i)].width = w
# ====== Sheet 4: 核心业务流程 ======
ws_flow = wb.create_sheet("核心业务流程")
ws_flow.sheet_view.showGridLines = False
ws_flow['B2'] = "系统核心业务流程"
ws_flow['B2'].font = Font(size=16, bold=True, color="1F4E79")
flow_headers = ["序号", "流程名称", "流程说明", "涉及模块", "关联页面", "关键节点"]
for col_idx, h in enumerate(flow_headers, 2):
cell = ws_flow.cell(row=4, column=col_idx, value=h)
cell.fill = header_fill
cell.font = white_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
ws_flow.row_dimensions[4].height = 24
flow_data = [
(1, "用户注册与登录流程", "用户进入小程序选择短信验证码登录或微信授权登录,阅读并同意隐私政策/服务协议后进入首页。需修改密码通过短信验证码重置。会员身份通过手机号匹配后激活。", "登录注册、首页", "登录页、首页", "手机号验证 → 身份匹配 → 权限激活"),
(2, "会员开通与推广下级流程", "用户购买会员支付198元注册费会员获得推广权限。会员线下收取下级298元会员费后在平台为下级录入手机号。下级用该手机号登录系统自动匹配身份成为该会员下级。", "会员中心、用户管理", "会员中心、注册页", "支付注册费 → 录入下级手机号 → 下级登录匹配"),
(3, "商品购物与支付流程", "用户通过首页/商城搜索/分类查找商品,查看详情后加入购物车。购物车勾选商品统一结算,同一订单仅收一笔运费。完成支付生成待发货订单,商家发货用户收货后进入售后,售后期结束货款到账后佣金自动结算。", "首页、商城、购物车、订单", "商品详情、购物车、订单页", "加入购物车 → 统一结算 → 支付发货 → 售后结算"),
(4, "积分商城与积分使用流程", "用户每日签到获得固定积分。进入积分商城查看总积分与可兑商品/优惠券。积分充足直接兑换,积分不足积分+补差价兑换。提交兑换订单后平台审核/配送。", "首页、积分商城", "积分商城页、兑换订单页", "每日签到 → 积分累积 → 兑换商品/优惠券"),
(5, "佣金结算与提现流程", "下级完成订单并过售后期后佣金结算至可提现佣金。会员进入会员中心查看可提现金额发起提现至绑定微信遵守200元起/单日上限2000元规则。后台审核提现申请审核通过资金到账用户微信。", "会员中心、财务", "会员中心、提现申请页", "订单完成 → 佣金结算 → 申请提现 → 后台审核"),
(6, "活动报名流程", "用户在首页查看活动列表(名称、时间、人数限制),选择活动填写后台设置的必填信息(尺码等),提交报名后可在我的服务查看/取消报名。", "首页、我的-我的活动", "活动列表页、报名表单页", "查看活动 → 填写信息 → 提交报名 → 查看/取消"),
(7, "穿线预约流程", "用户进入穿线预约查看门店列表(名称、地址、距离、营业时间),支持按门店名称搜索。点击地址跳转地图,点击电话直接拨号。选择门店提交预约生成预约订单,后台按门店查询预约订单进行核销服务。", "首页、预约管理", "门店列表页、预约订单页", "查看门店 → 选择预约 → 生成订单 → 门店核销"),
(8, "优惠券领取与使用流程", "用户从首页进入领券中心领取优惠券(满减/无门槛/折扣),在我的优惠券查看未使用/已使用/已过期。下单时自动匹配可用券抵扣金额。后台可定向发券给会员/非会员/指定用户并记录使用与核销信息。", "首页、营销、订单", "领券中心、我的优惠券、订单页", "领取优惠券 → 下单匹配 → 抵扣金额 → 核销记录"),
(9, "后台管理核心流程", "包含商品与订单管理(商品添加/上下架/规格/佣金,订单状态处理)、会员与分销管理(会员信息/价格/权限,下级关系与佣金跟踪)、活动与预约管理(创建活动/查看名单/处理预约)、营销与内容配置(轮播图/公告/积分/优惠券/积分商城)、财务与数据统计(按日期统计/导出/审核提现/查询记录)、系统权限管理(后台用户/角色权限/门店管理)等六大模块。", "所有后台模块", "各后台管理页面", "全链路业务支撑"),
]
for i, row_data in enumerate(flow_data):
row = 5 + i
for col_idx, val in enumerate(row_data, 2):
cell = ws_flow.cell(row=row, column=col_idx, value=val)
cell.font = normal_font
cell.border = thin_border
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
ws_flow.row_dimensions[row].height = 60
# 列宽
flow_widths = [4, 20, 60, 20, 16, 28]
for i, w in enumerate(flow_widths, 2):
ws_flow.column_dimensions[get_column_letter(i)].width = w
# ====== Sheet 5: 开发里程碑 ======
ws_mile = wb.create_sheet("开发里程碑")
ws_mile.sheet_view.showGridLines = False
ws_mile['B2'] = "开发里程碑计划"
ws_mile['B2'].font = Font(size=16, bold=True, color="1F4E79")
mile_headers = ["阶段", "里程碑名称", "主要交付物", "包含模块", "预计周期", "开始日期", "结束日期", "状态"]
for col_idx, h in enumerate(mile_headers, 2):
cell = ws_mile.cell(row=4, column=col_idx, value=h)
cell.fill = header_fill
cell.font = white_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = thin_border
ws_mile.row_dimensions[4].height = 24
mile_data = [
("Phase 1", "基础架构搭建", "完成项目框架搭建、数据库设计、API接口定义", "整体架构", "2周", "Day 1", "Day 14", "待启动"),
("Phase 2", "用户端核心功能", "登录注册、首页框架、商城基础功能", "登录注册、首页、商城", "3周", "Day 15", "Day 35", "待启动"),
("Phase 3", "交易核心功能", "购物车、订单流程、支付集成", "购物车、订单、支付", "2周", "Day 36", "Day 49", "待启动"),
("Phase 4", "会员与分销系统", "会员中心、佣金体系、提现功能", "会员中心、财务", "2周", "Day 50", "Day 63", "待启动"),
("Phase 5", "营销与活动", "优惠券、积分商城、活动报名", "营销、比赛活动", "2周", "Day 64", "Day 77", "待启动"),
("Phase 6", "后台管理端", "商品管理、订单管理、会员管理、财务统计", "后台所有模块", "3周", "Day 78", "Day 98", "待启动"),
("Phase 7", "系统集成测试", "全链路测试、Bug修复、性能优化", "全部模块", "2周", "Day 99", "Day 112", "待启动"),
("Phase 8", "上线部署", "生产环境部署、数据迁移、上线文档", "全部模块", "1周", "Day 113", "Day 119", "待启动"),
]
for i, row_data in enumerate(mile_data):
row = 5 + i
for col_idx, val in enumerate(row_data, 2):
cell = ws_mile.cell(row=row, column=col_idx, value=val)
cell.font = normal_font
cell.border = thin_border
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
if col_idx == 8: # 状态列
cell.fill = p3_fill
cell.font = Font(color="FFFFFF", bold=True, size=10)
ws_mile.row_dimensions[row].height = 30
# 列宽
mile_widths = [8, 18, 30, 24, 10, 10, 10, 10]
for i, w in enumerate(mile_widths, 2):
ws_mile.column_dimensions[get_column_letter(i)].width = w
# 保存
output_path = "/Users/gxwebsoft/JAVA/paopao-java/docs/output/paopao开发计划表.xlsx"
wb.save(output_path)
print(f"✅ 开发计划表已生成: {output_path}")

Binary file not shown.

124
docs/功能明细.md Normal file
View File

@@ -0,0 +1,124 @@
# 系统功能明细(用户端小程序 + 后台管理Web端
## 一、用户端(小程序端)
### 登录/注册
| 功能名称 | 需求效果 |
| ---- | ---- |
| 手机短信验证码登录/注册 | 可通过手机短信验证方式注册,修改密码 |
| 微信授权登录/注册 | 可直接通过微信账号注册进入小程序 |
| 隐私/政策协议 | 可自由上传修改文案内容 |
### 首页
| 功能名称 | 需求效果 |
| ---- | ---- |
| LOGO+小程序名称 | 可自由修改 |
| 搜索 | 关键字搜索:可通过搜索栏搜索关键字查找到商城内相应商品(模糊查询) |
| 消息 | 系统通知:所有小程序内订单状态变化及支付记录,佣金收入等通知 |
| 轮播图 | 自定义替换图片:可自由设置图片尺寸规格 |
| 精选商城 | “精选商城”同“商城”内容 |
| 活动报名 | 活动列表:展示活动名称,活动时间等概要信息,如对应的信息过长时可自动换行,把重要信息展示完全 |
| 穿线预约 | 1. 显示门店名称、地址、营业时间距离2. 点击定位可跳转到地图显示3. 点击电话可直接拨打4. 门店名称查找 |
| 积分商城 | 1. 显示总积分可积分兑换的商品、查询兑换订单进度2. 可兑实物或优惠券(积分不足时可补差价) |
| 热销商品 | 展示购买率高的商品列表 |
| 会员登录 | 1. 点击自动跳转到会员中心页面2. 点商品内的会员价也会跳转到会员登录注册界面 |
| 热门活动 | 后台设置 |
| 领券中心 | - |
| 积分签到 | 每天签到固定可获取X积分 |
### 会员中心
| 功能名称 | 需求效果 |
| ---- | ---- |
| 购买会员 | 1. 会员升级成为会员即可获得推广的权益会员在进行推广时会员需线下收取下级的会员费298元再由会员帮自己下级录入注册信息手机号如录错需要修改则需经过平台客服支付198元的注册费之后自己下级在平台注册登录时通过判断手机号匹配身份匹配成功下级即成为平台会员2. 会员权益包:成为会员后,可在平台申请兑换权益包,再由平台进行配送 |
| 分佣 | 自己下级在平台产生消费,会员可获得相应比例的分佣(必须保持会员身份才能持续获得) |
| 分佣结算 | 订单完成货款到账后(过了售后期),佣金才能结算到上级的佣金账户 |
| 可提现佣金 | 1. 用户可申请提现至会员手机号绑定的微信2. 可对返佣金时间可以进行设置提现上限进行设置3. 温馨提示单次提现200以下每天不超过2000元 |
### 商城
| 功能名称 | 需求效果 |
| ---- | ---- |
| 搜索 | 可通过搜索栏搜索关键字查找到商城内相应商品(模糊查询) |
| 商品分类 | 三级分类 |
| 商品详情 | 商品主图/视频 |
### 购物车
| 功能名称 | 需求效果 |
| ---- | ---- |
| 商品列表 | - |
| 管理 | 删除选购商品 |
| 下单 | 多选商品,下单支付,同一个订单中,只收取一笔快递费(可设置多种模板) |
### 我的
| 功能名称 | 需求效果 |
| ---- | ---- |
| 头像昵称 | - |
| 消息 | 系统通知、公告消息 |
| 设置 | 1. 点头像设置头像、昵称、性别、手机号、显示ID、注册时间2. 点设置:服务协议、隐私政策、版权信息、关于我们 |
| 会员 | 会员等级 |
| 我的钱包 | 余额充值 |
| 积分 | 我的积分 |
| 优惠券 | 1. 已使用、未使用、已过期2. 显示所有已领取优惠券(可指定发放用户) |
| 我的订单 | 在全部订单列表,订单支付:待支付,待发货,待收货,商品评价、申请售后(可部分退款) |
| 我的服务 | 我的活动:已报名活动,取消报名 |
| 分销推广 | 我的邀请人 |
| 门店登录 | 根据用户账号权限判断 |
---
## 二、后台管理端Web端
### 首页
| 功能名称 | 需求效果 |
| ---- | ---- |
| 统计 | 普通会员人数 |
| 趋势图 | 注册人数、会员人数趋势 |
### 会员管理
| 功能名称 | 需求效果 |
| ---- | ---- |
| 会员列表 | 普通会员:登录注册的用户信息,手机号、昵称、余额、积分等 |
| 会员权限 | VIP会员开通价格、会员时间、享受权限等 |
### 商城
| 功能名称 | 需求效果 |
| ---- | ---- |
| 商品管理 | 1. 商品添加、上下架、商品规格设置如服装码数颜色等商品参数2. 能实现线上导出导入数据,实现批量修改导入数据 |
| 商品设置 | 商品单独设置分销佣金 |
| 订单管理 | 商品订单:待支付,待发货,待收货,商品评价,退款/售后 |
| 商品品牌 | - |
| 商品类型 | 三级分类 |
### 比赛活动
| 功能名称 | 需求效果 |
| ---- | ---- |
| 活动列表 | 1. 添加比赛信息限制报名人数可设置热门2. 后台可自定义参赛人员需填信息的必填项3. 衣服尺码改为后台自由设置尺码规格4. 限制报名人数或者队伍数 |
| 预约报名 | 活动报名人员 |
### 预约管理
| 功能名称 | 需求效果 |
| ---- | ---- |
| 预约订单 | 用户预约穿线订单,分门店查询 |
### 营销
| 功能名称 | 需求效果 |
| ---- | ---- |
| 优惠券 | 1. 场地使用券、无门槛、满减券、折扣券2. 可设置优惠券指定发放给:会员或者非会员、单独某个账户 |
| 使用记录 | 优惠券使用记录,核销门店等信息 |
| 积分抵扣 | 积分抵扣设置 |
| 积分商城 | 使用积分兑换商品 |
| 轮播图 | 广告图替换 |
| 系统公告 | 发布系统公告通知 |
### 系统管理
| 功能名称 | 需求效果 |
| ---- | ---- |
| 用户管理 | 添加系统用户账户 |
| 角色管理 | 用户角色权限设置 |
| 门店管理 | 添加门店信息、账号、密码 |
### 财务
| 功能名称 | 需求效果 |
| ---- | ---- |
| 账务概况 | 订单、会员、分销分别进行数据统计,可按日期查询,可导出表格 |
| 充值记录 | 查询所有充值记录,可按账号、日期查询,可导出表格 |
| 资金记录 | 查询所有账号资金账单,可按账号、日期查询,可导出表格 |
| 积分记录 | 查询所有账号积分情况,可按账号、日期查询,可导出表格 |
| 佣金提现 | 审核所有佣金提现 |
| 佣金记录 | 所有佣金账单,可按账号、日期查询,可导出表格 |

View File

@@ -0,0 +1,193 @@
# 数据库设计对比分析报告
## 📋 分析时间
2026-05-08
## 📊 对比结果概览
| 项目 | 原始版本 | 新版本(v2) | 变化 |
|------|---------|-----------|------|
| 表数量 | 32张 | 36张 | +4张 |
| 新增表 | - | 4张 | ✅ |
| 调整字段 | 3处 | - | ✅ |
---
## ✅ 已匹配的功能模块32项
| 功能模块 | 对应表 | 状态 |
|---------|--------|------|
| 用户登录注册 | `app_user` | ✅ 完整 |
| 会员等级管理 | `app_member_level` | ✅ 完整 |
| 会员购买记录 | `app_user_member_order` | ✅ 完整 |
| 商品分类(三级) | `app_product_category` | ✅ 完整 |
| 商品品牌 | `app_product_brand` | ✅ 完整 |
| 商品规格 | `app_product_spec` + `app_product_spec_value` | ✅ 完整 |
| 商品主表 | `app_product` | ✅ 完整 |
| 商品SKU | `app_product_sku` | ✅ 完整 |
| 购物车 | `app_cart` | ✅ 完整 |
| 订单 | `app_order` | ✅ 完整 |
| 订单商品 | `app_order_item` | ✅ 完整 |
| 退款/售后 | `app_order_refund` | ✅ 完整 |
| 积分记录 | `app_points_record` | ✅ 完整 |
| 签到记录 | `app_signin_record` | ✅ 完整 |
| 优惠券 | `app_coupon` | ✅ 完整 |
| 用户优惠券 | `app_user_coupon` | ✅ 完整 |
| 积分兑换商品 | `app_points_product` | ✅ 完整 |
| 积分兑换订单 | `app_points_order` | ✅ 完整 |
| 分销关系 | `app_distribution_relation` | ✅ 完整 |
| 佣金记录 | `app_commission_record` | ✅ 完整 |
| 佣金提现 | `app_commission_withdraw` | ✅ 完整 |
| 活动 | `app_activity` | ✅ 完整 |
| 活动报名 | `app_activity_signup` | ✅ 完整 |
| 门店 | `app_store` | ✅ 完整 |
| 穿线预约 | `app_stringing_appointment` | ✅ 完整 |
| 轮播图 | `app_banner` | ✅ 完整 |
| 文章/公告 | `app_article` | ✅ 完整 |
| 后台管理员 | `app_admin_user` | ✅ 完整 |
| 角色权限 | `app_role` | ✅ 完整 |
| 余额充值 | `app_recharge_record` | ✅ 完整 |
| 资金流水 | `app_fund_flow` | ✅ 完整 |
| 积分配置 | `app_points_config` | ✅ 完整 |
| 签到配置 | `app_signin_config` | ✅ 完整 |
| 系统配置 | `app_system_config` | ✅ 完整 |
| 消息通知 | `app_notification` | ✅ 完整 |
| 文件上传 | `app_file_upload` | ✅ 完整 |
| 用户地址 | `app_user_address` | ✅ 完整 |
| 订单支付 | `app_order_payment` | ✅ 完整 |
---
## 🆕 新增表4张
### 1. 会员推广下级记录表
**表名**: `app_member_subordinate`
**用途**: 会员录入下级手机号记录(核心业务流程:会员开通与推广下级流程)
**关键字段**:
- `member_id` - 会员ID(上级)
- `sub_phone` - 下级手机号
- `sub_user_id` - 下级用户ID(注册后填充)
- `member_fee` - 收取的下级会员费(298元)
- `fee_paid` - 会员费是否已收
- `register_status` - 注册状态
### 2. 优惠券指定用户表
**表名**: `app_coupon_specified_user`
**用途**: 后台定向发放优惠券给指定用户
**关键字段**:
- `coupon_id` - 优惠券ID
- `user_id` - 指定用户ID
- `status` - 发送状态
### 3. 协议内容表
**表名**: `app_agreement`
**用途**: 隐私政策、服务协议等协议内容管理
**关键字段**:
- `agreement_key` - 协议标识(privacy/service/copyright/about)
- `agreement_name` - 协议名称
- `title` - 前端显示标题
- `content` - 协议正文
- `version` - 版本号
- `is_required` - 是否必读
### 4. (订单表新增字段)
**表名**: `app_order`
**新增字段**:
- `aftersale_end_time` - 售后期结束时间(支持佣金结算)
---
## 🔧 调整字段
### 1. 用户表 `app_user`
| 字段名 | 调整内容 | 原因 |
|-------|---------|------|
| 新增 `display_id` | 用户专属推广码 | 分销推广功能需要显示ID |
### 2. 会员等级表 `app_member_level`
| 字段名 | 调整内容 | 原因 |
|-------|---------|------|
| 新增 `member_fee` | 下级会员费 | 会员收取下级的298元费用 |
### 3. 活动表 `app_activity`
| 字段名 | 调整内容 | 原因 |
|-------|---------|------|
| 新增 `max_teams` | 最大报名队伍数 | 支持限制队伍数 |
### 4. 用户优惠券表 `app_user_coupon`
| 字段名 | 调整内容 | 原因 |
|-------|---------|------|
| 新增 `receive_type` | 领取方式 | 区分主动领取/后台发放/活动奖励 |
### 5. 佣金提现表 `app_commission_withdraw`
| 字段名 | 调整内容 | 原因 |
|-------|---------|------|
| 调整 `single_limit` 注释 | 单次提现限额(最低) | 200元起提 |
---
## 📌 与开发计划的完整映射
### 小程序端功能 → 数据库表
| 功能 | 数据库表 | 备注 |
|-----|---------|------|
| 手机短信登录 | `app_user.phone` | ✅ |
| 微信授权登录 | `app_user.openid/unionid` | ✅ |
| 隐私政策协议 | `app_agreement` | 🆕新增 |
| LOGO/名称 | `app_system_config` | ✅ |
| 搜索商品 | `app_product` | ✅ |
| 消息通知 | `app_notification` | ✅ |
| 轮播图 | `app_banner` | ✅ |
| 活动报名 | `app_activity` + `app_activity_signup` | ✅ |
| 穿线预约 | `app_store` + `app_stringing_appointment` | ✅ |
| 积分商城 | `app_points_product` | ✅ |
| 积分签到 | `app_signin_record` | ✅ |
| 会员升级 | `app_member_level` + `app_user_member_order` | ✅ |
| 分佣查看 | `app_commission_record` | ✅ |
| 佣金提现 | `app_commission_withdraw` | ✅ |
| 商城/分类 | `app_product` + `app_product_category` | ✅ |
| 购物车 | `app_cart` | ✅ |
| 我的订单 | `app_order` + `app_order_item` | ✅ |
| 优惠券 | `app_user_coupon` | ✅ |
### 后台管理端功能 → 数据库表
| 功能 | 数据库表 | 备注 |
|-----|---------|------|
| 会员列表 | `app_user` + `app_member_level` | ✅ |
| 商品管理 | `app_product` | ✅ |
| 商品规格 | `app_product_spec` | ✅ |
| 批量导入导出 | 业务逻辑层实现 | ✅ |
| 订单管理 | `app_order` | ✅ |
| 退款/售后 | `app_order_refund` | ✅ |
| 优惠券管理 | `app_coupon` | ✅ |
| 定向发券 | `app_coupon_specified_user` | 🆕新增 |
| 积分抵扣设置 | `app_points_config` | ✅ |
| 积分商城管理 | `app_points_product` | ✅ |
| 轮播图管理 | `app_banner` | ✅ |
| 系统公告 | `app_article` | ✅ |
| 用户管理 | `app_admin_user` | ✅ |
| 角色权限 | `app_role` | ✅ |
| 门店管理 | `app_store` | ✅ |
| 账务概况 | `app_fund_flow` + `app_recharge_record` | ✅ |
| 佣金提现审核 | `app_commission_withdraw` | ✅ |
| 佣金记录 | `app_commission_record` | ✅ |
| 积分记录 | `app_points_record` | ✅ |
---
## ✅ 结论
**数据库设计与开发计划完全匹配!**
- 新版本 SQL 文件:`/Users/gxwebsoft/JAVA/paopao-java/docs/db_paopao_v2.sql`
- 包含 36 张数据表,覆盖所有功能需求
- 支持 9 大核心业务流程
- 包含初始化数据(默认协议、系统配置)
### 建表执行命令
```bash
# 连接数据库执行
mysql -h 47.119.165.234 -P 13308 -u root -p db_paopao < db_paopao_v2.sql
```

View File

@@ -0,0 +1,93 @@
# 核心业务流程整理
## 一、用户注册与登录流程
1. 用户进入小程序,选择**短信验证码登录/注册**或**微信授权登录**
2. 阅读并同意**隐私政策/服务协议**
3. 验证通过→进入小程序首页
4. 需修改密码→通过短信验证码重置
5. 会员身份校验→手机号匹配后激活会员权限
---
## 二、会员开通与推广下级流程
1. 用户购买会员→支付**198元注册费**
2. 会员获得推广权限,线下收取下级**298元会员费**
3. 会员在平台为下级录入手机号(录错需平台客服修改)
4. 下级用该手机号登录→系统自动匹配身份→成为该会员的下级
5. 会员保持身份有效→持续获得下级消费分佣
---
## 三、商品购物与支付流程
1. 首页/商城搜索/分类查找商品
2. 查看商品详情(主图/视频/规格)→加入购物车
3. 购物车勾选商品→统一结算(同一订单仅收一笔运费)
4. 选择支付方式→完成支付→生成待发货订单
5. 商家发货→用户收货→订单完成→进入售后期
6. 售后期结束→货款到账→佣金自动结算到上级账户
---
## 四、积分商城与积分使用流程
1. 用户每日签到获得固定积分
2. 进入积分商城→查看总积分与可兑商品/优惠券
3. 积分充足→直接兑换;积分不足→积分+补差价兑换
4. 提交兑换订单→平台审核/配送→查看兑换进度
---
## 五、佣金结算与提现流程
1. 下级完成订单并过售后期→佣金结算至可提现佣金
2. 会员进入会员中心→查看可提现金额
3. 发起提现→提现至绑定微信(遵守**200元起/单日上限2000元**规则)
4. 后台审核提现申请→审核通过→资金到账用户微信
---
## 六、活动报名流程
1. 首页查看活动列表(名称、时间、人数限制)
2. 选择活动→填写后台设置的必填信息(尺码等)
3. 提交报名→报名成功→可在“我的服务”查看/取消报名
---
## 七、穿线预约流程
1. 进入穿线预约→查看门店列表(名称、地址、距离、营业时间)
2. 支持按门店名称搜索
3. 点击地址→跳转地图;点击电话→直接拨号
4. 选择门店→提交预约→生成预约订单
5. 后台按门店查询预约订单→门店核销服务
---
## 八、优惠券领取与使用流程
1. 首页进入领券中心领取优惠券(满减/无门槛/折扣)
2. 我的优惠券→查看未使用/已使用/已过期
3. 下单时自动匹配可用券→抵扣金额
4. 后台可定向发券给会员/非会员/指定用户→记录使用与核销信息
---
## 九、后台管理核心流程
### 1. 商品与订单管理
- 商品添加/上下架/设置规格/佣金→批量导入导出数据
- 订单状态处理(待支付/待发货/待收货/售后退款)
### 2. 会员与分销管理
- 查看普通会员/VIP会员信息→设置会员价格与权限
- 统计会员发展下级关系→跟踪下级消费与上级佣金
### 3. 活动与预约管理
- 创建比赛活动→设置报名人数/必填项/尺码规格
- 查看活动报名名单→处理穿线预约订单
### 4. 营销与内容配置
- 配置轮播图/首页名称/LOGO→发布系统公告
- 设置积分抵扣/优惠券规则→积分商城商品上下架
### 5. 财务与数据统计
- 按日期统计订单/会员/分销数据→导出表格
- 审核佣金提现→查询充值/资金/积分/佣金记录
### 6. 系统权限管理
- 添加后台用户→设置角色权限
- 管理门店信息与账号→分配门店操作权限

310
mvnw vendored Normal file
View File

@@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

224
pom.xml Normal file
View File

@@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.paopao</groupId>
<artifactId>paopao-api</artifactId>
<version>1.0.0</version>
<name>paopao-api</name>
<description>Paopao Java API project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring-boot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- spring-boot-configuration-processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.3</version>
</dependency>
<!-- mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.7.11</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>5.7.11</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.7.11</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.7.11</version>
</dependency>
<!-- easy poi -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.4.0</version>
</dependency>
<!-- tika, 用于FileServer获取content-type -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.1.0</version>
</dependency>
<!-- open office, 用于文档转pdf实现在线预览 -->
<dependency>
<groupId>com.github.livesense</groupId>
<artifactId>jodconverter-core</artifactId>
<version>1.0.5</version>
</dependency>
<!-- spring-boot-mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 模板引擎, 用于邮件、代码生成等 -->
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>3.6.1.RELEASE</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
<!-- 图形验证码 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*Mapper.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
<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>
</plugins>
</build>
<repositories>
<repository>
<id>aliYunMaven</id>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,26 @@
package com.gxwebsoft;
import com.eleadmin.common.core.config.ConfigProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 启动类
* Created by EleAdmin on 2018-02-22 11:29:03
*/
@EnableAsync
@EnableTransactionManagement
@MapperScan("com.gxwebsoft.**.mapper")
@EnableConfigurationProperties(ConfigProperties.class)
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

View File

@@ -0,0 +1,93 @@
package com.eleadmin.common.core;
/**
* 系统常量
* Created by EleAdmin on 2019-10-29 15:55
*/
public class Constants {
/**
* 默认成功码
*/
public static final int RESULT_OK_CODE = 0;
/**
* 默认失败码
*/
public static final int RESULT_ERROR_CODE = 1;
/**
* 默认成功信息
*/
public static final String RESULT_OK_MSG = "操作成功";
/**
* 默认失败信息
*/
public static final String RESULT_ERROR_MSG = "操作失败";
/**
* 无权限错误码
*/
public static final int UNAUTHORIZED_CODE = 403;
/**
* 无权限提示信息
*/
public static final String UNAUTHORIZED_MSG = "没有访问权限";
/**
* 未认证错误码
*/
public static final int UNAUTHENTICATED_CODE = 401;
/**
* 未认证提示信息
*/
public static final String UNAUTHENTICATED_MSG = "请先登录";
/**
* 登录过期错误码
*/
public static final int TOKEN_EXPIRED_CODE = 401;
/**
* 登录过期提示信息
*/
public static final String TOKEN_EXPIRED_MSG = "登录已过期";
/**
* 非法token错误码
*/
public static final int BAD_CREDENTIALS_CODE = 401;
/**
* 非法token提示信息
*/
public static final String BAD_CREDENTIALS_MSG = "请退出重新登录";
/**
* 表示升序的值
*/
public static final String ORDER_ASC_VALUE = "asc";
/**
* 表示降序的值
*/
public static final String ORDER_DESC_VALUE = "desc";
/**
* token通过header传递的名称
*/
public static final String TOKEN_HEADER_NAME = "Authorization";
/**
* token通过参数传递的名称
*/
public static final String TOKEN_PARAM_NAME = "access_token";
/**
* token认证类型
*/
public static final String TOKEN_TYPE = "Bearer";
}

View File

@@ -0,0 +1,41 @@
package com.eleadmin.common.core.annotation;
import java.lang.annotation.*;
/**
* 操作日志记录注解
*
* @author EleAdmin
* @since 2020-03-21 17:03:08
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
/**
* 操作功能
*/
String value() default "";
/**
* 操作模块
*/
String module() default "";
/**
* 备注
*/
String comments() default "";
/**
* 是否记录请求参数
*/
boolean param() default true;
/**
* 是否记录返回结果
*/
boolean result() default true;
}

View File

@@ -0,0 +1,21 @@
package com.eleadmin.common.core.annotation;
import java.lang.annotation.*;
/**
* 操作日志模块注解
*
* @author EleAdmin
* @since 2021-09-01 20:48:16
*/
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationModule {
/**
* 模块名称
*/
String value();
}

View File

@@ -0,0 +1,22 @@
package com.eleadmin.common.core.annotation;
import java.lang.annotation.*;
/**
* 查询条件注解
*
* @author EleAdmin
* @since 2021-09-01 20:48:16
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface QueryField {
// 字段名称
String value() default "";
// 查询方式
QueryType type() default QueryType.LIKE;
}

View File

@@ -0,0 +1,42 @@
package com.eleadmin.common.core.annotation;
/**
* 查询方式
*
* @author EleAdmin
* @since 2021-09-01 20:48:16
*/
public enum QueryType {
// 等于
EQ,
// 不等于
NE,
// 大于
GT,
// 大于等于
GE,
// 小于
LT,
// 小于等于
LE,
// 包含
LIKE,
// 不包含
NOT_LIKE,
// 结尾等于
LIKE_LEFT,
// 开头等于
LIKE_RIGHT,
// 为NULL
IS_NULL,
// 不为空
IS_NOT_NULL,
// IN
IN,
// NOT IN
NOT_IN,
// IN条件解析逗号分割
IN_STR,
// NOT IN条件解析逗号分割
NOT_IN_STR
}

View File

@@ -0,0 +1,210 @@
package com.eleadmin.common.core.aspect;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
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.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.annotation.OperationModule;
import com.eleadmin.common.core.utils.JSONUtil;
import com.eleadmin.common.system.entity.OperationRecord;
import com.eleadmin.common.system.entity.User;
import com.eleadmin.common.system.service.OperationRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 操作日志记录
*
* @author EleAdmin
* @since 2020-03-21 16:58:16:05
*/
@Aspect
@Component
public class OperationLogAspect {
@Resource
private OperationRecordService operationRecordService;
// 参数、返回结果、错误信息等最大保存长度
private static final int MAX_LENGTH = 1000;
// 用于记录请求耗时
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("@annotation(com.eleadmin.common.core.annotation.OperationLog)")
public void operationLog() {
}
@Before("operationLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "operationLog()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
saveLog(joinPoint, result, null);
}
@AfterThrowing(value = "operationLog()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
saveLog(joinPoint, null, e);
}
/**
* 保存操作记录
*/
private void saveLog(JoinPoint joinPoint, Object result, Exception e) {
OperationRecord record = new OperationRecord();
// 记录操作耗时
if (startTime.get() != null) {
record.setSpendTime(System.currentTimeMillis() - startTime.get());
}
// 记录当前登录用户id、租户id
User user = getLoginUser();
if (user != null) {
record.setUserId(user.getUserId());
record.setTenantId(user.getTenantId());
}
// 记录请求地址、请求方式、ip
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (attributes == null ? null : attributes.getRequest());
if (request != null) {
record.setUrl(request.getRequestURI());
record.setRequestMethod(request.getMethod());
UserAgent ua = UserAgentUtil.parse(ServletUtil.getHeaderIgnoreCase(request, "User-Agent"));
record.setOs(ua.getPlatform().toString());
record.setDevice(ua.getOs().toString());
record.setBrowser(ua.getBrowser().toString());
record.setIp(ServletUtil.getClientIP(request));
}
// 记录异常信息
if (e != null) {
record.setStatus(1);
record.setError(StrUtil.sub(e.toString(), 0, MAX_LENGTH));
}
// 记录模块名、操作功能、请求方法、请求参数、返回结果
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
record.setMethod(joinPoint.getTarget().getClass().getName() + "." + signature.getName());
Method method = signature.getMethod();
if (method != null) {
OperationLog ol = method.getAnnotation(OperationLog.class);
if (ol != null) {
// 记录操作功能
record.setDescription(getDescription(method, ol));
// 记录操作模块
record.setModule(getModule(joinPoint, ol));
// 记录备注
if (StrUtil.isNotEmpty(ol.comments())) {
record.setComments(ol.comments());
}
// 记录请求参数
if (ol.param() && request != null) {
record.setParams(StrUtil.sub(getParams(joinPoint, request), 0, MAX_LENGTH));
}
// 记录请求结果
if (ol.result() && result != null) {
record.setResult(StrUtil.sub(JSONUtil.toJSONString(result), 0, MAX_LENGTH));
}
}
}
operationRecordService.saveAsync(record);
}
/**
* 获取当前登录用户
*/
private User getLoginUser() {
Authentication subject = SecurityContextHolder.getContext().getAuthentication();
if (subject != null) {
Object object = subject.getPrincipal();
if (object instanceof User) {
return (User) object;
}
}
return null;
}
/**
* 获取请求参数
*
* @param joinPoint JoinPoint
* @param request HttpServletRequest
* @return String
*/
private String getParams(JoinPoint joinPoint, HttpServletRequest request) {
String params;
Map<String, String> paramsMap = ServletUtil.getParamMap(request);
if (paramsMap.keySet().size() > 0) {
params = JSONUtil.toJSONString(paramsMap);
} else {
StringBuilder sb = new StringBuilder();
for (Object arg : joinPoint.getArgs()) {
if (ObjectUtil.isNull(arg)
|| arg instanceof MultipartFile
|| arg instanceof HttpServletRequest
|| arg instanceof HttpServletResponse) {
continue;
}
sb.append(JSONUtil.toJSONString(arg)).append(" ");
}
params = sb.toString();
}
return params;
}
/**
* 获取操作模块
*
* @param joinPoint JoinPoint
* @param ol OperationLog
* @return String
*/
private String getModule(JoinPoint joinPoint, OperationLog ol) {
if (StrUtil.isNotEmpty(ol.module())) {
return ol.module();
}
OperationModule om = joinPoint.getTarget().getClass().getAnnotation(OperationModule.class);
if (om != null && StrUtil.isNotEmpty(om.value())) {
return om.value();
}
Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
if (api != null && api.tags() != null) {
return ArrayUtil.join(api.tags(), ",");
}
return null;
}
/**
* 获取操作功能
*
* @param method Method
* @param ol OperationLog
* @return String
*/
private String getDescription(Method method, OperationLog ol) {
if (StrUtil.isNotEmpty(ol.value())) {
return ol.value();
}
ApiOperation ao = method.getAnnotation(ApiOperation.class);
if (ao != null && StrUtil.isNotEmpty(ao.value())) {
return ao.value();
}
return null;
}
}

View File

@@ -0,0 +1,75 @@
package com.eleadmin.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 系统配置属性
*
* @author EleAdmin
* @since 2021-08-30 17:58:16
*/
@Data
@ConfigurationProperties(prefix = "config")
public class ConfigProperties {
/**
* 文件上传磁盘位置
*/
private Integer uploadLocation = 0;
/**
* 文件上传是否使用uuid命名
*/
private Boolean uploadUuidName = true;
/**
* 文件上传生成缩略图的大小(kb)
*/
private Integer thumbnailSize = 60;
/**
* OpenOffice的安装目录
*/
private String openOfficeHome;
/**
* swagger扫描包
*/
private String swaggerBasePackage;
/**
* swagger文档标题
*/
private String swaggerTitle;
/**
* swagger文档描述
*/
private String swaggerDescription;
/**
* swagger文档版本号
*/
private String swaggerVersion;
/**
* swagger地址
*/
private String swaggerHost;
/**
* token过期时间, 单位秒
*/
private Long tokenExpireTime = 60 * 60 * 24L;
/**
* token快要过期自动刷新时间, 单位分钟
*/
private int tokenRefreshTime = 30;
/**
* 生成token的密钥Key的base64字符
*/
private String tokenKey;
}

View File

@@ -0,0 +1,77 @@
package com.eleadmin.common.core.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.eleadmin.common.system.entity.User;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Arrays;
/**
* MybatisPlus配置
*
* @author EleAdmin
* @since 2018-02-22 11:29:28
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件配置
TenantLineHandler tenantLineHandler = new TenantLineHandler() {
@Override
public Expression getTenantId() {
return getLoginUserTenantId();
}
@Override
public boolean ignoreTable(String tableName) {
return Arrays.asList(
"sys_tenant",
"sys_dictionary",
"sys_dictionary_data"
).contains(tableName);
}
};
TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(tenantLineHandler);
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
// 分页插件配置
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
/**
* 获取当前登录用户的租户id
*
* @return Integer
*/
public Expression getLoginUserTenantId() {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object object = authentication.getPrincipal();
if (object instanceof User) {
return new LongValue(((User) object).getTenantId());
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return new NullValue();
}
}

View File

@@ -0,0 +1,72 @@
package com.eleadmin.common.core.config;
import cn.hutool.core.util.StrUtil;
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.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* Swagger配置
*
* @author EleAdmin
* @since 2018-02-22 11:29:05
*/
@EnableOpenApi
@Configuration
public class SwaggerConfig {
@Resource
private ConfigProperties config;
@Bean
public Docket createRestApi() {
Docket docket = new Docket(DocumentationType.OAS_30);
if (StrUtil.isNotBlank(config.getSwaggerHost())) {
docket.host(config.getSwaggerHost());
}
return docket
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(config.getSwaggerBasePackage()))
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(config.getSwaggerTitle())
.description(config.getSwaggerDescription())
.version(config.getSwaggerVersion())
.termsOfServiceUrl("")
.build();
}
private List<SecurityScheme> securitySchemes() {
return Collections.singletonList(
new ApiKey("Authorization", "Authorization", "header")
);
}
private List<SecurityContext> securityContexts() {
AuthorizationScope[] scopes = {new AuthorizationScope("global", "accessEverything")};
List<SecurityReference> references = Collections.singletonList(
new SecurityReference("Authorization", scopes)
);
return Collections.singletonList(SecurityContext.builder()
.securityReferences(references)
.build());
}
}

View File

@@ -0,0 +1,31 @@
package com.eleadmin.common.core.config;
import com.eleadmin.common.core.Constants;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvc配置, 拦截器、资源映射等都在此配置
*
* @author EleAdmin
* @since 2019-06-12 10:11:16
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 支持跨域访问
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedHeaders("*")
.exposedHeaders(Constants.TOKEN_HEADER_NAME)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
.allowCredentials(true)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,48 @@
package com.eleadmin.common.core.exception;
import com.eleadmin.common.core.Constants;
/**
* 自定义业务异常
*
* @author EleAdmin
* @since 2018-02-22 11:29:28
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
public BusinessException() {
this(Constants.RESULT_ERROR_MSG);
}
public BusinessException(String message) {
this(Constants.RESULT_ERROR_CODE, message);
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}

View File

@@ -0,0 +1,56 @@
package com.eleadmin.common.core.exception;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.core.web.ApiResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常处理器
*
* @author EleAdmin
* @since 2018-02-22 11:29:30
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
@ResponseBody
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResult<?> methodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e,
HttpServletResponse response) {
CommonUtil.addCrossHeaders(response);
return new ApiResult<>(Constants.RESULT_ERROR_CODE, "请求方式不正确").setError(e.toString());
}
@ResponseBody
@ExceptionHandler(AccessDeniedException.class)
public ApiResult<?> accessDeniedExceptionHandler(AccessDeniedException e, HttpServletResponse response) {
CommonUtil.addCrossHeaders(response);
return new ApiResult<>(Constants.UNAUTHORIZED_CODE, Constants.UNAUTHORIZED_MSG).setError(e.toString());
}
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ApiResult<?> businessExceptionHandler(BusinessException e, HttpServletResponse response) {
CommonUtil.addCrossHeaders(response);
return new ApiResult<>(e.getCode(), e.getMessage());
}
@ResponseBody
@ExceptionHandler(Throwable.class)
public ApiResult<?> exceptionHandler(Throwable e, HttpServletResponse response) {
logger.error(e.getMessage(), e);
CommonUtil.addCrossHeaders(response);
return new ApiResult<>(Constants.RESULT_ERROR_CODE, Constants.RESULT_ERROR_MSG).setError(e.toString());
}
}

View File

@@ -0,0 +1,29 @@
package com.eleadmin.common.core.security;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.utils.CommonUtil;
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;
/**
* 没有访问权限异常处理
*
* @author EleAdmin
* @since 2020-03-25 00:35:03
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
throws IOException, ServletException {
CommonUtil.responseError(response, Constants.UNAUTHORIZED_CODE, Constants.UNAUTHORIZED_MSG, e.getMessage());
}
}

View File

@@ -0,0 +1,30 @@
package com.eleadmin.common.core.security;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.utils.CommonUtil;
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;
/**
* 没有登录异常处理
*
* @author EleAdmin
* @since 2020-03-25 00:35:03
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException, ServletException {
CommonUtil.responseError(response, Constants.UNAUTHENTICATED_CODE, Constants.UNAUTHENTICATED_MSG,
e.getMessage());
}
}

View File

@@ -0,0 +1,85 @@
package com.eleadmin.common.core.security;
import cn.hutool.core.util.StrUtil;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.config.ConfigProperties;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.system.entity.LoginRecord;
import com.eleadmin.common.system.entity.Menu;
import com.eleadmin.common.system.entity.User;
import com.eleadmin.common.system.service.LoginRecordService;
import com.eleadmin.common.system.service.UserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
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;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 处理携带token的请求过滤器
*
* @author EleAdmin
* @since 2020-03-30 20:48:05
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
private ConfigProperties configProperties;
@Resource
private UserService userService;
@Resource
private LoginRecordService loginRecordService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String access_token = JwtUtil.getAccessToken(request);
if (StrUtil.isNotBlank(access_token)) {
try {
// 解析token
Claims claims = JwtUtil.parseToken(access_token, configProperties.getTokenKey());
JwtSubject jwtSubject = JwtUtil.getJwtSubject(claims);
User user = userService.getByUsername(jwtSubject.getUsername(), jwtSubject.getTenantId());
if (user == null) {
throw new UsernameNotFoundException("Username not found");
}
List<Menu> authorities = user.getAuthorities().stream()
.filter(m -> StrUtil.isNotBlank(m.getAuthority())).collect(Collectors.toList());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
user, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
// token将要过期签发新token, 防止突然退出登录
long expiration = (claims.getExpiration().getTime() - new Date().getTime()) / 1000 / 60;
if (expiration < configProperties.getTokenRefreshTime()) {
String token = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(),
configProperties.getTokenKey());
response.addHeader(Constants.TOKEN_HEADER_NAME, token);
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REFRESH, null,
user.getTenantId(), request);
}
} catch (ExpiredJwtException e) {
CommonUtil.responseError(response, Constants.TOKEN_EXPIRED_CODE, Constants.TOKEN_EXPIRED_MSG,
e.getMessage());
return;
} catch (Exception e) {
CommonUtil.responseError(response, Constants.BAD_CREDENTIALS_CODE, Constants.BAD_CREDENTIALS_MSG,
e.toString());
return;
}
}
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,31 @@
package com.eleadmin.common.core.security;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Jwt载体
*
* @author EleAdmin
* @since 2021-09-03 00:11:12
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtSubject implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 账号
*/
private String username;
/**
* 租户id
*/
private Integer tenantId;
}

View File

@@ -0,0 +1,141 @@
package com.eleadmin.common.core.security;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.utils.JSONUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
/**
* JWT工具类
*
* @author EleAdmin
* @since 2018-01-21 16:30:59
*/
public class JwtUtil {
/**
* 获取请求中的access_token
*
* @param request HttpServletRequest
* @return String
*/
public static String getAccessToken(HttpServletRequest request) {
String access_token = ServletUtil.getHeaderIgnoreCase(request, Constants.TOKEN_HEADER_NAME);
if (StrUtil.isNotBlank(access_token)) {
if (access_token.startsWith(Constants.TOKEN_TYPE)) {
access_token = StrUtil.removePrefix(access_token, Constants.TOKEN_TYPE).trim();
}
} else {
access_token = request.getParameter(Constants.TOKEN_PARAM_NAME);
}
return access_token;
}
/**
* 生成token
*
* @param subject 载体
* @param expire 过期时间
* @param base64EncodedKey base64编码的Key
* @return token
*/
public static String buildToken(JwtSubject subject, Long expire, String base64EncodedKey) {
return buildToken(JSONUtil.toJSONString(subject), expire, decodeKey(base64EncodedKey));
}
/**
* 生成token
*
* @param subject 载体
* @param expire 过期时间
* @param key 密钥
* @return token
*/
public static String buildToken(String subject, Long expire, Key key) {
Date expireDate = new Date(new Date().getTime() + 1000 * expire);
return Jwts.builder()
.setSubject(subject)
.setExpiration(expireDate)
.setIssuedAt(new Date())
.signWith(key)
.compact();
}
/**
* 解析token
*
* @param token token
* @param base64EncodedKey base64编码的Key
* @return Claims
*/
public static Claims parseToken(String token, String base64EncodedKey) {
return parseToken(token, decodeKey(base64EncodedKey));
}
/**
* 解析token
*
* @param token token
* @param key 密钥
* @return Claims
*/
public static Claims parseToken(String token, Key key) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 获取JwtSubject
*
* @param claims Claims
* @return JwtSubject
*/
public static JwtSubject getJwtSubject(Claims claims) {
return JSONUtil.parseObject(claims.getSubject(), JwtSubject.class);
}
/**
* 生成Key
*
* @return Key
*/
public static Key randomKey() {
return Keys.secretKeyFor(SignatureAlgorithm.HS256);
}
/**
* base64编码key
*
* @return String
*/
public static String encodeKey(Key key) {
return Encoders.BASE64.encode(key.getEncoded());
}
/**
* base64编码Key
*
* @param base64EncodedKey base64编码的key
* @return Key
*/
public static Key decodeKey(String base64EncodedKey) {
if (StrUtil.isBlank(base64EncodedKey)) {
return null;
}
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64EncodedKey));
}
}

View File

@@ -0,0 +1,79 @@
package com.eleadmin.common.core.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
/**
* Spring Security配置
*
* @author EleAdmin
* @since 2020-03-23 18:04:52
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Resource
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Resource
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/file/**", "/api/captcha", "/")
.permitAll()
.antMatchers(
"/api/login",
"/druid/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/v2/api-docs",
"/v3/api-docs",
"/swagger-ui/**"
)
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable()
.cors()
.and()
.logout()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.exceptionHandling()
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,174 @@
package com.eleadmin.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.web.ApiResult;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 常用工具方法
*
* @author EleAdmin
* @since 2017-06-10 10:10:22
*/
public class CommonUtil {
// 生成uuid的字符
private static final String[] chars = new String[]{
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
};
/**
* 生成8位uuid
*
* @return String
*/
public static String randomUUID8() {
StringBuilder sb = new StringBuilder();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
sb.append(chars[x % 0x3E]);
}
return sb.toString();
}
/**
* 生成16位uuid
*
* @return String
*/
public static String randomUUID16() {
StringBuilder sb = new StringBuilder();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 16; i++) {
String str = uuid.substring(i * 2, i * 2 + 2);
int x = Integer.parseInt(str, 16);
sb.append(chars[x % 0x3E]);
}
return sb.toString();
}
/**
* 检查List是否有重复元素
*
* @param list List
* @param mapper 获取需要检查的字段的Function
* @param <T> 数据的类型
* @param <R> 需要检查的字段的类型
* @return boolean
*/
public static <T, R> boolean checkRepeat(List<T> list, Function<? super T, ? extends R> mapper) {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size(); j++) {
if (i != j && mapper.apply(list.get(i)).equals(mapper.apply(list.get(j)))) {
return true;
}
}
}
return false;
}
/**
* List转为树形结构
*
* @param data List
* @param parentId 顶级的parentId
* @param parentIdMapper 获取parentId的Function
* @param idMapper 获取id的Function
* @param consumer 赋值children的Consumer
* @param <T> 数据的类型
* @param <R> parentId的类型
* @return List<T>
*/
public static <T, R> List<T> toTreeData(List<T> data, R parentId,
Function<? super T, ? extends R> parentIdMapper,
Function<? super T, ? extends R> idMapper,
BiConsumer<T, List<T>> consumer) {
List<T> result = new ArrayList<>();
for (T d : data) {
R dParentId = parentIdMapper.apply(d);
if (ObjectUtil.equals(parentId, dParentId)) {
R dId = idMapper.apply(d);
List<T> children = toTreeData(data, dId, parentIdMapper, idMapper, consumer);
consumer.accept(d, children);
result.add(d);
}
}
return result;
}
/**
* 遍历树形结构数据
*
* @param data List
* @param consumer 回调
* @param mapper 获取children的Function
* @param <T> 数据的类型
*/
public static <T> void eachTreeData(List<T> data, Consumer<T> consumer, Function<T, List<T>> mapper) {
for (T d : data) {
consumer.accept(d);
List<T> children = mapper.apply(d);
if (children != null && children.size() > 0) {
eachTreeData(children, consumer, mapper);
}
}
}
/**
* 获取集合中的第一条数据
*
* @param records 集合
* @return 第一条数据
*/
public static <T> T listGetOne(List<T> records) {
return records == null || records.size() == 0 ? null : records.get(0);
}
/**
* 支持跨域
*
* @param response HttpServletResponse
*/
public static void addCrossHeaders(HttpServletResponse response) {
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Expose-Headers", Constants.TOKEN_HEADER_NAME);
}
/**
* 输出错误信息
*
* @param response HttpServletResponse
* @param code 错误码
* @param message 提示信息
* @param error 错误信息
*/
public static void responseError(HttpServletResponse response, Integer code, String message, String error) {
response.setContentType("application/json;charset=UTF-8");
try {
PrintWriter out = response.getWriter();
out.write(JSONUtil.toJSONString(new ApiResult<>(code, message, null, error)));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,394 @@
package com.eleadmin.common.core.utils;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.tika.Tika;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 文件上传下载工具类
*
* @author EleAdmin
* @since 2018-12-14 08:38:53
*/
public class FileServerUtil {
// 除 text/* 外也需要设置输出编码的 content-type
private final static List<String> SET_CHARSET_CONTENT_TYPES = Arrays.asList(
"application/json",
"application/javascript"
);
/**
* 上传文件
*
* @param file MultipartFile
* @param directory 文件保存的目录
* @param uuidName 是否用uuid命名
* @return File
*/
public static File upload(MultipartFile file, String directory, boolean uuidName)
throws IOException, IllegalStateException {
File outFile = getUploadFile(file.getOriginalFilename(), directory, uuidName);
if (!outFile.getParentFile().exists()) {
if (!outFile.getParentFile().mkdirs()) {
throw new RuntimeException("make directory fail");
}
}
file.transferTo(outFile);
return outFile;
}
/**
* 上传base64格式文件
*
* @param base64 base64编码字符
* @param fileName 文件名称, 为空使用uuid命名
* @param directory 文件保存的目录
* @return File
*/
public static File upload(String base64, String fileName, String directory)
throws FileNotFoundException, IORuntimeException {
if (StrUtil.isBlank(base64) || !base64.startsWith("data:image/") || !base64.contains(";base64,")) {
throw new RuntimeException("base64 data error");
}
String suffix = "." + base64.substring(11, base64.indexOf(";")); // 获取文件后缀
boolean uuidName = StrUtil.isBlank(fileName);
File outFile = getUploadFile(uuidName ? suffix : fileName, directory, uuidName);
byte[] bytes = Base64.getDecoder().decode(base64.substring(base64.indexOf(";") + 8).getBytes());
IoUtil.write(new FileOutputStream(outFile), true, bytes);
return outFile;
}
/**
* 获取上传文件位置
*
* @param name 文件名称
* @param directory 上传目录
* @param uuidName 是否使用uuid命名
* @return File
*/
public static File getUploadFile(String name, String directory, boolean uuidName) {
// 当前日期作为上传子目录
String dir = new SimpleDateFormat("yyyyMMdd/").format(new Date());
// 获取文件后缀
String suffix = (name == null || !name.contains(".")) ? "" : name.substring(name.lastIndexOf("."));
// 使用uuid命名
if (uuidName || name == null) {
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
return new File(directory, dir + uuid + suffix);
}
// 使用原名称, 存在相同则加(1)
File file = new File(directory, dir + name);
String prefix = StrUtil.removeSuffix(name, suffix);
int sameSize = 2;
while (file.exists()) {
file = new File(directory, dir + prefix + "(" + sameSize + ")" + suffix);
sameSize++;
}
return file;
}
/**
* 查看文件, 支持断点续传
*
* @param file 文件
* @param pdfDir office转pdf输出目录
* @param officeHome openOffice安装目录
* @param response HttpServletResponse
* @param request HttpServletRequest
*/
public static void preview(File file, String pdfDir, String officeHome,
HttpServletResponse response, HttpServletRequest request) {
preview(file, false, null, pdfDir, officeHome, response, request);
}
/**
* 查看文件, 支持断点续传
*
* @param file 文件
* @param forceDownload 是否强制下载
* @param fileName 强制下载的文件名称
* @param pdfDir office转pdf输出目录
* @param officeHome openOffice安装目录
* @param response HttpServletResponse
* @param request HttpServletRequest
*/
public static void preview(File file, boolean forceDownload, String fileName, String pdfDir, String officeHome,
HttpServletResponse response, HttpServletRequest request) {
CommonUtil.addCrossHeaders(response);
if (file == null || !file.exists()) {
outNotFund(response);
return;
}
if (forceDownload) {
setDownloadHeader(response, StrUtil.isBlank(fileName) ? file.getName() : fileName);
} else {
// office转pdf预览
if (OpenOfficeUtil.canConverter(file.getName())) {
File pdfFile = OpenOfficeUtil.converterToPDF(file.getAbsolutePath(), pdfDir, officeHome);
if (pdfFile != null) {
file = pdfFile;
}
}
// 获取文件类型
String contentType = getContentType(file);
if (contentType != null) {
response.setContentType(contentType);
// 设置编码
if (contentType.startsWith("text/") || SET_CHARSET_CONTENT_TYPES.contains(contentType)) {
try {
String charset = JChardetFacadeUtil.detectCodepage(file.toURI().toURL());
if (charset != null) {
response.setCharacterEncoding(charset);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
} else {
setDownloadHeader(response, file.getName());
}
}
response.setHeader("Cache-Control", "public");
output(file, response, request);
}
/**
* 查看缩略图
*
* @param file 原文件
* @param thumbnail 缩略图文件
* @param size 缩略图文件的最大值(kb)
* @param response HttpServletResponse
* @param request HttpServletRequest
*/
public static void previewThumbnail(File file, File thumbnail, Integer size,
HttpServletResponse response, HttpServletRequest request) {
// 如果是图片并且缩略图不存在则生成
if (!thumbnail.exists() && isImage(file)) {
long fileSize = file.length();
if ((fileSize / 1024) > size) {
try {
if (thumbnail.getParentFile().mkdirs()) {
ImgUtil.scale(file, thumbnail, size / (fileSize / 1024f));
if (thumbnail.exists() && thumbnail.length() > file.length()) {
FileUtil.copy(file, thumbnail, true);
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
preview(file, null, null, response, request);
return;
}
}
preview(thumbnail.exists() ? thumbnail : file, null, null, response, request);
}
/**
* 输出文件流, 支持断点续传
*
* @param file 文件
* @param response HttpServletResponse
* @param request HttpServletRequest
*/
public static void output(File file, HttpServletResponse response, HttpServletRequest request) {
long length = file.length(); // 文件总大小
long start = 0, to = length - 1; // 开始读取位置, 结束读取位置
long lastModified = file.lastModified(); // 文件修改时间
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("ETag", "\"" + length + "-" + lastModified + "\"");
response.setHeader("Last-Modified", new Date(lastModified).toString());
String range = request.getHeader("Range");
if (range != null) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String[] ranges = range.replace("bytes=", "").split("-");
start = Long.parseLong(ranges[0].trim());
if (ranges.length > 1) {
to = Long.parseLong(ranges[1].trim());
}
response.setHeader("Content-Range", "bytes " + start + "-" + to + "/" + length);
}
response.setHeader("Content-Length", String.valueOf(to - start + 1));
try {
output(file, response.getOutputStream(), 2048, start, to);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 输出文件流
*
* @param file 文件
* @param os 输出流
*/
public static void output(File file, OutputStream os) {
output(file, os, null);
}
/**
* 输出文件流
*
* @param file 文件
* @param os 输出流
* @param size 读取缓冲区大小
*/
public static void output(File file, OutputStream os, Integer size) {
output(file, os, size, null, null);
}
/**
* 输出文件流, 支持分片
*
* @param file 文件
* @param os 输出流
* @param size 读取缓冲区大小
* @param start 开始位置
* @param to 结束位置
*/
public static void output(File file, OutputStream os, Integer size, Long start, Long to) {
BufferedInputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
if (start != null) {
long skip = is.skip(start);
if (skip < start) {
System.out.println("ERROR: skip fail[ skipped=" + skip + ", start= " + start + " ]");
}
to = to - start + 1;
}
byte[] bytes = new byte[size == null ? 2048 : size];
int len;
if (to == null) {
while ((len = is.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
} else {
while (to > 0 && (len = is.read(bytes)) != -1) {
os.write(bytes, 0, to < len ? (int) ((long) to) : len);
to -= len;
}
}
os.flush();
} catch (IOException ignored) {
} catch (Exception e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException ignored) {
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
/**
* 获取文件类型
*
* @param file 文件
* @return String
*/
public static String getContentType(File file) {
String contentType = null;
if (file.exists()) {
try {
contentType = new Tika().detect(file);
} catch (IOException e) {
e.printStackTrace();
}
}
return contentType;
}
/**
* 判断文件是否是图片类型
*
* @param file 文件
* @return boolean
*/
public static boolean isImage(File file) {
return isImage(getContentType(file));
}
/**
* 判断文件是否是图片类型
*
* @param contentType 文件类型
* @return boolean
*/
public static boolean isImage(String contentType) {
return contentType != null && contentType.startsWith("image/");
}
/**
* 设置下载文件的header
*
* @param response HttpServletResponse
* @param fileName 文件名称
*/
public static void setDownloadHeader(HttpServletResponse response, String fileName) {
response.setContentType("application/force-download");
try {
fileName = URLEncoder.encode(fileName, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
}
/**
* 输出404错误页面
*
* @param response HttpServletResponse
*/
public static void outNotFund(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
outMessage("404 Not Found", null, response);
}
/**
* 输出错误页面
*
* @param title 标题
* @param message 内容
* @param response HttpServletResponse
*/
public static void outMessage(String title, String message, HttpServletResponse response) {
response.setContentType("text/html;charset=UTF-8");
try {
PrintWriter writer = response.getWriter();
writer.write("<!doctype html>");
writer.write("<title>" + title + "</title>");
writer.write("<h1 style=\"text-align: center\">" + title + "</h1>");
if (message != null) {
writer.write(message);
}
writer.write("<hr/><p style=\"text-align: center\">EleAdmin File Server</p>");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
package com.eleadmin.common.core.utils;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
/**
* JSON解析工具类
*
* @author EleAdmin
* @since 2017-06-10 10:10:39
*/
public class JSONUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
/**
* 对象转json字符串
*
* @param value 对象
* @return String
*/
public static String toJSONString(Object value) {
return toJSONString(value, false);
}
/**
* 对象转json字符串
*
* @param value 对象
* @param pretty 是否格式化输出
* @return String
*/
public static String toJSONString(Object value, boolean pretty) {
if (value != null) {
if (value instanceof String) {
return (String) value;
}
try {
if (pretty) {
return objectWriter.writeValueAsString(value);
}
return objectMapper.writeValueAsString(value);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* json字符串转对象
*
* @param json String
* @param clazz Class
* @return T
*/
public static <T> T parseObject(String json, Class<T> clazz) {
if (StrUtil.isNotBlank(json) && clazz != null) {
try {
return objectMapper.readValue(json, clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}

View File

@@ -0,0 +1,124 @@
package com.eleadmin.common.core.utils;
import cn.hutool.core.util.StrUtil;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeManager;
import java.io.File;
import java.util.Arrays;
import java.util.Base64;
/**
* OpenOfficeUtil
*
* @author EleAdmin
* @since 2018-12-14 08:38:19
*/
public class OpenOfficeUtil {
// 支持转换pdf的文件后缀列表
private static final String[] CAN_CONVERTER_FILES = new String[]{
"doc", "docx", "xls", "xlsx", "ppt", "pptx"
};
/**
* 文件转pdf
*
* @param filePath 源文件路径
* @param outDir 输出目录
* @param officeHome OpenOffice安装路径
* @return File
*/
public static File converterToPDF(String filePath, String outDir, String officeHome) {
return converterToPDF(filePath, outDir, officeHome, true);
}
/**
* 文件转pdf
*
* @param filePath 源文件路径
* @param outDir 输出目录
* @param officeHome OpenOffice安装路径
* @param cache 是否使用上次转换过的文件
* @return File
*/
public static File converterToPDF(String filePath, String outDir, String officeHome, boolean cache) {
if (StrUtil.isBlank(filePath)) {
return null;
}
File srcFile = new File(filePath);
if (!srcFile.exists()) {
return null;
}
// 是否转换过
String outPath = Base64.getEncoder().encodeToString(filePath.getBytes())
.replace("/", "-").replace("+", "-");
File outFile = new File(outDir, outPath + ".pdf");
if (cache && outFile.exists()) {
return outFile;
}
// 转换
OfficeManager officeManager = null;
try {
officeManager = getOfficeManager(officeHome);
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
return converterFile(srcFile, outFile, converter);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (officeManager != null) {
officeManager.stop();
}
}
return null;
}
/**
* 转换文件
*
* @param inFile 源文件
* @param outFile 输出文件
* @param converter OfficeDocumentConverter
* @return File
*/
public static File converterFile(File inFile, File outFile, OfficeDocumentConverter converter) {
if (!outFile.getParentFile().exists()) {
if (!outFile.getParentFile().mkdirs()) {
return outFile;
}
}
converter.convert(inFile, outFile);
return outFile;
}
/**
* 判断文件后缀是否可以转换pdf
*
* @param path 文件路径
* @return boolean
*/
public static boolean canConverter(String path) {
try {
String suffix = path.substring(path.lastIndexOf(".") + 1);
return Arrays.asList(CAN_CONVERTER_FILES).contains(suffix);
} catch (Exception e) {
return false;
}
}
/**
* 连接并启动OpenOffice
*
* @param officeHome OpenOffice安装路径
* @return OfficeManager
*/
public static OfficeManager getOfficeManager(String officeHome) {
if (officeHome == null || officeHome.trim().isEmpty()) return null;
DefaultOfficeManagerConfiguration config = new DefaultOfficeManagerConfiguration();
config.setOfficeHome(officeHome); // 设置OpenOffice安装目录
OfficeManager officeManager = config.buildOfficeManager();
officeManager.start(); // 启动OpenOffice服务
return officeManager;
}
}

View File

@@ -0,0 +1,88 @@
package com.eleadmin.common.core.web;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* 返回结果
*
* @author EleAdmin
* @since 2017-06-10 10:10:50
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "状态码")
private Integer code;
@ApiModelProperty(value = "状态信息")
private String message;
@ApiModelProperty(value = "返回数据")
private T data;
@ApiModelProperty(value = "错误信息")
private String error;
public ApiResult() {
}
public ApiResult(Integer code) {
this(code, null);
}
public ApiResult(Integer code, String message) {
this(code, message, null);
}
public ApiResult(Integer code, String message, T data) {
this(code, message, data, null);
}
public ApiResult(Integer code, String message, T data, String error) {
setCode(code);
setMessage(message);
setData(data);
setError(error);
}
public Integer getCode() {
return this.code;
}
public ApiResult<T> setCode(Integer code) {
this.code = code;
return this;
}
public String getMessage() {
return this.message;
}
public ApiResult<T> setMessage(String message) {
this.message = message;
return this;
}
public T getData() {
return this.data;
}
public ApiResult<T> setData(T data) {
this.data = data;
return this;
}
public String getError() {
return this.error;
}
public ApiResult<T> setError(String error) {
this.error = error;
return this;
}
}

View File

@@ -0,0 +1,160 @@
package com.eleadmin.common.core.web;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.system.entity.User;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import java.util.List;
/**
* Controller基类
*
* @author EleAdmin
* @since 2017-06-10 10:10:19
*/
public class BaseController {
/**
* 获取当前登录的user
*
* @return User
*/
public User getLoginUser() {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object object = authentication.getPrincipal();
if (object instanceof User) {
return (User) object;
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 获取当前登录的userId
*
* @return userId
*/
public Integer getLoginUserId() {
User loginUser = getLoginUser();
return loginUser == null ? null : loginUser.getUserId();
}
/**
* 返回成功
*
* @return ApiResult
*/
public ApiResult<?> success() {
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG);
}
/**
* 返回成功
*
* @param message 状态信息
* @return ApiResult
*/
public ApiResult<?> success(String message) {
return success().setMessage(message);
}
/**
* 返回成功
*
* @param data 返回数据
* @return ApiResult
*/
public <T> ApiResult<T> success(T data) {
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, data);
}
/**
* 返回成功
*
* @param message 状态信息
* @return ApiResult
*/
public <T> ApiResult<T> success(String message, T data) {
return success(data).setMessage(message);
}
/**
* 返回分页查询数据
*
* @param list 当前页数据
* @param count 总数量
* @return ApiResult
*/
public <T> ApiResult<PageResult<T>> success(List<T> list, Long count) {
return success(new PageResult<>(list, count));
}
/**
* 返回分页查询数据
*
* @param iPage IPage
* @return ApiResult
*/
public <T> ApiResult<PageResult<T>> success(IPage<T> iPage) {
return success(iPage.getRecords(), iPage.getTotal());
}
/**
* 返回失败
*
* @return ApiResult
*/
public ApiResult<?> fail() {
return new ApiResult<>(Constants.RESULT_ERROR_CODE, Constants.RESULT_ERROR_MSG);
}
/**
* 返回失败
*
* @param message 状态信息
* @return ApiResult
*/
public ApiResult<?> fail(String message) {
return fail().setMessage(message);
}
/**
* 返回失败
*
* @param data 返回数据
* @return ApiResult
*/
public <T> ApiResult<T> fail(T data) {
return fail(Constants.RESULT_ERROR_MSG, data);
}
/**
* 返回失败
*
* @param message 状态信息
* @param data 返回数据
* @return ApiResult
*/
public <T> ApiResult<T> fail(String message, T data) {
return new ApiResult<>(Constants.RESULT_ERROR_CODE, message, data);
}
/**
* 请求参数的空字符串转为null
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}

View File

@@ -0,0 +1,59 @@
package com.eleadmin.common.core.web;
import com.baomidou.mybatisplus.annotation.TableField;
import com.eleadmin.common.core.annotation.QueryField;
import com.eleadmin.common.core.annotation.QueryType;
import com.eleadmin.common.core.utils.CommonUtil;
import lombok.Data;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
/**
* 查询参数基本字段
*
* @author EleAdmin
* @since 2021-08-26 22:14:43
*/
@Data
public class BaseParam implements Serializable {
private static final long serialVersionUID = 1L;
@TableField(exist = false)
@ApiModelProperty("分页查询页码")
private Long page;
@TableField(exist = false)
@ApiModelProperty("分页查询每页数量")
private Long limit;
@TableField(exist = false)
@ApiModelProperty(value = "排序字段", notes = "排序字段或sql, 如果是sql则order字段无用, 如: `id asc, name desc`")
private String sort;
@TableField(exist = false)
@ApiModelProperty(value = "排序方式", notes = "sort是字段名称时对应的排序方式, asc升序, desc降序")
private String order;
@QueryField(value = "create_time", type = QueryType.GE)
@TableField(exist = false)
@ApiModelProperty("创建时间起始值")
private String createTimeStart;
@QueryField(value = "create_time", type = QueryType.LE)
@TableField(exist = false)
@ApiModelProperty("创建时间结束值")
private String createTimeEnd;
/**
* 获取集合中的第一条数据
*
* @param records 集合
* @return 第一条数据
*/
public <T> T getOne(List<T> records) {
return CommonUtil.listGetOne(records);
}
}

View File

@@ -0,0 +1,57 @@
package com.eleadmin.common.core.web;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 批量修改通用参数
*
* @author EleAdmin
* @since 2020-03-13 00:11:06
*/
@Data
public class BatchParam<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "需要修改的数据id集合")
private List<Serializable> ids;
@ApiModelProperty(value = "需要修改的字段和值")
private T data;
/**
* 通用批量修改方法
*
* @param service IService
* @param idField id字段名称
* @return boolean
*/
public boolean update(IService<T> service, String idField) {
if (this.data == null) {
return false;
}
return service.update(this.data, new UpdateWrapper<T>().in(idField, this.ids));
}
/**
* 通用批量修改方法
*
* @param service IService
* @param idField id字段名称
* @return boolean
*/
public boolean update(IService<T> service, SFunction<T, ?> idField) {
if (this.data == null) {
return false;
}
return service.update(this.data, new LambdaUpdateWrapper<T>().in(idField, this.ids));
}
}

View File

@@ -0,0 +1,96 @@
package com.eleadmin.common.core.web;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 检查是否存在通用参数
*
* @author EleAdmin
* @since 2021-09-07 22:24:39
*/
@Data
public class ExistenceParam<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "检查的字段")
private String field;
@ApiModelProperty(value = "字段的值")
private String value;
@ApiModelProperty(value = "修改时的主键")
private Integer id;
/**
* 检查是否存在
*
* @param service IService
* @param idField 修改时的主键字段
* @return boolean
*/
public boolean isExistence(IService<T> service, String idField) {
return isExistence(service, idField, true);
}
/**
* 检查是否存在
*
* @param service IService
* @param idField 修改时的主键字段
* @param isToUnderlineCase 是否需要把field转为下划线格式
* @return boolean
*/
public boolean isExistence(IService<T> service, String idField, boolean isToUnderlineCase) {
if (StrUtil.hasBlank(this.field, this.value)) {
return false;
}
String fieldName = isToUnderlineCase ? StrUtil.toUnderlineCase(field) : field;
QueryWrapper<T> wrapper = new QueryWrapper<>();
wrapper.eq(fieldName, value);
if (id != null) {
wrapper.ne(idField, id);
}
return service.count(wrapper) > 0;
}
/**
* 检查是否存在
*
* @param service IService
* @param idField 修改时的主键字段
* @return boolean
*/
public boolean isExistence(IService<T> service, SFunction<T, ?> idField) {
return isExistence(service, idField, true);
}
/**
* 检查是否存在
*
* @param service IService
* @param idField 修改时的主键字段
* @param isToUnderlineCase 是否需要把field转为下划线格式
* @return boolean
*/
public boolean isExistence(IService<T> service, SFunction<T, ?> idField, boolean isToUnderlineCase) {
if (StrUtil.hasBlank(this.field, this.value)) {
return false;
}
String fieldName = isToUnderlineCase ? StrUtil.toUnderlineCase(field) : field;
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
wrapper.apply(fieldName + " = {0}", value);
if (id != null) {
wrapper.ne(idField, id);
}
return service.count(wrapper) > 0;
}
}

View File

@@ -0,0 +1,343 @@
package com.eleadmin.common.core.web;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.eleadmin.common.core.Constants;
import com.eleadmin.common.core.annotation.QueryField;
import com.eleadmin.common.core.annotation.QueryType;
import com.eleadmin.common.core.utils.CommonUtil;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 分页、排序、搜索参数封装
*
* @author EleAdmin
* @since 2019-04-26 10:34:35
*/
public class PageParam<T, U extends BaseParam> extends Page<T> {
private static final long serialVersionUID = 1L;
/**
* 租户id字段名称
*/
private static final String TENANT_ID_FIELD = "tenantId";
/**
* 查询条件
*/
private final U where;
/**
* 是否把字段名称驼峰转下划线
*/
private final boolean isToUnderlineCase;
public PageParam() {
this(null);
}
public PageParam(U where) {
this(where, true);
}
public PageParam(U where, boolean isToUnderlineCase) {
super();
this.where = where;
this.isToUnderlineCase = isToUnderlineCase;
if (where != null) {
// 获取分页页码
if (where.getPage() != null) {
setCurrent(where.getPage());
}
// 获取分页每页数量
if (where.getLimit() != null) {
setSize(where.getLimit());
}
// 获取排序方式
if (where.getSort() != null) {
if (sortIsSQL(where.getSort())) {
setOrders(parseOrderSQL(where.getSort()));
} else {
List<OrderItem> orderItems = new ArrayList<>();
String column = this.isToUnderlineCase ? StrUtil.toUnderlineCase(where.getSort()) : where.getSort();
boolean asc = !Constants.ORDER_DESC_VALUE.equals(where.getOrder());
orderItems.add(new OrderItem(column, asc));
setOrders(orderItems);
}
}
}
}
/**
* 排序字段是否是sql
*/
private boolean sortIsSQL(String sort) {
return sort != null && (sort.contains(",") || sort.trim().contains(" "));
}
/**
* 解析排序sql
*/
private List<OrderItem> parseOrderSQL(String orderSQL) {
List<OrderItem> orders = new ArrayList<>();
if (StrUtil.isNotBlank(orderSQL)) {
for (String item : orderSQL.split(",")) {
String[] temp = item.trim().split(" ");
if (!temp[0].isEmpty()) {
String column = this.isToUnderlineCase ? StrUtil.toUnderlineCase(temp[0]) : temp[0];
boolean asc = temp.length == 1 || !temp[temp.length - 1].equals(Constants.ORDER_DESC_VALUE);
orders.add(new OrderItem(column, asc));
}
}
}
return orders;
}
/**
* 设置默认排序方式
*
* @param orderItems 排序方式
* @return PageParam
*/
public PageParam<T, U> setDefaultOrder(List<OrderItem> orderItems) {
if (orders() == null || orders().size() == 0) {
setOrders(orderItems);
}
return this;
}
/**
* 设置默认排序方式
*
* @param orderSQL 排序方式
* @return PageParam
*/
public PageParam<T, U> setDefaultOrder(String orderSQL) {
setDefaultOrder(parseOrderSQL(orderSQL));
return this;
}
/**
* 获取查询条件
*
* @param excludes 不包含的字段
* @return QueryWrapper
*/
public QueryWrapper<T> getWrapper(String... excludes) {
return buildWrapper(null, Arrays.asList(excludes));
}
/**
* 获取查询条件
*
* @param columns 只包含的字段
* @return QueryWrapper
*/
public QueryWrapper<T> getWrapperWith(String... columns) {
return buildWrapper(Arrays.asList(columns), null);
}
/**
* 构建QueryWrapper
*
* @param columns 包含的字段
* @param excludes 排除的字段
* @return QueryWrapper
*/
private QueryWrapper<T> buildWrapper(List<String> columns, List<String> excludes) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
Map<String, Object> map = BeanUtil.beanToMap(where, false, true);
for (String fieldName : map.keySet()) {
Object fieldValue = map.get(fieldName);
Field field = ReflectUtil.getField(where.getClass(), fieldName);
// 过滤不包含的字段
if (columns != null && !columns.contains(fieldName)) {
continue;
}
// 过滤排除的字段
if (excludes != null && excludes.contains(fieldName)) {
continue;
}
// 过滤逻辑删除字段
if (field.getAnnotation(TableLogic.class) != null) {
continue;
}
// 过滤租户id字段
if (fieldName.equals(TENANT_ID_FIELD)) {
continue;
}
// 获取注解指定的查询字段及查询方式
QueryType queryType = QueryType.LIKE;
QueryField queryField = field.getAnnotation(QueryField.class);
if (queryField != null) {
if (StrUtil.isNotEmpty(queryField.value())) {
fieldName = queryField.value();
}
if (queryField.type() != null) {
queryType = queryField.type();
}
} else {
// 过滤非本表的字段
TableField tableField = field.getAnnotation(TableField.class);
if (tableField != null && !tableField.exist()) {
continue;
}
}
// 字段名驼峰转下划线
if (this.isToUnderlineCase) {
fieldName = StrUtil.toUnderlineCase(fieldName);
}
//
switch (queryType) {
case EQ:
queryWrapper.eq(fieldName, fieldValue);
break;
case NE:
queryWrapper.ne(fieldName, fieldValue);
break;
case GT:
queryWrapper.gt(fieldName, fieldValue);
break;
case GE:
queryWrapper.ge(fieldName, fieldValue);
break;
case LT:
queryWrapper.lt(fieldName, fieldValue);
break;
case LE:
queryWrapper.le(fieldName, fieldValue);
break;
case LIKE:
queryWrapper.like(fieldName, fieldValue);
break;
case NOT_LIKE:
queryWrapper.notLike(fieldName, fieldValue);
break;
case LIKE_LEFT:
queryWrapper.likeLeft(fieldName, fieldValue);
break;
case LIKE_RIGHT:
queryWrapper.likeRight(fieldName, fieldValue);
break;
case IS_NULL:
queryWrapper.isNull(fieldName);
break;
case IS_NOT_NULL:
queryWrapper.isNotNull(fieldName);
break;
case IN:
queryWrapper.in(fieldName, fieldValue);
break;
case NOT_IN:
queryWrapper.notIn(fieldName, fieldValue);
break;
case IN_STR:
if (fieldValue instanceof String) {
queryWrapper.in(fieldName, Arrays.asList(((String) fieldValue).split(",")));
}
break;
case NOT_IN_STR:
if (fieldValue instanceof String) {
queryWrapper.notIn(fieldName, Arrays.asList(((String) fieldValue).split(",")));
}
break;
}
}
return queryWrapper;
}
/**
* 获取包含排序的查询条件
*
* @return 包含排序的QueryWrapper
*/
public QueryWrapper<T> getOrderWrapper() {
return getOrderWrapper(getWrapper());
}
/**
* 获取包含排序的查询条件
*
* @param queryWrapper 不含排序的QueryWrapper
* @return 包含排序的QueryWrapper
*/
public QueryWrapper<T> getOrderWrapper(QueryWrapper<T> queryWrapper) {
if (queryWrapper == null) {
queryWrapper = new QueryWrapper<>();
}
for (OrderItem orderItem : orders()) {
if (orderItem.isAsc()) {
queryWrapper.orderByAsc(orderItem.getColumn());
} else {
queryWrapper.orderByDesc(orderItem.getColumn());
}
}
return queryWrapper;
}
/**
* 获取集合中的第一条数据
*
* @param records 集合
* @return 第一条数据
*/
public T getOne(List<T> records) {
return CommonUtil.listGetOne(records);
}
/**
* 代码排序集合
*
* @param records 集合
* @return 排序后的集合
*/
public List<T> sortRecords(List<T> records) {
List<OrderItem> orderItems = orders();
if (records == null || records.size() < 2 || orderItems == null || orderItems.size() == 0) {
return records;
}
Comparator<T> comparator = null;
for (OrderItem item : orderItems) {
if (item.getColumn() == null) {
continue;
}
String field = this.isToUnderlineCase ? StrUtil.toCamelCase(item.getColumn()) : item.getColumn();
Function keyExtractor = t -> ReflectUtil.getFieldValue(t, field);
if (comparator == null) {
if (item.isAsc()) {
comparator = Comparator.comparing(keyExtractor);
} else {
comparator = Comparator.comparing(keyExtractor, Comparator.reverseOrder());
}
} else {
if (item.isAsc()) {
comparator.thenComparing(keyExtractor);
} else {
comparator.thenComparing(keyExtractor, Comparator.reverseOrder());
}
}
}
if (comparator != null) {
return records.stream().sorted(comparator).collect(Collectors.toList());
}
return records;
}
}

View File

@@ -0,0 +1,51 @@
package com.eleadmin.common.core.web;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
/**
* 分页查询返回结果
*
* @author EleAdmin
* @since 2017-06-10 10:10:02
*/
public class PageResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "当前页数据")
private List<T> list;
@ApiModelProperty(value = "总数量")
private Long count;
public PageResult() {
}
public PageResult(List<T> list) {
this(list, null);
}
public PageResult(List<T> list, Long count) {
setList(list);
setCount(count);
}
public List<T> getList() {
return this.list;
}
public void setList(List<T> list) {
this.list = list;
}
public Long getCount() {
return this.count;
}
public void setCount(Long count) {
this.count = count;
}
}

View File

@@ -0,0 +1,152 @@
package com.eleadmin.common.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.core.web.*;
import com.eleadmin.common.system.entity.Dictionary;
import com.eleadmin.common.system.param.DictionaryParam;
import com.eleadmin.common.system.service.DictionaryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 字典控制器
*
* @author EleAdmin
* @since 2020-03-14 11:29:03
*/
@Api(tags = "字典管理")
@RestController
@RequestMapping("/api/system/dictionary")
public class DictionaryController extends BaseController {
@Resource
private DictionaryService dictionaryService;
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("分页查询字典")
@GetMapping("/page")
public ApiResult<PageResult<Dictionary>> page(DictionaryParam param) {
PageParam<Dictionary, DictionaryParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number");
return success(dictionaryService.page(page, page.getWrapper()));
}
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("查询全部字典")
@GetMapping()
public ApiResult<List<Dictionary>> list(DictionaryParam param) {
PageParam<Dictionary, DictionaryParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number");
return success(dictionaryService.list(page.getOrderWrapper()));
}
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("根据id查询字典")
@GetMapping("/{id}")
public ApiResult<Dictionary> get(@PathVariable("id") Integer id) {
return success(dictionaryService.getById(id));
}
@PreAuthorize("hasAuthority('sys:dict:save')")
@ApiOperation("添加字典")
@PostMapping()
public ApiResult<?> add(@RequestBody Dictionary dictionary) {
if (dictionaryService.count(new LambdaQueryWrapper<Dictionary>()
.eq(Dictionary::getDictCode, dictionary.getDictCode())) > 0) {
return fail("字典标识已存在");
}
if (dictionaryService.count(new LambdaQueryWrapper<Dictionary>()
.eq(Dictionary::getDictName, dictionary.getDictName())) > 0) {
return fail("字典名称已存在");
}
if (dictionaryService.save(dictionary)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:dict:update')")
@OperationLog
@ApiOperation("修改字典")
@PutMapping()
public ApiResult<?> update(@RequestBody Dictionary dictionary) {
if (dictionaryService.count(new LambdaQueryWrapper<Dictionary>()
.eq(Dictionary::getDictCode, dictionary.getDictCode())
.ne(Dictionary::getDictId, dictionary.getDictId())) > 0) {
return fail("字典标识已存在");
}
if (dictionaryService.count(new LambdaQueryWrapper<Dictionary>()
.eq(Dictionary::getDictName, dictionary.getDictName())
.ne(Dictionary::getDictId, dictionary.getDictId())) > 0) {
return fail("字典名称已存在");
}
if (dictionaryService.updateById(dictionary)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:dict:remove')")
@OperationLog
@ApiOperation("删除字典")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (dictionaryService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:dict:save')")
@OperationLog
@ApiOperation("批量添加字典")
@PostMapping("/batch")
public ApiResult<List<String>> saveBatch(@RequestBody List<Dictionary> list) {
if (CommonUtil.checkRepeat(list, Dictionary::getDictCode)) {
return fail("字典标识不能重复", null);
}
if (CommonUtil.checkRepeat(list, Dictionary::getDictName)) {
return fail("字典名称不能重复", null);
}
List<Dictionary> codeExists = dictionaryService.list(new LambdaQueryWrapper<Dictionary>()
.in(Dictionary::getDictCode, list.stream().map(Dictionary::getDictCode)
.collect(Collectors.toList())));
if (codeExists.size() > 0) {
return fail("字典标识已存在", codeExists.stream().map(Dictionary::getDictCode)
.collect(Collectors.toList())).setCode(2);
}
List<Dictionary> nameExists = dictionaryService.list(new LambdaQueryWrapper<Dictionary>()
.in(Dictionary::getDictName, list.stream().map(Dictionary::getDictCode)
.collect(Collectors.toList())));
if (nameExists.size() > 0) {
return fail("字典名称已存在", nameExists.stream().map(Dictionary::getDictName)
.collect(Collectors.toList())).setCode(3);
}
if (dictionaryService.saveBatch(list)) {
return success("添加成功", null);
}
return fail("添加失败", null);
}
@PreAuthorize("hasAuthority('sys:dict:remove')")
@OperationLog
@ApiOperation("批量删除字典")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (dictionaryService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,131 @@
package com.eleadmin.common.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.*;
import com.eleadmin.common.system.entity.DictionaryData;
import com.eleadmin.common.system.param.DictionaryDataParam;
import com.eleadmin.common.system.service.DictionaryDataService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 字典数据控制器
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
@Api(tags = "字典数据管理")
@RestController
@RequestMapping("/api/system/dictionary-data")
public class DictionaryDataController extends BaseController {
@Resource
private DictionaryDataService dictionaryDataService;
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("分页查询字典数据")
@GetMapping("/page")
public ApiResult<PageResult<DictionaryData>> page(DictionaryDataParam param) {
return success(dictionaryDataService.pageRel(param));
}
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("查询全部字典数据")
@GetMapping()
public ApiResult<List<DictionaryData>> list(DictionaryDataParam param) {
return success(dictionaryDataService.listRel(param));
}
@PreAuthorize("hasAuthority('sys:dict:list')")
@OperationLog
@ApiOperation("根据id查询字典数据")
@GetMapping("/{id}")
public ApiResult<DictionaryData> get(@PathVariable("id") Integer id) {
return success(dictionaryDataService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('sys:dict:save')")
@OperationLog
@ApiOperation("添加字典数据")
@PostMapping()
public ApiResult<?> add(@RequestBody DictionaryData dictionaryData) {
if (dictionaryDataService.count(new LambdaQueryWrapper<DictionaryData>()
.eq(DictionaryData::getDictId, dictionaryData.getDictId())
.eq(DictionaryData::getDictDataName, dictionaryData.getDictDataName())) > 0) {
return fail("字典数据名称已存在");
}
if (dictionaryDataService.count(new LambdaQueryWrapper<DictionaryData>()
.eq(DictionaryData::getDictId, dictionaryData.getDictId())
.eq(DictionaryData::getDictDataCode, dictionaryData.getDictDataCode())) > 0) {
return fail("字典数据标识已存在");
}
if (dictionaryDataService.save(dictionaryData)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:dict:update')")
@OperationLog
@ApiOperation("修改字典数据")
@PutMapping()
public ApiResult<?> update(@RequestBody DictionaryData dictionaryData) {
if (dictionaryDataService.count(new LambdaQueryWrapper<DictionaryData>()
.eq(DictionaryData::getDictId, dictionaryData.getDictId())
.eq(DictionaryData::getDictDataName, dictionaryData.getDictDataName())
.ne(DictionaryData::getDictDataId, dictionaryData.getDictDataId())) > 0) {
return fail("字典数据名称已存在");
}
if (dictionaryDataService.count(new LambdaQueryWrapper<DictionaryData>()
.eq(DictionaryData::getDictId, dictionaryData.getDictId())
.eq(DictionaryData::getDictDataCode, dictionaryData.getDictDataCode())
.ne(DictionaryData::getDictDataId, dictionaryData.getDictDataId())) > 0) {
return fail("字典数据标识已存在");
}
if (dictionaryDataService.updateById(dictionaryData)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:dict:remove')")
@OperationLog
@ApiOperation("删除字典数据")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (dictionaryDataService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:dict:save')")
@OperationLog
@ApiOperation("批量添加字典数据")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<DictionaryData> dictDataList) {
if (dictionaryDataService.saveBatch(dictDataList)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:dict:remove')")
@OperationLog
@ApiOperation("批量删除字典数据")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (dictionaryDataService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,49 @@
package com.eleadmin.common.system.controller;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.system.entity.EmailRecord;
import com.eleadmin.common.system.service.EmailRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.mail.MessagingException;
/**
* 邮件功能控制器
*
* @author EleAdmin
* @since 2020-03-21 00:37:11
*/
@Api(tags = "邮件功能")
@RestController
@RequestMapping("/api/system/email")
public class EmailController extends BaseController {
@Resource
private EmailRecordService emailRecordService;
@PreAuthorize("hasAuthority('sys:email:send')")
@OperationLog
@ApiOperation("发送邮件")
@PostMapping()
public ApiResult<?> send(@RequestBody EmailRecord emailRecord) {
try {
emailRecordService.sendFullTextEmail(emailRecord.getTitle(), emailRecord.getContent(),
emailRecord.getReceiver().split(","));
emailRecord.setCreateUserId(getLoginUserId());
emailRecordService.save(emailRecord);
return success("发送成功");
} catch (MessagingException e) {
e.printStackTrace();
}
return fail("发送失败");
}
}

View File

@@ -0,0 +1,242 @@
package com.eleadmin.common.system.controller;
import cn.hutool.core.util.StrUtil;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.config.ConfigProperties;
import com.eleadmin.common.core.utils.FileServerUtil;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.core.web.PageResult;
import com.eleadmin.common.system.entity.FileRecord;
import com.eleadmin.common.system.param.FileRecordParam;
import com.eleadmin.common.system.service.FileRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 文件上传下载控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:24
*/
@Api(tags = "文件上传下载")
@RestController
@RequestMapping("/api/file")
public class FileController extends BaseController {
@Resource
private ConfigProperties config;
@Resource
private FileRecordService fileRecordService;
@PreAuthorize("hasAuthority('sys:file:upload')")
@OperationLog
@ApiOperation("上传文件")
@PostMapping("/upload")
public ApiResult<FileRecord> upload(@RequestParam MultipartFile file, HttpServletRequest request) {
FileRecord result = null;
try {
String dir = getUploadDir();
File upload = FileServerUtil.upload(file, dir, config.getUploadUuidName());
String path = upload.getAbsolutePath().replace("\\", "/").substring(dir.length() - 1);
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/upload");
String originalName = file.getOriginalFilename();
result = new FileRecord();
result.setCreateUserId(getLoginUserId());
result.setName(StrUtil.isBlank(originalName) ? upload.getName() : originalName);
result.setLength(upload.length());
result.setPath(path);
result.setUrl(requestURL + "/" + path);
String contentType = FileServerUtil.getContentType(upload);
result.setContentType(contentType);
if (FileServerUtil.isImage(contentType)) {
result.setThumbnail(requestURL + "/thumbnail/" + path);
}
result.setDownloadUrl(requestURL + "/download/" + path);
fileRecordService.save(result);
return success(result);
} catch (Exception e) {
e.printStackTrace();
return fail("上传失败", result).setError(e.toString());
}
}
@PreAuthorize("hasAuthority('sys:file:upload')")
@OperationLog
@ApiOperation("上传base64文件")
@ApiImplicitParams({
@ApiImplicitParam(name = "base64", value = "base64", required = true, dataType = "string"),
@ApiImplicitParam(name = "fileName", value = "文件名称", dataType = "string")
})
@PostMapping("/upload/base64")
public ApiResult<FileRecord> uploadBase64(String base64, String fileName, HttpServletRequest request) {
FileRecord result = null;
try {
String dir = getUploadDir();
File upload = FileServerUtil.upload(base64, fileName, getUploadDir());
String path = upload.getAbsolutePath().substring(dir.length()).replace("\\", "/");
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/upload/base64");
result = new FileRecord();
result.setCreateUserId(getLoginUserId());
result.setName(StrUtil.isBlank(fileName) ? upload.getName() : fileName);
result.setLength(upload.length());
result.setPath(path);
result.setUrl(requestURL + path);
result.setThumbnail(FileServerUtil.isImage(upload) ? (requestURL + "/thumbnail" + path) : null);
fileRecordService.save(result);
return success(result);
} catch (Exception e) {
e.printStackTrace();
return fail("上传失败", result).setError(e.toString());
}
}
@ApiOperation("查看原文件")
@GetMapping("/{dir}/{name:.+}")
public void preview(@PathVariable("dir") String dir, @PathVariable("name") String name,
HttpServletResponse response, HttpServletRequest request) {
File file = new File(getUploadDir(), dir + "/" + name);
FileServerUtil.preview(file, getPdfOutDir(), config.getOpenOfficeHome(), response, request);
}
@ApiOperation("下载原文件")
@GetMapping("/download/{dir}/{name:.+}")
public void download(@PathVariable("dir") String dir, @PathVariable("name") String name,
HttpServletResponse response, HttpServletRequest request) {
String path = dir + "/" + name;
FileRecord record = fileRecordService.getByIdPath(path);
File file = new File(getUploadDir(), path);
String fileName = record == null ? file.getName() : record.getName();
FileServerUtil.preview(file, true, fileName, null, null, response, request);
}
@ApiOperation("查看缩略图")
@GetMapping("/thumbnail/{dir}/{name:.+}")
public void thumbnail(@PathVariable("dir") String dir, @PathVariable("name") String name,
HttpServletResponse response, HttpServletRequest request) {
File file = new File(getUploadDir(), dir + "/" + name);
File thumbnail = new File(getUploadSmDir(), dir + "/" + name);
FileServerUtil.previewThumbnail(file, thumbnail, config.getThumbnailSize(), response, request);
}
@PreAuthorize("hasAuthority('sys:file:remove')")
@OperationLog
@ApiOperation("删除文件")
@DeleteMapping("/remove/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
FileRecord record = fileRecordService.getById(id);
if (fileRecordService.removeById(id)) {
if (StrUtil.isNotBlank(record.getPath())) {
fileRecordService.deleteFileAsync(Arrays.asList(
new File(getUploadDir(), record.getPath()),
new File(getUploadSmDir(), record.getPath())
));
}
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:file:remove')")
@OperationLog
@ApiOperation("批量删除文件")
@ApiImplicitParams({
@ApiImplicitParam(name = "ids", value = "id数组", required = true, dataType = "string")
})
@DeleteMapping("/remove/batch")
public ApiResult<?> deleteBatch(@RequestBody List<Integer> ids) {
List<FileRecord> fileRecords = fileRecordService.listByIds(ids);
if (fileRecordService.removeByIds(ids)) {
List<File> files = new ArrayList<>();
for (FileRecord record : fileRecords) {
if (StrUtil.isNotBlank(record.getPath())) {
files.add(new File(getUploadDir(), record.getPath()));
files.add(new File(getUploadSmDir(), record.getPath()));
}
}
fileRecordService.deleteFileAsync(files);
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:file:list')")
@OperationLog
@ApiOperation("分页查询文件")
@GetMapping("/page")
public ApiResult<PageResult<FileRecord>> page(FileRecordParam param, HttpServletRequest request) {
PageResult<FileRecord> result = fileRecordService.pageRel(param);
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/page");
for (FileRecord record : result.getList()) {
if (StrUtil.isNotBlank(record.getPath())) {
record.setUrl(requestURL + "/" + record.getPath());
if (FileServerUtil.isImage(record.getContentType())) {
record.setThumbnail(requestURL + "/thumbnail/" + record.getPath());
}
record.setDownloadUrl(requestURL + "/download/" + record.getPath());
}
}
return success(result);
}
@PreAuthorize("hasAuthority('sys:file:list')")
@OperationLog
@ApiOperation("查询全部文件")
@GetMapping("/list")
public ApiResult<List<FileRecord>> list(FileRecordParam param, HttpServletRequest request) {
List<FileRecord> records = fileRecordService.listRel(param);
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/list");
for (FileRecord record : records) {
if (StrUtil.isNotBlank(record.getPath())) {
record.setUrl(requestURL + "/" + record.getPath());
if (FileServerUtil.isImage(record.getContentType())) {
record.setThumbnail(requestURL + "/thumbnail/" + record.getPath());
}
record.setDownloadUrl(requestURL + "/download/" + record.getPath());
}
}
return success(records);
}
/**
* 文件上传基目录
*/
private String getUploadBaseDir() {
return File.listRoots()[config.getUploadLocation()].getAbsolutePath()
.replace("\\", "/") + "/upload/";
}
/**
* 文件上传位置
*/
private String getUploadDir() {
return getUploadBaseDir() + "file/";
}
/**
* 缩略图生成位置
*/
private String getUploadSmDir() {
return getUploadBaseDir() + "thumbnail/";
}
/**
* office转pdf输出位置
*/
private String getPdfOutDir() {
return getUploadBaseDir() + "pdf/";
}
}

View File

@@ -0,0 +1,58 @@
package com.eleadmin.common.system.controller;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.core.web.PageResult;
import com.eleadmin.common.system.entity.LoginRecord;
import com.eleadmin.common.system.param.LoginRecordParam;
import com.eleadmin.common.system.service.LoginRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 登录日志控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:31
*/
@Api(tags = "登录日志")
@RestController
@RequestMapping("/api/system/login-record")
public class LoginRecordController extends BaseController {
@Resource
private LoginRecordService loginRecordService;
@PreAuthorize("hasAuthority('sys:login-record:list')")
@OperationLog
@ApiOperation("分页查询登录日志")
@GetMapping("/page")
public ApiResult<PageResult<LoginRecord>> page(LoginRecordParam param) {
return success(loginRecordService.pageRel(param));
}
@PreAuthorize("hasAuthority('sys:login-record:list')")
@OperationLog
@ApiOperation("查询全部登录日志")
@GetMapping()
public ApiResult<List<LoginRecord>> list(LoginRecordParam param) {
return success(loginRecordService.listRel(param));
}
@PreAuthorize("hasAuthority('sys:login-record:list')")
@OperationLog
@ApiOperation("根据id查询登录日志")
@GetMapping("/{id}")
public ApiResult<LoginRecord> get(@PathVariable("id") Integer id) {
return success(loginRecordService.getByIdRel(id));
}
}

View File

@@ -0,0 +1,140 @@
package com.eleadmin.common.system.controller;
import cn.hutool.core.util.StrUtil;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.config.ConfigProperties;
import com.eleadmin.common.core.security.JwtSubject;
import com.eleadmin.common.core.security.JwtUtil;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.system.entity.LoginRecord;
import com.eleadmin.common.system.entity.Menu;
import com.eleadmin.common.system.entity.User;
import com.eleadmin.common.system.result.CaptchaResult;
import com.eleadmin.common.system.param.LoginParam;
import com.eleadmin.common.system.result.LoginResult;
import com.eleadmin.common.system.param.UpdatePasswordParam;
import com.eleadmin.common.system.service.LoginRecordService;
import com.eleadmin.common.system.service.RoleMenuService;
import com.eleadmin.common.system.service.UserService;
import com.wf.captcha.SpecCaptcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* 登录认证控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:11
*/
@Api(tags = "登录认证")
@RestController
@RequestMapping("/api")
public class MainController extends BaseController {
@Resource
private ConfigProperties configProperties;
@Resource
private UserService userService;
@Resource
private RoleMenuService roleMenuService;
@Resource
private LoginRecordService loginRecordService;
@ApiOperation("用户登录")
@PostMapping("/login")
public ApiResult<LoginResult> login(@RequestBody LoginParam param, HttpServletRequest request) {
String username = param.getUsername();
Integer tenantId = param.getTenantId();
User user = userService.getByUsername(username, tenantId);
if (user == null) {
String message = "账号不存在";
loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
return fail(message, null);
}
if (!user.getStatus().equals(0)) {
String message = "账号被冻结";
loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
return fail(message, null);
}
if (!userService.comparePassword(user.getPassword(), param.getPassword())) {
String message = "密码错误";
loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
return fail(message, null);
}
loginRecordService.saveAsync(username, LoginRecord.TYPE_LOGIN, null, tenantId, request);
// 签发token
String access_token = JwtUtil.buildToken(new JwtSubject(username, tenantId),
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
return success("登录成功", new LoginResult(access_token, user));
}
@ApiOperation("获取登录用户信息")
@GetMapping("/auth/user")
public ApiResult<User> userInfo() {
return success(userService.getByIdRel(getLoginUserId()));
}
@ApiOperation("获取登录用户菜单")
@GetMapping("/auth/menu")
public ApiResult<List<Menu>> userMenu() {
List<Menu> menus = roleMenuService.listMenuByUserId(getLoginUserId(), Menu.TYPE_MENU);
return success(CommonUtil.toTreeData(menus, 0, Menu::getParentId, Menu::getMenuId, Menu::setChildren));
}
@PreAuthorize("hasAuthority('sys:auth:user')")
@OperationLog
@ApiOperation("修改个人信息")
@PutMapping("/auth/user")
public ApiResult<User> updateInfo(@RequestBody User user) {
user.setUserId(getLoginUserId());
// 不能修改的字段
user.setUsername(null);
user.setPassword(null);
user.setEmailVerified(null);
user.setOrganizationId(null);
user.setStatus(null);
if (userService.updateById(user)) {
return success(userService.getByIdRel(user.getUserId()));
}
return fail("保存失败", null);
}
@PreAuthorize("hasAuthority('sys:auth:password')")
@OperationLog
@ApiOperation("修改自己密码")
@PutMapping("/auth/password")
public ApiResult<?> updatePassword(@RequestBody UpdatePasswordParam param) {
if (StrUtil.hasBlank(param.getOldPassword(), param.getPassword())) {
return fail("参数不能为空");
}
Integer userId = getLoginUserId();
if (userId == null) {
return fail("未登录");
}
if (!userService.comparePassword(userService.getById(userId).getPassword(), param.getOldPassword())) {
return fail("原密码输入不正确");
}
User user = new User();
user.setUserId(userId);
user.setPassword(userService.encodePassword(param.getPassword()));
if (userService.updateById(user)) {
return success("修改成功");
}
return fail("修改失败");
}
@ApiOperation("图形验证码")
@GetMapping("/captcha")
public ApiResult<CaptchaResult> captcha() {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
return success(new CaptchaResult(specCaptcha.toBase64(), specCaptcha.text().toLowerCase()));
}
}

View File

@@ -0,0 +1,126 @@
package com.eleadmin.common.system.controller;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.*;
import com.eleadmin.common.system.entity.Menu;
import com.eleadmin.common.system.param.MenuParam;
import com.eleadmin.common.system.service.MenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 菜单控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:23
*/
@Api(tags = "菜单管理")
@RestController
@RequestMapping("/api/system/menu")
public class MenuController extends BaseController {
@Resource
private MenuService menuService;
@PreAuthorize("hasAuthority('sys:menu:list')")
@OperationLog
@ApiOperation("分页查询菜单")
@GetMapping("/page")
public ApiResult<PageResult<Menu>> page(MenuParam param) {
PageParam<Menu, MenuParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number");
return success(menuService.page(page, page.getWrapper()));
}
@PreAuthorize("hasAuthority('sys:menu:list')")
@OperationLog
@ApiOperation("查询全部菜单")
@GetMapping()
public ApiResult<List<Menu>> list(MenuParam param) {
PageParam<Menu, MenuParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number");
return success(menuService.list(page.getOrderWrapper()));
}
@PreAuthorize("hasAuthority('sys:menu:list')")
@OperationLog
@ApiOperation("根据id查询菜单")
@GetMapping("/{id}")
public ApiResult<Menu> get(@PathVariable("id") Integer id) {
return success(menuService.getById(id));
}
@PreAuthorize("hasAuthority('sys:menu:save')")
@OperationLog
@ApiOperation("添加菜单")
@PostMapping()
public ApiResult<?> add(@RequestBody Menu menu) {
if (menu.getParentId() == null) {
menu.setParentId(0);
}
if (menuService.save(menu)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:menu:update')")
@OperationLog
@ApiOperation("修改菜单")
@PutMapping()
public ApiResult<?> update(@RequestBody Menu menu) {
if (menuService.updateById(menu)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:menu:remove')")
@OperationLog
@ApiOperation("删除菜单")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (menuService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:menu:save')")
@OperationLog
@ApiOperation("批量添加菜单")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<Menu> menus) {
if (menuService.saveBatch(menus)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:menu:update')")
@OperationLog
@ApiOperation("批量修改菜单")
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<Menu> batchParam) {
if (batchParam.update(menuService, "menu_id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:menu:remove')")
@OperationLog
@ApiOperation("批量删除菜单")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (menuService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,62 @@
package com.eleadmin.common.system.controller;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.core.web.PageResult;
import com.eleadmin.common.system.entity.OperationRecord;
import com.eleadmin.common.system.param.OperationRecordParam;
import com.eleadmin.common.system.service.OperationRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 操作日志控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:12
*/
@Api(tags = "操作日志")
@RestController
@RequestMapping("/api/system/operation-record")
public class OperationRecordController extends BaseController {
@Resource
private OperationRecordService operationRecordService;
/**
* 分页查询操作日志
*/
@PreAuthorize("hasAuthority('sys:operation-record:list')")
@ApiOperation("分页查询操作日志")
@GetMapping("/page")
public ApiResult<PageResult<OperationRecord>> page(OperationRecordParam param) {
return success(operationRecordService.pageRel(param));
}
/**
* 查询全部操作日志
*/
@PreAuthorize("hasAuthority('sys:operation-record:list')")
@OperationLog
@ApiOperation("查询全部操作日志")
@GetMapping()
public ApiResult<List<OperationRecord>> list(OperationRecordParam param) {
return success(operationRecordService.listRel(param));
}
/**
* 根据id查询操作日志
*/
@PreAuthorize("hasAuthority('sys:operation-record:list')")
@ApiOperation("根据id查询操作日志")
@GetMapping("/{id}")
public ApiResult<OperationRecord> get(@PathVariable("id") Integer id) {
return success(operationRecordService.getByIdRel(id));
}
}

View File

@@ -0,0 +1,139 @@
package com.eleadmin.common.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.*;
import com.eleadmin.common.system.entity.Organization;
import com.eleadmin.common.system.param.OrganizationParam;
import com.eleadmin.common.system.service.OrganizationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 组织机构控制器
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
@Api(tags = "组织机构管理")
@RestController
@RequestMapping("/api/system/organization")
public class OrganizationController extends BaseController {
@Resource
private OrganizationService organizationService;
@PreAuthorize("hasAuthority('sys:org:list')")
@OperationLog
@ApiOperation("分页查询组织机构")
@GetMapping("/page")
public ApiResult<PageResult<Organization>> page(OrganizationParam param) {
return success(organizationService.pageRel(param));
}
@PreAuthorize("hasAuthority('sys:org:list')")
@OperationLog
@ApiOperation("查询全部组织机构")
@GetMapping()
public ApiResult<List<Organization>> list(OrganizationParam param) {
return success(organizationService.listRel(param));
}
@PreAuthorize("hasAuthority('sys:org:list')")
@OperationLog
@ApiOperation("根据id查询组织机构")
@GetMapping("/{id}")
public ApiResult<Organization> get(@PathVariable("id") Integer id) {
return success(organizationService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('sys:org:save')")
@OperationLog
@ApiOperation("添加组织机构")
@PostMapping()
public ApiResult<?> add(@RequestBody Organization organization) {
if (organization.getParentId() == null) {
organization.setParentId(0);
}
if (organizationService.count(new LambdaQueryWrapper<Organization>()
.eq(Organization::getOrganizationName, organization.getOrganizationName())
.eq(Organization::getParentId, organization.getParentId())) > 0) {
return fail("机构名称已存在");
}
if (organizationService.save(organization)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:org:update')")
@OperationLog
@ApiOperation("修改组织机构")
@PutMapping()
public ApiResult<?> update(@RequestBody Organization organization) {
if (organization.getOrganizationName() != null) {
if (organization.getParentId() == null) {
organization.setParentId(0);
}
if (organizationService.count(new LambdaQueryWrapper<Organization>()
.eq(Organization::getOrganizationName, organization.getOrganizationName())
.eq(Organization::getParentId, organization.getParentId())
.ne(Organization::getOrganizationId, organization.getOrganizationId())) > 0) {
return fail("机构名称已存在");
}
}
if (organizationService.updateById(organization)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:org:remove')")
@OperationLog
@ApiOperation("删除组织机构")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (organizationService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:org:save')")
@OperationLog
@ApiOperation("批量添加组织机构")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<Organization> organizationList) {
if (organizationService.saveBatch(organizationList)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:org:update')")
@OperationLog
@ApiOperation("批量修改组织机构")
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<Organization> batchParam) {
if (batchParam.update(organizationService, Organization::getOrganizationId)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:org:remove')")
@OperationLog
@ApiOperation("批量删除组织机构")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (organizationService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,152 @@
package com.eleadmin.common.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.core.web.PageParam;
import com.eleadmin.common.core.web.PageResult;
import com.eleadmin.common.system.entity.Role;
import com.eleadmin.common.system.param.RoleParam;
import com.eleadmin.common.system.service.RoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 角色控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:02
*/
@Api(tags = "角色管理")
@RestController
@RequestMapping("/api/system/role")
public class RoleController extends BaseController {
@Resource
private RoleService roleService;
@PreAuthorize("hasAuthority('sys:role:list')")
@OperationLog
@ApiOperation("分页查询角色")
@GetMapping("/page")
public ApiResult<PageResult<Role>> page(RoleParam param) {
PageParam<Role, RoleParam> page = new PageParam<>(param);
return success(roleService.page(page, page.getWrapper()));
}
@PreAuthorize("hasAuthority('sys:role:list')")
@OperationLog
@ApiOperation("查询全部角色")
@GetMapping()
public ApiResult<List<Role>> list(RoleParam param) {
PageParam<Role, RoleParam> page = new PageParam<>(param);
return success(roleService.list(page.getOrderWrapper()));
}
@PreAuthorize("hasAuthority('sys:role:list')")
@OperationLog
@ApiOperation("根据id查询角色")
@GetMapping("/{id}")
public ApiResult<Role> get(@PathVariable("id") Integer id) {
return success(roleService.getById(id));
}
@PreAuthorize("hasAuthority('sys:role:save')")
@OperationLog
@ApiOperation("添加角色")
@PostMapping()
public ApiResult<?> save(@RequestBody Role role) {
if (roleService.count(new LambdaQueryWrapper<Role>().eq(Role::getRoleCode, role.getRoleCode())) > 0) {
return fail("角色标识已存在");
}
if (roleService.count(new LambdaQueryWrapper<Role>().eq(Role::getRoleName, role.getRoleName())) > 0) {
return fail("角色名称已存在");
}
if (roleService.save(role)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("修改角色")
@PutMapping()
public ApiResult<?> update(@RequestBody Role role) {
if (role.getRoleCode() != null && roleService.count(new LambdaQueryWrapper<Role>()
.eq(Role::getRoleCode, role.getRoleCode())
.ne(Role::getRoleId, role.getRoleId())) > 0) {
return fail("角色标识已存在");
}
if (role.getRoleName() != null && roleService.count(new LambdaQueryWrapper<Role>()
.eq(Role::getRoleName, role.getRoleName())
.ne(Role::getRoleId, role.getRoleId())) > 0) {
return fail("角色名称已存在");
}
if (roleService.updateById(role)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:role:remove')")
@OperationLog
@ApiOperation("删除角色")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (roleService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:role:save')")
@OperationLog
@ApiOperation("批量添加角色")
@PostMapping("/batch")
public ApiResult<List<String>> saveBatch(@RequestBody List<Role> list) {
// 校验是否重复
if (CommonUtil.checkRepeat(list, Role::getRoleName)) {
return fail("角色名称存在重复", null);
}
if (CommonUtil.checkRepeat(list, Role::getRoleCode)) {
return fail("角色标识存在重复", null);
}
// 校验是否存在
List<Role> codeExists = roleService.list(new LambdaQueryWrapper<Role>().in(Role::getRoleCode,
list.stream().map(Role::getRoleCode).collect(Collectors.toList())));
if (codeExists.size() > 0) {
return fail("角色标识已存在", codeExists.stream().map(Role::getRoleCode)
.collect(Collectors.toList())).setCode(2);
}
List<Role> nameExists = roleService.list(new LambdaQueryWrapper<Role>().in(Role::getRoleName,
list.stream().map(Role::getRoleCode).collect(Collectors.toList())));
if (nameExists.size() > 0) {
return fail("角色标识已存在", nameExists.stream().map(Role::getRoleCode)
.collect(Collectors.toList())).setCode(3);
}
if (roleService.saveBatch(list)) {
return success("添加成功", null);
}
return fail("添加失败", null);
}
@PreAuthorize("hasAuthority('sys:role:remove')")
@OperationLog
@ApiOperation("批量删除角色")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (roleService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,100 @@
package com.eleadmin.common.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.core.exception.BusinessException;
import com.eleadmin.common.system.entity.Menu;
import com.eleadmin.common.system.entity.RoleMenu;
import com.eleadmin.common.system.service.MenuService;
import com.eleadmin.common.system.service.RoleMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* 角色菜单控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:01
*/
@Api(tags = "角色菜单管理")
@RestController
@RequestMapping("/api/system/role-menu")
public class RoleMenuController extends BaseController {
@Resource
private RoleMenuService roleMenuService;
@Resource
private MenuService menuService;
@PreAuthorize("hasAuthority('sys:role:list')")
@OperationLog
@ApiOperation("查询角色菜单")
@GetMapping("/{id}")
public ApiResult<List<Menu>> list(@PathVariable("id") Integer roleId) {
List<Menu> menus = menuService.list(new LambdaQueryWrapper<Menu>().orderByAsc(Menu::getSortNumber));
List<RoleMenu> roleMenus = roleMenuService.list(new LambdaQueryWrapper<RoleMenu>()
.eq(RoleMenu::getRoleId, roleId));
for (Menu menu : menus) {
menu.setChecked(roleMenus.stream().anyMatch((d) -> d.getMenuId().equals(menu.getMenuId())));
}
return success(menus);
}
@Transactional(rollbackFor = {Exception.class})
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("修改角色菜单")
@PutMapping("/{id}")
public ApiResult<?> update(@PathVariable("id") Integer roleId, @RequestBody List<Integer> menuIds) {
roleMenuService.remove(new LambdaUpdateWrapper<RoleMenu>().eq(RoleMenu::getRoleId, roleId));
if (menuIds != null && menuIds.size() > 0) {
List<RoleMenu> roleMenuList = new ArrayList<>();
for (Integer menuId : menuIds) {
RoleMenu roleMenu = new RoleMenu();
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
roleMenuList.add(roleMenu);
}
if (!roleMenuService.saveBatch(roleMenuList)) {
throw new BusinessException("保存失败");
}
}
return success("保存成功");
}
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("添加角色菜单")
@PostMapping("/{id}")
public ApiResult<?> addRoleAuth(@PathVariable("id") Integer roleId, @RequestBody Integer menuId) {
RoleMenu roleMenu = new RoleMenu();
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
if (roleMenuService.save(roleMenu)) {
return success();
}
return fail();
}
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("移除角色菜单")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer roleId, @RequestBody Integer menuId) {
if (roleMenuService.remove(new LambdaUpdateWrapper<RoleMenu>()
.eq(RoleMenu::getRoleId, roleId).eq(RoleMenu::getMenuId, menuId))) {
return success();
}
return fail();
}
}

View File

@@ -0,0 +1,298 @@
package com.eleadmin.common.system.controller;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.eleadmin.common.core.annotation.OperationLog;
import com.eleadmin.common.core.utils.CommonUtil;
import com.eleadmin.common.core.web.*;
import com.eleadmin.common.system.entity.DictionaryData;
import com.eleadmin.common.system.entity.Organization;
import com.eleadmin.common.system.entity.Role;
import com.eleadmin.common.system.entity.User;
import com.eleadmin.common.system.param.UserImportParam;
import com.eleadmin.common.system.param.UserParam;
import com.eleadmin.common.system.service.DictionaryDataService;
import com.eleadmin.common.system.service.OrganizationService;
import com.eleadmin.common.system.service.RoleService;
import com.eleadmin.common.system.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户控制器
*
* @author EleAdmin
* @since 2018-12-24 16:10:41
*/
@Api(tags = "用户管理")
@RestController
@RequestMapping("/api/system/user")
public class UserController extends BaseController {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Resource
private OrganizationService organizationService;
@Resource
private DictionaryDataService dictionaryDataService;
@PreAuthorize("hasAuthority('sys:user:list')")
@OperationLog
@ApiOperation("分页查询用户")
@GetMapping("/page")
public ApiResult<PageResult<User>> page(UserParam param) {
return success(userService.pageRel(param));
}
@PreAuthorize("hasAuthority('sys:user:list')")
@OperationLog
@ApiOperation("查询全部用户")
@GetMapping()
public ApiResult<List<User>> list(UserParam param) {
return success(userService.listRel(param));
}
@PreAuthorize("hasAuthority('sys:user:list')")
@OperationLog
@ApiOperation("根据id查询用户")
@GetMapping("/{id}")
public ApiResult<User> get(@PathVariable("id") Integer id) {
return success(userService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('sys:user:save')")
@OperationLog
@ApiOperation("添加用户")
@PostMapping()
public ApiResult<?> add(@RequestBody User user) {
user.setStatus(0);
user.setPassword(userService.encodePassword(user.getPassword()));
if (userService.saveUser(user)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("修改用户")
@PutMapping()
public ApiResult<?> update(@RequestBody User user) {
user.setStatus(null);
user.setUsername(null);
user.setPassword(null);
if (userService.updateUser(user)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:user:remove')")
@OperationLog
@ApiOperation("删除用户")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (userService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("批量修改用户")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<User> batchParam) {
if (batchParam.update(userService, User::getUserId)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:user:remove')")
@OperationLog
@ApiOperation("批量删除用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "ids", value = "id数组", required = true, dataType = "string")
})
@DeleteMapping("/batch")
public ApiResult<?> deleteBatch(@RequestBody List<Integer> ids) {
if (userService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("修改用户状态")
@PutMapping("/status")
public ApiResult<?> updateStatus(@RequestBody User user) {
if (user.getUserId() == null || user.getStatus() == null || !Arrays.asList(0, 1).contains(user.getStatus())) {
return fail("参数不正确");
}
User u = new User();
u.setUserId(user.getUserId());
u.setStatus(user.getStatus());
if (userService.updateById(u)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("批量修改用户状态")
@PutMapping("/status/batch")
public ApiResult<?> updateStatusBatch(@RequestBody BatchParam<Integer> batchParam) {
if (!Arrays.asList(0, 1).contains(batchParam.getData())) {
return fail("状态值不正确");
}
if (userService.update(new LambdaUpdateWrapper<User>()
.in(User::getUserId, batchParam.getIds())
.set(User::getStatus, batchParam.getData()))) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("重置密码")
@PutMapping("/password")
public ApiResult<?> resetPassword(@RequestBody User user) {
if (user.getUserId() == null || StrUtil.isBlank(user.getPassword())) {
return fail("参数不正确");
}
User u = new User();
u.setUserId(user.getUserId());
u.setPassword(userService.encodePassword(user.getPassword()));
if (userService.updateById(u)) {
return success("重置成功");
} else {
return fail("重置失败");
}
}
@PreAuthorize("hasAuthority('sys:user:update')")
@OperationLog
@ApiOperation("批量重置密码")
@PutMapping("/password/batch")
public ApiResult<?> resetPasswordBatch(@RequestBody BatchParam<String> batchParam) {
if (batchParam.getIds() == null || batchParam.getIds().size() == 0) {
return fail("请选择用户");
}
if (batchParam.getData() == null) {
return fail("请输入密码");
}
if (userService.update(new LambdaUpdateWrapper<User>()
.in(User::getUserId, batchParam.getIds())
.set(User::getPassword, userService.encodePassword(batchParam.getData())))) {
return success("重置成功");
} else {
return fail("重置失败");
}
}
@PreAuthorize("hasAuthority('sys:user:list')")
@OperationLog
@ApiOperation("检查用户是否存在")
@GetMapping("/existence")
public ApiResult<?> existence(ExistenceParam<User> param) {
if (param.isExistence(userService, User::getUserId)) {
return success(param.getValue() + "已存在");
}
return fail(param.getValue() + "不存在");
}
/**
* excel导入用户
*/
@PreAuthorize("hasAuthority('sys:user:save')")
@OperationLog
@ApiOperation("导入用户")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/import")
public ApiResult<List<String>> importBatch(MultipartFile file) {
ImportParams importParams = new ImportParams();
try {
List<UserImportParam> list = ExcelImportUtil.importExcel(file.getInputStream(),
UserImportParam.class, importParams);
// 校验是否重复
if (CommonUtil.checkRepeat(list, UserImportParam::getUsername)) {
return fail("账号存在重复", null);
}
if (CommonUtil.checkRepeat(list, UserImportParam::getPhone)) {
return fail("手机号存在重复", null);
}
// 校验是否存在
List<User> usernameExists = userService.list(new LambdaQueryWrapper<User>().in(User::getUsername,
list.stream().map(UserImportParam::getUsername).collect(Collectors.toList())));
if (usernameExists.size() > 0) {
return fail("账号已经存在",
usernameExists.stream().map(User::getUsername).collect(Collectors.toList()));
}
List<User> phoneExists = userService.list(new LambdaQueryWrapper<User>().in(User::getPhone,
list.stream().map(UserImportParam::getPhone).collect(Collectors.toList())));
if (phoneExists.size() > 0) {
return fail("手机号已经存在",
phoneExists.stream().map(User::getPhone).collect(Collectors.toList()));
}
// 添加
List<User> users = new ArrayList<>();
for (UserImportParam one : list) {
User u = new User();
u.setStatus(0);
u.setUsername(one.getUsername());
u.setPassword(userService.encodePassword(one.getPassword()));
u.setNickname(one.getNickname());
u.setPhone(one.getPhone());
Role role = roleService.getOne(new QueryWrapper<Role>()
.eq("role_name", one.getRoleName()), false);
if (role == null) {
return fail("角色不存在", Collections.singletonList(one.getRoleName()));
} else {
u.setRoles(Collections.singletonList(role));
}
Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
.eq("organization_full_name", one.getOrganizationName()), false);
if (organization == null) {
return fail("机构不存在", Collections.singletonList(one.getOrganizationName()));
} else {
u.setOrganizationId(organization.getOrganizationId());
}
DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
if (sex == null) {
return fail("性别不存在", Collections.singletonList(one.getSexName()));
} else {
u.setSex(sex.getDictDataCode());
}
}
if (userService.saveBatch(users)) {
return success("导入成功", null);
}
} catch (Exception e) {
e.printStackTrace();
}
return fail("导入失败", null);
}
}

View File

@@ -0,0 +1,164 @@
package com.eleadmin.common.system.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.eleadmin.common.core.utils.FileServerUtil;
import com.eleadmin.common.core.web.BaseController;
import com.eleadmin.common.system.service.UserFileService;
import com.eleadmin.common.system.entity.UserFile;
import com.eleadmin.common.system.param.UserFileParam;
import com.eleadmin.common.core.web.ApiResult;
import com.eleadmin.common.core.web.PageResult;
import com.eleadmin.common.core.web.PageParam;
import com.eleadmin.common.core.annotation.OperationLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 用户文件控制器
*
* @author EleAdmin
* @since 2022-07-21 14:34:40
*/
@Api(tags = "用户文件管理")
@RestController
@RequestMapping("/api/system/user-file")
public class UserFileController extends BaseController {
@Resource
private UserFileService userFileService;
@OperationLog
@ApiOperation("分页查询用户文件")
@GetMapping("/page")
public ApiResult<PageResult<UserFile>> page(UserFileParam param, HttpServletRequest request) {
param.setUserId(getLoginUserId());
PageParam<UserFile, UserFileParam> page = new PageParam<>(param);
page.setDefaultOrder("is_directory desc");
PageParam<UserFile, UserFileParam> result = userFileService.page(page, page.getWrapper());
List<UserFile> records = result.getRecords();
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/system/user-file") + "/file";
for (UserFile record : records) {
if (StrUtil.isNotBlank(record.getPath())) {
record.setUrl(requestURL + "/" + record.getPath());
if (FileServerUtil.isImage(record.getContentType())) {
record.setThumbnail(requestURL + "/thumbnail/" + record.getPath());
}
record.setDownloadUrl(requestURL + "/download/" + record.getPath());
}
}
return success(records, result.getTotal());
}
@OperationLog
@ApiOperation("查询全部用户文件")
@GetMapping()
public ApiResult<List<UserFile>> list(UserFileParam param, HttpServletRequest request) {
param.setUserId(getLoginUserId());
PageParam<UserFile, UserFileParam> page = new PageParam<>(param);
page.setDefaultOrder("is_directory desc");
List<UserFile> records = userFileService.list(page.getOrderWrapper());
String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/system/user-file") + "/file";
for (UserFile record : records) {
if (StrUtil.isNotBlank(record.getPath())) {
record.setUrl(requestURL + "/" + record.getPath());
if (FileServerUtil.isImage(record.getContentType())) {
record.setThumbnail(requestURL + "/thumbnail/" + record.getPath());
}
record.setDownloadUrl(requestURL + "/download/" + record.getPath());
}
}
return success(records);
}
@PreAuthorize("hasAuthority('sys:auth:user')")
@OperationLog
@ApiOperation("添加用户文件")
@PostMapping()
public ApiResult<?> save(@RequestBody UserFile userFile) {
userFile.setUserId(getLoginUserId());
if (userFile.getParentId() == null) {
userFile.setParentId(0);
}
if (userFile.getIsDirectory() != null && userFile.getIsDirectory().equals(1)) {
if (userFileService.count(new LambdaQueryWrapper<UserFile>()
.eq(UserFile::getName, userFile.getName())
.eq(UserFile::getParentId, userFile.getParentId())
.eq(UserFile::getUserId, userFile.getUserId())) > 0) {
return fail("文件夹名称已存在");
}
}
if (userFileService.save(userFile)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('sys:auth:user')")
@OperationLog
@ApiOperation("修改用户文件")
@PutMapping()
public ApiResult<?> update(@RequestBody UserFile userFile) {
Integer loginUserId = getLoginUserId();
UserFile old = userFileService.getById(userFile.getId());
UserFile entity = new UserFile();
if (StrUtil.isNotBlank(userFile.getName())) {
entity.setName(userFile.getName());
}
if (userFile.getParentId() != null) {
entity.setParentId(userFile.getParentId());
}
if (!old.getUserId().equals(loginUserId) ||
(entity.getName() == null && entity.getParentId() == null)) {
return fail("修改失败");
}
if (old.getIsDirectory() != null && old.getIsDirectory().equals(1)) {
if (userFileService.count(new LambdaQueryWrapper<UserFile>()
.eq(UserFile::getName, entity.getName() == null ? old.getName() : entity.getName())
.eq(UserFile::getParentId, entity.getParentId() == null ? old.getParentId() : entity.getParentId())
.eq(UserFile::getUserId, loginUserId)
.ne(UserFile::getId, old.getId())) > 0) {
return fail("文件夹名称已存在");
}
}
if (userFileService.update(entity, new LambdaUpdateWrapper<UserFile>()
.eq(UserFile::getId, userFile.getId())
.eq(UserFile::getUserId, loginUserId))) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('sys:auth:user')")
@OperationLog
@ApiOperation("删除用户文件")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (userFileService.remove(new LambdaUpdateWrapper<UserFile>()
.eq(UserFile::getId, id)
.eq(UserFile::getUserId, getLoginUserId()))) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('sys:auth:user')")
@OperationLog
@ApiOperation("批量删除用户文件")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (userFileService.remove(new LambdaUpdateWrapper<UserFile>()
.in(UserFile::getId, ids)
.eq(UserFile::getUserId, getLoginUserId()))) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,53 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 字典
*
* @author EleAdmin
* @since 2020-03-14 11:29:03
*/
@Data
@ApiModel(description = "字典")
@TableName("sys_dictionary")
public class Dictionary implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "字典id")
@TableId(type = IdType.AUTO)
private Integer dictId;
@ApiModelProperty(value = "字典标识")
private String dictCode;
@ApiModelProperty(value = "字典名称")
private String dictName;
@ApiModelProperty(value = "排序号")
private Integer sortNumber;
@ApiModelProperty(value = "备注")
private String comments;
@ApiModelProperty(value = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "修改时间")
private Date updateTime;
}

View File

@@ -0,0 +1,61 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 字典数据
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
@Data
@ApiModel(description = "字典数据")
@TableName("sys_dictionary_data")
public class DictionaryData implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "字典数据id")
@TableId(type = IdType.AUTO)
private Integer dictDataId;
@ApiModelProperty(value = "字典id")
private Integer dictId;
@ApiModelProperty(value = "字典数据标识")
private String dictDataCode;
@ApiModelProperty(value = "字典数据名称")
private String dictDataName;
@ApiModelProperty(value = "排序号")
private Integer sortNumber;
@ApiModelProperty(value = "备注")
private String comments;
@ApiModelProperty(value = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "修改时间")
private Date updateTime;
@ApiModelProperty(value = "字典代码")
@TableField(exist = false)
private String dictCode;
@ApiModelProperty(value = "字典名称")
@TableField(exist = false)
private String dictName;
}

View File

@@ -0,0 +1,56 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 邮件发送记录
*
* @author EleAdmin
* @since 2021-08-29 12:36:35
*/
@Data
@ApiModel(description = "邮件发送记录")
@TableName("sys_email_record")
public class EmailRecord implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("邮件标题")
private String title;
@ApiModelProperty("邮件内容")
private String content;
@ApiModelProperty("收件邮箱")
private String receiver;
@ApiModelProperty("发件邮箱")
private String sender;
@ApiModelProperty("创建人")
private Integer createUserId;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
}

View File

@@ -0,0 +1,78 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 文件上传记录
*
* @author EleAdmin
* @since 2021-08-29 12:36:32
*/
@Data
@ApiModel(description = "文件上传记录")
@TableName("sys_file_record")
public class FileRecord implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("文件名称")
private String name;
@ApiModelProperty("文件存储路径")
private String path;
@ApiModelProperty("文件大小")
private Long length;
@ApiModelProperty("文件类型")
private String contentType;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty("创建人")
private Integer createUserId;
@ApiModelProperty("是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("文件访问地址")
@TableField(exist = false)
private String url;
@ApiModelProperty("文件缩略图访问地址")
@TableField(exist = false)
private String thumbnail;
@ApiModelProperty("文件下载地址")
@TableField(exist = false)
private String downloadUrl;
@ApiModelProperty("创建人账号")
@TableField(exist = false)
private String createUsername;
@ApiModelProperty("创建人名称")
@TableField(exist = false)
private String createNickname;
}

View File

@@ -0,0 +1,72 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 登录日志
*
* @author EleAdmin
* @since 2018-12-24 16:10:41
*/
@Data
@ApiModel(description = "登录日志")
@TableName("sys_login_record")
public class LoginRecord implements Serializable {
private static final long serialVersionUID = 1L;
public static final int TYPE_LOGIN = 0; // 登录成功
public static final int TYPE_ERROR = 1; // 登录失败
public static final int TYPE_LOGOUT = 2; // 退出登录
public static final int TYPE_REFRESH = 3; // 续签token
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("用户账号")
private String username;
@ApiModelProperty("操作系统")
private String os;
@ApiModelProperty("设备名称")
private String device;
@ApiModelProperty("浏览器类型")
private String browser;
@ApiModelProperty("ip地址")
private String ip;
@ApiModelProperty("操作类型, 0登录成功, 1登录失败, 2退出登录, 3续签token")
private Integer loginType;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("操作时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("用户id")
@TableField(exist = false)
private Integer userId;
@ApiModelProperty("用户昵称")
@TableField(exist = false)
private String nickname;
}

View File

@@ -0,0 +1,81 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import java.util.Date;
import java.util.List;
/**
* 菜单
*
* @author EleAdmin
* @since 2018-12-24 16:10:17
*/
@Data
@ApiModel(description = "菜单")
@TableName("sys_menu")
public class Menu implements GrantedAuthority {
private static final long serialVersionUID = 1L;
public static final int TYPE_MENU = 0; // 菜单类型
public static final int TYPE_BTN = 1; // 按钮类型
@ApiModelProperty("菜单id")
@TableId(type = IdType.AUTO)
private Integer menuId;
@ApiModelProperty("上级id, 0是顶级")
private Integer parentId;
@ApiModelProperty("菜单名称")
private String title;
@ApiModelProperty("菜单路由地址")
private String path;
@ApiModelProperty("菜单组件地址")
private String component;
@ApiModelProperty("菜单类型, 0菜单, 1按钮")
private Integer menuType;
@ApiModelProperty("排序号")
private Integer sortNumber;
@ApiModelProperty("权限标识")
private String authority;
@ApiModelProperty("菜单图标")
private String icon;
@ApiModelProperty("是否隐藏, 0否, 1是(仅注册路由不显示左侧菜单)")
private Integer hide;
@ApiModelProperty("路由元信息")
private String meta;
@ApiModelProperty("是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("子菜单")
@TableField(exist = false)
private List<Menu> children;
@ApiModelProperty("角色权限树选中状态")
@TableField(exist = false)
private Boolean checked;
}

View File

@@ -0,0 +1,95 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 操作日志
*
* @author EleAdmin
* @since 2018-12-24 16:10:33
*/
@Data
@ApiModel(description = "操作日志")
@TableName("sys_operation_record")
public class OperationRecord implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("用户id")
private Integer userId;
@ApiModelProperty("操作模块")
private String module;
@ApiModelProperty("操作功能")
private String description;
@ApiModelProperty("请求地址")
private String url;
@ApiModelProperty("请求方式")
private String requestMethod;
@ApiModelProperty("调用方法")
private String method;
@ApiModelProperty("请求参数")
private String params;
@ApiModelProperty("返回结果")
private String result;
@ApiModelProperty("异常信息")
private String error;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty("消耗时间, 单位毫秒")
private Long spendTime;
@ApiModelProperty("操作系统")
private String os;
@ApiModelProperty("设备名称")
private String device;
@ApiModelProperty("浏览器类型")
private String browser;
@ApiModelProperty("ip地址")
private String ip;
@ApiModelProperty("状态, 0成功, 1异常")
private Integer status;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("操作时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("用户昵称")
@TableField(exist = false)
private String nickname;
@ApiModelProperty("用户账号")
@TableField(exist = false)
private String username;
}

View File

@@ -0,0 +1,77 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 组织机构
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
@Data
@ApiModel(description = "组织机构")
@TableName("sys_organization")
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "机构id")
@TableId(type = IdType.AUTO)
private Integer organizationId;
@ApiModelProperty(value = "上级id, 0是顶级")
private Integer parentId;
@ApiModelProperty(value = "机构名称")
private String organizationName;
@ApiModelProperty(value = "机构全称")
private String organizationFullName;
@ApiModelProperty(value = "机构代码")
private String organizationCode;
@ApiModelProperty(value = "机构类型, 字典标识")
private String organizationType;
@ApiModelProperty(value = "负责人id")
private Integer leaderId;
@ApiModelProperty(value = "排序号")
private Integer sortNumber;
@ApiModelProperty(value = "备注")
private String comments;
@ApiModelProperty(value = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "修改时间")
private Date updateTime;
@ApiModelProperty(value = "机构类型名称")
@TableField(exist = false)
private String organizationTypeName;
@ApiModelProperty(value = "负责人姓名")
@TableField(exist = false)
private String leaderNickname;
@ApiModelProperty(value = "负责人账号")
@TableField(exist = false)
private String leaderUsername;
}

View File

@@ -0,0 +1,53 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 角色
*
* @author EleAdmin
* @since 2018-12-24 16:10:01
*/
@Data
@ApiModel(description = "角色")
@TableName("sys_role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("角色id")
@TableId(type = IdType.AUTO)
private Integer roleId;
@ApiModelProperty("角色标识")
private String roleCode;
@ApiModelProperty("角色名称")
private String roleName;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty("是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty(hidden = true)
@TableField(exist = false)
private Integer userId;
}

View File

@@ -0,0 +1,44 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 角色菜单
*
* @author EleAdmin
* @since 2018-12-24 16:10:54
*/
@Data
@ApiModel(description = "角色权限")
@TableName("sys_role_menu")
public class RoleMenu implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("角色id")
private Integer roleId;
@ApiModelProperty("菜单id")
private Integer menuId;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
}

View File

@@ -0,0 +1,46 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 租户
*
* @author EleAdmin
* @since 2021-08-28 11:31:06
*/
@Data
@ApiModel(description = "租户")
@TableName("sys_tenant")
public class Tenant implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("租户id")
@TableId(type = IdType.AUTO)
private Integer tenantId;
@ApiModelProperty("租户名称")
private String tenantName;
@ApiModelProperty("备注")
private String comments;
@ApiModelProperty("是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
}

View File

@@ -0,0 +1,121 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Date;
import java.util.List;
/**
* 用户
*
* @author EleAdmin
* @since 2018-12-24 16:10:13
*/
@Data
@ApiModel(description = "用户")
@TableName("sys_user")
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
@ApiModelProperty("用户id")
@TableId(type = IdType.AUTO)
private Integer userId;
@ApiModelProperty("账号")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("性别, 字典标识")
private String sex;
@ApiModelProperty("手机号")
private String phone;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("邮箱是否验证, 0否, 1是")
private Integer emailVerified;
@ApiModelProperty("真实姓名")
private String realName;
@ApiModelProperty("身份证号")
private String idCard;
@ApiModelProperty("出生日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@ApiModelProperty("个人简介")
private String introduction;
@ApiModelProperty("机构id")
private Integer organizationId;
@ApiModelProperty("状态, 0正常, 1冻结")
private Integer status;
@ApiModelProperty("是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty("注册时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("机构名称")
@TableField(exist = false)
private String organizationName;
@ApiModelProperty("性别名称")
@TableField(exist = false)
private String sexName;
@ApiModelProperty("角色列表")
@TableField(exist = false)
private List<Role> roles;
@ApiModelProperty("权限列表")
@TableField(exist = false)
private List<Menu> authorities;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return this.status != null && this.status == 0;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,76 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.util.Date;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 用户文件
*
* @author EleAdmin
* @since 2022-07-21 14:34:40
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "UserFile对象", description = "用户文件")
@TableName("sys_user_file")
public class UserFile implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "用户id")
private Integer userId;
@ApiModelProperty(value = "文件名称")
private String name;
@ApiModelProperty(value = "是否是文件夹, 0否, 1是")
private Integer isDirectory;
@ApiModelProperty(value = "上级id")
private Integer parentId;
@ApiModelProperty(value = "文件路径")
private String path;
@ApiModelProperty(value = "文件大小")
private Integer length;
@ApiModelProperty("文件类型")
private String contentType;
@ApiModelProperty(value = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "修改时间")
private Date updateTime;
@ApiModelProperty("文件访问地址")
@TableField(exist = false)
private String url;
@ApiModelProperty("文件缩略图访问地址")
@TableField(exist = false)
private String thumbnail;
@ApiModelProperty("文件下载地址")
@TableField(exist = false)
private String downloadUrl;
}

View File

@@ -0,0 +1,46 @@
package com.eleadmin.common.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户角色
*
* @author EleAdmin
* @since 2018-12-24 16:10:23
*/
@Data
@ApiModel(description = "用户角色")
@TableName("sys_user_role")
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键id")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("用户id")
private Integer userId;
@ApiModelProperty("角色id")
private Integer roleId;
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("修改时间")
private Date updateTime;
@ApiModelProperty("角色名称")
@TableField(exist = false)
private String roleName;
}

View File

@@ -0,0 +1,47 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.DictionaryData;
import com.eleadmin.common.system.param.DictionaryDataParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 字典数据Mapper
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
public interface DictionaryDataMapper extends BaseMapper<DictionaryData> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<DictionaryData>
*/
List<DictionaryData> selectPageRel(@Param("page") IPage<DictionaryData> page,
@Param("param") DictionaryDataParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<DictionaryData>
*/
List<DictionaryData> selectListRel(@Param("param") DictionaryDataParam param);
/**
* 根据dictCode和dictDataName查询
*
* @param dictCode 字典标识
* @param dictDataName 字典项名称
* @return List<DictionaryData>
*/
List<DictionaryData> getByDictCodeAndName(@Param("dictCode") String dictCode,
@Param("dictDataName") String dictDataName);
}

View File

@@ -0,0 +1,14 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.Dictionary;
/**
* 字典Mapper
*
* @author EleAdmin
* @since 2020-03-14 11:29:03
*/
public interface DictionaryMapper extends BaseMapper<Dictionary> {
}

View File

@@ -0,0 +1,14 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.EmailRecord;
/**
* 邮件记录Mapper
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
public interface EmailRecordMapper extends BaseMapper<EmailRecord> {
}

View File

@@ -0,0 +1,47 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.FileRecord;
import com.eleadmin.common.system.param.FileRecordParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 文件上传记录Mapper
*
* @author EleAdmin
* @since 2021-08-30 11:18:04
*/
public interface FileRecordMapper extends BaseMapper<FileRecord> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<FileRecord>
*/
List<FileRecord> selectPageRel(@Param("page") IPage<FileRecord> page,
@Param("param") FileRecordParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<FileRecord>
*/
List<FileRecord> selectListRel(@Param("param") FileRecordParam param);
/**
* 根据path查询
*
* @param path 文件路径
* @return FileRecord
*/
@InterceptorIgnore(tenantLine = "true")
List<FileRecord> getByIdPath(@Param("path") String path);
}

View File

@@ -0,0 +1,48 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.LoginRecord;
import com.eleadmin.common.system.param.LoginRecordParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 登录日志Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:11
*/
public interface LoginRecordMapper extends BaseMapper<LoginRecord> {
/**
* 添加, 排除租户拦截
*
* @param entity LoginRecord
* @return int
*/
@Override
@InterceptorIgnore(tenantLine = "true")
int insert(LoginRecord entity);
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<LoginRecord>
*/
List<LoginRecord> selectPageRel(@Param("page") IPage<LoginRecord> page,
@Param("param") LoginRecordParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<LoginRecord>
*/
List<LoginRecord> selectListRel(@Param("param") LoginRecordParam param);
}

View File

@@ -0,0 +1,14 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.Menu;
/**
* 菜单Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:32
*/
public interface MenuMapper extends BaseMapper<Menu> {
}

View File

@@ -0,0 +1,48 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.OperationRecord;
import com.eleadmin.common.system.param.OperationRecordParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 操作日志Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:03
*/
public interface OperationRecordMapper extends BaseMapper<OperationRecord> {
/**
* 添加, 排除租户拦截
*
* @param entity OperationRecord
* @return int
*/
@Override
@InterceptorIgnore(tenantLine = "true")
int insert(OperationRecord entity);
/**
* 分页查询
*
* @param page 分页参数
* @param param 查询参数
* @return List<OperationRecord>
*/
List<OperationRecord> selectPageRel(@Param("page") IPage<OperationRecord> page,
@Param("param") OperationRecordParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<OperationRecord>
*/
List<OperationRecord> selectListRel(@Param("param") OperationRecordParam param);
}

View File

@@ -0,0 +1,37 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.Organization;
import com.eleadmin.common.system.param.OrganizationParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 组织机构Mapper
*
* @author EleAdmin
* @since 2020-03-14 11:29:04
*/
public interface OrganizationMapper extends BaseMapper<Organization> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<Organization>
*/
List<Organization> selectPageRel(@Param("page") IPage<Organization> page,
@Param("param") OrganizationParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<Organization>
*/
List<Organization> selectListRel(@Param("param") OrganizationParam param);
}

View File

@@ -0,0 +1,14 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.Role;
/**
* 角色Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:44
*/
public interface RoleMapper extends BaseMapper<Role> {
}

View File

@@ -0,0 +1,39 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.Menu;
import com.eleadmin.common.system.entity.RoleMenu;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色菜单Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:21
*/
public interface RoleMenuMapper extends BaseMapper<RoleMenu> {
/**
* 查询用户的菜单
*
* @param userId 用户id
* @param menuType 菜单类型
* @return List<Menu>
*/
@InterceptorIgnore(tenantLine = "true")
List<Menu> listMenuByUserId(@Param("userId") Integer userId, @Param("menuType") Integer menuType);
/**
* 根据角色id查询菜单
*
* @param roleIds 角色id
* @param menuType 菜单类型
* @return List<Menu>
*/
@InterceptorIgnore(tenantLine = "true")
List<Menu> listMenuByRoleIds(@Param("roleIds") List<Integer> roleIds, @Param("menuType") Integer menuType);
}

View File

@@ -0,0 +1,14 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.UserFile;
/**
* 用户文件Mapper
*
* @author EleAdmin
* @since 2022-07-21 14:34:40
*/
public interface UserFileMapper extends BaseMapper<UserFile> {
}

View File

@@ -0,0 +1,48 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.eleadmin.common.system.entity.User;
import com.eleadmin.common.system.param.UserParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:14
*/
public interface UserMapper extends BaseMapper<User> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<User>
*/
List<User> selectPageRel(@Param("page") IPage<User> page,
@Param("param") UserParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<User>
*/
List<User> selectListRel(@Param("param") UserParam param);
/**
* 根据账号查询
*
* @param username 账号
* @param tenantId 租户id
* @return User
*/
@InterceptorIgnore(tenantLine = "true")
User selectByUsername(@Param("username") String username, @Param("tenantId") Integer tenantId);
}

View File

@@ -0,0 +1,45 @@
package com.eleadmin.common.system.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eleadmin.common.system.entity.Role;
import com.eleadmin.common.system.entity.UserRole;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户角色Mapper
*
* @author EleAdmin
* @since 2018-12-24 16:10:02
*/
public interface UserRoleMapper extends BaseMapper<UserRole> {
/**
* 批量添加用户角色
*
* @param userId 用户id
* @param roleIds 角色id集合
* @return int
*/
int insertBatch(@Param("userId") Integer userId, @Param("roleIds") List<Integer> roleIds);
/**
* 根据用户id查询角色
*
* @param userId 用户id
* @return List<Role>
*/
@InterceptorIgnore(tenantLine = "true")
List<Role> selectByUserId(@Param("userId") Integer userId);
/**
* 批量根据用户id查询角色
*
* @param userIds 用户id集合
* @return List<RoleResult>
*/
List<Role> selectByUserIds(@Param("userIds") List<Integer> userIds);
}

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.eleadmin.common.system.mapper.DictionaryDataMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*,
b.dict_code,
b.dict_name
FROM sys_dictionary_data a
LEFT JOIN sys_dictionary b ON a.dict_id = b.dict_id
<where>
AND a.deleted = 0
<if test="param.dictDataId != null">
AND a.dict_data_id = #{param.dictDataId}
</if>
<if test="param.dictId != null">
AND a.dict_id = #{param.dictId}
</if>
<if test="param.dictDataCode != null">
AND a.dict_data_code LIKE CONCAT('%', #{param.dictDataCode}, '%')
</if>
<if test="param.dictDataName != null">
AND a.dict_data_name LIKE CONCAT('%', #{param.dictDataName}, '%')
</if>
<if test="param.comments != null">
AND a.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.dictCode != null">
AND b.dict_code = #{param.dictCode}
</if>
<if test="param.dictName != null">
AND b.dict_name = #{param.dictName}
</if>
<if test="param.keywords != null">
AND (
a.dict_data_code LIKE CONCAT('%', #{param.keywords}, '%')
OR a.dict_data_name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.eleadmin.common.system.entity.DictionaryData">
<include refid="selectSql"/>
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.eleadmin.common.system.entity.DictionaryData">
<include refid="selectSql"/>
</select>
<!-- 根据dictCode和dictDataName查询 -->
<select id="getByDictCodeAndName" resultType="com.eleadmin.common.system.entity.DictionaryData">
SELECT a.*,
b.dict_code,
b.dict_name
FROM sys_dictionary_data a
LEFT JOIN sys_dictionary b ON a.dict_id = b.dict_id
WHERE a.dict_data_name = #{dictDataName}
AND b.dict_code = #{dictCode}
</select>
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.eleadmin.common.system.mapper.DictionaryMapper">
</mapper>

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