增加文件服务的展示

This commit is contained in:
YunaiV 2021-03-13 13:37:59 +08:00
parent daa38661d2
commit cee1aa3e60
33 changed files with 756 additions and 378 deletions

View File

@ -50,6 +50,7 @@
| --- | --- | --- | | --- | --- | --- |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | | 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | | | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持本地文件存储,同时支持兼容 Amazon S3 协议的云服务、开源组件 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 | | 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 | | | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 |监控 Redis 数据库的使用情况,使用的 Redis Key 管理 | | | Redis 监控 |监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
@ -64,7 +65,6 @@
计划新增: 计划新增:
* 工作流 * 工作流
* 错误码 * 错误码
* 文件服务
### 研发工具 ### 研发工具

26
pom.xml
View File

@ -40,8 +40,8 @@
<skywalking.version>8.3.0</skywalking.version> <skywalking.version>8.3.0</skywalking.version>
<spring-boot-admin.version>2.3.1</spring-boot-admin.version> <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
<!-- 工具类相关 --> <!-- 工具类相关 -->
<mapstruct.version>1.4.2.Final</mapstruct.version> <lombok.version>1.16.14</lombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version> <mapstruct.version>1.4.1.Final</mapstruct.version>
<hutool.version>5.5.6</hutool.version> <hutool.version>5.5.6</hutool.version>
<easyexcel.verion>2.2.7</easyexcel.verion> <easyexcel.verion>2.2.7</easyexcel.verion>
<velocity.version>2.2</velocity.version> <velocity.version>2.2</velocity.version>
@ -227,14 +227,13 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <version>${lombok.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher --> <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
<version>${mapstruct.version}</version> <version>${mapstruct.version}</version>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
@ -246,13 +245,6 @@
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId> <artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version> <version>${mapstruct.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@ -331,6 +323,18 @@
<configuration> <configuration>
<source>${java.version}</source> <!-- or higher, depending on your project --> <source>${java.version}</source> <!-- or higher, depending on your project -->
<target>${java.version}</target> <!-- or higher, depending on your project --> <target>${java.version}</target> <!-- or higher, depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 删除文件
export function deleteFile(id) {
return request({
url: '/infra/file/delete?id=' + id,
method: 'delete'
})
}
// 获得文件分页
export function getFilePage(query) {
return request({
url: '/infra/file/page',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,202 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件路径" prop="id">
<el-input v-model="queryParams.id" placeholder="请输入文件路径" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="文件类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择文件类型" clearable size="small">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">上传文件</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="文件路径" align="center" prop="id" width="300" />
<el-table-column label="文件类型" align="center" prop="type" width="80" />
<el-table-column label="文件内容" align="center" prop="content">
<template slot-scope="scope">
<img v-if="scope.row.type === 'jpg' || scope.row.type === 'png' || scope.row.type === 'gif'"
width="200px" :src="getFileUrl + scope.row.id">
<i v-else>非图片无法预览</i>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:file:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload ref="upload" :limit="1" accept=".jpg, .png, .gif" :auto-upload="false" drag
:headers="upload.headers" :action="upload.url" :data="upload.data" :disabled="upload.isUploading"
:on-change="handleFileChange"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess">
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处 <em>点击上传</em>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示仅允许导入 jpgpnggif 格式文件</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { deleteFile, getFilePage } from "@/api/infra/file";
import {getToken} from "@/utils/auth";
export default {
name: "File",
data() {
return {
getFileUrl: process.env.VUE_APP_BASE_API + '/api/infra/file/get/',
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
id: null,
type: null,
},
//
upload: {
open: false, //
title: "", //
isUploading: false, //
url: process.env.VUE_APP_BASE_API + '/api/' + "/infra/file/upload", //
headers: { Authorization: "Bearer " + getToken() }, //
data: {} //
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getFilePage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
content: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.upload.open = true;
this.upload.title = "上传文件";
},
/** 处理上传的文件发生变化 */
handleFileChange(file, fileList) {
this.upload.data.path = file.name;
},
/** 处理文件上传中 */
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true; //
},
/** 发起文件上窜 */
submitFileForm() {
this.$refs.upload.submit();
},
/** 文件上传成功处理 */
handleFileSuccess(response, file, fileList) {
//
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
//
this.msgSuccess("上传成功");
this.getList();
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除文件编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteFile(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
}
};
</script>

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.framework.file.config; package cn.iocoder.dashboard.framework.file.config;
import cn.iocoder.dashboard.modules.system.controller.common.SysFileController; import cn.iocoder.dashboard.modules.infra.controller.file.InfFileController;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
public class FileProperties { public class FileProperties {
/** /**
* 对应 {@link SysFileController#} * 对应 {@link InfFileController#}
*/ */
@NotNull(message = "基础文件路径不能为空") @NotNull(message = "基础文件路径不能为空")
private String basePath; private String basePath;

View File

@ -134,7 +134,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 静态资源可匿名访问 // 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问 // 文件的获取接口可匿名访问
.antMatchers(webProperties.getApiPrefix() + "/system/file/get/**").anonymous() .antMatchers(webProperties.getApiPrefix() + "/infra/file/get/**").anonymous()
// Swagger 接口文档 // Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous() .antMatchers("/swagger-resources/**").anonymous()

View File

@ -1,9 +1,13 @@
package cn.iocoder.dashboard.modules.system.controller.common; package cn.iocoder.dashboard.modules.infra.controller.file;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult; import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.modules.system.dal.dataobject.common.SysFileDO; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.service.common.SysFileService; import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFilePageReqVO;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFileRespVO;
import cn.iocoder.dashboard.modules.infra.convert.file.InfFileConvert;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
import cn.iocoder.dashboard.util.servlet.ServletUtils; import cn.iocoder.dashboard.util.servlet.ServletUtils;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
@ -11,40 +15,53 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException; import java.io.IOException;
import static cn.iocoder.dashboard.common.pojo.CommonResult.success; import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
@Api(tags = "文件存储") @Api(tags = "文件存储")
@RestController @RestController
@RequestMapping("/system/file") @RequestMapping("/infra/file")
@Validated
@Slf4j @Slf4j
public class SysFileController { public class InfFileController {
@Resource @Resource
private SysFileService fileService; private InfFileService fileService;
@PostMapping("/upload")
@ApiOperation("上传文件") @ApiOperation("上传文件")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class), @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
@ApiImplicitParam(name = "path", value = "文件路径", required = true, example = "yudaoyuanma.png", dataTypeClass = Long.class) @ApiImplicitParam(name = "path", value = "文件路径", required = false, example = "yudaoyuanma.png", dataTypeClass = String.class)
}) })
@PostMapping("/upload")
public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file, public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("path") String path) throws IOException { @RequestParam("path") String path) throws IOException {
return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream()))); return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
} }
@DeleteMapping("/delete")
@ApiOperation("删除文件")
@ApiImplicitParam(name = "id", value = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:file:delete')")
public CommonResult<Boolean> deleteFile(@RequestParam("id") String id) {
fileService.deleteFile(id);
return success(true);
}
@GetMapping("/get/{path}")
@ApiOperation("下载文件") @ApiOperation("下载文件")
@ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class) @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class)
@GetMapping("/get/{path}")
public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException { public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException {
SysFileDO file = fileService.getFile(path); InfFileDO file = fileService.getFile(path);
if (file == null) { if (file == null) {
log.warn("[getFile][path({}) 文件不存在]", path); log.warn("[getFile][path({}) 文件不存在]", path);
response.setStatus(HttpStatus.NOT_FOUND.value()); response.setStatus(HttpStatus.NOT_FOUND.value());
@ -53,4 +70,12 @@ public class SysFileController {
ServletUtils.writeAttachment(response, path, file.getContent()); ServletUtils.writeAttachment(response, path, file.getContent());
} }
@GetMapping("/page")
@ApiOperation("获得文件分页")
@PreAuthorize("@ss.hasPermission('infra:file:query')")
public CommonResult<PageResult<InfFileRespVO>> getFilePage(@Valid InfFilePageReqVO pageVO) {
PageResult<InfFileDO> pageResult = fileService.getFilePage(pageVO);
return success(InfFileConvert.INSTANCE.convertPage(pageResult));
}
} }

View File

@ -0,0 +1,35 @@
package cn.iocoder.dashboard.modules.infra.controller.file.vo;
import cn.iocoder.dashboard.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("文件分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InfFilePageReqVO extends PageParam {
@ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配")
private String id;
@ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配")
private String type;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.dashboard.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

@ -0,0 +1,18 @@
package cn.iocoder.dashboard.modules.infra.convert.file;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFileRespVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface InfFileConvert {
InfFileConvert INSTANCE = Mappers.getMapper(InfFileConvert.class);
InfFileRespVO convert(InfFileDO bean);
PageResult<InfFileRespVO> convertPage(PageResult<InfFileDO> page);
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.dashboard.modules.infra.dal.dataobject.file;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.io.InputStream;
/**
* 文件表
*
* @author 芋道源码
*/
@Data
@TableName("inf_file")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfFileDO extends BaseDO {
/**
* 文件路径
*/
@TableId(type = IdType.INPUT)
private String id;
/**
* 文件类型
*
* 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取
*/
@TableField(value = "`type`")
private String type;
/**
* 文件内容
*/
private byte[] content;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.dashboard.modules.infra.dal.mysql.file;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFilePageReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface InfFileMapper extends BaseMapperX<InfFileDO> {
default Integer selectCountById(String id) {
return selectCount("id", id);
}
default PageResult<InfFileDO> selectPage(InfFilePageReqVO reqVO) {
return selectPage(reqVO, new QueryWrapperX<InfFileDO>()
.likeIfPresent("id", reqVO.getId())
.likeIfPresent("type", reqVO.getType())
.betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc("create_time"));
}
}

View File

@ -27,4 +27,7 @@ public interface InfErrorCodeConstants {
ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在"); ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在");
ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理"); ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理");
// ========== 文件 1001003000 ==========
ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003000, "文件不存在");
} }

