Merge branch 'master' of https://gitee.com/youlaiorg/youlai-boot
This commit is contained in:
74
README.md
74
README.md
@@ -20,13 +20,11 @@
|
|||||||

|

|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a target="_blank" href="https://admin.youlai.tech/">🔍 在线预览</a> | <a target="_blank" href="https://doc.youlai.tech/%E5%89%8D%E5%90%8E%E7%AB%AF%E6%A8%A1%E6%9D%BF/%E5%90%8E%E7%AB%AF%E6%89%8B%E5%86%8C/%E9%A1%B9%E7%9B%AE%E7%AE%80%E4%BB%8B.html">📖 阅读文档</a> | <a href="./README.en-US.md">🌐English</a>
|
<a target="_blank" href="https://admin.youlai.tech/">🔍 在线预览</a> | <a target="_blank" href="https://youlai.blog.csdn.net/article/details/145178880">📖 阅读文档</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 📢 项目简介
|
## 📢 项目简介
|
||||||
|
|
||||||
**在线预览**: [https://vue3.youlai.tech](https://vue3.youlai.tech)
|
|
||||||
|
|
||||||
基于 JDK 17、Spring Boot 3、Spring Security 6、JWT、Redis、Mybatis-Plus、Knife4j、Vue 3、Element-Plus 构建的前后端分离单体权限管理系统。
|
基于 JDK 17、Spring Boot 3、Spring Security 6、JWT、Redis、Mybatis-Plus、Knife4j、Vue 3、Element-Plus 构建的前后端分离单体权限管理系统。
|
||||||
|
|
||||||
- **🚀 开发框架**: 使用 Spring Boot 3 和 Vue 3,以及 Element-Plus 等主流技术栈,实时更新。
|
- **🚀 开发框架**: 使用 Spring Boot 3 和 Vue 3,以及 Element-Plus 等主流技术栈,实时更新。
|
||||||
@@ -37,7 +35,37 @@
|
|||||||
|
|
||||||
- **🛠️ 功能模块**: 包括用户管理、角色管理、菜单管理、部门管理、字典管理等多个功能。
|
- **🛠️ 功能模块**: 包括用户管理、角色管理、菜单管理、部门管理、字典管理等多个功能。
|
||||||
|
|
||||||
- **📘 接口文档**: 自动生成接口文档,支持在线调试,提高开发效率。
|
|
||||||
|
## 🌈 项目地址
|
||||||
|
|
||||||
|
- **在线预览**:[https://vue.youlai.tech](https://vue.youlai.tech)
|
||||||
|
- **前端项目**:[vue3-element-admin](https://gitee.com/youlaiorg/vue3-element-admin)
|
||||||
|
- **接口文档**:[https://www.apifox.cn/apidoc](https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5)
|
||||||
|
- **项目文档**:[youlai-boot 企业级权限管理系统全功能详解](https://youlai.blog.csdn.net/article/details/145178880)
|
||||||
|
- **从0到1文档**:[从0到1搭建 youlai-boot 企业级权限管理系统](https://youlai.blog.csdn.net/article/details/145177011)
|
||||||
|
|
||||||
|
## 🚀 项目启动
|
||||||
|
|
||||||
|
1. **克隆项目**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitee.com/youlaiorg/youlai-boot.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **数据库初始化**
|
||||||
|
|
||||||
|
执行 [youlai_boot.sql](sql/mysql8/youlai_boot.sql) 脚本完成数据库创建、表结构和基础数据的初始化。
|
||||||
|
|
||||||
|
3. **修改配置**
|
||||||
|
|
||||||
|
[application-dev.yml](src/main/resources/application-dev.yml) 修改MySQL、Redis连接配置;
|
||||||
|
|
||||||
|
4. **启动项目**
|
||||||
|
|
||||||
|
执行 [YoulaiBootApplication.java](src/main/java/com/youlai/boot/YoulaiBootApplication.java) 的 main 方法完成后端项目启动;
|
||||||
|
|
||||||
|
访问接口文档地址 [http://localhost:8989/doc.html](http://localhost:8989/doc.html) 验证项目启动是否成功。
|
||||||
|
|
||||||
|
|
||||||
## 📁 项目目录
|
## 📁 项目目录
|
||||||
```
|
```
|
||||||
@@ -47,6 +75,7 @@ youlai-boot
|
|||||||
│ └── mysql8 # MySQL8 脚本
|
│ └── mysql8 # MySQL8 脚本
|
||||||
├── src # 源码目录
|
├── src # 源码目录
|
||||||
│ ├── common # 公共模块
|
│ ├── common # 公共模块
|
||||||
|
│ │ ├── annotation # 注解定义
|
||||||
│ │ ├── base # 基础类
|
│ │ ├── base # 基础类
|
||||||
│ │ ├── constant # 常量
|
│ │ ├── constant # 常量
|
||||||
│ │ ├── enums # 枚举类型
|
│ │ ├── enums # 枚举类型
|
||||||
@@ -71,7 +100,7 @@ youlai-boot
|
|||||||
│ │ ├── WebSocketConfig # WebSocket 自动装配配置
|
│ │ ├── WebSocketConfig # WebSocket 自动装配配置
|
||||||
│ │ └── XxlJobConfig # XXL-JOB 自动装配配置
|
│ │ └── XxlJobConfig # XXL-JOB 自动装配配置
|
||||||
│ ├── core # 核心功能
|
│ ├── core # 核心功能
|
||||||
│ │ ├── annotation # 注解定义
|
|
||||||
│ │ ├── aspect # 切面
|
│ │ ├── aspect # 切面
|
||||||
│ │ │ ├── LogAspect # 日志切面
|
│ │ │ ├── LogAspect # 日志切面
|
||||||
│ │ │ └── RepeatSubmitAspect # 防重提交切面
|
│ │ │ └── RepeatSubmitAspect # 防重提交切面
|
||||||
@@ -81,7 +110,7 @@ youlai-boot
|
|||||||
│ │ ├── handler # 处理器
|
│ │ ├── handler # 处理器
|
||||||
│ │ │ ├── MyDataPermissionHandler # 数据权限处理器
|
│ │ │ ├── MyDataPermissionHandler # 数据权限处理器
|
||||||
│ │ │ └── MyMetaObjectHandler # 元对象字段填充处理器
|
│ │ │ └── MyMetaObjectHandler # 元对象字段填充处理器
|
||||||
│ │ └── security # Security 安全中心
|
│ │ └── security # Spring Security 安全模块
|
||||||
│ ├── modules # 业务模块
|
│ ├── modules # 业务模块
|
||||||
│ │ ├── member # 会员模块【业务模块演示】
|
│ │ ├── member # 会员模块【业务模块演示】
|
||||||
│ │ ├── order # 订单模块【业务模块演示】
|
│ │ ├── order # 订单模块【业务模块演示】
|
||||||
@@ -112,35 +141,8 @@ youlai-boot
|
|||||||
└── end
|
└── end
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🌺 前端工程
|
|
||||||
| Gitee | Github |
|
|
||||||
|-------|------|
|
|
||||||
| [vue3-element-admin](https://gitee.com/youlaiorg/vue3-element-admin) | [vue3-element-admin](https://github.com/youlaitech/vue3-element-admin) |
|
|
||||||
|
|
||||||
|
|
||||||
## 🌈 接口文档
|
|
||||||
|
|
||||||
- `knife4j` 接口文档:[http://localhost:8989/doc.html](http://localhost:8989/doc.html)
|
|
||||||
- `swagger` 接口文档:[http://localhost:8989/swagger-ui/index.html](http://localhost:8989/swagger-ui/index.html)
|
|
||||||
- `apifox` 在线接口文档:[https://www.apifox.cn/apidoc](https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5)
|
|
||||||
|
|
||||||
|
|
||||||
## 🚀 项目启动
|
|
||||||
|
|
||||||
1. **数据库初始化**
|
|
||||||
|
|
||||||
执行 [youlai_boot.sql](sql/mysql8/youlai_boot.sql) 脚本完成数据库创建、表结构和基础数据的初始化。
|
|
||||||
|
|
||||||
2. **修改配置**
|
|
||||||
|
|
||||||
[application-dev.yml](src/main/resources/application-dev.yml) 修改MySQL、Redis连接配置;
|
|
||||||
|
|
||||||
3. **启动项目**
|
|
||||||
|
|
||||||
执行 [SystemApplication.java](src/main/java/com/youlai/boot/YouLaiApplication.java) 的 main 方法完成后端项目启动;
|
|
||||||
|
|
||||||
访问接口文档地址 [http://localhost:8989/doc.html](http://localhost:8989/doc.html) 验证项目启动是否成功。
|
|
||||||
|
|
||||||
## ✅ 项目统计
|
## ✅ 项目统计
|
||||||
|
|
||||||

|

|
||||||
@@ -152,9 +154,7 @@ Thanks to all the contributors!
|
|||||||
|
|
||||||
## 💖 加交流群
|
## 💖 加交流群
|
||||||
|
|
||||||
> 关注公众号【有来技术】,获取交流群二维码,不想关注公众号或二维码过期欢迎加我微信(`haoxianrui`)备注【有来】即可,拉你进群。
|
> 关注公众号 有来技术 ,点击菜单 交流群 获取加群二维码。
|
||||||
|
|
||||||
|  |
|
|
||||||
|---------------------------------------------------------|
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ public class BusinessException extends RuntimeException {
|
|||||||
this.resultCode = errorCode;
|
this.resultCode = errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BusinessException(IResultCode errorCode,String message) {
|
||||||
|
super(message);
|
||||||
|
this.resultCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public BusinessException(String message, Throwable cause) {
|
public BusinessException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局系统异常处理器
|
* 全局系统异常处理器
|
||||||
* <p>
|
* <p>
|
||||||
@@ -219,9 +218,9 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
public <T> Result<T> handleBizException(BusinessException e) {
|
public <T> Result<T> handleBizException(BusinessException e) {
|
||||||
log.error("biz exception: {}", e.getMessage());
|
log.error("biz exception", e);
|
||||||
if (e.getResultCode() != null) {
|
if (e.getResultCode() != null) {
|
||||||
return Result.failed(e.getResultCode());
|
return Result.failed(e.getResultCode(), e.getMessage());
|
||||||
}
|
}
|
||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -239,8 +238,7 @@ public class GlobalExceptionHandler {
|
|||||||
|| e instanceof AuthenticationException) {
|
|| e instanceof AuthenticationException) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
log.error("unknown exception: {}", e.getMessage());
|
log.error("unknown exception", e);
|
||||||
e.printStackTrace();
|
|
||||||
return Result.failed(e.getLocalizedMessage());
|
return Result.failed(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package com.youlai.boot.common.result;
|
|||||||
/**
|
/**
|
||||||
* 响应码接口
|
* 响应码接口
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 2022/2/18
|
* @since 1.0.0
|
||||||
**/
|
**/
|
||||||
public interface IResultCode {
|
public interface IResultCode {
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public enum ResultCode implements IResultCode, Serializable {
|
|||||||
VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"),
|
VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"),
|
||||||
|
|
||||||
USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"),
|
USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"),
|
||||||
USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证<EFBFBD><EFBFBD>类型未选择"),
|
USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证件类型未选择"),
|
||||||
MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"),
|
MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"),
|
||||||
|
|
||||||
USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"),
|
USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"),
|
||||||
@@ -127,12 +127,14 @@ public enum ResultCode implements IResultCode, Serializable {
|
|||||||
USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"),
|
USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"),
|
||||||
|
|
||||||
/** 二级宏观错误码 */
|
/** 二级宏观错误码 */
|
||||||
USER_UPLOAD_FILE_EXCEPTION("A0700", "用户上传文件异常"),
|
UPLOAD_FILE_EXCEPTION("A0700", "上传文件异常"),
|
||||||
USER_UPLOAD_FILE_TYPE_MISMATCH("A0701", "用户上传文件类型不匹配"),
|
UPLOAD_FILE_TYPE_MISMATCH("A0701", "上传文件类型不匹配"),
|
||||||
USER_UPLOAD_FILE_TOO_LARGE("A0702", "用户上传文件太大"),
|
UPLOAD_FILE_TOO_LARGE("A0702", "上传文件太大"),
|
||||||
USER_UPLOAD_IMAGE_TOO_LARGE("A0703", "用户上传图片太大"),
|
UPLOAD_IMAGE_TOO_LARGE("A0703", "上传图片太大"),
|
||||||
USER_UPLOAD_VIDEO_TOO_LARGE("A0704", "用户上传视频太大"),
|
UPLOAD_VIDEO_TOO_LARGE("A0704", "上传视频太大"),
|
||||||
USER_UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "用户上传压缩文件太大"),
|
UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "上传压缩文件太大"),
|
||||||
|
|
||||||
|
DELETE_FILE_EXCEPTION("A0710", "删除文件异常"),
|
||||||
|
|
||||||
/** 二级宏观错误码 */
|
/** 二级宏观错误码 */
|
||||||
USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"),
|
USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"),
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.youlai.boot.config;
|
package com.youlai.boot.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateTime;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import jakarta.validation.Validation;
|
import jakarta.validation.Validation;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
import jakarta.validation.ValidatorFactory;
|
import jakarta.validation.ValidatorFactory;
|
||||||
@@ -21,6 +24,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,8 +56,12 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
SimpleModule simpleModule = new SimpleModule();
|
SimpleModule simpleModule = new SimpleModule();
|
||||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||||
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
|
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
|
||||||
|
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
|
||||||
|
DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN).withZone(ZoneId.of( "GMT+8"))
|
||||||
|
));
|
||||||
objectMapper.registerModule(simpleModule);
|
objectMapper.registerModule(simpleModule);
|
||||||
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN));
|
||||||
|
|
||||||
|
|
||||||
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
|
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
|
||||||
converters.add(1, jackson2HttpMessageConverter);
|
converters.add(1, jackson2HttpMessageConverter);
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ package com.youlai.boot.shared.file.model;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件信息对象
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
@Schema(description = "文件对象")
|
@Schema(description = "文件对象")
|
||||||
@Data
|
@Data
|
||||||
public class FileInfo {
|
public class FileInfo {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.youlai.boot.shared.file.service.impl;
|
package com.youlai.boot.shared.file.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
@@ -7,6 +8,7 @@ import com.youlai.boot.shared.file.model.FileInfo;
|
|||||||
import com.youlai.boot.shared.file.service.FileService;
|
import com.youlai.boot.shared.file.service.FileService;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
@@ -23,30 +25,37 @@ import java.time.LocalDateTime;
|
|||||||
* @author Theo
|
* @author Theo
|
||||||
* @since 2024-12-09 17:11
|
* @since 2024-12-09 17:11
|
||||||
*/
|
*/
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnProperty(value = "oss.type", havingValue = "local")
|
@ConditionalOnProperty(value = "oss.type", havingValue = "local")
|
||||||
@ConfigurationProperties(prefix = "oss.local")
|
@ConfigurationProperties(prefix = "oss.local")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Data
|
|
||||||
public class LocalFileService implements FileService {
|
public class LocalFileService implements FileService {
|
||||||
|
|
||||||
@Value("${oss.local.storage-path}")
|
@Value("${oss.local.storage-path}")
|
||||||
private String storagePath;
|
private String storagePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件方法
|
||||||
|
*
|
||||||
|
* @param file 表单文件对象
|
||||||
|
* @return 文件信息
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public FileInfo uploadFile(MultipartFile file) {
|
public FileInfo uploadFile(MultipartFile file) {
|
||||||
// 生成文件名(日期文件夹)
|
// 生成文件名(日期文件夹)
|
||||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||||
String uuid = IdUtil.simpleUUID();
|
String uuid = IdUtil.simpleUUID();
|
||||||
String folder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + File.separator;
|
String folder = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
|
||||||
String fileName = uuid + "." + suffix;
|
String fileName = uuid + "." + suffix;
|
||||||
String filePrefix = storagePath.endsWith(File.separator) ? storagePath : storagePath + File.separator;
|
String filePrefix = storagePath.endsWith(File.separator) ? storagePath : storagePath + File.separator;
|
||||||
// try-with-resource 语法糖自动释放流
|
// try-with-resource 语法糖自动释放流
|
||||||
try (InputStream inputStream = file.getInputStream()) {
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
// 上传文件
|
// 上传文件
|
||||||
FileUtil.writeFromStream(inputStream, filePrefix +folder+ fileName);
|
FileUtil.writeFromStream(inputStream, filePrefix + folder + File.separator + fileName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("文件上传失败", e);
|
||||||
throw new RuntimeException("文件上传失败");
|
throw new RuntimeException("文件上传失败");
|
||||||
}
|
}
|
||||||
// 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀
|
// 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀
|
||||||
@@ -57,6 +66,12 @@ public class LocalFileService implements FileService {
|
|||||||
return fileInfo;
|
return fileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
* @param filePath 文件完整URL
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteFile(String filePath) {
|
public boolean deleteFile(String filePath) {
|
||||||
//判断文件是否为空
|
//判断文件是否为空
|
||||||
|
|||||||
@@ -5,30 +5,30 @@ import cn.hutool.core.io.FileUtil;
|
|||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
import com.youlai.boot.shared.file.service.FileService;
|
import com.youlai.boot.shared.file.service.FileService;
|
||||||
import com.youlai.boot.shared.file.model.FileInfo;
|
import com.youlai.boot.shared.file.model.FileInfo;
|
||||||
import io.minio.*;
|
import io.minio.*;
|
||||||
import io.minio.errors.*;
|
|
||||||
import io.minio.http.Method;
|
import io.minio.http.Method;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MinIO 文件上传服务类
|
* MinIO 文件上传服务类
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author Ray.Hao
|
||||||
* @since 2023/6/2
|
* @since 2023/6/2
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@@ -36,6 +36,7 @@ import java.time.LocalDateTime;
|
|||||||
@ConfigurationProperties(prefix = "oss.minio")
|
@ConfigurationProperties(prefix = "oss.minio")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Data
|
@Data
|
||||||
|
@Slf4j
|
||||||
public class MinioFileService implements FileService {
|
public class MinioFileService implements FileService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +78,7 @@ public class MinioFileService implements FileService {
|
|||||||
* 上传文件
|
* 上传文件
|
||||||
*
|
*
|
||||||
* @param file 表单文件对象
|
* @param file 表单文件对象
|
||||||
* @return
|
* @return 文件信息
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public FileInfo uploadFile(MultipartFile file) {
|
public FileInfo uploadFile(MultipartFile file) {
|
||||||
@@ -85,16 +86,19 @@ public class MinioFileService implements FileService {
|
|||||||
// 创建存储桶(存储桶不存在),如果有搭建好的minio服务,建议放在init方法中
|
// 创建存储桶(存储桶不存在),如果有搭建好的minio服务,建议放在init方法中
|
||||||
createBucketIfAbsent(bucketName);
|
createBucketIfAbsent(bucketName);
|
||||||
|
|
||||||
// 生成文件名(日期文件夹)
|
// 文件后缀
|
||||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||||
String uuid = IdUtil.simpleUUID();
|
// 文件夹名称
|
||||||
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
|
String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd");
|
||||||
|
// 文件名称
|
||||||
|
String fileName = IdUtil.simpleUUID() + "." + suffix;
|
||||||
|
|
||||||
// try-with-resource 语法糖自动释放流
|
// try-with-resource 语法糖自动释放流
|
||||||
try (InputStream inputStream = file.getInputStream()) {
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
// 文件上传
|
// 文件上传
|
||||||
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
|
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
|
||||||
.bucket(bucketName)
|
.bucket(bucketName)
|
||||||
.object(fileName)
|
.object(dateFolder + "/"+ fileName)
|
||||||
.contentType(file.getContentType())
|
.contentType(file.getContentType())
|
||||||
.stream(inputStream, inputStream.available(), -1)
|
.stream(inputStream, inputStream.available(), -1)
|
||||||
.build();
|
.build();
|
||||||
@@ -104,15 +108,18 @@ public class MinioFileService implements FileService {
|
|||||||
String fileUrl;
|
String fileUrl;
|
||||||
// 未配置自定义域名
|
// 未配置自定义域名
|
||||||
if (StrUtil.isBlank(customDomain)) {
|
if (StrUtil.isBlank(customDomain)) {
|
||||||
|
// 获取文件URL
|
||||||
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
|
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
|
||||||
.bucket(bucketName).object(fileName)
|
.bucket(bucketName)
|
||||||
|
.object(dateFolder + "/"+ fileName)
|
||||||
.method(Method.GET)
|
.method(Method.GET)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
|
fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
|
||||||
fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
|
fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
|
||||||
} else { // 配置自定义文件路径域名
|
} else {
|
||||||
fileUrl = customDomain + '/' + bucketName + "/" + fileName;
|
// 配置自定义文件路径域名
|
||||||
|
fileUrl = customDomain + "/"+ bucketName + "/"+ dateFolder + "/"+ fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
FileInfo fileInfo = new FileInfo();
|
||||||
@@ -120,7 +127,8 @@ public class MinioFileService implements FileService {
|
|||||||
fileInfo.setUrl(fileUrl);
|
fileInfo.setUrl(fileUrl);
|
||||||
return fileInfo;
|
return fileInfo;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("文件上传失败");
|
log.error("上传文件失败", e);
|
||||||
|
throw new BusinessException(ResultCode.UPLOAD_FILE_EXCEPTION, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +136,8 @@ public class MinioFileService implements FileService {
|
|||||||
/**
|
/**
|
||||||
* 删除文件
|
* 删除文件
|
||||||
*
|
*
|
||||||
* @param filePath 文件路径 http://localhost:9000/default/20221120/test.jpg
|
* @param filePath 文件完整路径
|
||||||
*
|
* @return 是否删除成功
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteFile(String filePath) {
|
public boolean deleteFile(String filePath) {
|
||||||
@@ -152,7 +159,8 @@ public class MinioFileService implements FileService {
|
|||||||
minioClient.removeObject(removeObjectArgs);
|
minioClient.removeObject(removeObjectArgs);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("文件删除失败", e);
|
log.error("删除文件失败", e);
|
||||||
|
throw new BusinessException(ResultCode.DELETE_FILE_EXCEPTION, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,17 +169,11 @@ public class MinioFileService implements FileService {
|
|||||||
* PUBLIC桶策略
|
* PUBLIC桶策略
|
||||||
* 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied
|
* 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied
|
||||||
*
|
*
|
||||||
* @param bucketName
|
* @param bucketName 存储桶名称
|
||||||
* @return
|
* @return 存储桶策略
|
||||||
*/
|
*/
|
||||||
private static String publicBucketPolicy(String bucketName) {
|
private static String publicBucketPolicy(String bucketName) {
|
||||||
/**
|
// AWS的S3存储桶策略 JSON 格式 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example-bucket-policies.html
|
||||||
* AWS的S3存储桶策略
|
|
||||||
* Principal: 生效用户对象
|
|
||||||
* Resource: 指定存储桶
|
|
||||||
* Action: 操作行为
|
|
||||||
*/
|
|
||||||
|
|
||||||
return "{\"Version\":\"2012-10-17\","
|
return "{\"Version\":\"2012-10-17\","
|
||||||
+ "\"Statement\":[{\"Effect\":\"Allow\","
|
+ "\"Statement\":[{\"Effect\":\"Allow\","
|
||||||
+ "\"Principal\":{\"AWS\":[\"*\"]},"
|
+ "\"Principal\":{\"AWS\":[\"*\"]},"
|
||||||
@@ -185,7 +187,7 @@ public class MinioFileService implements FileService {
|
|||||||
/**
|
/**
|
||||||
* 创建存储桶(存储桶不存在)
|
* 创建存储桶(存储桶不存在)
|
||||||
*
|
*
|
||||||
* @param bucketName
|
* @param bucketName 存储桶名称
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void createBucketIfAbsent(String bucketName) {
|
private void createBucketIfAbsent(String bucketName) {
|
||||||
|
|||||||
@@ -10,29 +10,29 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.youlai.boot.common.constant.RedisConstants;
|
import com.youlai.boot.common.constant.RedisConstants;
|
||||||
import com.youlai.boot.common.constant.SystemConstants;
|
import com.youlai.boot.common.constant.SystemConstants;
|
||||||
import com.youlai.boot.core.security.manager.TokenManager;
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
import com.youlai.boot.common.model.Option;
|
import com.youlai.boot.common.model.Option;
|
||||||
|
import com.youlai.boot.core.security.manager.TokenManager;
|
||||||
|
import com.youlai.boot.core.security.service.PermissionService;
|
||||||
|
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||||
import com.youlai.boot.shared.mail.service.MailService;
|
import com.youlai.boot.shared.mail.service.MailService;
|
||||||
import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
|
import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
|
||||||
import com.youlai.boot.shared.sms.service.SmsService;
|
import com.youlai.boot.shared.sms.service.SmsService;
|
||||||
|
import com.youlai.boot.system.converter.UserConverter;
|
||||||
|
import com.youlai.boot.system.enums.DictCodeEnum;
|
||||||
|
import com.youlai.boot.system.mapper.UserMapper;
|
||||||
|
import com.youlai.boot.system.model.bo.UserBO;
|
||||||
|
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||||
|
import com.youlai.boot.system.model.dto.UserExportDTO;
|
||||||
|
import com.youlai.boot.system.model.entity.DictData;
|
||||||
import com.youlai.boot.system.model.entity.User;
|
import com.youlai.boot.system.model.entity.User;
|
||||||
import com.youlai.boot.system.model.entity.UserRole;
|
import com.youlai.boot.system.model.entity.UserRole;
|
||||||
import com.youlai.boot.system.model.form.*;
|
import com.youlai.boot.system.model.form.*;
|
||||||
import com.youlai.boot.system.converter.UserConverter;
|
|
||||||
import com.youlai.boot.common.exception.BusinessException;
|
|
||||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
|
||||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
|
||||||
import com.youlai.boot.system.mapper.UserMapper;
|
|
||||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
|
||||||
import com.youlai.boot.system.model.bo.UserBO;
|
|
||||||
import com.youlai.boot.system.model.query.UserPageQuery;
|
import com.youlai.boot.system.model.query.UserPageQuery;
|
||||||
import com.youlai.boot.system.model.dto.UserExportDTO;
|
|
||||||
import com.youlai.boot.system.model.vo.UserInfoVO;
|
import com.youlai.boot.system.model.vo.UserInfoVO;
|
||||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||||
import com.youlai.boot.core.security.service.PermissionService;
|
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||||
import com.youlai.boot.system.service.RoleService;
|
import com.youlai.boot.system.service.*;
|
||||||
import com.youlai.boot.system.service.UserRoleService;
|
|
||||||
import com.youlai.boot.system.service.UserService;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
@@ -69,6 +69,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
|
|
||||||
private final TokenManager tokenManager;
|
private final TokenManager tokenManager;
|
||||||
|
|
||||||
|
private final DictDataService dictDataService;
|
||||||
|
|
||||||
private final UserConverter userConverter;
|
private final UserConverter userConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,7 +276,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<UserExportDTO> listExportUsers(UserPageQuery queryParams) {
|
public List<UserExportDTO> listExportUsers(UserPageQuery queryParams) {
|
||||||
return this.baseMapper.listExportUsers(queryParams);
|
List<UserExportDTO> userExportDTOS = this.baseMapper.listExportUsers(queryParams);
|
||||||
|
//获取角色的字典数据
|
||||||
|
List<DictData> list = dictDataService.list(new LambdaQueryWrapper<DictData>().eq(DictData::getDictCode, DictCodeEnum.GENDER.getValue()));
|
||||||
|
Map<String, String> genderMap = list.stream().collect(Collectors.toMap(DictData::getValue, DictData::getLabel));
|
||||||
|
userExportDTOS.forEach(userExportDTO -> {
|
||||||
|
String genderLabel = genderMap.get(userExportDTO.getGender());
|
||||||
|
userExportDTO.setGender(genderLabel);
|
||||||
|
});
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
u.gender,
|
u.gender,
|
||||||
u.avatar,
|
u.avatar,
|
||||||
u.STATUS,
|
u.STATUS,
|
||||||
|
u.email,
|
||||||
d.NAME AS dept_name,
|
d.NAME AS dept_name,
|
||||||
GROUP_CONCAT( r.NAME ) AS roleNames,
|
GROUP_CONCAT( r.NAME ) AS roleNames,
|
||||||
u.create_time
|
u.create_time
|
||||||
@@ -182,11 +183,8 @@
|
|||||||
u.username,
|
u.username,
|
||||||
u.nickname,
|
u.nickname,
|
||||||
u.mobile,
|
u.mobile,
|
||||||
CASE u.gender
|
u.email,
|
||||||
WHEN 1 THEN '男'
|
u.gender,
|
||||||
WHEN 2 THEN '女'
|
|
||||||
ELSE '保密'
|
|
||||||
END gender,
|
|
||||||
d.NAME AS dept_name,
|
d.NAME AS dept_name,
|
||||||
u.create_time
|
u.create_time
|
||||||
FROM
|
FROM
|
||||||
@@ -219,6 +217,7 @@
|
|||||||
u.gender,
|
u.gender,
|
||||||
u.avatar,
|
u.avatar,
|
||||||
u.STATUS,
|
u.STATUS,
|
||||||
|
u.email,
|
||||||
d.NAME AS deptName,
|
d.NAME AS deptName,
|
||||||
GROUP_CONCAT(r.NAME) AS roleNames,
|
GROUP_CONCAT(r.NAME) AS roleNames,
|
||||||
u.create_time
|
u.create_time
|
||||||
|
|||||||
Reference in New Issue
Block a user