Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/user-social

 Conflicts:
	yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/enums/SysErrorCodeConstants.java
This commit is contained in:
YunaiV
2021-10-28 08:30:24 +08:00
39 changed files with 646 additions and 185 deletions

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.coreservice.modules.infra.controller.file.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel(value = "文件 Response VO", description = "不返回 content 字段,太大")
@Data
public class InfFileRespVO {
@ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg")
private String id;
@ApiModelProperty(value = "文件类型", required = true, example = "jpg")
private String type;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -6,5 +6,7 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface InfFileCoreMapper extends BaseMapperX<InfFileDO> {
default Integer selectCountById(String id) {
return selectCount("id", id);
}
}

View File

@ -0,0 +1,12 @@
package cn.iocoder.yudao.coreservice.modules.infra.framework.file.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 文件 配置类
*/
@Configuration
@EnableConfigurationProperties(FileProperties.class)
public class FileConfiguration {
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.coreservice.modules.infra.framework.file.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "yudao.file")
@Validated
@Data
public class FileProperties {
/**
* 对应 InfFileController 的 getFile 方法
*/
@NotNull(message = "基础文件路径不能为空")
private String basePath;
// TODO 七牛、等等
}

View File

@ -0,0 +1,16 @@
/**
* 文件的存储,推荐使用七牛、阿里云、华为云、腾讯云等文件服务
*
* 在不采用云服务的情况下,我们有几种技术选型:
* 方案 1. 使用自建的文件服务,例如说 minIO、FastDFS 等等
* 方案 2. 使用服务器的文件系统存储
* 方案 3. 使用数据库进行存储
*
* 如果考虑额外在搭建服务,推荐方案 1。
* 对于方案 2 来说,如果要实现文件存储的高可用,需要多台服务器之间做实时同步,可以基于 rsync + inotify 来做
* 对于方案 3 的话,实现起来最简单,但是数据库本身不适合存储海量的文件
*
* 综合考虑,暂时使用方案 3 的方式,比较适合这样一个 all in one 的项目。
* 随着文件的量级大了之后,还是推荐采用云服务。
*/
package cn.iocoder.yudao.coreservice.modules.infra.framework.file;

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.coreservice.modules.infra.service.file;
import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
/**
* core service 文件接口
*
* @author 宋天
*/
public interface InfFileCoreService {
/**
* 保存文件,并返回文件的访问路径
*
* @param path 文件路径
* @param content 文件内容
* @return 文件路径
*/
String createFile(String path, byte[] content);
/**
* 删除文件
*
* @param id 编号
*/
void deleteFile(String id);
/**
* 获得文件
*
* @param path 文件路径
* @return 文件
*/
InfFileDO getFile(String path);
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.coreservice.modules.infra.service.file.impl;
import cn.hutool.core.io.FileTypeUtil;
import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.file.InfFileCoreMapper;
import cn.iocoder.yudao.coreservice.modules.infra.framework.file.config.FileProperties;
import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
/**
* core service 文件实现类
*
* @author 宋天
*/
@Service
public class InfFileCoreServiceImpl implements InfFileCoreService {
@Resource
private InfFileCoreMapper fileMapper;
@Resource
private FileProperties fileProperties;
@Override
public String createFile(String path, byte[] content) {
if (fileMapper.selectCountById(path) > 0) {
throw exception(FILE_PATH_EXISTS);
}
// 保存到数据库
InfFileDO file = new InfFileDO();
file.setId(path);
file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
file.setContent(content);
fileMapper.insert(file);
// 拼接路径返回
return fileProperties.getBasePath() + path;
}
@Override
public void deleteFile(String id) {
// 校验存在
this.validateFileExists(id);
// 更新
fileMapper.deleteById(id);
}
private void validateFileExists(String id) {
if (fileMapper.selectById(id) == null) {
throw exception(FILE_NOT_EXISTS);
}
}
@Override
public InfFileDO getFile(String path) {
return fileMapper.selectById(path);
}
}

View File

@ -14,9 +14,12 @@ public interface SysErrorCodeConstants {
ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1006000001, "模板参数({})缺失");
ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1006000000, "短信模板不存在");
// ========= 文件相关 1006001000=================
ErrorCode FILE_PATH_EXISTS = new ErrorCode(1006001000, "文件路径已存在");
ErrorCode FILE_NOT_EXISTS = new ErrorCode(1006001002, "文件不存在");
// ========== 社交模块 1006001000 ==========
ErrorCode SOCIAL_AUTH_FAILURE = new ErrorCode(1006001000, "社交授权失败,原因是:{}");
ErrorCode SOCIAL_UNBIND_NOT_SELF = new ErrorCode(1006001001, "社交解绑失败,非当前用户绑定");
// ========== 社交模块 1006002000 ==========
ErrorCode SOCIAL_AUTH_FAILURE = new ErrorCode(1006002000, "社交授权失败,原因是:{}");
ErrorCode SOCIAL_UNBIND_NOT_SELF = new ErrorCode(1006002001, "社交解绑失败,非当前用户绑定");
}

