Feat: 实现用户模块和文件模块
This commit is contained in:
parent
7ef112775b
commit
470683473d
@ -1,5 +1,7 @@
|
||||
package com.flyfish.framework.annotations;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
@ -16,8 +18,22 @@ public @interface Property {
|
||||
* 显示标题
|
||||
* @return 结果
|
||||
*/
|
||||
@AliasFor("title")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 显示标题(别名)
|
||||
* @return 结果
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String title() default "";
|
||||
|
||||
/**
|
||||
* 描述
|
||||
* @return 结果
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* 被继承的,用于父类,自动拼接名称
|
||||
* @return 结果
|
||||
|
@ -25,21 +25,21 @@ public abstract class AuditDomain extends Domain {
|
||||
* 创建日期
|
||||
*/
|
||||
@CreatedDate
|
||||
@Property(title = "创建日期")
|
||||
@Property("创建日期")
|
||||
protected Date createTime;
|
||||
|
||||
/**
|
||||
* 修改日期
|
||||
*/
|
||||
@LastModifiedDate
|
||||
@Property(title = "更新日期")
|
||||
@Property("更新日期")
|
||||
protected Date modifyTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@CreatedBy
|
||||
@Property(title = "创建人")
|
||||
@Property("创建人")
|
||||
protected String creator;
|
||||
|
||||
/**
|
||||
@ -51,7 +51,7 @@ public abstract class AuditDomain extends Domain {
|
||||
* 修改者
|
||||
*/
|
||||
@LastModifiedBy
|
||||
@Property(title = "更新人")
|
||||
@Property("更新人")
|
||||
protected String modifier;
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 属性菜单的qo
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class TreeQo<T extends Domain> extends NameLikeQo<T> {
|
||||
|
||||
private Boolean leaf;
|
||||
|
||||
private Integer level;
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder<T> criteriaBuilder() {
|
||||
return super.criteriaBuilder()
|
||||
.with("level", "leaf");
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||
public class User extends AuditDomain implements IUser {
|
||||
|
||||
private static final long serialVersionUID = -960011918745179950L;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
|
27
flyfish-file/pom.xml
Normal file
27
flyfish-file/pom.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-file</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-web</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,26 @@
|
||||
package com.flyfish.framework.file.config;
|
||||
|
||||
|
||||
import com.flyfish.framework.file.utils.SystemUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "upload")
|
||||
@Getter
|
||||
@Setter
|
||||
public class UploadConfiguration {
|
||||
|
||||
private String localPath = SystemUtils.isWindows() ? "C:/file-temp/" : "/usr/local/resources/";
|
||||
|
||||
public String path(List<Supplier<String>> generator) {
|
||||
return generator.stream().map(Supplier::get)
|
||||
.collect(Collectors.joining("/"));
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.file.controller;
|
||||
|
||||
|
||||
import com.flyfish.framework.controller.BaseController;
|
||||
import com.flyfish.framework.file.domain.Attachment;
|
||||
import com.flyfish.framework.file.domain.AttachmentQo;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/attachments")
|
||||
public class AttachmentController extends BaseController<Attachment, AttachmentQo> {
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.flyfish.framework.file.controller;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.beans.annotations.RestMapping;
|
||||
import com.flyfish.framework.file.config.UploadConfiguration;
|
||||
import com.flyfish.framework.file.domain.Attachment;
|
||||
import com.flyfish.framework.file.service.AttachmentService;
|
||||
import com.flyfish.framework.file.utils.DownloadUtils;
|
||||
import com.flyfish.framework.service.BaseService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 附件上传相关
|
||||
* @author wangyu
|
||||
*/
|
||||
@RestMapping("/attachments")
|
||||
public class AttachmentUploadController {
|
||||
|
||||
@Resource
|
||||
private UploadConfiguration configuration;
|
||||
@Resource
|
||||
private AttachmentService attachmentService;
|
||||
|
||||
/**
|
||||
* 上传媒体,支持多个同时上传
|
||||
*
|
||||
* @param files 文件
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("")
|
||||
public Mono<Result<List<Attachment>>> uploadAttachment(@RequestPart("file") Flux<FilePart> files) {
|
||||
return files.flatMap(attachmentService::upload)
|
||||
.reduce(Result.accept(new ArrayList<>()), ((listResult, attachment) -> {
|
||||
listResult.getData().add(attachment);
|
||||
return listResult;
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/**")
|
||||
public Mono<Void> downloadStatic(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
String path = StringUtils.substringAfterLast(request.getURI().getPath(), "/attachments");
|
||||
return DownloadUtils.download(configuration.getLocalPath() + path, response);
|
||||
}
|
||||
|
||||
@GetMapping("/downloads/{id}")
|
||||
public Mono<Void> downloadAttachment(@PathVariable String id, ServerHttpResponse response) {
|
||||
return Mono.justOrEmpty(attachmentService.getById(id))
|
||||
.flatMap(attachment -> DownloadUtils.download(configuration.getLocalPath() +
|
||||
attachment.getPath(), response));
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.flyfish.framework.file.domain;
|
||||
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import lombok.*;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Document(collection = "attachments")
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class Attachment extends AuditDomain {
|
||||
|
||||
/**
|
||||
* 大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 附件路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 附件Url
|
||||
*/
|
||||
private String url;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.flyfish.framework.file.domain;
|
||||
|
||||
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
|
||||
public class AttachmentQo extends NameLikeQo<Attachment> {
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.flyfish.framework.file.domain;
|
||||
|
||||
import com.flyfish.framework.file.enums.MediaType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Media {
|
||||
|
||||
/**
|
||||
* 媒体类型
|
||||
*/
|
||||
private MediaType type;
|
||||
|
||||
/**
|
||||
* 媒体路径
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 媒体大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
*/
|
||||
private String thumb;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.flyfish.framework.file.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MediaType {
|
||||
|
||||
UNKNOWN(Collections.emptyList(), "未知媒体"),
|
||||
AUDIO(Arrays.asList("mp3", "wma", "amr"), "音频"),
|
||||
VIDEO(Arrays.asList("mp4", "avi", "wmv", "mkv", "rmvb", "flv", "3gp", "m3u", "m3u8"), "视频"),
|
||||
GRAPHICS(Arrays.asList("jpg", "jpeg", "bpm", "tiff", "png", "gif", "svg", "ico"), "图像"),
|
||||
ARCHIVE(Arrays.asList("zip", "gz"), "压缩文档");
|
||||
|
||||
private final List<String> suffix;
|
||||
|
||||
private final String name;
|
||||
|
||||
public static MediaType accept(String filename) {
|
||||
String extend = StringUtils.substringAfterLast(filename, ".");
|
||||
return Arrays.stream(values()).filter(value -> value.suffix.contains(extend)).findFirst().orElse(UNKNOWN);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.flyfish.framework.file.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum MediaTypes {
|
||||
|
||||
OCT_STREAM(Collections.emptyList(), extend -> MediaType.APPLICATION_OCTET_STREAM),
|
||||
IMAGE(Arrays.asList("jpg", "jpeg", "png", "gif"), extend -> {
|
||||
return MediaType.valueOf("image/" + (extend.equalsIgnoreCase("jpg") ? "jpeg" : extend));
|
||||
}),
|
||||
HLS(Arrays.asList("m3u8"), extend -> {
|
||||
return new MediaType("application", "vnd.apple.mpegurl");
|
||||
});
|
||||
|
||||
|
||||
private List<String> suffix;
|
||||
|
||||
private Function<String, MediaType> mediaType;
|
||||
|
||||
public static MediaType getMediaType(String filename) {
|
||||
String extend = StringUtils.substringAfterLast(filename, ".");
|
||||
return Arrays.stream(values()).filter(value -> value.suffix.contains(extend))
|
||||
.map(type -> type.getMediaType().apply(extend))
|
||||
.findFirst().orElse(MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.flyfish.framework.file.repository;
|
||||
|
||||
|
||||
import com.flyfish.framework.file.domain.Attachment;
|
||||
import com.flyfish.framework.repository.DefaultRepository;
|
||||
|
||||
public interface AttachmentRepository extends DefaultRepository<Attachment> {
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.flyfish.framework.file.service;
|
||||
|
||||
|
||||
import com.flyfish.framework.file.domain.Attachment;
|
||||
import com.flyfish.framework.file.utils.FileSizeUtils;
|
||||
import com.flyfish.framework.service.impl.BaseServiceImpl;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class AttachmentService extends BaseServiceImpl<Attachment> {
|
||||
|
||||
private static String URL = "/api/attachments/";
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
/**
|
||||
* 上传媒体文件
|
||||
*
|
||||
* @param part 文件
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<Attachment> upload(FilePart part) {
|
||||
return fileService.saveLocal(part)
|
||||
.map(path -> {
|
||||
Attachment attachment = Attachment.builder()
|
||||
.size(FileSizeUtils.size(part.headers().getContentLength()))
|
||||
.path(path)
|
||||
.url(URL + path)
|
||||
.build();
|
||||
attachment.setName(part.filename());
|
||||
return create(attachment);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.flyfish.framework.file.service;
|
||||
|
||||
|
||||
import com.flyfish.framework.file.config.UploadConfiguration;
|
||||
import com.flyfish.framework.file.utils.SystemUtils;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 文件服务
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Service
|
||||
public class FileService {
|
||||
|
||||
@Resource
|
||||
private UploadConfiguration uploadConfiguration;
|
||||
|
||||
private List<Supplier<String>> generator = Arrays.asList(
|
||||
SystemUtils::generateDatePath
|
||||
);
|
||||
|
||||
/**
|
||||
* 保存本地文件
|
||||
*
|
||||
* @return 保存到本地,并返回相对路径
|
||||
*/
|
||||
public Mono<String> saveLocal(FilePart part) {
|
||||
String localPath = uploadConfiguration.getLocalPath();
|
||||
String path = uploadConfiguration.path(generator) + "/" + part.filename();
|
||||
String fullPath = localPath + path;
|
||||
SystemUtils.testPath(fullPath);
|
||||
return part.transferTo(Paths.get(fullPath))
|
||||
.then(Mono.just(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定目录保存文件
|
||||
*
|
||||
* @param part 文件
|
||||
* @param path 路径
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<String> saveLocalPath(FilePart part, String path) {
|
||||
SystemUtils.testPath(path);
|
||||
return part.transferTo(Paths.get(path))
|
||||
.then(Mono.just(path));
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.flyfish.framework.file.service;
|
||||
|
||||
|
||||
import com.flyfish.framework.file.config.UploadConfiguration;
|
||||
import com.flyfish.framework.file.domain.Media;
|
||||
import com.flyfish.framework.file.enums.MediaType;
|
||||
import com.flyfish.framework.file.utils.FileSizeUtils;
|
||||
import com.flyfish.framework.file.utils.ZipUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 媒体服务
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MediaService {
|
||||
|
||||
private static String URL = "/api/media/";
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
@Resource
|
||||
private UploadConfiguration uploadConfiguration;
|
||||
|
||||
/**
|
||||
* 上传媒体文件
|
||||
*
|
||||
* @param part 文件
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<Media> upload(FilePart part) {
|
||||
return fileService.saveLocal(part)
|
||||
.map(path -> Media.builder()
|
||||
.size(FileSizeUtils.size(part.headers().getContentLength()))
|
||||
.type(MediaType.accept(part.filename()))
|
||||
.url(URL + path)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析原媒体文件,尝试解压
|
||||
*
|
||||
* @param part 原文件
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<Media> slice(FilePart part) {
|
||||
String localPath = uploadConfiguration.getLocalPath();
|
||||
return fileService.saveLocal(part)
|
||||
.flatMap(path -> {
|
||||
try {
|
||||
return ZipUtils.unzip(localPath + path)
|
||||
.stream().filter(file -> file.endsWith(".m3u8"))
|
||||
.map(file -> file.replace(localPath, ""))
|
||||
.findFirst()
|
||||
.map(Mono::just)
|
||||
.orElse(Mono.just(path));
|
||||
} catch (IOException e) {
|
||||
log.error("很不幸,解压发生了错误:", e);
|
||||
}
|
||||
return Mono.just(path);
|
||||
})
|
||||
.map(path -> Media.builder()
|
||||
.size(FileSizeUtils.size(part.headers().getContentLength()))
|
||||
.type(MediaType.accept(part.filename()))
|
||||
.url(URL + path)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到绝对路径
|
||||
*
|
||||
* @param part 文件
|
||||
* @param absolutePath 绝对路径
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<Media> upload(FilePart part, String absolutePath) {
|
||||
Mono<String> saver = StringUtils.isNotBlank(absolutePath) ? fileService.saveLocalPath(part, absolutePath) :
|
||||
fileService.saveLocal(part);
|
||||
return saver.map(path -> Media.builder()
|
||||
.size(FileSizeUtils.size(part.headers().getContentLength()))
|
||||
.type(MediaType.accept(part.filename()))
|
||||
.url(URL + path)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.flyfish.framework.file.utils;
|
||||
|
||||
import com.flyfish.framework.file.enums.MediaTypes;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ZeroCopyHttpOutputMessage;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public abstract class DownloadUtils {
|
||||
|
||||
public static Mono<Void> download(String path, ServerHttpResponse response) {
|
||||
ZeroCopyHttpOutputMessage zeroCopyResponse = (ZeroCopyHttpOutputMessage) response;
|
||||
HttpHeaders headers = zeroCopyResponse.getHeaders();
|
||||
headers.setContentType(MediaTypes.getMediaType(path));
|
||||
headers.setContentDisposition(ContentDisposition.builder("attachment")
|
||||
.filename(StringUtils.substringAfterLast(path, "/"))
|
||||
.build());
|
||||
Path file = Paths.get(path);
|
||||
return zeroCopyResponse.writeWith(file, 0, file.toFile().length());
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* FileMd5Util.java
|
||||
* com.bibenet.cedp.utils
|
||||
* <p>
|
||||
* ****************************************************************************
|
||||
* Change Log
|
||||
* <p>
|
||||
* 1. wangyu create me at 2016年7月16日上午9:48:14 for class DESC.
|
||||
* 2.
|
||||
* ****************************************************************************
|
||||
* Copyright (c) 2016, www.bibenet.com All Rights Reserved.O(∩_∩)O
|
||||
*/
|
||||
|
||||
package com.flyfish.framework.file.utils;
|
||||
|
||||
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
|
||||
/**
|
||||
* @author wangyu
|
||||
* @name <p>FileMD5Util</p>
|
||||
* @desc 文件MD5校验工具 <1GB
|
||||
*/
|
||||
public class FileMD5Utils {
|
||||
|
||||
/**
|
||||
* <p>getMd5ByFile</p>
|
||||
* 获取文件的Md5
|
||||
*
|
||||
* @param file 文件
|
||||
* @return md5
|
||||
*/
|
||||
public static String getMd5ByFile(File file) {
|
||||
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return getMd5ByInputStream(in, file.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getMd5ByInputStream</p>
|
||||
* 获取流的Md5
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param length 文件长度
|
||||
* @return md5
|
||||
*/
|
||||
public static String getMd5ByInputStream(InputStream in, Long length) {
|
||||
String value = null;
|
||||
Assert.notNull(in, "计算md5的输入流为null!");
|
||||
if (in instanceof FileInputStream) {
|
||||
try {
|
||||
MappedByteBuffer byteBuffer = ((FileInputStream) in).getChannel().map(FileChannel.MapMode.READ_ONLY, 0, length);
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
md5.update(byteBuffer);
|
||||
BigInteger bi = new BigInteger(1, md5.digest());
|
||||
value = bi.toString(16);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
value = getMd5ByASCIIStream(in, length);
|
||||
} catch (NoSuchAlgorithmException | IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String getMd5ByASCIIStream(InputStream in, Long length) throws NoSuchAlgorithmException, IOException {
|
||||
StringBuilder md5 = new StringBuilder();
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] dataBytes = new byte[1024];
|
||||
|
||||
int nread;
|
||||
while ((nread = in.read(dataBytes)) != -1) {
|
||||
md.update(dataBytes, 0, nread);
|
||||
}
|
||||
byte[] mdbytes = md.digest();
|
||||
|
||||
// 转换二进制到HEX
|
||||
for (byte mdByte : mdbytes) {
|
||||
md5.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
return md5.toString();
|
||||
}
|
||||
|
||||
public static String getMd5ByPath(String path, Long length) {
|
||||
Assert.notNull(path, "md5验证时文件路径为空!");
|
||||
try {
|
||||
return getMd5ByInputStream(new FileInputStream(path), length);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* create by InteliJ Idea
|
||||
* <p>
|
||||
* ****************************************************************************
|
||||
* Change Log
|
||||
* <p>
|
||||
* 1. wangyu create me at 2018年03月22日17:59 for class DESC. 2.
|
||||
* ****************************************************************************
|
||||
* Copyright (c) 2018, www.bibenet.com All Rights Reserved.O(∩_∩)O
|
||||
*/
|
||||
package com.flyfish.framework.file.utils;
|
||||
|
||||
/**
|
||||
* 文件大小工具类
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class FileSizeUtils {
|
||||
|
||||
public static String size(Long size) {
|
||||
if (null != size) {
|
||||
//如果字节数少于1024,则直接以B为单位,否则先除于1024,后3位因太少无意义
|
||||
if (size < 1024) {
|
||||
return size + "B";
|
||||
} else {
|
||||
size = size / 1024;
|
||||
}
|
||||
//如果原字节数除于1024之后,少于1024,则可以直接以KB作为单位
|
||||
//因为还没有到达要使用另一个单位的时候
|
||||
//接下去以此类推
|
||||
if (size < 1024) {
|
||||
return size + "KB";
|
||||
} else {
|
||||
size = size / 1024;
|
||||
}
|
||||
if (size < 1024) {
|
||||
//因为如果以MB为单位的话,要保留最后1位小数,
|
||||
//因此,把此数乘以100之后再取余
|
||||
size = size * 100;
|
||||
return size / 100 + "."
|
||||
+ size % 100 + "MB";
|
||||
} else {
|
||||
//否则如果要以GB为单位的,先除于1024再作同样的处理
|
||||
size = size * 100 / 1024;
|
||||
return size / 100 + "."
|
||||
+ size % 100 + "GB";
|
||||
}
|
||||
}
|
||||
return "0KB";
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package com.flyfish.framework.file.utils;
|
||||
|
||||
import com.flyfish.framework.context.DateContext;
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SystemUtils {
|
||||
|
||||
/**
|
||||
* 判断是否是windows系统
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isWindows() {
|
||||
String os = System.getProperty("os.name");
|
||||
return os.toLowerCase().startsWith("win");
|
||||
}
|
||||
|
||||
public static String generateDatePath() {
|
||||
return DateContext.formatDate(new Date(), "yyyy/MM/dd");
|
||||
}
|
||||
|
||||
public static String generateDatePath(Date date) {
|
||||
return DateContext.formatDate(date, "yyyy/MM/dd");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前位置生成按照指定步长的区间
|
||||
*
|
||||
* @param pos 位置
|
||||
* @param step 步长
|
||||
* @return 结果
|
||||
*/
|
||||
public static String range(long pos, int step) {
|
||||
// 求余
|
||||
long left = pos % step;
|
||||
// 求整数商
|
||||
long times = pos / step;
|
||||
// 得出区间
|
||||
long min = times * step;
|
||||
long max = (times + 1) * step;
|
||||
// 返回
|
||||
return min + "-" + max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据基本路径生成时间戳(年/月/日)的目录接口,并创建文件
|
||||
*
|
||||
* @param fullPath 基本路径
|
||||
* @param fileName 文件名
|
||||
* @return 结果
|
||||
*/
|
||||
public static File createFile(String fullPath, String fileName) {
|
||||
testPath(fullPath);
|
||||
return new File(fullPath + fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试路径,并创建
|
||||
*
|
||||
* @param fullPath 全路径
|
||||
*/
|
||||
public static void testPath(String fullPath) {
|
||||
if (fullPath.contains(".")) {
|
||||
fullPath = getPath(fullPath);
|
||||
}
|
||||
File directory = new File(fullPath);
|
||||
if (!directory.exists()) {
|
||||
if (!directory.mkdirs()) {
|
||||
Assert.isTrue(directory.exists(), "目录创建失败!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getPath(String fullPath) {
|
||||
return StringUtils.substringBeforeLast(fullPath, "/");
|
||||
}
|
||||
|
||||
public static long getLineNumber(String fullPath) throws Exception {
|
||||
try (FileReader in = new FileReader(fullPath);
|
||||
LineNumberReader reader = new LineNumberReader(in)) {
|
||||
long skipped = reader.skip(Long.MAX_VALUE);
|
||||
if (skipped == 0) {
|
||||
return 0;
|
||||
}
|
||||
return reader.getLineNumber();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.flyfish.framework.file.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* zip解压缩工具
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZipUtils {
|
||||
|
||||
/**
|
||||
* 解压到当前目录
|
||||
*
|
||||
* @param path 路径
|
||||
*/
|
||||
public static List<String> unzip(String path) throws IOException {
|
||||
// 目录
|
||||
String directory = StringUtils.substringBeforeLast(path, "/");
|
||||
// 解压
|
||||
return unzip(path, directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件到指定目录
|
||||
* 解压后的文件名,和之前一致
|
||||
*
|
||||
* @param path zip文件目录
|
||||
* @param target 目标解压目录
|
||||
*/
|
||||
public static List<String> unzip(String path, String target) throws IOException {
|
||||
// 解决中文文件夹乱码
|
||||
ZipFile zip = new ZipFile(path, Charset.defaultCharset());
|
||||
// 取得真实名字
|
||||
String name = StringUtils.substringBefore(StringUtils.substringAfterLast(path, "/"), ".");
|
||||
// 父目录
|
||||
String parentPath = target + "/" + name;
|
||||
Path pathFile = Paths.get(parentPath);
|
||||
if (!Files.exists(pathFile)) {
|
||||
Files.createDirectories(pathFile);
|
||||
}
|
||||
|
||||
// 循环zip内的文件
|
||||
List<String> files = zip.stream().map(entry -> {
|
||||
String entryName = entry.getName();
|
||||
String output;
|
||||
try (InputStream in = zip.getInputStream(entry)) {
|
||||
OutputStream out = prepare(output = parentPath + "/" + entryName);
|
||||
// 拷贝所有流
|
||||
StreamUtils.copy(in, out);
|
||||
return output;
|
||||
} catch (IOException e) {
|
||||
log.error("[zip]解压某个文件发生异常:{}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
log.debug("解压了:{}", files);
|
||||
return files;
|
||||
}
|
||||
|
||||
// public static void main(String[] args) throws IOException {
|
||||
// unzip("/Users/wangyu/Documents/ceshi.m3u8");
|
||||
// }
|
||||
|
||||
/**
|
||||
* output
|
||||
* 准备安全的输出环境
|
||||
*
|
||||
* @param output 输出路径
|
||||
* @return 结果
|
||||
*/
|
||||
private static FileOutputStream prepare(String output) throws IOException {
|
||||
// 输出文件路径信息
|
||||
log.info("抽取{}", output);
|
||||
// 判断路径是否存在,不存在则创建文件路径
|
||||
Path targetPath = Paths.get(StringUtils.substringBeforeLast(output, "/"));
|
||||
if (!Files.exists(targetPath)) {
|
||||
Files.createDirectories(targetPath);
|
||||
}
|
||||
// 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
|
||||
if (Files.isDirectory(Paths.get(output))) {
|
||||
throw new IOException("[unzip]文件路径为目录,跳过写入");
|
||||
}
|
||||
return new FileOutputStream(output);
|
||||
}
|
||||
}
|
||||
|
3
flyfish-user/README.md
Normal file
3
flyfish-user/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 用户模块
|
||||
|
||||
提供快速的用户集成能力,提供多种组合配置
|
@ -17,4 +17,17 @@
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-web</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.domain.DepartmentQo;
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 部分或者校区controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/departments")
|
||||
public class DepartmentController extends BaseController<Department, DepartmentQo> {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 登录controller
|
||||
* @author wangyu
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/login")
|
||||
public class LoginController {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.domain.PermissionQo;
|
||||
import com.flyfish.framework.domain.po.Permission;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 权限controller
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/permissions")
|
||||
public class PermissionController extends BaseController<Permission, PermissionQo> {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.domain.RoleQo;
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 角色controller
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/roles")
|
||||
public class RoleController extends BaseController<Role, RoleQo> {
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.configuration.annotations.CurrentUser;
|
||||
import com.flyfish.framework.domain.UserPasswordDto;
|
||||
import com.flyfish.framework.domain.UserQo;
|
||||
import com.flyfish.framework.domain.base.IUser;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.service.UserService;
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
public class UserController extends BaseController<User, UserQo> {
|
||||
|
||||
/**
|
||||
* 修改密码逻辑
|
||||
*
|
||||
* @param passwordDto 密码dto
|
||||
* @param user 用户
|
||||
* @return 结果
|
||||
*/
|
||||
@PutMapping("/passwords")
|
||||
public Result<Void> changePassword(@RequestBody UserPasswordDto passwordDto, @CurrentUser User user) {
|
||||
// 检查原密码
|
||||
Assert.isTrue(user.getPassword().equals(passwordDto.getOldPassword()), "原密码不正确!");
|
||||
Assert.isTrue(!user.getPassword().equals(passwordDto.getPassword()), "新密码和旧密码一致,输入个新的吧!");
|
||||
userContext.setUser(user);
|
||||
// 更新密码
|
||||
User updating = new User();
|
||||
updating.setId(user.getId());
|
||||
updating.setPassword(passwordDto.getPassword());
|
||||
service.updateSelectiveById(updating);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@GetMapping("/current")
|
||||
public Mono<Result<IUser>> getCurrentUser() {
|
||||
UserService userService = getService();
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(context -> (IUser) context.getAuthentication().getPrincipal())
|
||||
.map(Result::ok);
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.flyfish.framework.domain.base.IUser;
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.enums.UserStatus;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import com.flyfish.framework.utils.CopyUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户端用户详情
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AdminUserDetails implements UserDetails, IUser {
|
||||
|
||||
private static final long serialVersionUID = -2441854985340378429L;
|
||||
|
||||
private static final List<UserType> adminTypes = Arrays.asList(UserType.ADMIN, UserType.SUPER_ADMIN);
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否是管理员
|
||||
* @param user 用户
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isAdmin(User user) {
|
||||
return adminTypes.contains(user.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
protected String id;
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
protected String code;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private UserType userType;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*/
|
||||
private UserStatus userStatus;
|
||||
|
||||
/**
|
||||
* 冗余的电话号码
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean enable;
|
||||
|
||||
/**
|
||||
* 能否登录移动端
|
||||
*/
|
||||
private Boolean app;
|
||||
|
||||
/**
|
||||
* 有效期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date validDate;
|
||||
|
||||
/**
|
||||
* 可操作校区
|
||||
*/
|
||||
private List<Department> departments;
|
||||
|
||||
/**
|
||||
* 所属角色
|
||||
*/
|
||||
private List<Role> roles;
|
||||
|
||||
/**
|
||||
* 微信openId
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 查询冗余,标记用户信息
|
||||
*/
|
||||
private Object detail;
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isAccountNonExpired() {
|
||||
return adminTypes.contains(getUserType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isAccountNonLocked() {
|
||||
return isAccountNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return isAccountNonExpired();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return BooleanUtils.isTrue(getEnable());
|
||||
}
|
||||
|
||||
public User toUser() {
|
||||
return CopyUtils.copyProps(this, new User());
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 部门(校区)查询实体
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class DepartmentQo extends NameLikeQo<Department> {
|
||||
|
||||
private Boolean leaf;
|
||||
|
||||
private Integer level;
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder<Department> criteriaBuilder() {
|
||||
return super.criteriaBuilder()
|
||||
.with("level", "leaf");
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录DTO
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
public class LoginDto {
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.domain.po.Permission;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 权限查询实体
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class PermissionQo extends NameLikeQo<Permission> {
|
||||
|
||||
private Boolean admin;
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder<Permission> criteriaBuilder() {
|
||||
return super.criteriaBuilder().with("admin");
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 角色查询实体
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleQo extends NameLikeQo<Role> {
|
||||
|
||||
private Boolean admin;
|
||||
|
||||
private Boolean system;
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder<Role> criteriaBuilder() {
|
||||
return super.criteriaBuilder().with("admin", "system");
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 修改密码dto
|
||||
* @author wangyu
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserPasswordDto {
|
||||
|
||||
private String oldPassword;
|
||||
|
||||
private String password;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.flyfish.framework.domain;
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserQo extends NameLikeQo<User> {
|
||||
|
||||
private String userType;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder<User> criteriaBuilder() {
|
||||
return super.criteriaBuilder().with("userType", "username", "password");
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
|
||||
/**
|
||||
* 部门仓库
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface DepartmentRepository extends DefaultRepository<Department> {
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
|
||||
import com.flyfish.framework.domain.po.Permission;
|
||||
|
||||
/**
|
||||
* 权限仓库
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface PermissionRepository extends DefaultRepository<Permission> {
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 异步用户仓库
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface ReactiveUserRepository extends DefaultReactiveRepository<User> {
|
||||
|
||||
Mono<User> findByUsername(String username);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
|
||||
/**
|
||||
* 角色仓库
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface RoleRepository extends DefaultRepository<Role> {
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户repo
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface UserRepository extends DefaultRepository<User> {
|
||||
|
||||
/**
|
||||
* 通过用户名密码查询用户
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<User> findByUsernameAndPassword(String username, String password);
|
||||
|
||||
/**
|
||||
* 通过用户名查询
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<User> findByUsername(String username);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
import com.flyfish.framework.service.impl.BaseServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DepartmentService extends BaseServiceImpl<Department> {
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.domain.AdminUserDetails;
|
||||
import com.flyfish.framework.domain.base.IUser;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.enums.UserStatus;
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
import com.flyfish.framework.utils.CopyUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.security.authentication.*;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 基于mongo实现的用户详情
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class MongoUserDetailsServiceImpl implements MongoUserDetailsService {
|
||||
|
||||
// 存储用户校验规则的map
|
||||
private static final Map<Function<User, Boolean>, Supplier<AuthenticationException>> checkMap;
|
||||
|
||||
static {
|
||||
checkMap = new HashMap<>();
|
||||
// 初始化用户校验规则
|
||||
checkMap.put(user -> null != user.getEnable() && !user.getEnable() ||
|
||||
user.getUserStatus() == UserStatus.DISABLED, () -> new DisabledException("用户被禁用"));
|
||||
checkMap.put(user -> user.getUserStatus() == UserStatus.LOCKED,
|
||||
() -> new LockedException("账户已经锁定!请联系管理员修改密码!"));
|
||||
}
|
||||
|
||||
private final UserService service;
|
||||
private final ReactiveUserService userService;
|
||||
|
||||
@Resource
|
||||
private ServerSecurityContextRepository contextRepository;
|
||||
|
||||
private ReactiveAuthenticationManager authenticationManager;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
UserDetailsRepositoryReactiveAuthenticationManager manager =
|
||||
new UserDetailsRepositoryReactiveAuthenticationManager(this);
|
||||
manager.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
|
||||
authenticationManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserDetails> updatePassword(UserDetails userDetails, String s) {
|
||||
return userService.updateById(((IUser) userDetails).toUser()).map(this::mapToUserDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserDetails> findByUsername(String s) {
|
||||
return userService.findByUsername(s)
|
||||
.flatMap(this::validate)
|
||||
.map(this::mapToUserDetails)
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new UsernameNotFoundException("用户不存在!"))));
|
||||
}
|
||||
|
||||
private UserDetails mapToUserDetails(User user) {
|
||||
AdminUserDetails userDetail = new AdminUserDetails();
|
||||
CopyUtils.copyProps(user, userDetail);
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验
|
||||
*
|
||||
* @param user 用户
|
||||
* @return 结果
|
||||
*/
|
||||
private Mono<User> validate(User user) {
|
||||
return checkMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().apply(user))
|
||||
.findFirst()
|
||||
.map(entry -> Mono.<User>error(entry.getValue().get()))
|
||||
.orElse(Mono.just(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户名密码直接认证
|
||||
*
|
||||
* @param user 用户
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(UserDetails user, ServerWebExchange exchange) {
|
||||
return loadContext(user)
|
||||
.flatMap(securityContext -> contextRepository.save(exchange, securityContext)
|
||||
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(
|
||||
Mono.just(securityContext)))
|
||||
.then(Mono.just(securityContext)))
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.doOnNext(authentication -> {
|
||||
if (authentication.getPrincipal() instanceof IUser) {
|
||||
IUser userDetail = (IUser) authentication.getPrincipal();
|
||||
Assert.isTrue(BooleanUtils.isTrue(userDetail.getApp()), "用户没有移动端登录权限!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SecurityContext> loadContext(UserDetails user) {
|
||||
// 构建账号信息
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
user.getUsername(), user.getPassword());
|
||||
// 登录先
|
||||
return authenticationManager.authenticate(token)
|
||||
.map(authentication -> {
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl();
|
||||
securityContext.setAuthentication(authentication);
|
||||
return securityContext;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*
|
||||
* @param exchange 数据请求
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> logout(ServerWebExchange exchange) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.switchIfEmpty(contextRepository.load(exchange))
|
||||
.flatMap(context -> contextRepository.save(exchange, context));
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
|
||||
import com.flyfish.framework.domain.po.Permission;
|
||||
import com.flyfish.framework.service.impl.BaseServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PermissionService extends BaseServiceImpl<Permission> {
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.repository.ReactiveUserRepository;
|
||||
import com.flyfish.framework.service.impl.BaseReactiveServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 异步用户service
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Service
|
||||
public class ReactiveUserService extends BaseReactiveServiceImpl<User> {
|
||||
|
||||
/**
|
||||
* 获取用户数据
|
||||
*
|
||||
* @param username 用户
|
||||
* @return 结果
|
||||
*/
|
||||
public Mono<User> findByUsername(String username) {
|
||||
return ((ReactiveUserRepository) repository).findByUsername(username);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
|
||||
import com.flyfish.framework.domain.PermissionQo;
|
||||
import com.flyfish.framework.domain.po.Permission;
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
import com.flyfish.framework.enums.RoleType;
|
||||
import com.flyfish.framework.service.impl.BaseServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RoleService extends BaseServiceImpl<Role> {
|
||||
|
||||
private final PermissionService permissionService;
|
||||
|
||||
/**
|
||||
* 如果是管理员,设置拥有所有权限
|
||||
*
|
||||
* @param entity 实体
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Role create(Role entity) {
|
||||
if (entity.isSystem()) {
|
||||
entity.setPermissions(permissionService.getAll());
|
||||
} else if (BooleanUtils.isTrue(entity.getAdmin())) {
|
||||
// 如果是管理员,拥有所有权限
|
||||
PermissionQo qo = new PermissionQo();
|
||||
qo.setAdmin(false);
|
||||
List<Permission> permissions = permissionService.getList(qo);
|
||||
entity.setPermissions(permissions);
|
||||
}
|
||||
if (null == entity.getType()) {
|
||||
entity.setType(RoleType.PC);
|
||||
}
|
||||
return super.create(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.domain.UserQo;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.enums.UserStatus;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import com.flyfish.framework.repository.UserRepository;
|
||||
import com.flyfish.framework.service.impl.BaseServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserService extends BaseServiceImpl<User> {
|
||||
|
||||
@Resource
|
||||
private DepartmentService departmentService;
|
||||
|
||||
/**
|
||||
* 生成超级管理员
|
||||
*/
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
UserQo qo = new UserQo();
|
||||
qo.setUserType(UserType.SUPER_ADMIN.name());
|
||||
if (count(qo) == 0) {
|
||||
User user = new User();
|
||||
user.setUsername("admin");
|
||||
user.setPassword("admin");
|
||||
user.setUserType(UserType.SUPER_ADMIN);
|
||||
user.setEnable(true);
|
||||
user.setApp(false);
|
||||
user.setUserStatus(UserStatus.NORMAL);
|
||||
user.setPhone("10000000000");
|
||||
user.setName("超级管理员");
|
||||
user.setCode("Administrator");
|
||||
createSelective(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户数据
|
||||
*
|
||||
* @param username 用户
|
||||
* @return 结果
|
||||
*/
|
||||
public Optional<User> findByUsername(String username) {
|
||||
return ((UserRepository) repository).findByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getList(Qo<User> query) {
|
||||
return super.getList(query);
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ public class EnumController {
|
||||
Set<Class<? extends NamedEnum>> classSet = reflections.getSubTypesOf(NamedEnum.class);
|
||||
// 注入
|
||||
classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> {
|
||||
String name = StringFormats.camel2Line(clazz.getSimpleName());
|
||||
String name = StringFormats.camel2Line(ClassUtils.getShortClassName(clazz));
|
||||
List<EnumValue> values = Arrays.stream(clazz.getEnumConstants()).reduce(new ArrayList<>(), (result, item) -> {
|
||||
result.add(new EnumValue(((Enum<?>) item).name(), item.getName()));
|
||||
return result;
|
||||
|
Loading…
Reference in New Issue
Block a user