View File

@ -0,0 +1,46 @@
package cn.iocoder.dashboard.modules.infra.service.file;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFilePageReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
/**
* 文件 Service 接口
*
* @author 芋道源码
*/
public interface InfFileService {
/**
* 保存文件并返回文件的访问路径
*
* @param path 文件路径
* @param content 文件内容
* @return 文件路径
*/
String createFile(String path, byte[] content);
/**
* 删除文件
*
* @param id 编号
*/
void deleteFile(String id);
/**
* 获得文件
*
* @param path 文件路径
* @return 文件
*/
InfFileDO getFile(String path);
/**
* 获得文件分页
*
* @param pageReqVO 分页查询
* @return 文件分页
*/
PageResult<InfFileDO> getFilePage(InfFilePageReqVO pageReqVO);
}

View File

@ -0,0 +1,72 @@
package cn.iocoder.dashboard.modules.infra.service.file.impl;
import cn.hutool.core.io.FileTypeUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.file.config.FileProperties;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFilePageReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
import cn.iocoder.dashboard.modules.infra.dal.mysql.file.InfFileMapper;
import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.FILE_NOT_EXISTS;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
/**
* 文件 Service 实现类
*
* @author 芋道源码
*/
@Service
public class InfFileServiceImpl implements InfFileService {
@Resource
private InfFileMapper 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);
}
@Override
public PageResult<InfFileDO> getFilePage(InfFilePageReqVO pageReqVO) {
return fileMapper.selectPage(pageReqVO);
}
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.dashboard.modules.system.dal.dataobject.common;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 文件表
*
* @author 芋道源码
*/
@Data
@TableName("sys_file")
@EqualsAndHashCode(callSuper = true)
public class SysFileDO extends BaseDO {
/**
* 文件路径
*/
@TableId(type = IdType.INPUT)
private String id;
/**
* 文件内容
*/
private byte[] content;
}

