提交 7931c583 作者: 方治民

feat: 新增通用用户及权限相关、MinIO 文件上传实现, 依赖更新等

上级 a36fee51
...@@ -24,4 +24,7 @@ ...@@ -24,4 +24,7 @@
<!-- prettier-ignore --> <!-- prettier-ignore -->
- [x] 完成项目构建,开发文档编写 - [x] 完成项目构建,开发文档编写
- [x] [conventional-changelog](https://www.cnblogs.com/mengfangui/p/12634845.html) - [x] [conventional-changelog](https://www.cnblogs.com/mengfangui/p/12634845.html)
- [ ] 设计权限模块 - [x] 用户及权限模块(菜单/按钮)
- [x] 通用文件上传模块
- [ ] 通用字典管理模块
- [ ] XXL-JOB 定时任务模块
...@@ -6,23 +6,28 @@ bootJar { ...@@ -6,23 +6,28 @@ bootJar {
} }
dependencies { dependencies {
implementation project(":basic-auth")
implementation project(":basic-common:core")
implementation project(":basic-common:util")
implementation project(":basic-common:doc")
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// 💬 Mock/Test Env // 💬 Mock/Test Env
runtimeOnly 'com.h2database:h2' runtimeOnly 'com.h2database:h2'
// 💬 Prod/Dev Env // 💬 Prod/Dev Env
// runtimeOnly 'mysql:mysql-connector-java' runtimeOnly 'mysql:mysql-connector-java'
// runtimeOnly 'org.postgresql:postgresql' // runtimeOnly 'org.postgresql:postgresql'
// sa-token implementation project(":basic-common:core")
implementation "cn.dev33:sa-token-spring-boot-starter:${saTokenVersion}" implementation project(":basic-common:util")
// swagger // Optional: Doc
implementation project(":basic-common:doc")
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}" implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}" implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}"
// Optional: Auth
implementation project(":basic-auth")
implementation "cn.dev33:sa-token-spring-boot-starter:${saTokenVersion}"
// Optional: Minio S3
implementation project(":basic-common:minio")
// FIX: minio dep
implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}"
} }
...@@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.ResponseBody; ...@@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* 全局错误处理 * 全局错误处理
* *
* @author fangzhimin * @author ifzm
* 2017年11月30日 上午11:36:31 * 2017年11月30日 上午11:36:31
*/ */
@Slf4j @Slf4j
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.app.constant;
import io.swagger.annotations.ApiModel;
/**
* 业务状态码
* eg: <code>Result.no(Status.BAD_REQUEST, Code.SUCCESS.value(), Code.SUCCESS.reason())</code>
*
* @author Jim
* @version 0.1
* 2022/3/25 9:23
*/
@SuppressWarnings({ "unused" })
@ApiModel("业务状态码")
public enum Code {
SUCCESS(0, "成功"),
// TODO: 扩展业务状态
// eg:
// 10001: 用户被禁止登录
FAIL(10000, "测试错误");
private final int value;
private final String reasonPhrase;
Code(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
/**
* Return the integer value of this status code.
*/
public int value() {
return this.value;
}
/**
* Return the reason phrase of this status code.
*/
public String reason() {
return this.reasonPhrase;
}
}
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.core;
// 核心包
// 原则: 命名简明扼要, 写明用途
// 通常用于存放封装后的 Redis、Minio、Sms 等工具包
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.domain;
// 业务数据库表实体包
// 原则: 命名简明扼要, 写明用途
// 通常用于存放业务数据表 Domain + Repository
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.param;
// 接口输入参数包
// 原则: 命名简明扼要, 写明用途
// 通常用于存放接口入参类,一般与 Controller 包对应
// eg: 例如存在一个 UserController, 内部有一个 find 的接口实现, 接口对应的入参类应为 FindUserParam
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.service;
// 业务实现类包
// 原则: 命名简明扼要, 写明用途
// 通常用于存放 Controller 对应的业务实现类,以及一些通用的业务实现类(需要事务控制的业务实现必须编写 Service 接口及实现)
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.util;
// 工具类包
// 原则: 命名简明扼要, 写明用途
// 避免重复实现,尽可能利用导入的工具包实现,减少手动编写重复代码,可将编写的一些代码片段封装后放着此处供其他地方使用
/**
* @author Jim
* @version 0.1
* 2022/3/25 16:28
*/
package com.yiring.app.vo;
// 接口输出参数包
// 原则: 命名简明扼要, 写明用途
// 通常用于存放接口出参类,一般与 Controller 包对应
// eg: 例如存在一个 UserController, 内部有一个 find 的接口实现, 接口对应的入参类应为 FindUserVo, 通用/泛型包装类除外
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.web; package com.yiring.app.web;
import com.yiring.app.param.IdParam; import com.yiring.app.constant.Code;
import com.yiring.app.param.PageParam;
import com.yiring.app.vo.PageVo;
import com.yiring.common.core.Result; import com.yiring.common.core.Result;
import com.yiring.common.core.Status; import com.yiring.common.param.PageParam;
import com.yiring.common.vo.PageVo;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -13,24 +12,29 @@ import javax.validation.Valid; ...@@ -13,24 +12,29 @@ import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@Validated @Validated
@Api(tags = "Hello World") @Api(tags = "Hello World")
@RestController("/hello/") @RequestMapping("/hello/")
@RestController
public class HelloController { public class HelloController {
String text = "😎 Hello World"; String text = "😎 Hello World";
@GetMapping @GetMapping
public Result<String> hello() { public Result<String> hello() {
return Result.no(Status.BAD_REQUEST, "测试错误"); return Result.ok(text);
} }
@GetMapping("id") /**
public Result<Long> id(@Valid IdParam param) { * 测试失败自定义状态信息输出
return Result.ok(param.getId()); */
@GetMapping("fail")
public Result<String> fail() {
return Result.no(Code.FAIL.value(), Code.FAIL.reason());
} }
@GetMapping("page") @GetMapping("page")
......
spring: spring:
datasource: datasource:
url: jdbc:mysql://127.0.0.1:3306/basic?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai url: jdbc:mysql://127.0.0.1:3306/basic_app?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root username: root
password: 123456 password: 123456
jpa: jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
open-in-view: true open-in-view: true
hibernate: hibernate:
ddl-auto: update ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
redis:
database: 5
host: 127.0.0.1
password: 123456
# knife4j
knife4j: knife4j:
enable: true enable: true
basic: basic:
enable: true enable: false
username: admin username: admin
password: 123456 password: 123456
setting: setting:
enableOpenApi: false enableOpenApi: false
enableDebug: true enableDebug: true
# minio
minio:
access-key: minioadmin
secret-key: minioadmin
end-point: "http://127.0.0.1:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
logging:
level:
# sql bind parameter
org.hibernate.type.descriptor.sql.BasicBinder: trace
...@@ -12,13 +12,26 @@ spring: ...@@ -12,13 +12,26 @@ spring:
h2: h2:
console: console:
enabled: true enabled: true
redis:
database: 5
host: 127.0.0.1
password: 123456
# knife4j
knife4j: knife4j:
enable: true enable: true
basic: basic:
enable: true enable: false
username: admin username: admin
password: 123456 password: 123456
setting: setting:
enableOpenApi: false enableOpenApi: false
enableDebug: true enableDebug: true
# minio
minio:
access-key: minioadmin
secret-key: minioadmin
end-point: "http://127.0.0.1:18100"
bucket: basic
domain: ${minio.endpoint}/${minio.bucket}
...@@ -8,7 +8,7 @@ spring: ...@@ -8,7 +8,7 @@ spring:
name: "basic-api-app" name: "basic-api-app"
profiles: profiles:
include: auth include: auth
active: mock active: dev
# DEBUG # DEBUG
debug: false debug: false
dependencies { dependencies {
implementation project(':basic-common:core') implementation project(':basic-common:core')
implementation project(':basic-common:util')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// 本地依赖
implementation fileTree(dir: project.rootDir.getPath() + '\\libs', includes: ['*jar'])
// swagger annotations
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}"
// sa-token
implementation "cn.dev33:sa-token-spring-boot-starter:${saTokenVersion}" implementation "cn.dev33:sa-token-spring-boot-starter:${saTokenVersion}"
implementation "cn.dev33:sa-token-dao-redis-jackson:${saTokenVersion}"
// hutool-core
implementation "cn.hutool:hutool-core:${hutoolVersion}"
} }
...@@ -30,8 +30,11 @@ public class SaTokenConfigure implements WebMvcConfigurer { ...@@ -30,8 +30,11 @@ public class SaTokenConfigure implements WebMvcConfigurer {
SaRouter SaRouter
.match("/**") .match("/**")
.notMatchMethod(SaHttpMethod.OPTIONS.name()) .notMatchMethod(SaHttpMethod.OPTIONS.name())
// TODO: 实现用户权限相关后应移除下行代码 // 实现用户权限相关后应移除下行代码
.notMatch("/**") // .notMatch("/**")
// 示例接口
.notMatch("/hello/**")
// 授权相关接口(登录、登出、注册等)
.notMatch("/auth/**") .notMatch("/auth/**")
.notMatch("/favicon.ico", "/**/*.html", "/**/*.js", "/**/*.css") .notMatch("/favicon.ico", "/**/*.html", "/**/*.js", "/**/*.css")
.notMatch("/v2/api-docs", "/v3/api-docs", "/swagger-resources/**") .notMatch("/v2/api-docs", "/v3/api-docs", "/swagger-resources/**")
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.config;
import cn.dev33.satoken.stp.StpInterface;
import com.yiring.auth.domain.permission.Permission;
import com.yiring.auth.domain.role.Role;
import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* 获取登录用户权限信息实现
*
* @author Jim
* @version 0.1
* 2022/3/25 9:37
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
UserRepository userRepository;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
User user = getUser(loginId);
return user
.getRoles()
.stream()
.map(Role::getPermissions)
.flatMap(Set::stream)
.map(Permission::getUid)
.collect(Collectors.toList());
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
User user = getUser(loginId);
return user.getRoles().stream().map(Role::getUid).collect(Collectors.toList());
}
/**
* 根据 id 获取用户信息
* @param loginId 登录 ID
* @return 用户信息
*/
private User getUser(Object loginId) {
Long id = Long.parseLong(Objects.toString(loginId));
Optional<User> optional = userRepository.findById(id);
if (optional.isEmpty()) {
throw new RuntimeException("用户不存在");
}
return optional.get();
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.permission;
import java.io.Serializable;
import javax.persistence.*;
import lombok.*;
import lombok.experimental.FieldDefaults;
import lombok.experimental.FieldNameConstants;
import org.hibernate.annotaions.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.snowflake.SnowflakeId;
/**
* 权限
*
* @author ifzm
* 2018/9/3 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults(level = AccessLevel.PRIVATE)
@Entity
@Comment("系统权限")
@Table(
name = "SYS_PERMISSION",
indexes = {
@Index(columnList = "type"),
@Index(columnList = "pid"),
@Index(columnList = "tree"),
@Index(columnList = "uid", unique = true),
}
)
public class Permission implements Serializable {
private static final long serialVersionUID = -2001221843529000953L;
@Comment("主键")
@Id
@GeneratedValue(generator = SnowflakeId.GENERATOR)
@GenericGenerator(name = SnowflakeId.GENERATOR, strategy = SnowflakeId.Strategy.LONG)
Long id;
@Comment("类型(MENU: 菜单, BUTTON: 按钮)")
@Enumerated(EnumType.STRING)
Type type;
@Comment("序号")
Integer serial;
@Comment("标识")
@Column(unique = true, nullable = false)
String uid;
@Comment("名称")
String name;
@Comment("路径")
String path;
@Comment("组件")
String component;
@Comment("图标")
String icon;
@Comment("是否隐藏")
Boolean hidden;
@Comment("是否启用")
Boolean enable;
@Comment("权限父级ID")
Long pid;
@Comment("树节点标识")
String tree;
@SuppressWarnings({ "unused" })
public enum Type {
/**
* 菜单
*/
MENU,
/**
* 按钮
*/
BUTTON,
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.permission;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* 权限接口
*
* @author ifzm
* 2018/9/4 8:48
*/
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Serializable> {}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.role;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.yiring.auth.domain.permission.Permission;
import com.yiring.auth.domain.user.User;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import lombok.*;
import lombok.experimental.FieldDefaults;
import lombok.experimental.FieldNameConstants;
import org.hibernate.annotaions.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.snowflake.SnowflakeId;
/**
* 角色
*
* @author ifzm
* 2018/9/3 15:45
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults(level = AccessLevel.PRIVATE)
@Entity
@Comment("系统角色")
@Table(name = "SYS_ROLE", indexes = { @Index(columnList = "uid", unique = true) })
public class Role implements Serializable {
private static final long serialVersionUID = 910404402503275957L;
@Comment("主键")
@Id
@GeneratedValue(generator = SnowflakeId.GENERATOR)
@GenericGenerator(name = SnowflakeId.GENERATOR, strategy = SnowflakeId.Strategy.LONG)
Long id;
@Comment("标识")
@Column(unique = true, nullable = false)
String uid;
@Comment("名称")
String name;
@JsonIgnore
@Builder.Default
@Comment("权限集合")
@ManyToMany
private Set<Permission> permissions = new HashSet<>();
@JsonIgnore
@EqualsAndHashCode.Exclude
@ToString.Exclude
@Builder.Default
@Comment("用户集合")
@ManyToMany
@JoinTable(
name = "SYS_USER_ROLES",
joinColumns = { @JoinColumn(name = "role_id") },
inverseJoinColumns = { @JoinColumn(name = "user_id") }
)
private Set<User> users = new HashSet<>();
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.role;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 角色接口
*
* @author ifzm
* 2018/9/4 8:49
*/
public interface RoleRepository extends JpaRepository<Role, Serializable> {}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.yiring.auth.domain.role.Role;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import lombok.*;
import lombok.experimental.FieldDefaults;
import lombok.experimental.FieldNameConstants;
import org.hibernate.annotaions.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.snowflake.SnowflakeId;
/**
* 用户
*
* @author ifzm
* 2018/9/3 15:27
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults(level = AccessLevel.PRIVATE)
@Entity
@Table(
name = "SYS_USER",
indexes = {
@Index(columnList = "mobile", unique = true),
@Index(columnList = "username", unique = true),
@Index(columnList = "email", unique = true),
@Index(columnList = "enabled"),
@Index(columnList = "deleted"),
}
)
@Comment("系统用户")
public class User implements Serializable {
private static final long serialVersionUID = -5787847701210907511L;
@Comment("主键")
@Id
@GeneratedValue(generator = SnowflakeId.GENERATOR)
@GenericGenerator(name = SnowflakeId.GENERATOR, strategy = SnowflakeId.Strategy.LONG)
Long id;
@Comment("真实姓名")
String realName;
@Comment("用户名")
@Column(unique = true)
String username;
@Comment("手机号")
@Column(unique = true)
String mobile;
@Comment("邮箱")
@Column(unique = true)
String email;
@Comment("密码")
String password;
@Comment("职称")
String title;
@Comment("头像")
String avatar;
@Comment("是否启用")
Boolean enabled;
@Comment("是否删除")
Boolean deleted;
@JsonIgnore
@Builder.Default
@Comment("角色集合")
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "SYS_USER_ROLES",
joinColumns = { @JoinColumn(name = "user_id") },
inverseJoinColumns = { @JoinColumn(name = "role_id") }
)
private Set<Role> roles = new HashSet<>();
@Comment("最后登录IP地址")
String lastLoginIp;
@Comment("最后登录时间")
LocalDateTime lastLoginTime;
@Comment("创建时间")
LocalDateTime createTime;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.domain.user;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
/**
* 用户接口
*
* @author ifzm
* 2018/9/4 8:50
*/
@Repository
public interface UserRepository extends JpaRepository<User, Serializable> {
/**
* 根据用户名称查询用户信息
*
* @param username 用户名
* @return 用户信息
*/
User findByUsername(String username);
/**
* 根据手机号查询用户信息
*
* @param mobile 手机号
* @return 用户信息
*/
User findByMobile(String mobile);
/**
* 根据邮箱查询用户信息
*
* @param email 邮箱
* @return 用户信息
*/
User findByEmail(String email);
/**
* 根据用户名/手机号/邮箱查询用户信息
*
* @param account 账号
* @return 用户信息
*/
@Query("SELECT u FROM User u WHERE u.username = ?1 OR u.mobile = ?1 OR u.email = ?1")
User findByAccount(String account);
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 公共的 ID 集合查询参数类
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@ApiModel("IdsParam")
@Valid
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class IdsParam implements Serializable {
private static final long serialVersionUID = -8379896695668632733L;
@ApiModelProperty(value = "ids 多个以逗号分割", example = "1,2", required = true)
@NotEmpty(message = "ids 不能为空")
String ids;
/**
* 获取 Long 类型的 ID 集合
* @return ID 集合
*/
public Set<Long> toIds() {
return Arrays.stream(this.ids.split(",")).map(Long::parseLong).collect(Collectors.toSet());
}
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.param.auth;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 用户登录参数
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@ApiModel("LoginParam")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LoginParam implements Serializable {
private static final long serialVersionUID = -8690942241103456895L;
@ApiModelProperty(value = "账号(支持用户名/手机号/邮箱)", example = "admin", required = true)
@NotEmpty(message = "账号不能为空")
String account;
@ApiModelProperty(value = "密码", example = "123456", required = true)
@NotEmpty(message = "密码不能为空")
String password;
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.param.auth;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 用户注册参数
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@ApiModel("RegisterParam")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class RegisterParam implements Serializable {
private static final long serialVersionUID = -8690942241103456895L;
@ApiModelProperty(value = "用户名", example = "admin", required = true)
@NotEmpty(message = "用户名不能为空")
String username;
@ApiModelProperty(value = "密码", example = "123456", required = true)
@NotEmpty(message = "密码不能为空")
String password;
@ApiModelProperty(value = "真实姓名", example = "管理员", required = true)
@NotEmpty(message = "真实姓名不能为空")
String realName;
@ApiModelProperty(value = "手机号", example = "13012345678", required = true)
@NotEmpty(message = "手机号不能为空")
@Pattern(regexp = "^1[0-9]{10}$", message = "手机号码格式不正确")
String mobile;
@ApiModelProperty(value = "头像", example = "http://img.ifzm.cn/cat.jpg")
String avatar;
@ApiModelProperty(value = "邮箱", example = "developer@yiring.com")
String email;
@ApiModelProperty(value = "职称", example = "平台管理员")
String title;
@ApiModelProperty(value = "是否启用", example = "true")
Boolean enable;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.param.permission;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.yiring.auth.domain.permission.Permission;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 权限信息入参类
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiModel("AddPermissionParam")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PermissionParam implements Serializable {
private static final long serialVersionUID = -6781934969837655538L;
@ApiModelProperty(value = "权限类型", example = "MENU", required = true)
@NotNull(message = "权限类型不能为空")
Permission.Type type;
@ApiModelProperty(value = "序号", example = "1")
Integer serial;
@ApiModelProperty(value = "标识", example = "home", required = true)
@NotEmpty(message = "权限标识不能为空")
String uid;
@ApiModelProperty(value = "名称", example = "主页", required = true)
@NotEmpty(message = "权限名称不能为空")
String name;
@ApiModelProperty(value = "路径", example = "/")
String path;
@ApiModelProperty(value = "组件", example = "/home")
String component;
@ApiModelProperty(value = "图标", example = "menu")
String icon;
@ApiModelProperty(value = "是否隐藏", example = "false")
Boolean hidden;
@ApiModelProperty(value = "是否启用", example = "true")
Boolean enable;
@ApiModelProperty(value = "父级ID", example = "0")
@Builder.Default
Long pid = 0L;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.param.role;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 角色信息入参类
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@ApiModel("RoleParam")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class RoleParam implements Serializable {
private static final long serialVersionUID = 6572751635422870217L;
@ApiModelProperty(value = "标识", example = "admin", required = true)
@NotEmpty(message = "角色标识不能为空")
String uid;
@ApiModelProperty(value = "名称", example = "管理员", required = true)
@NotEmpty(message = "角色名称不能为空")
String name;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.util;
import com.yiring.auth.domain.permission.Permission;
import com.yiring.auth.domain.role.Role;
import com.yiring.auth.vo.permission.PermissionVo;
import com.yiring.auth.vo.role.RoleVo;
import java.util.*;
import java.util.stream.Collectors;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import org.springframework.beans.BeanUtils;
/**
* 权限工具栏
*
* @author Jim
* @version 0.1
* 2022/3/24 17:46
*/
@UtilityClass
public class Permissions {
/**
* 将角色集合转换成 Vo 集合
* @param roles 角色集合
* @return vos
*/
public List<RoleVo> toRoleVos(Set<Role> roles) {
return roles
.stream()
.map(role -> {
RoleVo vo = new RoleVo();
BeanUtils.copyProperties(role, vo, Role.Fields.permissions);
return vo;
})
.collect(Collectors.toList());
}
/**
* 将权限集合转换成 Vo 集合
* @param permissions 权限集合
* @return vos
*/
public List<PermissionVo> toPermissionVos(List<Permission> permissions) {
return permissions
.stream()
.map(permission -> {
PermissionVo vo = new PermissionVo();
BeanUtils.copyProperties(permission, vo);
return vo;
})
.collect(Collectors.toList());
}
/**
* 提取角色集合含有的权限去重结果
* @param roles 角色集合
* @return 权限集合
*/
public List<Permission> toPermissions(Set<Role> roles) {
// 提取角色中的所有权限并去重, 同时按照树节点标识排序
return roles
.stream()
.map(Role::getPermissions)
.flatMap(Set::stream)
.sorted(Comparator.comparing(Permission::getTree, Comparator.comparingInt(String::length)))
.collect(Collectors.toList());
}
/**
* 根据 pid 构建树状权限集合
* @param permissions 权限集合
* @param pid 权限父级 ID
* @return 树状权限集合
*/
public List<PermissionVo> toTree(List<Permission> permissions, @NonNull Long pid) {
return permissions
.stream()
.filter(permission -> pid.equals(permission.getPid()))
.map(permission -> {
PermissionVo vo = new PermissionVo();
BeanUtils.copyProperties(permission, vo);
vo.setChildren(toTree(permissions, permission.getId()));
return vo;
})
.collect(Collectors.toList());
}
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.vo.auth;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 用户登录输出信息
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@ApiModel("LoginVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LoginVo implements Serializable {
private static final long serialVersionUID = -8690942241103456896L;
@ApiModelProperty(value = "token", example = "c68ca9c8c04b4a59afeafd2fb7c04741")
String token;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.vo.permission;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.yiring.auth.domain.permission.Permission;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 权限输出类
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiModel("PermissionVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PermissionVo implements Serializable {
private static final long serialVersionUID = -9139328772148985141L;
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "主键", example = "1")
Long id;
@ApiModelProperty(value = "权限类型", example = "MENU")
Permission.Type type;
@ApiModelProperty(value = "序号", example = "1")
Integer serial;
@ApiModelProperty(value = "标识", example = "home")
String uid;
@ApiModelProperty(value = "名称", example = "主页")
String name;
@ApiModelProperty(value = "路径", example = "/")
String path;
@ApiModelProperty(value = "组件", example = "/home")
String component;
@ApiModelProperty(value = "图标", example = "menu")
String icon;
@ApiModelProperty(value = "是否隐藏", example = "false")
Boolean hidden;
@ApiModelProperty(value = "是否启用", example = "true")
Boolean enable;
@ApiModelProperty(value = "父级ID", example = "0")
Long pid;
@ApiModelProperty(value = "子权限")
List<PermissionVo> children;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.vo.role;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.yiring.auth.vo.permission.PermissionVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 角色响应类
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@ApiModel("RoleVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class RoleVo implements Serializable {
private static final long serialVersionUID = -9154497137563970840L;
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "主键", example = "1")
Long id;
@ApiModelProperty(value = "标识", example = "admin")
String uid;
@ApiModelProperty(value = "名称", example = "系统管理员")
String name;
@ApiModelProperty("权限")
List<PermissionVo> permissions;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.vo.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.yiring.auth.vo.permission.PermissionVo;
import com.yiring.auth.vo.role.RoleVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 用户信息
* @author ifzm
* 2022/03/03 10:35
**/
@ApiModel("UserInfo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class UserInfoVo implements Serializable {
private static final long serialVersionUID = -5319037883240327088L;
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "主键", example = "1")
Long userId;
@ApiModelProperty(value = "真实姓名", example = "超级用户")
String realName;
@ApiModelProperty(value = "用户名", example = "admin")
String username;
@ApiModelProperty(value = "手机号", example = "13012345678")
String mobile;
@ApiModelProperty(value = "邮箱", example = "developer@yiring.com")
String email;
@ApiModelProperty(value = "职称", example = "系统管理员")
String title;
@ApiModelProperty(value = "头像", example = "http://img.ifzm.cn/cat.jpg")
String avatar;
@ApiModelProperty("角色")
@Builder.Default
List<RoleVo> roles = new ArrayList<>(0);
@ApiModelProperty("权限")
@Builder.Default
List<PermissionVo> permissions = new ArrayList<>(0);
/**
* 通常用于前端决定登录成功后跳转到哪个模块页面
*/
@ApiModelProperty(value = "主页地址", example = "/")
String homePath;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.vo.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 用户信息
* @author ifzm
* 2022/03/03 10:35
**/
@ApiModel("UserVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class UserVo implements Serializable {
private static final long serialVersionUID = -2184378273450466835L;
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "主键", example = "1")
Long id;
@ApiModelProperty(value = "真实姓名", example = "超级用户")
String realName;
@ApiModelProperty(value = "用户名", example = "admin")
String username;
@ApiModelProperty(value = "手机号", example = "13012345678")
String mobile;
@ApiModelProperty(value = "邮箱", example = "developer@yiring.com")
String email;
@ApiModelProperty(value = "职称", example = "系统管理员")
String title;
@ApiModelProperty(value = "头像", example = "http://img.ifzm.cn/cat.jpg")
String avatar;
@ApiModelProperty(value = "是否启用", example = "true")
Boolean enabled;
@ApiModelProperty(value = "是否删除", example = "false")
Boolean deleted;
@ApiModelProperty(value = "最后登录IP地址", example = "127.0.0.1")
String lastLoginIp;
@ApiModelProperty(value = "最后登录时间", example = "2022-10-24 10:24:00")
LocalDateTime lastLoginTime;
@ApiModelProperty(value = "最后登录时间", example = "2022-01-01 00:00:00")
LocalDateTime createTime;
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.web.auth;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository;
import com.yiring.auth.param.auth.LoginParam;
import com.yiring.auth.param.auth.RegisterParam;
import com.yiring.auth.vo.auth.LoginVo;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.util.Commons;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Example;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户认证控制器
*
* @author Jim
* @version 0.1
* 2022/1/24 14:13
*/
@Slf4j
@Validated
@Api(tags = "Auth")
@RestController
@RequestMapping("/auth/")
public class AuthController {
@Resource
UserRepository userRepository;
@ApiOperation(value = "注册")
@PostMapping("register")
public Result<String> register(@Valid RegisterParam param) {
// 检查用户名是否存在
long count = userRepository.count(Example.of(User.builder().username(param.getUsername()).build()));
if (count > 0) {
return Result.no(Status.BAD_REQUEST, "用户名已存在");
}
// 检查手机号是否存在
count = userRepository.count(Example.of(User.builder().mobile(param.getMobile()).build()));
if (count > 0) {
return Result.no(Status.BAD_REQUEST, "手机号已存在");
}
// 检查邮箱是否存在
if (StrUtil.isNotBlank(param.getEmail())) {
count = userRepository.count(Example.of(User.builder().email(param.getEmail()).build()));
if (count > 0) {
return Result.no(Status.BAD_REQUEST, "邮箱已存在");
}
}
// 构建用户信息写入数据库
User user = User
.builder()
.title(param.getTitle())
.avatar(param.getAvatar())
.mobile(param.getMobile())
.realName(param.getRealName())
.username(param.getUsername())
.password(SaSecureUtil.sha256(param.getPassword()))
.enabled(param.getEnable())
.deleted(Boolean.FALSE)
.createTime(LocalDateTime.now())
.build();
userRepository.saveAndFlush(user);
return Result.ok();
}
@ApiOperation(value = "登录")
@PostMapping("login")
public Result<LoginVo> login(@Valid LoginParam param, HttpServletRequest request) {
String details = "账号密码错误";
// 查询用户信息是否匹配
User user = userRepository.findByAccount(param.getAccount());
if (user == null) {
return Result.no(Status.BAD_REQUEST, details);
}
// 检查密码
String cps = SaSecureUtil.sha256(param.getPassword());
if (!cps.equals(user.getPassword())) {
return Result.no(Status.BAD_REQUEST, details);
}
// 检查用户是否已被删除
if (!Boolean.FALSE.equals(user.getDeleted())) {
return Result.no(Status.FORBIDDEN, "用户被禁用, 请联系管理员");
}
// 检查用户是否被允许登录
if (!Boolean.TRUE.equals(user.getEnabled())) {
return Result.no(Status.FORBIDDEN, "用户被禁止登录, 请联系管理员");
}
// 登录
StpUtil.login(user.getId());
// 更新用户信息
user.setLastLoginIp(Commons.getClientIpAddress(request));
user.setLastLoginTime(LocalDateTime.now());
userRepository.saveAndFlush(user);
// 构建用户所需信息
LoginVo vo = LoginVo.builder().token(StpUtil.getTokenValue()).build();
return Result.ok(vo);
}
@ApiOperation(value = "登出")
@GetMapping("logout")
public Result<String> logout() {
StpUtil.logout();
return Result.ok();
}
}
/**
* @author Jim
* @version 0.1
* 2022/3/24 16:37
*/
package com.yiring.auth.web;
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.web.permission;
import com.yiring.auth.domain.permission.Permission;
import com.yiring.auth.domain.permission.PermissionRepository;
import com.yiring.auth.param.permission.PermissionParam;
import com.yiring.auth.util.Permissions;
import com.yiring.auth.vo.permission.PermissionVo;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.param.IdParam;
import com.yiring.common.param.PageParam;
import com.yiring.common.vo.PageVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Resource;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 系统权限管理控制器
*
* @author Jim
* @version 0.1
* 2022/3/25 15:27
*/
@Slf4j
@Validated
@Api(tags = "Permission")
@RestController
@RequestMapping("/manage/permission/")
public class PermissionController {
@Resource
PermissionRepository permissionRepository;
@ApiOperation(value = "新增")
@PostMapping("add")
public Result<String> add(@Valid PermissionParam param) {
if (has(param.getUid())) {
return Result.no(Status.BAD_REQUEST, "权限标识重复");
}
Permission entity = new Permission();
BeanUtils.copyProperties(param, entity);
entity.setTree(getTreeNode(param.getPid()));
permissionRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "修改")
@PostMapping("modify")
public Result<String> modify(@Valid PermissionParam param, @Valid IdParam idParam) {
Optional<Permission> optional = permissionRepository.findById(idParam.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
Permission entity = optional.get();
if (!entity.getUid().equals(param.getUid())) {
// 仅当修改了角色标识时才检查重复
if (has(param.getUid())) {
return Result.no(Status.BAD_REQUEST, "权限标识重复");
}
}
BeanUtils.copyProperties(param, entity);
entity.setTree(getTreeNode(param.getPid()));
permissionRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "删除")
@PostMapping("deleted")
public Result<String> deleted(@Valid IdParam param) {
Optional<Permission> optional = permissionRepository.findById(param.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
Permission entity = optional.get();
permissionRepository.delete(entity);
return Result.ok();
}
@ApiOperation(value = "查询")
@GetMapping("find")
public Result<PermissionVo> find(@Valid IdParam param) {
Optional<Permission> optional = permissionRepository.findById(param.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
PermissionVo vo = new PermissionVo();
BeanUtils.copyProperties(optional.get(), vo);
return Result.ok(vo);
}
@ApiOperation(value = "分页查询")
@GetMapping("page")
public Result<PageVo<PermissionVo>> page(@Valid PageParam param) {
Page<Permission> page = permissionRepository.findAll(PageParam.toPageable(param));
List<PermissionVo> data = Permissions.toPermissionVos(page.toList());
PageVo<PermissionVo> vo = PageVo.build(data, page.getTotalElements());
return Result.ok(vo);
}
@ApiOperation(value = "树结构查询")
@GetMapping(value = "tree", headers = "Content-Type=" + MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public Result<ArrayList<PermissionVo>> tree(
@ApiParam(value = "父级 id", example = "0") @RequestParam(required = false) Long pid
) {
List<Permission> permissions = permissionRepository.findAll();
List<PermissionVo> vos = Permissions.toTree(permissions, Objects.isNull(pid) ? 0L : pid);
return Result.ok((ArrayList<PermissionVo>) vos);
}
/**
* 根据父级 ID 获取当前树节点标识
* @param pid 父级 ID
* @return 树节点标识
*/
private String getTreeNode(Long pid) {
Optional<Permission> parent = permissionRepository.findById(pid);
if (parent.isEmpty()) {
return String.valueOf(pid);
} else {
return String.format("%s.%s", parent.get().getTree(), pid);
}
}
/**
* 检查是否存在已有相同标识的权限
* @param uid 权限标识
* @return 是否存在
*/
private boolean has(String uid) {
Permission entity = Permission.builder().uid(uid).build();
return permissionRepository.count(Example.of(entity)) > 0;
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.web.role;
import com.yiring.auth.domain.permission.Permission;
import com.yiring.auth.domain.permission.PermissionRepository;
import com.yiring.auth.domain.role.Role;
import com.yiring.auth.domain.role.RoleRepository;
import com.yiring.auth.param.IdsParam;
import com.yiring.auth.param.role.RoleParam;
import com.yiring.auth.util.Permissions;
import com.yiring.auth.vo.role.RoleVo;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.param.IdParam;
import com.yiring.common.param.PageParam;
import com.yiring.common.vo.PageVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统角色管理控制器
*
* @author Jim
* @version 0.1
* 2022/3/25 15:27
*/
@Slf4j
@Validated
@Api(tags = "Role")
@RestController
@RequestMapping("/manage/role/")
public class RoleController {
@Resource
RoleRepository roleRepository;
@Resource
PermissionRepository permissionRepository;
@ApiOperation(value = "新增")
@PostMapping("add")
public Result<String> add(@Valid RoleParam param) {
if (has(param.getUid())) {
return Result.no(Status.BAD_REQUEST, "角色标识重复");
}
Role entity = new Role();
BeanUtils.copyProperties(param, entity);
roleRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "修改")
@PostMapping("modify")
public Result<String> modify(@Valid RoleParam param, @Valid IdParam idParam) {
Optional<Role> optional = roleRepository.findById(idParam.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
Role entity = optional.get();
if (!entity.getUid().equals(param.getUid())) {
// 仅当修改了角色标识时才检查重复
if (has(param.getUid())) {
return Result.no(Status.BAD_REQUEST, "角色标识重复");
}
}
BeanUtils.copyProperties(param, entity);
roleRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "分配权限")
@PostMapping("assign")
public Result<String> assign(@Valid IdParam idParam, @Valid IdsParam idsParam) {
Optional<Role> optional = roleRepository.findById(idParam.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
// 查询权限集合
Set<Long> ids = idsParam.toIds();
Set<Permission> permissions = permissionRepository
.findAll()
.stream()
.filter(permission -> ids.contains(permission.getId()))
.collect(Collectors.toSet());
Role entity = optional.get();
entity.setPermissions(permissions);
roleRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "删除")
@PostMapping("deleted")
public Result<String> deleted(@Valid IdParam param) {
Optional<Role> optional = roleRepository.findById(param.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
Role entity = optional.get();
roleRepository.delete(entity);
return Result.ok();
}
@ApiOperation(value = "查询")
@GetMapping("find")
public Result<RoleVo> find(@Valid IdParam param) {
Optional<Role> optional = roleRepository.findById(param.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
Role entity = optional.get();
RoleVo vo = new RoleVo();
BeanUtils.copyProperties(entity, vo, Role.Fields.permissions);
vo.setPermissions(Permissions.toPermissionVos(Permissions.toPermissions(Collections.singleton(entity))));
return Result.ok(vo);
}
@ApiOperation(value = "分页查询")
@GetMapping("page")
public Result<PageVo<RoleVo>> page(@Valid PageParam param) {
Page<Role> page = roleRepository.findAll(PageParam.toPageable(param));
List<RoleVo> data = new ArrayList<>(Permissions.toRoleVos(page.toSet()));
PageVo<RoleVo> vo = PageVo.build(data, page.getTotalElements());
return Result.ok(vo);
}
@ApiOperation(value = "选项查询")
@GetMapping("selector")
public Result<ArrayList<RoleVo>> selector() {
List<Role> roles = roleRepository.findAll();
List<RoleVo> vos = Permissions.toRoleVos(new HashSet<>(roles));
return Result.ok((ArrayList<RoleVo>) vos);
}
/**
* 检查是否存在已有相同标识的角色
* @param uid 角色标识
* @return 是否存在
*/
private boolean has(String uid) {
Role entity = Role.builder().uid(uid).build();
return roleRepository.count(Example.of(entity)) > 0;
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.web.user;
import cn.dev33.satoken.stp.StpUtil;
import com.yiring.auth.domain.role.Role;
import com.yiring.auth.domain.role.RoleRepository;
import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository;
import com.yiring.auth.param.IdsParam;
import com.yiring.auth.util.Permissions;
import com.yiring.auth.vo.user.UserInfoVo;
import com.yiring.auth.vo.user.UserVo;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.param.IdParam;
import com.yiring.common.param.PageParam;
import com.yiring.common.vo.PageVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统用户控制器
*
* @author Jim
* @version 0.1
* 2022/1/24 14:13
*/
@Slf4j
@Validated
@Api(tags = "User")
@RestController
@RequestMapping("/user/")
public class UserController {
@Resource
UserRepository userRepository;
@Resource
RoleRepository roleRepository;
@ApiOperation(value = "获取登录用户信息")
@GetMapping("info")
public Result<UserInfoVo> info() {
Long id = StpUtil.getLoginIdAsLong();
Optional<User> optional = userRepository.findById(id);
if (optional.isEmpty()) {
StpUtil.logout(id);
return Result.no(Status.UNAUTHORIZED);
}
User user = optional.get();
UserInfoVo userInfoVo = UserInfoVo
.builder()
.userId(user.getId())
.realName(user.getRealName())
.avatar(user.getAvatar())
.title(user.getTitle())
.roles(Permissions.toRoleVos(user.getRoles()))
.permissions(Permissions.toTree(Permissions.toPermissions(user.getRoles()), 0L))
.build();
return Result.ok(userInfoVo);
}
@ApiOperation(value = "分配角色")
@PostMapping("/manage/assign")
public Result<String> assign(@Valid IdParam idParam, @Valid IdsParam idsParam) {
Optional<User> optional = userRepository.findById(idParam.getId());
if (optional.isEmpty()) {
return Result.no(Status.NOT_FOUND);
}
// 查询权限集合
Set<Long> ids = idsParam.toIds();
Set<Role> roles = roleRepository
.findAll()
.stream()
.filter(role -> ids.contains(role.getId()))
.collect(Collectors.toSet());
User entity = optional.get();
entity.setRoles(roles);
userRepository.saveAndFlush(entity);
return Result.ok();
}
@ApiOperation(value = "分页查询")
@GetMapping("/manage/page")
public Result<PageVo<UserVo>> page(@Valid PageParam param) {
Page<User> page = userRepository.findAll(PageParam.toPageable(param));
List<UserVo> data = page
.get()
.map(role -> {
UserVo vo = new UserVo();
BeanUtils.copyProperties(role, vo);
return vo;
})
.collect(Collectors.toList());
PageVo<UserVo> vo = PageVo.build(data, page.getTotalElements());
return Result.ok(vo);
}
}
...@@ -3,9 +3,11 @@ dependencies { ...@@ -3,9 +3,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// swagger annotations // swagger annotations
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}" implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}"
// hutool-extra // hutool-extra
implementation "cn.hutool:hutool-extra:${hutoolVersion}" implementation "cn.hutool:hutool-extra:${hutoolVersion}"
// hutool-json // hutool-json
......
...@@ -55,7 +55,12 @@ public class RequestAspect { ...@@ -55,7 +55,12 @@ public class RequestAspect {
extra += String.format("\nHeaders: %s", new JSONObject(ServletUtil.getHeaderMap(request)).toStringPretty()); extra += String.format("\nHeaders: %s", new JSONObject(ServletUtil.getHeaderMap(request)).toStringPretty());
extra += String.format("\nParams: %s", new JSONObject(ServletUtil.getParamMap(request)).toStringPretty()); extra += String.format("\nParams: %s", new JSONObject(ServletUtil.getParamMap(request)).toStringPretty());
if (result instanceof Result) { if (result instanceof Result) {
extra += String.format("\nResponse Status: %s", ((Result<?>) result).getStatus()); extra +=
String.format(
"\nResponse: %s, %s",
((Result<?>) result).getStatus(),
((Result<?>) result).getMessage()
);
} }
} }
log.info( log.info(
......
/* (C) 2022 YiRing, Inc. */ /* (C) 2022 YiRing, Inc. */
package com.yiring.common.config; package com.yiring.common.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
...@@ -38,7 +39,7 @@ public class DateTimeConfig { ...@@ -38,7 +39,7 @@ public class DateTimeConfig {
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateFormatter.TIME)); timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateFormatter.TIME));
// feat: add AdminServerModule // feat: add AdminServerModule
// .setSerializationInclusion(JsonInclude.Include.NON_NULL) // .setSerializationInclusion(JsonInclude.Include.NON_NULL)
return new ObjectMapper().registerModules(timeModule); return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL).registerModules(timeModule);
} }
@Bean @Bean
......
...@@ -3,7 +3,6 @@ package com.yiring.common.config; ...@@ -3,7 +3,6 @@ package com.yiring.common.config;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** /**
...@@ -26,11 +25,4 @@ public class WebMvcConfig implements WebMvcConfigurer { ...@@ -26,11 +25,4 @@ public class WebMvcConfig implements WebMvcConfigurer {
.allowedHeaders("*") .allowedHeaders("*")
.exposedHeaders("Content-Disposition"); .exposedHeaders("Content-Disposition");
} }
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
} }
...@@ -12,7 +12,6 @@ import lombok.experimental.UtilityClass; ...@@ -12,7 +12,6 @@ import lombok.experimental.UtilityClass;
* 2019/3/22 14:08 * 2019/3/22 14:08
*/ */
@SuppressWarnings({ "unused" })
@UtilityClass @UtilityClass
public class DateFormatter { public class DateFormatter {
......
...@@ -14,13 +14,13 @@ import lombok.extern.slf4j.Slf4j; ...@@ -14,13 +14,13 @@ import lombok.extern.slf4j.Slf4j;
/** /**
* 标准的响应对象(所有的接口响应内容格式都应该是一致的) * 标准的响应对象(所有的接口响应内容格式都应该是一致的)
* *
* @author fangzhimin * @author ifzm
* @version 1.1 * @version 1.1
* 2018/9/4 11:05 * 2018/9/4 11:05
*/ */
@SuppressWarnings({ "unchecked", "unused" }) @SuppressWarnings({ "unchecked", "unused" })
@ApiModel("请求响应标准") @ApiModel("Result")
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
@Slf4j @Slf4j
@Data @Data
...@@ -139,6 +139,16 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -139,6 +139,16 @@ public class Result<T extends Serializable> implements Serializable {
* @return Result * @return Result
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Integer code, String details) {
return no(Status.BAD_REQUEST, code, details, null);
}
/**
* 返回失败响应内容
*
* @return Result
* @see Status
*/
public static <T extends Serializable> Result<T> no(Status status, Integer code, String details, Throwable error) { public static <T extends Serializable> Result<T> no(Status status, Integer code, String details, Throwable error) {
Result<T> result = (Result<T>) Result Result<T> result = (Result<T>) Result
.builder() .builder()
......
...@@ -33,7 +33,7 @@ public enum Status { ...@@ -33,7 +33,7 @@ public enum Status {
/** /**
* Token 错误/失效 * Token 错误/失效
*/ */
FORBIDDEN(403, "Token 错误/失效"), FORBIDDEN(403, "禁止访问"),
/** /**
* 找不到资源 * 找不到资源
......
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.param; package com.yiring.common.param;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable; import java.io.Serializable;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import lombok.AccessLevel; import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
/** /**
* 公共的ID查询参数类 * 公共的ID查询参数类
* *
* @author ifzm * @author ifzm
* @version 0.1 2019/5/28 22:11 * @version 0.1
* 2019/5/28 22:11
*/ */
@ApiModel("公共的ID查询参数类") @ApiModel("IdParam")
@Valid @Valid
@Data @Data
@SuperBuilder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
......
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.param; package com.yiring.common.param;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
...@@ -53,18 +53,18 @@ public class PageParam implements Serializable { ...@@ -53,18 +53,18 @@ public class PageParam implements Serializable {
/** /**
* 根据参数构建分页对象 * 根据参数构建分页对象
* *
* @param paginationParam {@link PageParam} * @param param {@link PageParam}
* @return Pageable * @return Pageable
*/ */
public static Pageable toPageable(PageParam paginationParam) { public static Pageable toPageable(PageParam param) {
if (paginationParam == null) { if (param == null) {
return Pageable.unpaged(); return Pageable.unpaged();
} }
Sort sort = Sort.unsorted(); Sort sort = Sort.unsorted();
if (Objects.nonNull(paginationParam.getSortField())) { if (Objects.nonNull(param.getSortField())) {
sort = Sort.by(new Sort.Order(paginationParam.getSortOrder(), paginationParam.getSortField())); sort = Sort.by(new Sort.Order(param.getSortOrder(), param.getSortField()));
} }
return PageRequest.of(paginationParam.getPageNo() - 1, paginationParam.getPageSize(), sort); return PageRequest.of(param.getPageNo() - 1, param.getPageSize(), sort);
} }
} }
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.vo; package com.yiring.common.vo;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.AccessLevel; import lombok.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
/** /**
...@@ -18,10 +15,10 @@ import lombok.experimental.FieldDefaults; ...@@ -18,10 +15,10 @@ import lombok.experimental.FieldDefaults;
* @version 0.1 * @version 0.1
* 2022/3/23 16:47 * 2022/3/23 16:47
*/ */
@ApiModel("公共数据响应") @ApiModel("DataVo")
@Data
@NoArgsConstructor @NoArgsConstructor
@Getter(value = AccessLevel.PRIVATE) @AllArgsConstructor
@Setter(value = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class DataVo<T extends Serializable> implements Serializable { public class DataVo<T extends Serializable> implements Serializable {
......
/* (C) 2021 YiRing, Inc. */
package com.yiring.common.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 键值对输出
*
* @author ifzm
* @version 0.1
* 2022/3/24 17:29
*/
@ApiModel("KeyValueVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class KeyValueVo implements Serializable {
private static final long serialVersionUID = -5238793972067296346L;
@ApiModelProperty(value = "key", example = "key")
String key;
@ApiModelProperty(value = "value", example = "value")
String value;
/**
* 扩展字段,可用于文本输出
*/
@ApiModelProperty(value = "label", example = "label")
String label;
}
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.vo; package com.yiring.common.vo;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import lombok.AccessLevel; import lombok.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
/** /**
...@@ -18,10 +15,10 @@ import lombok.experimental.FieldDefaults; ...@@ -18,10 +15,10 @@ import lombok.experimental.FieldDefaults;
* @author ifzm * @author ifzm
* @version 0.1 2019/3/10 16:29 * @version 0.1 2019/3/10 16:29
*/ */
@ApiModel("分页查询响应公共类") @ApiModel("PageVo")
@Data
@NoArgsConstructor @NoArgsConstructor
@Getter(value = AccessLevel.PRIVATE) @AllArgsConstructor
@Setter(value = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class PageVo<T extends Serializable> implements Serializable { public class PageVo<T extends Serializable> implements Serializable {
......
dependencies {
implementation project(":basic-common:core")
implementation 'org.springframework.boot:spring-boot-starter-web'
// swagger
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
// minio
implementation "io.minio:minio:${minioVersion}"
// hutool
implementation "cn.hutool:hutool-core:${hutoolVersion}"
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.config;
import io.minio.MinioClient;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Minio 配置注入
*
* @author Jim
* @version 0.1
* 2021/9/16 16:37
*/
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
String endpoint;
String accessKey;
String secretKey;
String bucket;
String domain;
@Bean
public MinioClient getClient() {
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.core;
import com.yiring.common.config.MinioConfig;
import io.minio.*;
import io.minio.Result;
import io.minio.messages.Item;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
/**
* Minio
*
* @author Jim
* @version 0.1
* 2021/9/16 16:37
*/
@SuppressWarnings({ "unused" })
@Component
public class Minio {
@Resource
public MinioConfig config;
@Resource
public MinioClient client;
/**
* 业务数据存储桶
*/
public static final String BUSINESS_BUCKET = "business";
/**
* 获取文件访问地址
* @param object 文件相对地址(含路径)
* @return URI
*/
public String getURI(String object) {
return String.join("/", config.getDomain(), object);
}
/**
* 上传文件流
* @param is 文件流
* @param contentType 文件类型
* @param object 文件存储相对地址
* @return 上传结果
* @throws Exception 异常
*/
public ObjectWriteResponse putObject(InputStream is, String contentType, String object) throws Exception {
PutObjectArgs args = PutObjectArgs
.builder()
.bucket(config.getBucket())
.stream(is, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
.object(object)
.contentType(contentType)
.build();
return client.putObject(args);
}
/**
* 将文件上传到当前日期文件夹
* @param file 文件 MultipartFile
* @return 上传结果
* @throws Exception 异常
*/
public ObjectWriteResponse putObject(File file, String folder) throws Exception {
String object = file.getName();
if (folder != null) {
object = folder + "/" + object;
}
Path path = file.toPath();
return putObject(Files.newInputStream(path), Files.probeContentType(path), object);
}
/**
* 将文件上传到当前日期文件夹
* @param file 文件 MultipartFile
* @return 上传结果
* @throws Exception 异常
*/
public ObjectWriteResponse putObject(MultipartFile file, String folder) throws Exception {
String object = file.getOriginalFilename();
if (folder != null) {
object = folder + "/" + object;
}
return putObject(file.getInputStream(), file.getContentType(), object);
}
/**
* 删除文件
* @param object 文件相对地址
* @throws Exception 异常
*/
public void remove(String object) throws Exception {
RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(config.getBucket()).object(object).build();
client.removeObject(args);
}
/**
* 获取文件
* @param object 文件相对地址
* @return 文件流
* @throws Exception 异常
*/
public GetObjectResponse getObject(String object) throws Exception {
GetObjectArgs args = GetObjectArgs.builder().bucket(config.getBucket()).object(object).build();
return client.getObject(args);
}
/**
* 查询文件信息
* @param object 文件相对地址
* @return 文件信息
* @throws Exception 异常
*/
public StatObjectResponse objectStat(String object) throws Exception {
StatObjectArgs args = StatObjectArgs.builder().bucket(config.getBucket()).object(object).build();
return client.statObject(args);
}
/**
* 查询 list
* @param prefix 文件路径前缀
* @return 文件/文件夹集合
*/
public Iterable<Result<Item>> listObjects(String prefix) {
ListObjectsArgs args = ListObjectsArgs.builder().bucket(config.getBucket()).prefix(prefix).build();
return client.listObjects(args);
}
/**
* 拷贝一个文件
* @param source 源文件 object
* @param target 目标文件 object
* @throws Exception 异常
*/
public void copyObject(String source, String target) throws Exception {
CopySource copySource = CopySource.builder().bucket(config.getBucket()).object(source).build();
CopyObjectArgs args = CopyObjectArgs
.builder()
.bucket(config.getBucket())
.object(target)
.source(copySource)
.build();
client.copyObject(args);
}
/**
* 上传业务数据文件
* @param path 本地文件地址(通常是一个临时文件)
* @param object 文件 object
* @return 上传结果
*/
public ObjectWriteResponse putBusinessObject(Path path, String object) throws Exception {
UploadObjectArgs args = UploadObjectArgs
.builder()
// 固定的储存桶位置
.bucket(BUSINESS_BUCKET)
.filename(path.toString())
.object(object)
.build();
return client.uploadObject(args);
}
}
/**
* @author Jim
* @version 0.1
* 2022/3/26 10:36
*/
package com.yiring.common;
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.web;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.yiring.common.core.Minio;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import io.minio.ObjectWriteResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* Minio S3
*/
@Slf4j
@Validated
@Api(tags = "Minio S3")
@RestController
@RequestMapping("/common/minio/")
public class MinioController {
@Resource
Minio minio;
/**
* minio 上传文件,成功返回文件 url
*/
@ApiOperation(value = "文件上传")
@PostMapping(value = "upload", headers = "Content-Type=Multipart/Form-Data")
public Result<String> minio(@ApiParam(value = "文件", required = true) @RequestPart("file") MultipartFile file) {
try {
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
long uuid = snowflake.nextId();
// 文件上传
String folder =
"upload/" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/M/d")) + "/" + uuid;
ObjectWriteResponse response = minio.putObject(file, folder);
String uri = minio.getURI(response.object());
return Result.ok(uri);
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.no(Status.BAD_REQUEST, "上传失败");
}
}
}
...@@ -8,23 +8,25 @@ buildscript { ...@@ -8,23 +8,25 @@ buildscript {
// https://mvnrepository.com/artifact/io.swagger/swagger-annotations // https://mvnrepository.com/artifact/io.swagger/swagger-annotations
swaggerAnnotationsVersion = '1.6.4' swaggerAnnotationsVersion = '1.6.4'
// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator // https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
hibernateValidatorVersion = '6.0.22.Final' hibernateValidatorVersion = '6.0.23.Final'
// https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter // https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter
saTokenVersion = '1.28.0' saTokenVersion = '1.28.0'
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
commonsLangVersion = '3.12.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all // https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion = '5.7.22' hutoolVersion = '5.7.22'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
xxlJobVersion = '2.3.0'
// https://mvnrepository.com/artifact/com.alibaba/fastjson // https://mvnrepository.com/artifact/com.alibaba/fastjson
fastJsonVersion = '1.2.79' fastJsonVersion = '1.2.79'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
xxlJobVersion = '2.3.0'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
okhttpVersion = '4.9.3'
// https://mvnrepository.com/artifact/io.minio/minio
minioVersion = '8.3.7'
} }
} }
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '2.5.10' id 'org.springframework.boot' version '2.5.11'
// https://plugins.gradle.org/plugin/io.spring.dependency-management // https://plugins.gradle.org/plugin/io.spring.dependency-management
id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'io.spring.dependency-management' version '1.0.11.RELEASE'
// https://plugins.gradle.org/plugin/com.diffplug.spotless // https://plugins.gradle.org/plugin/com.diffplug.spotless
...@@ -33,8 +35,6 @@ plugins { ...@@ -33,8 +35,6 @@ plugins {
// id "com.github.spotbugs" version "4.7.10" // id "com.github.spotbugs" version "4.7.10"
} }
group = 'com.yiring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8' sourceCompatibility = '1.8'
allprojects { allprojects {
......
...@@ -10,3 +10,5 @@ include 'basic-auth' ...@@ -10,3 +10,5 @@ include 'basic-auth'
include 'basic-common:core' include 'basic-common:core'
include 'basic-common:util' include 'basic-common:util'
include 'basic-common:doc' include 'basic-common:doc'
include 'basic-common:minio'
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论