View File

@ -0,0 +1,87 @@
package cn.iocoder.yudao.coreservice.modules.infra.service.file;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.iocoder.yudao.coreservice.BaseDbUnitTest;
import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.file.InfFileCoreMapper;
import cn.iocoder.yudao.coreservice.modules.infra.framework.file.config.FileProperties;
import cn.iocoder.yudao.coreservice.modules.infra.service.file.impl.InfFileCoreServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.FILE_NOT_EXISTS;
import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
@Import({InfFileCoreServiceImpl.class, FileProperties.class})
public class InfFileCoreServiceTest extends BaseDbUnitTest {
@Resource
private InfFileCoreService fileCoreService;
@MockBean
private FileProperties fileProperties;
@Resource
private InfFileCoreMapper fileMapper;
@Test
public void testCreateFile_success() {
// 准备参数
String path = randomString();
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
// 调用
String url = fileCoreService.createFile(path, content);
// 断言
assertEquals(fileProperties.getBasePath() + path, url);
// 校验数据
InfFileDO file = fileMapper.selectById(path);
assertEquals(path, file.getId());
assertEquals("jpg", file.getType());
assertArrayEquals(content, file.getContent());
}
@Test
public void testCreateFile_exists() {
// mock 数据
InfFileDO dbFile = randomPojo(InfFileDO.class);
fileMapper.insert(dbFile);
// 准备参数
String path = dbFile.getId(); // 模拟已存在
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
// 调用,并断言异常
assertServiceException(() -> fileCoreService.createFile(path, content), FILE_PATH_EXISTS);
}
@Test
public void testDeleteFile_success() {
// mock 数据
InfFileDO dbFile = randomPojo(InfFileDO.class);
fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据
// 准备参数
String id = dbFile.getId();
// 调用
fileCoreService.deleteFile(id);
// 校验数据不存在了
assertNull(fileMapper.selectById(id));
}
@Test
public void testDeleteFile_notExists() {
// 准备参数
String id = randomString();
// 调用, 并断言异常
assertServiceException(() -> fileCoreService.deleteFile(id), FILE_NOT_EXISTS);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,5 +1,6 @@
-- inf 开头的 DB
DELETE FROM "inf_api_access_log";
DELETE FROM "inf_file";
DELETE FROM "inf_api_error_log";
-- sys 开头的 DB

View File

@ -1,5 +1,17 @@
-- inf 开头的 DB
CREATE TABLE IF NOT EXISTS "inf_file" (
"id" varchar(188) NOT NULL,
"type" varchar(63) DEFAULT NULL,
"content" blob NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '文件表';
-- sys 开头的 DB
CREATE TABLE IF NOT EXISTS `sys_user_session` (