refactor: 重构原有文件上传接口并优化配置文件配置格式

移除 ContiNew Starter 本地存储依赖
This commit is contained in:
Charles7c 2023-12-29 21:44:34 +08:00
parent 44227eab8f
commit 5e370254dd
18 changed files with 193 additions and 222 deletions

View File

@ -64,12 +64,6 @@
<artifactId>continew-starter-file-excel</artifactId> <artifactId>continew-starter-file-excel</artifactId>
</dependency> </dependency>
<!-- ContiNew Starter 存储模块 - 本地存储 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-storage-local</artifactId>
</dependency>
<!-- ContiNew Starter API 文档模块 --> <!-- ContiNew Starter API 文档模块 -->
<dependency> <dependency>
<groupId>top.charles7c.continew</groupId> <groupId>top.charles7c.continew</groupId>
@ -93,7 +87,7 @@
<groupId>org.dromara.x-file-storage</groupId> <groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId> <artifactId>x-file-storage-spring</artifactId>
</dependency> </dependency>
<!-- Amazon Simple Storage Service亚马逊简单存储服务,通用存储协议 S3兼容主流云厂商对象存储 --> <!-- Amazon S3Amazon Simple Storage Service亚马逊简单存储服务,通用存储协议 S3兼容主流云厂商对象存储 -->
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>aws-java-sdk-s3</artifactId>

View File

@ -16,6 +16,8 @@
package top.charles7c.continew.admin.system.config; package top.charles7c.continew.admin.system.config;
import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -25,6 +27,7 @@ import org.springframework.stereotype.Component;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EscapeUtil; import cn.hutool.core.util.EscapeUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.continew.admin.common.util.helper.LoginHelper; import top.charles7c.continew.admin.common.util.helper.LoginHelper;
import top.charles7c.continew.admin.system.enums.FileTypeEnum; import top.charles7c.continew.admin.system.enums.FileTypeEnum;
@ -32,6 +35,7 @@ import top.charles7c.continew.admin.system.mapper.FileMapper;
import top.charles7c.continew.admin.system.mapper.StorageMapper; import top.charles7c.continew.admin.system.mapper.StorageMapper;
import top.charles7c.continew.admin.system.model.entity.FileDO; import top.charles7c.continew.admin.system.model.entity.FileDO;
import top.charles7c.continew.admin.system.model.entity.StorageDO; import top.charles7c.continew.admin.system.model.entity.StorageDO;
import top.charles7c.continew.starter.core.constant.StringConstants;
/** /**
* 文件记录实现类 * 文件记录实现类
@ -50,7 +54,9 @@ public class FileRecorderImpl implements FileRecorder {
@Override @Override
public boolean save(FileInfo fileInfo) { public boolean save(FileInfo fileInfo) {
FileDO file = new FileDO(); FileDO file = new FileDO();
file.setName(EscapeUtil.unescape(fileInfo.getOriginalFilename())); String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename());
file.setName(StrUtil.contains(originalFilename, StringConstants.DOT)
? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) : originalFilename);
file.setSize(fileInfo.getSize()); file.setSize(fileInfo.getSize());
file.setUrl(fileInfo.getUrl()); file.setUrl(fileInfo.getUrl());
file.setExtension(fileInfo.getExt()); file.setExtension(fileInfo.getExt());
@ -66,17 +72,40 @@ public class FileRecorderImpl implements FileRecorder {
@Override @Override
public FileInfo getByUrl(String url) { public FileInfo getByUrl(String url) {
FileDO file = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).one(); FileDO file = this.getFileByUrl(url);
if (null == file) {
return null;
}
FileInfo fileInfo = new FileInfo(); FileInfo fileInfo = new FileInfo();
fileInfo.setOriginalFilename(file.getName()); String extension = file.getExtension();
fileInfo.setOriginalFilename(
StrUtil.isNotBlank(extension) ? file.getName() + StringConstants.DOT + extension : file.getName());
fileInfo.setSize(file.getSize()); fileInfo.setSize(file.getSize());
fileInfo.setUrl(file.getUrl()); fileInfo.setUrl(file.getUrl());
fileInfo.setExt(file.getExtension()); fileInfo.setExt(extension);
fileInfo.setBasePath(StringConstants.EMPTY);
fileInfo.setPath(StringConstants.EMPTY);
fileInfo.setFilename(StrUtil.subAfter(url, StringConstants.SLASH, true));
fileInfo.setPlatform(storageMapper.lambdaQuery().eq(StorageDO::getId, file.getStorageId()).one().getCode());
return fileInfo; return fileInfo;
} }
@Override @Override
public boolean delete(String url) { public boolean delete(String url) {
return fileMapper.lambdaUpdate().eq(FileDO::getUrl, url).remove(); FileDO file = this.getFileByUrl(url);
return fileMapper.lambdaUpdate().eq(FileDO::getUrl, file.getUrl()).remove();
}
/**
* 根据 URL 查询文件
*
* @param url
* URL
* @return 文件信息
*/
private FileDO getFileByUrl(String url) {
Optional<FileDO> fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt();
return fileOptional.orElseGet(() -> fileMapper.lambdaQuery()
.eq(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true)).oneOpt().orElse(null));
} }
} }