View File

@ -1,15 +0,0 @@
package cn.iocoder.dashboard.modules.system.dal.mysql.common;
import cn.iocoder.dashboard.modules.system.dal.dataobject.common.SysFileDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysFileMapper extends BaseMapper<SysFileDO> {
default Integer selectCountById(String id) {
return selectCount(new QueryWrapper<SysFileDO>().eq("id", id));
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.dashboard.modules.system.service.common;
import cn.iocoder.dashboard.modules.system.dal.dataobject.common.SysFileDO;
/**
* 文件 Service 接口
*
* @author 芋道源码
*/
public interface SysFileService {
/**
* 保存文件并返回文件的访问路径
*
* @param path 文件路径
* @param content 文件内容
* @return 文件路径
*/
String createFile(String path, byte[] content);
/**
* 获得文件
*
* @param path 文件路径
* @return 文件
*/
SysFileDO getFile(String path);
}

View File

@ -1,47 +0,0 @@
package cn.iocoder.dashboard.modules.system.service.common.impl;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.dashboard.framework.file.config.FileProperties;
import cn.iocoder.dashboard.modules.system.dal.dataobject.common.SysFileDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.common.SysFileMapper;
import cn.iocoder.dashboard.modules.system.service.common.SysFileService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
/**
* 文件 Service 实现类
*
* @author 芋道源码
*/
@Service
public class SysFileServiceImpl implements SysFileService {
@Resource
private SysFileMapper fileMapper;
@Resource
private FileProperties fileProperties;
@Override
public String createFile(String path, byte[] content) {
if (fileMapper.selectCountById(path) > 0) {
throw ServiceExceptionUtil.exception(FILE_PATH_EXISTS);
}
// 保存到数据库
SysFileDO file = new SysFileDO();
file.setId(path);
file.setContent(content);
fileMapper.insert(file);
// 拼接路径返回
return fileProperties.getBasePath() + path;
}
@Override
public SysFileDO getFile(String path) {
return fileMapper.selectById(path);
}
}

View File

@ -99,6 +99,7 @@ public class ToolCodegenBuilder {
.put(String.class.getSimpleName(), Sets.newHashSet("tinytext", "text", "mediumtext", "longtext", // 长文本 .put(String.class.getSimpleName(), Sets.newHashSet("tinytext", "text", "mediumtext", "longtext", // 长文本
"char", "varchar", "nvarchar", "varchar2")) // 短文本 "char", "varchar", "nvarchar", "varchar2")) // 短文本
.put(Date.class.getSimpleName(), Sets.newHashSet("datetime", "time", "date", "timestamp")) .put(Date.class.getSimpleName(), Sets.newHashSet("datetime", "time", "date", "timestamp"))
.put("byte[]", Sets.newHashSet("blob"))
.build(); .build();
static { static {

View File

@ -152,7 +152,7 @@ yudao:
width: 160 width: 160
height: 60 height: 60
file: file:
base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/ base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/infra/file/get/
codegen: codegen:
base-package: ${yudao.info.base-package} base-package: ${yudao.info.base-package}
db-schemas: ${spring.datasource.name} db-schemas: ${spring.datasource.name}

View File

@ -152,7 +152,7 @@ yudao:
width: 160 width: 160
height: 60 height: 60
file: file:
base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/ base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/infra/file/get/
codegen: codegen:
base-package: ${yudao.info.base-package} base-package: ${yudao.info.base-package}
db-schemas: ${spring.datasource.name} db-schemas: ${spring.datasource.name}

View File

@ -16,6 +16,7 @@ import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.
import ${ServiceExceptionUtilClassName}; import ${ServiceExceptionUtilClassName};
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*; import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
/** /**
@ -58,7 +59,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) { private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {
if (${classNameVar}Mapper.selectById(id) == null) { if (${classNameVar}Mapper.selectById(id) == null) {
throw ServiceExceptionUtil.exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
} }
} }

View File

@ -1,13 +1,11 @@
package ${basePackage}.modules.${table.moduleName}.service.${table.businessName}; package ${basePackage}.modules.${table.moduleName}.service.${table.businessName};
import ${basePackage}.BaseSpringBootUnitTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource; import javax.annotation.Resource;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import ${basePackage}.BaseDbUnitTest;
import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl.${table.className}ServiceImpl; import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl.${table.className}ServiceImpl;
import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*; import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
@ -16,6 +14,7 @@ import ${basePackage}.util.object.ObjectUtils;
import ${PageResultClassName}; import ${PageResultClassName};
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*; import java.util.*;
import static cn.hutool.core.util.RandomUtil.*; import static cn.hutool.core.util.RandomUtil.*;
@ -64,7 +63,8 @@ import static org.mockito.Mockito.*;
* *
* @author ${table.author} * @author ${table.author}
*/ */
public class ${table.className}ServiceTest extends BaseSpringBootUnitTest { @Import(${table.className}ServiceImpl.class)
public class ${table.className}ServiceTest extends BaseDbUnitTest {
@Resource @Resource
private ${table.className}ServiceImpl ${classNameVar}Service; private ${table.className}ServiceImpl ${classNameVar}Service;
@ -78,7 +78,7 @@ public class ${table.className}ServiceTest extends BaseSpringBootUnitTest {
${table.className}CreateReqVO reqVO = randomPojo(${table.className}CreateReqVO.class); ${table.className}CreateReqVO reqVO = randomPojo(${table.className}CreateReqVO.class);
// 调用 // 调用
Long ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(reqVO); ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(reqVO);
// 断言 // 断言
assertNotNull(${classNameVar}Id); assertNotNull(${classNameVar}Id);
// 校验记录的属性是否正确 // 校验记录的属性是否正确
@ -118,7 +118,7 @@ public class ${table.className}ServiceTest extends BaseSpringBootUnitTest {
${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class); ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据 ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
// 准备参数 // 准备参数
Long id = db${simpleClassName}.getId(); ${primaryColumn.javaType} id = db${simpleClassName}.getId();
// 调用 // 调用
${classNameVar}Service.delete${simpleClassName}(id); ${classNameVar}Service.delete${simpleClassName}(id);
@ -129,7 +129,7 @@ public class ${table.className}ServiceTest extends BaseSpringBootUnitTest {
@Test @Test
public void testDelete${simpleClassName}_notExists() { public void testDelete${simpleClassName}_notExists() {
// 准备参数 // 准备参数
Long id = randomLongId(); ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id();
// 调用, 并断言异常 // 调用, 并断言异常
assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);

View File

@ -4,7 +4,7 @@ INSERT INTO `sys_menu`(
`path`, `icon`, `component`, `status` `path`, `icon`, `component`, `status`
) )
VALUES ( VALUES (
'${table.classComment}管理', '${permissionPrefix}:query', 2, 0, ${table.parentMenuId}, '${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0 '${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0
); );
@ -12,8 +12,8 @@ VALUES (
SELECT @parentId := LAST_INSERT_ID(); SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL -- 按钮 SQL
#set ($functionNames = ['创建', '更新', '删除', '导出']) #set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
#set ($functionOps = ['create', 'update', 'delete', 'export']) #set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
#foreach ($functionName in $functionNames) #foreach ($functionName in $functionNames)
#set ($index = $foreach.count - 1) #set ($index = $foreach.count - 1)
INSERT INTO `sys_menu`( INSERT INTO `sys_menu`(
@ -21,7 +21,7 @@ INSERT INTO `sys_menu`(
`path`, `icon`, `component`, `status` `path`, `icon`, `component`, `status`
) )
VALUES ( VALUES (
'${table.tableComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId, '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
'', '', '', 0 '', '', '', 0
); );
#end #end

View File

@ -68,7 +68,7 @@
#if ($column.javaType == "Date")## 时间类型 #if ($column.javaType == "Date")## 时间类型
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180"> <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.${javaField}) }}</span>
</template> </template>
</el-table-column> </el-table-column>
#elseif("" != $column.dictType)## 数据字典 #elseif("" != $column.dictType)## 数据字典

View File

@ -0,0 +1,126 @@
package cn.iocoder.dashboard.modules.infra.service.file;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.file.config.FileProperties;
import cn.iocoder.dashboard.modules.infra.controller.file.vo.InfFilePageReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.file.InfFileDO;
import cn.iocoder.dashboard.modules.infra.dal.mysql.file.InfFileMapper;
import cn.iocoder.dashboard.modules.infra.service.file.impl.InfFileServiceImpl;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.FILE_NOT_EXISTS;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.RandomUtils.randomString;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*;
@Import({InfFileServiceImpl.class, FileProperties.class})
public class InfFileServiceTest extends BaseDbUnitTest {
@Resource
private InfFileServiceImpl fileService;
@Resource
private FileProperties fileProperties;
@Resource
private InfFileMapper fileMapper;
@Test
public void testCreateFile_success() {
// 准备参数
String path = randomString();
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
// 调用
String url = fileService.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(() -> fileService.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();
// 调用
fileService.deleteFile(id);
// 校验数据不存在了
assertNull(fileMapper.selectById(id));
}
@Test
public void testDeleteFile_notExists() {
// 准备参数
String id = randomString();
// 调用, 并断言异常
assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS);
}
@Test
public void testGetFilePage() {
// mock 数据
InfFileDO dbFile = randomPojo(InfFileDO.class, o -> { // 等会查询到
o.setId("yudao");
o.setType("jpg");
o.setCreateTime(buildTime(2021, 1, 15));
});
fileMapper.insert(dbFile);
// 测试 id 不匹配
fileMapper.insert(ObjectUtils.clone(dbFile, o -> o.setId("tudou")));
// 测试 type 不匹配
fileMapper.insert(ObjectUtils.clone(dbFile, o -> {
o.setId("yudao02");
o.setType("png");
}));
// 测试 createTime 不匹配
fileMapper.insert(ObjectUtils.clone(dbFile, o -> {
o.setId("yudao03");
o.setCreateTime(buildTime(2020, 1, 15));
}));
// 准备参数
InfFilePageReqVO reqVO = new InfFilePageReqVO();
reqVO.setId("yudao");
reqVO.setType("jp");
reqVO.setBeginCreateTime(buildTime(2021, 1, 10));
reqVO.setEndCreateTime(buildTime(2021, 1, 20));
// 调用
PageResult<InfFileDO> pageResult = fileService.getFilePage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbFile, pageResult.getList().get(0), "content");
}
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.auth;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.iocoder.dashboard.BaseDbAndRedisUnitTest; import cn.iocoder.dashboard.BaseDbAndRedisUnitTest;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
@ -32,24 +31,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @version 1.0 * @version 1.0
* @since <pre>3月 8, 2021</pre> * @since <pre>3月 8, 2021</pre>
*/ */
@Import( @Import(SysUserSessionServiceImpl.class)
SysUserSessionServiceImpl.class)
public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest { public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
@Resource @Resource
SysUserSessionServiceImpl sysUserSessionService; private SysUserSessionServiceImpl sysUserSessionService;
@Resource @Resource
SysUserSessionMapper sysUserSessionMapper; private SysUserSessionMapper sysUserSessionMapper;
@MockBean @MockBean
SecurityProperties securityProperties; private SecurityProperties securityProperties;
@MockBean @MockBean
SysDeptServiceImpl sysDeptService; private SysDeptServiceImpl sysDeptService;
@MockBean @MockBean
SysUserServiceImpl sysUserService; private SysUserServiceImpl sysUserService;
@MockBean @MockBean
SysLoginLogServiceImpl sysLoginLogService; private SysLoginLogServiceImpl sysLoginLogService;
@MockBean @MockBean
SysLoginUserRedisDAO sysLoginUserRedisDAO; private SysLoginUserRedisDAO sysLoginUserRedisDAO;
@Test @Test
public void testClearSessionTimeout_success() throws Exception { public void testClearSessionTimeout_success() throws Exception {

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

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

View File

@ -17,6 +17,18 @@ CREATE TABLE IF NOT EXISTS "inf_config" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '参数配置表'; ) COMMENT '参数配置表';
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 -- sys 开头的 DB
CREATE TABLE IF NOT EXISTS "sys_dept" ( CREATE TABLE IF NOT EXISTS "sys_dept" (