View File

@ -41,7 +41,7 @@ import top.charles7c.continew.admin.system.service.StorageService;
* @since 2023/12/24 22:31 * @since 2023/12/24 22:31
*/ */
@Slf4j @Slf4j
//@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class FileStorageConfigLoader implements ApplicationRunner { public class FileStorageConfigLoader implements ApplicationRunner {

View File

@ -18,6 +18,7 @@ package top.charles7c.continew.admin.system.service;
import java.util.List; import java.util.List;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import top.charles7c.continew.admin.system.model.query.FileQuery; import top.charles7c.continew.admin.system.model.query.FileQuery;
@ -39,9 +40,10 @@ public interface FileService extends BaseService<FileResp, FileDetailResp, FileQ
* *
* @param file * @param file
* 文件信息 * 文件信息
* @return 文件信息
*/ */
default void upload(MultipartFile file) { default FileInfo upload(MultipartFile file) {
upload(file, null); return upload(file, null);
} }
/** /**
@ -51,8 +53,9 @@ public interface FileService extends BaseService<FileResp, FileDetailResp, FileQ
* 文件信息 * 文件信息
* @param storageCode * @param storageCode
* 存储库编码 * 存储库编码
* @return 文件信息
*/ */
void upload(MultipartFile file, String storageCode); FileInfo upload(MultipartFile file, String storageCode);
/** /**
* 根据存储库 ID 列表查询 * 根据存储库 ID 列表查询

View File

@ -30,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil; import cn.hutool.core.util.URLUtil;
import top.charles7c.continew.admin.system.enums.StorageTypeEnum;
import top.charles7c.continew.admin.system.mapper.FileMapper; import top.charles7c.continew.admin.system.mapper.FileMapper;
import top.charles7c.continew.admin.system.model.entity.FileDO; import top.charles7c.continew.admin.system.model.entity.FileDO;
import top.charles7c.continew.admin.system.model.entity.StorageDO; import top.charles7c.continew.admin.system.model.entity.StorageDO;
@ -62,13 +63,16 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
private final FileStorageService fileStorageService; private final FileStorageService fileStorageService;
@Override @Override
public void upload(MultipartFile file, String storageCode) { public FileInfo upload(MultipartFile file, String storageCode) {
StorageDO storage;
if (StrUtil.isBlank(storageCode)) { if (StrUtil.isBlank(storageCode)) {
StorageDO storage = storageService.getDefaultStorage(); storage = storageService.getDefaultStorage();
CheckUtils.throwIfNull(storage, "请先指定默认存储库"); CheckUtils.throwIfNull(storage, "请先指定默认存储库");
storageCode = storage.getCode(); } else {
storage = storageService.getByCode(storageCode);
CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", storageCode);
} }
UploadPretreatment uploadPretreatment = fileStorageService.of(file).setPlatform(storageCode); UploadPretreatment uploadPretreatment = fileStorageService.of(file).setPlatform(storage.getCode());
uploadPretreatment.setProgressMonitor(new ProgressListener() { uploadPretreatment.setProgressMonitor(new ProgressListener() {
@Override @Override
public void start() { public void start() {
@ -85,7 +89,11 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
log.info("上传结束"); log.info("上传结束");
} }
}); });
uploadPretreatment.upload(); // 处理本地存储文件 URL
FileInfo fileInfo = uploadPretreatment.upload();
fileInfo.setUrl(StorageTypeEnum.LOCAL.equals(storage.getType())
? URLUtil.normalize(storage.getDomain() + StringConstants.SLASH + fileInfo.getUrl()) : fileInfo.getUrl());
return fileInfo;
} }
@Override @Override

View File

@ -196,7 +196,7 @@ public class StorageServiceImpl
new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper); new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
// 重新注册相同 Pattern 的静态资源映射 // 重新注册相同 Pattern 的静态资源映射
for (Map.Entry<String, String> entry : registerMapping.entrySet()) { for (Map.Entry<String, String> entry : registerMapping.entrySet()) {
String pathPattern = StrUtil.appendIfMissing(entry.getKey(), "/**"); String pathPattern = StrUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
String resourceLocations = StrUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH); String resourceLocations = StrUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
// 移除之前注册过的相同 Pattern 映射 // 移除之前注册过的相同 Pattern 映射
handlerMap.remove(pathPattern); handlerMap.remove(pathPattern);

View File

@ -16,7 +16,6 @@
package top.charles7c.continew.admin.system.service.impl; package top.charles7c.continew.admin.system.service.impl;
import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@ -24,15 +23,15 @@ import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@ -51,17 +50,12 @@ import top.charles7c.continew.admin.system.model.req.UserReq;
import top.charles7c.continew.admin.system.model.req.UserRoleUpdateReq; import top.charles7c.continew.admin.system.model.req.UserRoleUpdateReq;
import top.charles7c.continew.admin.system.model.resp.UserDetailResp; import top.charles7c.continew.admin.system.model.resp.UserDetailResp;
import top.charles7c.continew.admin.system.model.resp.UserResp; import top.charles7c.continew.admin.system.model.resp.UserResp;
import top.charles7c.continew.admin.system.service.DeptService; import top.charles7c.continew.admin.system.service.*;
import top.charles7c.continew.admin.system.service.RoleService;
import top.charles7c.continew.admin.system.service.UserRoleService;
import top.charles7c.continew.admin.system.service.UserService;
import top.charles7c.continew.starter.core.constant.StringConstants; import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.core.util.ExceptionUtils; import top.charles7c.continew.starter.core.util.ExceptionUtils;
import top.charles7c.continew.starter.core.util.FileUploadUtils;
import top.charles7c.continew.starter.core.util.validate.CheckUtils; import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.extension.crud.base.BaseServiceImpl; import top.charles7c.continew.starter.extension.crud.base.BaseServiceImpl;
import top.charles7c.continew.starter.extension.crud.base.CommonUserService; import top.charles7c.continew.starter.extension.crud.base.CommonUserService;
import top.charles7c.continew.starter.storage.local.autoconfigure.LocalStorageProperties;
/** /**
* 用户业务实现 * 用户业务实现
@ -75,11 +69,12 @@ import top.charles7c.continew.starter.storage.local.autoconfigure.LocalStoragePr
public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserResp, UserDetailResp, UserQuery, UserReq> public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserResp, UserDetailResp, UserQuery, UserReq>
implements UserService, CommonUserService { implements UserService, CommonUserService {
private final UserRoleService userRoleService;
private final RoleService roleService;
private final LocalStorageProperties localStorageProperties;
@Resource @Resource
private DeptService deptService; private DeptService deptService;
private final RoleService roleService;
private final UserRoleService userRoleService;
private final FileService fileService;
private final FileStorageService fileStorageService;
@Override @Override
public Long add(UserDO user) { public Long add(UserDO user) {
@ -164,26 +159,20 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public String uploadAvatar(MultipartFile avatarFile, Long id) { public String uploadAvatar(MultipartFile avatarFile, Long id) {
LocalStorageProperties.LocalStorageMapping storageMapping = localStorageProperties.getMapping().get("AVATAR");
DataSize maxFileSize = storageMapping.getMaxFileSize();
CheckUtils.throwIf(avatarFile.getSize() > maxFileSize.toBytes(), "请上传小于 {}MB 的图片", maxFileSize.toMegabytes());
String avatarImageType = FileNameUtil.extName(avatarFile.getOriginalFilename()); String avatarImageType = FileNameUtil.extName(avatarFile.getOriginalFilename());
String[] avatarSupportImgTypes = FileConstants.AVATAR_SUPPORTED_IMG_TYPES; String[] avatarSupportImgTypes = FileConstants.AVATAR_SUPPORTED_IMG_TYPES;
CheckUtils.throwIf(!StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportImgTypes), "头像仅支持 {} 格式的图片", CheckUtils.throwIf(!StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportImgTypes), "头像仅支持 {} 格式的图片",
String.join(StringConstants.CHINESE_COMMA, avatarSupportImgTypes)); String.join(StringConstants.CHINESE_COMMA, avatarSupportImgTypes));
// 上传新头像 // 上传新头像
UserDO user = super.getById(id); UserDO user = super.getById(id);
String avatarPath = storageMapping.getLocation(); FileInfo fileInfo = fileService.upload(avatarFile);
File newAvatarFile = FileUploadUtils.upload(avatarFile, avatarPath, false);
CheckUtils.throwIfNull(newAvatarFile, "上传头像失败");
assert null != newAvatarFile;
// 更新用户头像 // 更新用户头像
String newAvatar = newAvatarFile.getName(); String newAvatar = fileInfo.getUrl();
baseMapper.lambdaUpdate().set(UserDO::getAvatar, newAvatar).eq(UserDO::getId, id).update(); baseMapper.lambdaUpdate().set(UserDO::getAvatar, newAvatar).eq(UserDO::getId, id).update();
// 删除原头像 // 删除原头像
String oldAvatar = user.getAvatar(); String oldAvatar = user.getAvatar();
if (StrUtil.isNotBlank(oldAvatar)) { if (StrUtil.isNotBlank(oldAvatar)) {
FileUtil.del(avatarPath + oldAvatar); fileStorageService.delete(oldAvatar);
} }
return newAvatar; return newAvatar;
} }

View File

@ -2,7 +2,7 @@
<div class="navbar"> <div class="navbar">
<div class="left-side"> <div class="left-side">
<a-space> <a-space>
<img alt="logo" :src="getFile(appStore.getLogo)" height="33" /> <img alt="logo" :src="appStore.getLogo" height="33" />
<a-typography-title <a-typography-title
:style="{ margin: 0, fontSize: '18px' }" :style="{ margin: 0, fontSize: '18px' }"
:heading="5" :heading="5"
@ -199,7 +199,6 @@
import useUser from '@/hooks/user'; import useUser from '@/hooks/user';
import Menu from '@/components/menu/index.vue'; import Menu from '@/components/menu/index.vue';
import getAvatar from '@/utils/avatar'; import getAvatar from '@/utils/avatar';
import getFile from '@/utils/file';
import { setTimer } from '@/utils/auth'; import { setTimer } from '@/utils/auth';
import MessageBox from '../message-box/index.vue'; import MessageBox from '../message-box/index.vue';

View File

@ -7,14 +7,6 @@ export default function getAvatar(
gender: number | undefined, gender: number | undefined,
) { ) {
if (avatar) { if (avatar) {
const baseUrl = import.meta.env.VITE_API_BASE_URL;
if (
!avatar.startsWith('http://') &&
!avatar.startsWith('https://') &&
!avatar.startsWith('blob:')
) {
return `${baseUrl}/avatar/${avatar}`;
}
return avatar; return avatar;
} }

View File

@ -2,8 +2,8 @@
<div class="root"> <div class="root">
<div class="header"> <div class="header">
<img <img
:src="getFile(appStore.getLogo) ?? './logo.svg'" :src="appStore.getLogo ?? './logo.svg'"
alt="logo" alt="Logo"
height="33" height="33"
/> />
<div class="logo-text">{{ appStore.getTitle }}</div> <div class="logo-text">{{ appStore.getTitle }}</div>
@ -78,7 +78,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import getFile from '@/utils/file';
import useResponsive from '@/hooks/responsive'; import useResponsive from '@/hooks/responsive';
import { socialAuth } from '@/api/auth'; import { socialAuth } from '@/api/auth';
import AccountLogin from './components/account-login.vue'; import AccountLogin from './components/account-login.vue';

View File

@ -33,7 +33,7 @@
v-if="faviconFile && faviconFile.url" v-if="faviconFile && faviconFile.url"
class="arco-upload-list-picture custom-upload-avatar favicon" class="arco-upload-list-picture custom-upload-avatar favicon"
> >
<img :src="getFile(faviconFile.url)" /> <img :src="faviconFile.url" alt="favicon" />
<div <div
v-if="isEdit" v-if="isEdit"
class="arco-upload-list-picture-mask favicon" class="arco-upload-list-picture-mask favicon"
@ -77,7 +77,7 @@
v-if="logoFile && logoFile.url" v-if="logoFile && logoFile.url"
class="arco-upload-list-picture custom-upload-avatar logo" class="arco-upload-list-picture custom-upload-avatar logo"
> >
<img :src="getFile(logoFile.url)" /> <img :src="logoFile.url" alt="Logo" />
<div <div
v-if="isEdit" v-if="isEdit"
class="arco-upload-list-picture-mask logo" class="arco-upload-list-picture-mask logo"
@ -181,7 +181,6 @@
resetValue, resetValue,
} from '@/api/system/config'; } from '@/api/system/config';
import { upload } from '@/api/common'; import { upload } from '@/api/common';
import getFile from '@/utils/file';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;

View File

@ -63,6 +63,7 @@ import top.charles7c.continew.starter.core.util.TemplateUtils;
import top.charles7c.continew.starter.core.util.validate.CheckUtils; import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils; import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import top.charles7c.continew.starter.extension.crud.model.resp.R; import top.charles7c.continew.starter.extension.crud.model.resp.R;
import top.charles7c.continew.starter.log.common.annotation.Log;
import top.charles7c.continew.starter.messaging.mail.util.MailUtils; import top.charles7c.continew.starter.messaging.mail.util.MailUtils;
/** /**
@ -84,6 +85,7 @@ public class CaptchaController {
private final ProjectProperties projectProperties; private final ProjectProperties projectProperties;
private final GraphicCaptchaProperties graphicCaptchaProperties; private final GraphicCaptchaProperties graphicCaptchaProperties;
@Log(ignore = true)
@Operation(summary = "获取行为验证码", description = "获取行为验证码Base64编码") @Operation(summary = "获取行为验证码", description = "获取行为验证码Base64编码")
@GetMapping("/behavior") @GetMapping("/behavior")
public R<Object> getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) { public R<Object> getBehaviorCaptcha(CaptchaVO captchaReq, HttpServletRequest request) {
@ -91,12 +93,14 @@ public class CaptchaController {
return R.ok(captchaService.get(captchaReq).getRepData()); return R.ok(captchaService.get(captchaReq).getRepData());
} }
@Log(ignore = true)
@Operation(summary = "校验行为验证码", description = "校验行为验证码") @Operation(summary = "校验行为验证码", description = "校验行为验证码")
@PostMapping("/behavior") @PostMapping("/behavior")
public R<Object> checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq) { public R<Object> checkBehaviorCaptcha(@RequestBody CaptchaVO captchaReq) {
return R.ok(captchaService.check(captchaReq)); return R.ok(captchaService.check(captchaReq));
} }
@Log(ignore = true)
@Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64") @Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64")
@GetMapping("/img") @GetMapping("/img")
public R<CaptchaResp> getImageCaptcha() { public R<CaptchaResp> getImageCaptcha() {

View File

@ -16,7 +16,6 @@
package top.charles7c.continew.admin.webapi.common; package top.charles7c.continew.admin.webapi.common;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -32,8 +31,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.util.unit.DataSize;
import org.springframework.validation.annotation.Validated; 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;
@ -52,14 +51,11 @@ import top.charles7c.continew.admin.system.model.query.RoleQuery;
import top.charles7c.continew.admin.system.model.resp.RoleResp; import top.charles7c.continew.admin.system.model.resp.RoleResp;
import top.charles7c.continew.admin.system.service.*; import top.charles7c.continew.admin.system.service.*;
import top.charles7c.continew.starter.core.autoconfigure.project.ProjectProperties; import top.charles7c.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.charles7c.continew.starter.core.util.FileUploadUtils;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils; import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import top.charles7c.continew.starter.data.mybatis.plus.base.IBaseEnum; import top.charles7c.continew.starter.data.mybatis.plus.base.IBaseEnum;
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery; import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.R; import top.charles7c.continew.starter.extension.crud.model.resp.R;
import top.charles7c.continew.starter.log.common.annotation.Log; import top.charles7c.continew.starter.log.common.annotation.Log;
import top.charles7c.continew.starter.storage.local.autoconfigure.LocalStorageProperties;
/** /**
* 公共 API * 公共 API
@ -75,26 +71,20 @@ import top.charles7c.continew.starter.storage.local.autoconfigure.LocalStoragePr
@RequestMapping("/common") @RequestMapping("/common")
public class CommonController { public class CommonController {
private final ProjectProperties projectProperties;
private final FileService fileService;
private final DeptService deptService; private final DeptService deptService;
private final MenuService menuService; private final MenuService menuService;
private final RoleService roleService; private final RoleService roleService;
private final DictItemService dictItemService; private final DictItemService dictItemService;
private final ProjectProperties projectProperties;
private final LocalStorageProperties localStorageProperties;
private final OptionService optionService; private final OptionService optionService;
@Operation(summary = "上传文件", description = "上传文件") @Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file") @PostMapping("/file")
public R<String> upload(@NotNull(message = "文件不能为空") MultipartFile file) { public R<String> upload(@NotNull(message = "文件不能为空") MultipartFile file) {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
LocalStorageProperties.LocalStorageMapping storageMapping = localStorageProperties.getMapping().get("FILE"); FileInfo fileInfo = fileService.upload(file);
DataSize maxFileSize = storageMapping.getMaxFileSize(); return R.ok("上传成功", fileInfo.getUrl());
CheckUtils.throwIf(file.getSize() > maxFileSize.toBytes(), "请上传小于 {}MB 的文件", maxFileSize.toMegabytes());
String filePath = storageMapping.getLocation();
File newFile = FileUploadUtils.upload(file, filePath, false);
CheckUtils.throwIfNull(newFile, "上传文件失败");
assert null != newFile;
return R.ok("上传成功", newFile.getName());
} }
@Operation(summary = "查询部门树", description = "查询树结构的部门列表") @Operation(summary = "查询部门树", description = "查询树结构的部门列表")

View File

@ -84,6 +84,22 @@ spring.cache:
cache-null-values: true cache-null-values: true
--- ### 验证码配置 --- ### 验证码配置
continew-starter.captcha:
## 行为验证码
behavior:
enabled: true
cache-type: REDIS
water-mark: ${project.app-name}
## 图形验证码
graphic:
enabled: true
# 类型
type: SPEC
# 内容长度
length: 4
# 过期时间
expirationInMinutes: 2
## 其他验证码配置
captcha: captcha:
## 邮箱验证码配置 ## 邮箱验证码配置
mail: mail:
@ -104,41 +120,33 @@ captcha:
# 模板 ID # 模板 ID
templateId: 1 templateId: 1
--- ### ContiNew Starter 组件配置 --- ### 日志配置
continew-starter: continew-starter.log:
## 验证码配置 # 是否打印日志,开启后可打印访问日志(类似于 Nginx access log
captcha: is-print: true
# 行为验证码配置 ## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置)
behavior: logging:
enabled: true level:
cache-type: REDIS top.charles7c: DEBUG
water-mark: ${project.app-name} file:
# 图形验证码配置 path: ./logs
graphic:
enabled: true --- ### 跨域配置
# 类型 continew-starter.cors:
type: SPEC enabled: true
# 内容长度 # 配置允许跨域的域名
length: 4 allowed-origins: '*'
# 过期时间 # 配置允许跨域的请求方式
expirationInMinutes: 2 allowed-methods: '*'
## 日志配置 # 配置允许跨域的请求头
log: allowed-headers: '*'
# 是否打印日志,开启后可打印访问日志(类似于 Nginx access log # 配置允许跨域的响应头
is-print: true exposed-headers: '*'
## 本地存储配置
storage: --- ### 接口文档配置
local: springdoc:
enabled: true swagger-ui:
mapping: enabled: true
FILE:
path-pattern: /file/**
location: C:\${project.app-name}\data\file\
max-file-size: 10MB
AVATAR:
path-pattern: /avatar/**
location: C:\${project.app-name}\data\avatar\
max-file-size: 5MB
--- ### 短信配置 --- ### 短信配置
sms: sms:
@ -169,23 +177,6 @@ spring.mail:
class: javax.net.ssl.SSLSocketFactory class: javax.net.ssl.SSLSocketFactory
port: 465 port: 465
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
rsa:
# 私钥
privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
--- ### 日志配置
logging:
level:
top.charles7c: DEBUG
file:
path: ./logs
--- ### 接口文档配置
springdoc:
swagger-ui:
enabled: true
--- ### Just Auth 配置 --- ### Just Auth 配置
justauth: justauth:
enabled: true enabled: true
@ -220,7 +211,6 @@ sa-token.extension:
- /swagger-resources/** - /swagger-resources/**
- /*/api-docs/** - /*/api-docs/**
# 本地存储资源 # 本地存储资源
- /avatar/**
- /file/** - /file/**
--- ### 文件上传配置 --- ### 文件上传配置
@ -232,14 +222,7 @@ spring.servlet:
# 单次总上传文件大小限制 # 单次总上传文件大小限制
max-request-size: 20MB max-request-size: 20MB
--- ### 跨域配置 --- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
cors: rsa:
enabled: true # 私钥
# 配置允许跨域的域名 privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
allowed-origins: '*'
# 配置允许跨域的请求方式
allowed-methods: '*'
# 配置允许跨域的请求头
allowed-headers: '*'
# 配置允许跨域的响应头
exposed-headers: '*'

View File

@ -86,6 +86,22 @@ spring.cache:
cache-null-values: true cache-null-values: true
--- ### 验证码配置 --- ### 验证码配置
continew-starter.captcha:
# 行为验证码
behavior:
enabled: true
cache-type: REDIS
water-mark: ${project.app-name}
# 图形验证码
graphic:
enabled: true
# 类型
type: SPEC
# 内容长度
length: 4
# 过期时间
expirationInMinutes: 2
## 其他验证码配置
captcha: captcha:
## 邮箱验证码配置 ## 邮箱验证码配置
mail: mail:
@ -106,41 +122,38 @@ captcha:
# 模板 ID # 模板 ID
templateId: 1 templateId: 1
--- ### ContiNew Starter 组件配置 --- ### 日志配置
continew-starter: continew-starter.log:
## 验证码配置 # 是否打印日志,开启后可打印访问日志(类似于 Nginx access log
captcha: is-print: false
# 行为验证码配置 ## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置)
behavior: logging:
enabled: true level:
cache-type: REDIS top.charles7c: INFO
water-mark: ${project.app-name} file:
# 图形验证码配置 path: ../logs
graphic:
enabled: true --- ### 跨域配置
# 类型 continew-starter.cors:
type: SPEC enabled: true
# 内容长度 # 配置允许跨域的域名
length: 4 allowed-origins:
# 过期时间 - ${project.url}
expirationInMinutes: 2 # 配置允许跨域的请求方式
## 日志配置 allowed-methods: '*'
log: # 配置允许跨域的请求头
# 是否打印日志,开启后可打印访问日志(类似于 Nginx access log allowed-headers: '*'
is-print: false # 配置允许跨域的响应头
## 本地存储配置 exposed-headers: '*'
storage:
local: --- ### 接口文档配置
enabled: true springdoc:
mapping: swagger-ui:
FILE: enabled: false
path-pattern: /file/** ## 接口文档增强配置
location: ../data/file/ knife4j:
max-file-size: 10MB # 开启生产环境屏蔽
AVATAR: production: true
path-pattern: /avatar/**
location: ../data/avatar/
max-file-size: 5MB
--- ### 短信配置 --- ### 短信配置
sms: sms:
@ -171,27 +184,6 @@ spring.mail:
class: javax.net.ssl.SSLSocketFactory class: javax.net.ssl.SSLSocketFactory
port: 465 port: 465
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
rsa:
# 私钥
privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
--- ### 日志配置
logging:
level:
top.charles7c: INFO
file:
path: ../logs
--- ### 接口文档配置
springdoc:
swagger-ui:
enabled: false
## 接口文档增强配置
knife4j:
# 开启生产环境屏蔽
production: true
--- ### Just Auth 配置 --- ### Just Auth 配置
justauth: justauth:
enabled: true enabled: true
@ -219,7 +211,6 @@ sa-token.extension:
- /**/*.js - /**/*.js
- /webSocket/** - /webSocket/**
# 本地存储资源 # 本地存储资源
- /avatar/**
- /file/** - /file/**
--- ### 文件上传配置 --- ### 文件上传配置
@ -231,15 +222,7 @@ spring.servlet:
# 单次总上传文件大小限制 # 单次总上传文件大小限制
max-request-size: 20MB max-request-size: 20MB
--- ### 跨域配置 --- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
cors: rsa:
enabled: true # 私钥
# 配置允许跨域的域名 privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
allowed-origins:
- ${project.url}
# 配置允许跨域的请求方式
allowed-methods: '*'
# 配置允许跨域的请求头
allowed-headers: '*'
# 配置允许跨域的响应头
exposed-headers: '*'

View File

@ -22,10 +22,7 @@ project:
# 是否启用本地解析 IP 归属地 # 是否启用本地解析 IP 归属地
ip-addr-local-parse-enabled: true ip-addr-local-parse-enabled: true
--- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置) --- ### 日志配置
logging:
config: classpath:logback-spring.xml
## 日志配置
continew-starter.log: continew-starter.log:
enabled: true enabled: true
# 包含信息 # 包含信息
@ -39,6 +36,17 @@ continew-starter.log:
- OS - OS
- RESPONSE_HEADERS - RESPONSE_HEADERS
- RESPONSE_BODY - RESPONSE_BODY
## 项目日志配置
logging:
config: classpath:logback-spring.xml
--- ### 线程池配置
continew-starter.thread-pool:
enabled: true
# 队列容量
queue-capacity: 128
# 活跃时间(单位:秒)
keep-alive-seconds: 300
--- ### 接口文档配置 --- ### 接口文档配置
springdoc: springdoc:
@ -195,14 +203,6 @@ management.health:
# 关闭邮箱健康检查(邮箱配置错误或邮箱服务器不可用时,健康检查会报错) # 关闭邮箱健康检查(邮箱配置错误或邮箱服务器不可用时,健康检查会报错)
enabled: false enabled: false
--- ### 线程池配置
thread-pool:
enabled: true
# 队列容量
queue-capacity: 128
# 活跃时间(单位:秒)
keep-alive-seconds: 300
--- ### 代码生成器配置 --- ### 代码生成器配置
generator: generator:
# 排除数据表 # 排除数据表

View File

@ -20,5 +20,5 @@ VALUES
INSERT IGNORE INTO `sys_storage` INSERT IGNORE INTO `sys_storage`
(`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`, `update_user`, `update_time`) (`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`, `update_user`, `update_time`)
VALUES VALUES
(1, '本地存储-开发环境', 'local-dev', 2, NULL, NULL, NULL, 'C:/continew-admin/data/file', 'http://localhost:8000/file', '本地存储-开发环境', b'0', 1, 2, 1, NOW(), NULL, NULL), (1, '本地存储-开发环境', 'local-dev', 2, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储-开发环境', b'1', 1, 1, 1, NOW(), NULL, NULL),
(2, '本地存储', 'local', 2, NULL, NULL, NULL, '../data/file/', 'http://api.charles7c.top/file', '本地存储', b'1', 1, 1, 1, NOW(), NULL, NULL); (2, '本地存储-生产环境', 'local-prod', 2, NULL, NULL, NULL, '../data/file/', 'http://api.charles7c.top/file', '本地存储-生产环境', b'0', 2, 2, 1, NOW(), NULL, NULL);

View File

@ -55,7 +55,6 @@ services:
volumes: volumes:
- /docker/continew-admin/config/:/app/config/ - /docker/continew-admin/config/:/app/config/
- /docker/continew-admin/data/file/:/app/data/file/ - /docker/continew-admin/data/file/:/app/data/file/
- /docker/continew-admin/data/avatar/:/app/data/avatar/
- /docker/continew-admin/logs/:/app/logs/ - /docker/continew-admin/logs/:/app/logs/
- /docker/continew-admin/lib/:/app/lib/ - /docker/continew-admin/lib/:/app/lib/
depends_on: depends_on: