This commit is contained in:
2025-06-17 17:08:14 +08:00
commit 71179324c8
1205 changed files with 798946 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
package org.dromara.resource;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 资源服务
*
* @author Lion Li
*/
@EnableDubbo
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RuoYiResourceApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(RuoYiResourceApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ 资源服务模块启动成功 ლ(´ڡ`ლ)゙ ");
}
}

View File

@@ -0,0 +1,70 @@
package org.dromara.resource.controller;
import cn.hutool.core.util.RandomUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.constraints.NotBlank;
import java.time.Duration;
/**
* 邮件功能
*
* @author Lion Li
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/email")
public class SysEmailController extends BaseController {
private final MailProperties mailProperties;
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
SpringUtils.getAopProxy(this).emailCodeImpl(email);
return R.ok();
}
/**
* 邮箱验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(key = "#email", time = 60, count = 1)
public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,100 @@
package org.dromara.resource.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.resource.domain.bo.SysOssConfigBo;
import org.dromara.resource.domain.vo.SysOssConfigVo;
import org.dromara.resource.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
/**
* 对象存储配置Controller
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/oss/config")
public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService;
/**
* 查询对象存储配置列表
*/
@SaCheckPermission("system:ossConfig:list")
@GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
return iSysOssConfigService.queryPageList(bo, pageQuery);
}
/**
* 获取对象存储配置详细信息
*
* @param ossConfigId OSS配置ID
*/
@SaCheckPermission("system:ossConfig:list")
@GetMapping("/{ossConfigId}")
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable("ossConfigId") Long ossConfigId) {
return R.ok(iSysOssConfigService.queryById(ossConfigId));
}
/**
* 新增对象存储配置
*/
@SaCheckPermission("system:ossConfig:add")
@Log(title = "对象存储配置", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.insertByBo(bo));
}
/**
* 修改对象存储配置
*/
@SaCheckPermission("system:ossConfig:edit")
@Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateByBo(bo));
}
/**
* 删除对象存储配置
*
* @param ossConfigIds OSS配置ID串
*/
@SaCheckPermission("system:ossConfig:remove")
@Log(title = "对象存储配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ossConfigIds) {
return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true));
}
/**
* 状态修改
*/
@SaCheckPermission("system:ossConfig:edit")
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
}
}

View File

@@ -0,0 +1,106 @@
package org.dromara.resource.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssUploadVo;
import org.dromara.resource.domain.vo.SysOssVo;
import org.dromara.resource.service.ISysOssService;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* 文件上传 控制层
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/oss")
public class SysOssController extends BaseController {
private final ISysOssService iSysOssService;
/**
* 查询OSS对象存储列表
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
return iSysOssService.queryPageList(bo, pageQuery);
}
/**
* 查询OSS对象基于id串
*
* @param ossIds OSS对象ID串
*/
@SaCheckPermission("system:oss:query")
@GetMapping("/listByIds/{ossIds}")
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ossIds) {
List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
return R.ok(list);
}
/**
* 上传OSS对象存储
*
* @param file 文件
*/
@SaCheckPermission("system:oss:upload")
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) {
return R.fail("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
SysOssUploadVo uploadVo = new SysOssUploadVo();
uploadVo.setUrl(oss.getUrl());
uploadVo.setFileName(oss.getOriginalName());
uploadVo.setOssId(oss.getOssId().toString());
return R.ok(uploadVo);
}
/**
* 下载OSS对象存储
*
* @param ossId OSS对象ID
*/
@SaCheckPermission("system:oss:download")
@GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
iSysOssService.download(ossId, response);
}
/**
* 删除OSS对象存储
*
* @param ossIds OSS对象ID串
*/
@SaCheckPermission("system:oss:remove")
@Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ossIds) {
return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true));
}
}

View File

@@ -0,0 +1,61 @@
package org.dromara.resource.controller;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.web.core.BaseController;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.LinkedHashMap;
/**
* 短信功能
*
* @author Lion Li
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/sms")
public class SysSmsController extends BaseController {
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/code")
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("tx1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
}

View File

@@ -0,0 +1,55 @@
package org.dromara.resource.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
/**
* OSS对象存储对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss")
public class SysOss extends TenantEntity {
/**
* 对象存储主键
*/
@TableId(value = "oss_id")
private Long ossId;
/**
* 文件名
*/
private String fileName;
/**
* 原名
*/
private String originalName;
/**
* 文件后缀名
*/
private String fileSuffix;
/**
* URL地址
*/
private String url;
/**
* 扩展字段
*/
private String ext1;
/**
* 服务商
*/
private String service;
}

View File

@@ -0,0 +1,91 @@
package org.dromara.resource.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/**
* 对象存储配置对象 sys_oss_config
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss_config")
public class SysOssConfig extends BaseEntity {
/**
* 主键
*/
@TableId(value = "oss_config_id")
private Long ossConfigId;
/**
* 配置key
*/
private String configKey;
/**
* accessKey
*/
private String accessKey;
/**
* 秘钥
*/
private String secretKey;
/**
* 桶名称
*/
private String bucketName;
/**
* 前缀
*/
private String prefix;
/**
* 访问站点
*/
private String endpoint;
/**
* 自定义域名
*/
private String domain;
/**
* 是否https0否 1是
*/
private String isHttps;
/**
* 域
*/
private String region;
/**
* 是否默认0=是,1=否)
*/
private String status;
/**
* 扩展字段
*/
private String ext1;
/**
* 备注
*/
private String remark;
/**
* 桶权限类型(0private 1public 2custom)
*/
private String accessPolicy;
}

View File

@@ -0,0 +1,54 @@
package org.dromara.resource.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.resource.domain.SysOss;
/**
* OSS对象存储分页查询对象 sys_oss
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysOss.class, reverseConvertGenerate = false)
public class SysOssBo extends BaseEntity {
/**
* ossId
*/
private Long ossId;
/**
* 文件名
*/
private String fileName;
/**
* 原名
*/
private String originalName;
/**
* 文件后缀名
*/
private String fileSuffix;
/**
* URL地址
*/
private String url;
/**
* 扩展字段
*/
private String ext1;
/**
* 服务商
*/
private String service;
}

View File

@@ -0,0 +1,109 @@
package org.dromara.resource.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.resource.domain.SysOssConfig;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
/**
* 对象存储配置业务对象 sys_oss_config
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysOssConfig.class, reverseConvertGenerate = false)
public class SysOssConfigBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = {EditGroup.class})
private Long ossConfigId;
/**
* 配置key
*/
@NotBlank(message = "配置key不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(min = 2, max = 100, message = "configKey长度必须介于2和20 之间")
private String configKey;
/**
* accessKey
*/
@NotBlank(message = "accessKey不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(min = 2, max = 100, message = "accessKey长度必须介于2和100 之间")
private String accessKey;
/**
* 秘钥
*/
@NotBlank(message = "secretKey不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(min = 2, max = 100, message = "secretKey长度必须介于2和100 之间")
private String secretKey;
/**
* 桶名称
*/
@NotBlank(message = "桶名称不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(min = 2, max = 100, message = "bucketName长度必须介于2和100之间")
private String bucketName;
/**
* 前缀
*/
private String prefix;
/**
* 访问站点
*/
@NotBlank(message = "访问站点不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(min = 2, max = 100, message = "endpoint长度必须介于2和100之间")
private String endpoint;
/**
* 自定义域名
*/
private String domain;
/**
* 是否httpsY=是,N=否)
*/
private String isHttps;
/**
* 是否默认0=是,1=否)
*/
private String status;
/**
* 域
*/
private String region;
/**
* 扩展字段
*/
private String ext1;
/**
* 备注
*/
private String remark;
/**
* 桶权限类型(0private 1public 2custom)
*/
@NotBlank(message = "桶权限类型不能为空", groups = {AddGroup.class, EditGroup.class})
private String accessPolicy;
}

View File

@@ -0,0 +1,21 @@
package org.dromara.resource.domain.convert;
import io.github.linpeilie.BaseMapper;
import org.dromara.resource.api.domain.RemoteFile;
import org.dromara.resource.domain.vo.SysOssVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
/**
* 用户信息转换器
* @author zhujie
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface SysOssVoConvert extends BaseMapper<SysOssVo, RemoteFile> {
@Mapping(target = "name", source = "fileName")
RemoteFile convert(SysOssVo sysOssVo);
}

View File

@@ -0,0 +1,96 @@
package org.dromara.resource.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.resource.domain.SysOssConfig;
import java.io.Serial;
import java.io.Serializable;
/**
* 对象存储配置视图对象 sys_oss_config
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Data
@AutoMapper(target = SysOssConfig.class)
public class SysOssConfigVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long ossConfigId;
/**
* 配置key
*/
private String configKey;
/**
* accessKey
*/
private String accessKey;
/**
* 秘钥
*/
private String secretKey;
/**
* 桶名称
*/
private String bucketName;
/**
* 前缀
*/
private String prefix;
/**
* 访问站点
*/
private String endpoint;
/**
* 自定义域名
*/
private String domain;
/**
* 是否httpsY=是,N=否)
*/
private String isHttps;
/**
* 域
*/
private String region;
/**
* 是否默认0=是,1=否)
*/
private String status;
/**
* 扩展字段
*/
private String ext1;
/**
* 备注
*/
private String remark;
/**
* 桶权限类型(0private 1public 2custom)
*/
private String accessPolicy;
}

View File

@@ -0,0 +1,34 @@
package org.dromara.resource.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 上传对象信息
*
* @author Michelle.Chung
*/
@Data
public class SysOssUploadVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* URL地址
*/
private String url;
/**
* 文件名
*/
private String fileName;
/**
* 对象存储主键
*/
private String ossId;
}

View File

@@ -0,0 +1,77 @@
package org.dromara.resource.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.resource.domain.SysOss;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* OSS对象存储视图对象 sys_oss
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysOss.class)
public class SysOssVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 对象存储主键
*/
private Long ossId;
/**
* 文件名
*/
private String fileName;
/**
* 原名
*/
private String originalName;
/**
* 文件后缀名
*/
private String fileSuffix;
/**
* URL地址
*/
private String url;
/**
* 扩展字段
*/
private String ext1;
/**
* 创建时间
*/
private Date createTime;
/**
* 上传人
*/
private Long createBy;
/**
* 上传人名称
*/
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
private String createByName;
/**
* 服务商
*/
private String service;
}

View File

@@ -0,0 +1,89 @@
package org.dromara.resource.dubbo;
import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.resource.api.RemoteFileService;
import org.dromara.resource.api.domain.RemoteFile;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssVo;
import org.dromara.resource.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 文件请求处理
*
* @author Lion Li
*/
@Slf4j
@Service
@RequiredArgsConstructor
@DubboService
public class RemoteFileServiceImpl implements RemoteFileService {
private final ISysOssService sysOssService;
/**
* 文件上传请求
*/
@Transactional(rollbackFor = Exception.class)
@Override
public RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) throws ServiceException {
try {
String suffix = StringUtils.substring(originalFilename, originalFilename.lastIndexOf("."), originalFilename.length());
OssClient storage = OssFactory.instance();
UploadResult uploadResult = storage.uploadSuffix(file, suffix, contentType);
// 保存文件信息
SysOssBo oss = new SysOssBo();
oss.setUrl(uploadResult.getUrl());
oss.setFileSuffix(suffix);
oss.setFileName(uploadResult.getFilename());
oss.setOriginalName(originalFilename);
oss.setService(storage.getConfigKey());
sysOssService.insertByBo(oss);
RemoteFile sysFile = new RemoteFile();
sysFile.setOssId(oss.getOssId());
sysFile.setName(uploadResult.getFilename());
sysFile.setUrl(uploadResult.getUrl());
sysFile.setOriginalName(originalFilename);
sysFile.setFileSuffix(suffix);
return sysFile;
} catch (Exception e) {
log.error("上传文件失败", e);
throw new ServiceException("上传文件失败");
}
}
/**
* 通过ossId查询对应的url
*
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
@Override
public String selectUrlByIds(String ossIds) {
return sysOssService.selectUrlByIds(ossIds);
}
/**
* 通过ossId查询列表
*
* @param ossIds ossId串逗号分隔
* @return 列表
*/
@Override
public List<RemoteFile> selectByIds(String ossIds){
List<SysOssVo> sysOssVos = sysOssService.listByIds(StringUtils.splitTo(ossIds, Convert::toLong));
return MapstructUtils.convert(sysOssVos, RemoteFile.class);
}
}

View File

@@ -0,0 +1,34 @@
package org.dromara.resource.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.resource.api.RemoteMailService;
import org.springframework.stereotype.Service;
/**
* 邮件服务
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteMailServiceImpl implements RemoteMailService {
/**
* 发送邮件
*
* @param to 接收人
* @param subject 标题
* @param text 内容
*/
@Override
public void send(String to, String subject, String text) throws ServiceException {
MailUtils.sendText(to, subject, text);
}
}

View File

@@ -0,0 +1,48 @@
package org.dromara.resource.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.resource.api.RemoteMessageService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 消息服务
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteMessageServiceImpl implements RemoteMessageService {
/**
* 发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
@Override
public void publishMessage(List<Long> sessionKey, String message) {
SseMessageDto dto = new SseMessageDto();
dto.setMessage(message);
dto.setUserIds(sessionKey);
SseMessageUtils.publishMessage(dto);
}
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
@Override
public void publishAll(String message) {
SseMessageUtils.publishAll(message);
}
}

View File

@@ -0,0 +1,236 @@
package org.dromara.resource.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.resource.api.RemoteSmsService;
import org.dromara.resource.api.domain.RemoteSms;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 短信服务
*
* @author Feng
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteSmsServiceImpl implements RemoteSmsService {
/**
* 获取特定供应商类型的 SmsBlend 实例
*
* @return SmsBlend 实例,代表指定供应商类型
*/
private SmsBlend getSmsBlend() {
// 可自定义厂商配置获取规则 例如根据租户获取 或 负载均衡多个厂商等
return SmsFactory.getSmsBlend("config1");
}
/**
* 根据给定的 SmsResponse 对象创建并返回一个 RemoteSms 对象,封装短信发送的响应信息
*
* @param smsResponse 短信发送的响应信息
* @return 封装了短信发送结果的 RemoteSms 对象
*/
private RemoteSms getRemoteSms(SmsResponse smsResponse) {
// 创建一个 RemoteSms 对象,封装响应信息
RemoteSms sms = new RemoteSms();
sms.setSuccess(smsResponse.isSuccess());
sms.setResponse(smsResponse.getData().toString());
sms.setConfigId(smsResponse.getConfigId());
return sms;
}
/**
* 同步方法:发送简单文本短信
*
* @param phone 目标手机号
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@Override
public RemoteSms sendMessage(String phone, String message) {
// 调用 getSmsBlend 方法获取对应短信供应商的 SmsBlend 实例
SmsResponse smsResponse = getSmsBlend().sendMessage(phone, message);
return getRemoteSms(smsResponse);
}
/**
* 同步方法:发送固定消息模板多模板参数短信
*
* @param phone 目标手机号
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@Override
public RemoteSms sendMessage(String phone, LinkedHashMap<String, String> messages) {
SmsResponse smsResponse = getSmsBlend().sendMessage(phone, messages);
return getRemoteSms(smsResponse);
}
/**
* 同步方法:发送带参数的短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@Override
public RemoteSms sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
// 调用 getSmsBlend 方法获取对应短信供应商的 SmsBlend 实例
SmsResponse smsResponse = getSmsBlend().sendMessage(phone, templateId, messages);
return getRemoteSms(smsResponse);
}
/**
* 同步方法:群发简单文本短信
*
* @param phones 目标手机号列表
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@Override
public RemoteSms messageTexting(List<String> phones, String message) {
// 调用 getSmsBlend 方法获取对应短信供应商的 SmsBlend 实例
SmsResponse smsResponse = getSmsBlend().massTexting(phones, message);
return getRemoteSms(smsResponse);
}
/**
* 同步方法:群发带参数的短信
*
* @param phones 目标手机号列表
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@Override
public RemoteSms messageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
// 调用 getSmsBlend 方法获取对应短信供应商的 SmsBlend 实例
SmsResponse smsResponse = getSmsBlend().massTexting(phones, templateId, messages);
return getRemoteSms(smsResponse);
}
/**
* 异步方法:发送简单文本短信
*
* @param phone 目标手机号
* @param message 短信内容
*/
@Override
public void sendMessageAsync(String phone, String message) {
getSmsBlend().sendMessageAsync(phone, message);
}
/**
* 异步方法:发送带参数的短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
*/
@Override
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages) {
getSmsBlend().sendMessageAsync(phone, templateId, messages);
}
/**
* 延迟发送简单文本短信
*
* @param phone 目标手机号
* @param message 短信内容
* @param delayedTime 延迟发送时间(毫秒)
*/
@Override
public void delayMessage(String phone, String message, Long delayedTime) {
getSmsBlend().delayedMessage(phone, message, delayedTime);
}
/**
* 延迟发送带参数的短信
*
* @param phone 目标手机号
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间(毫秒)
*/
@Override
public void delayMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
getSmsBlend().delayedMessage(phone, templateId, messages, delayedTime);
}
/**
* 延迟群发简单文本短信
*
* @param phones 目标手机号列表
* @param message 短信内容
* @param delayedTime 延迟发送时间(毫秒)
*/
@Override
public void delayMessageTexting(List<String> phones, String message, Long delayedTime) {
getSmsBlend().delayMassTexting(phones, message, delayedTime);
}
/**
* 延迟批量发送带参数的短信
*
* @param phones 目标手机号列表
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间(毫秒)
*/
@Override
public void delayMessageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
getSmsBlend().delayMassTexting(phones, templateId, messages, delayedTime);
}
/**
* 加入黑名单
*
* @param phone 手机号
*/
@Override
public void addBlacklist(String phone) {
getSmsBlend().joinInBlacklist(phone);
}
/**
* 加入黑名单
*
* @param phones 手机号列表
*/
@Override
public void addBlacklist(List<String> phones) {
getSmsBlend().batchJoinBlacklist(phones);
}
/**
* 移除黑名单
*
* @param phone 手机号
*/
@Override
public void removeBlacklist(String phone) {
getSmsBlend().removeFromBlacklist(phone);
}
/**
* 移除黑名单
*
* @param phones 手机号
*/
@Override
public void removeBlacklist(List<String> phones) {
getSmsBlend().batchRemovalFromBlacklist(phones);
}
}

View File

@@ -0,0 +1,17 @@
package org.dromara.resource.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.resource.domain.SysOssConfig;
import org.dromara.resource.domain.vo.SysOssConfigVo;
/**
* 对象存储配置Mapper接口
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
public interface SysOssConfigMapper extends BaseMapperPlus<SysOssConfig, SysOssConfigVo> {
}

View File

@@ -0,0 +1,14 @@
package org.dromara.resource.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.resource.domain.SysOss;
import org.dromara.resource.domain.vo.SysOssVo;
/**
* 文件上传 数据层
*
* @author Lion Li
*/
public interface SysOssMapper extends BaseMapperPlus<SysOss, SysOssVo> {
}

View File

@@ -0,0 +1,28 @@
package org.dromara.resource.runner;
import org.dromara.resource.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 初始化 system 模块对应业务数据
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class ResourceApplicationRunner implements ApplicationRunner {
private final ISysOssConfigService ossConfigService;
@Override
public void run(ApplicationArguments args) throws Exception {
ossConfigService.init();
log.info("初始化OSS配置成功");
}
}

View File

@@ -0,0 +1,66 @@
package org.dromara.resource.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.resource.domain.bo.SysOssConfigBo;
import org.dromara.resource.domain.vo.SysOssConfigVo;
import java.util.Collection;
/**
* 对象存储配置Service接口
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
public interface ISysOssConfigService {
/**
* 初始化OSS配置
*/
void init();
/**
* 查询单个
*/
SysOssConfigVo queryById(Long ossConfigId);
/**
* 查询列表
*/
TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery);
/**
* 根据新增业务对象插入对象存储配置
*
* @param bo 对象存储配置新增业务对象
* @return 结果
*/
Boolean insertByBo(SysOssConfigBo bo);
/**
* 根据编辑业务对象修改对象存储配置
*
* @param bo 对象存储配置编辑业务对象
* @return 结果
*/
Boolean updateByBo(SysOssConfigBo bo);
/**
* 校验并删除数据
*
* @param ids 主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 启用停用状态
*/
int updateOssConfigStatus(SysOssConfigBo bo);
}

View File

@@ -0,0 +1,95 @@
package org.dromara.resource.service;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssVo;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
* 文件上传 服务层
*
* @author Lion Li
*/
public interface ISysOssService {
/**
* 查询OSS对象存储列表
*
* @param sysOss OSS对象存储分页查询对象
* @param pageQuery 分页查询实体类
* @return 结果
*/
TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
/**
* 根据一组 ossIds 获取对应的 SysOssVo 列表
*
* @param ossIds 一组文件在数据库中的唯一标识集合
* @return 包含 SysOssVo 对象的列表
*/
List<SysOssVo> listByIds(Collection<Long> ossIds);
/**
* 根据一组 ossIds 获取对应文件的 URL 列表
*
* @param ossIds 以逗号分隔的 ossId 字符串
* @return 以逗号分隔的文件 URL 字符串
*/
String selectUrlByIds(String ossIds);
/**
* 根据 ossId 从缓存或数据库中获取 SysOssVo 对象
*
* @param ossId 文件在数据库中的唯一标识
* @return SysOssVo 对象,包含文件信息
*/
SysOssVo getById(Long ossId);
/**
* 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库
*
* @param file 要上传的 MultipartFile 对象
* @return 上传成功后的 SysOssVo 对象,包含文件信息
*/
SysOssVo upload(MultipartFile file);
/**
* 上传文件到对象存储服务,并保存文件信息到数据库
*
* @param file 要上传的文件对象
* @return 上传成功后的 SysOssVo 对象,包含文件信息
*/
SysOssVo upload(File file);
/**
* 新增OSS对象存储
*
* @param bo SysOssBo 对象,包含待插入的数据
* @return 插入操作是否成功的布尔值
*/
Boolean insertByBo(SysOssBo bo);
/**
* 文件下载方法,支持一次性下载完整文件
*
* @param ossId OSS对象ID
* @param response HttpServletResponse对象用于设置响应头和向客户端发送文件内容
*/
void download(Long ossId, HttpServletResponse response) throws IOException;
/**
* 删除OSS对象存储
*
* @param ids OSS对象ID串
* @param isValid 判断是否需要校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,176 @@
package org.dromara.resource.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.resource.domain.SysOssConfig;
import org.dromara.resource.domain.bo.SysOssConfigBo;
import org.dromara.resource.domain.vo.SysOssConfigVo;
import org.dromara.resource.mapper.SysOssConfigMapper;
import org.dromara.resource.service.ISysOssConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* 对象存储配置Service业务层处理
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class SysOssConfigServiceImpl implements ISysOssConfigService {
private final SysOssConfigMapper baseMapper;
/**
* 项目启动时,初始化参数到缓存,加载配置类
*/
@Override
public void init() {
List<SysOssConfig> list = baseMapper.selectList();
// 加载OSS初始化配置
for (SysOssConfig config : list) {
String configKey = config.getConfigKey();
if ("0".equals(config.getStatus())) {
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
}
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
}
}
@Override
public SysOssConfigVo queryById(Long ossConfigId) {
return baseMapper.selectVoById(ossConfigId);
}
@Override
public TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysOssConfig> lqw = buildQueryWrapper(bo);
Page<SysOssConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
private LambdaQueryWrapper<SysOssConfig> buildQueryWrapper(SysOssConfigBo bo) {
LambdaQueryWrapper<SysOssConfig> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getConfigKey()), SysOssConfig::getConfigKey, bo.getConfigKey());
lqw.like(StringUtils.isNotBlank(bo.getBucketName()), SysOssConfig::getBucketName, bo.getBucketName());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysOssConfig::getStatus, bo.getStatus());
lqw.orderByAsc(SysOssConfig::getOssConfigId);
return lqw;
}
@Override
public Boolean insertByBo(SysOssConfigBo bo) {
SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
validEntityBeforeSave(config);
boolean flag = baseMapper.insert(config) > 0;
if (flag) {
// 从数据库查询完整的数据做缓存
config = baseMapper.selectById(config.getOssConfigId());
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
}
return flag;
}
@Override
public Boolean updateByBo(SysOssConfigBo bo) {
SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
validEntityBeforeSave(config);
LambdaUpdateWrapper<SysOssConfig> luw = new LambdaUpdateWrapper<>();
luw.set(ObjectUtil.isNull(config.getPrefix()), SysOssConfig::getPrefix, "");
luw.set(ObjectUtil.isNull(config.getRegion()), SysOssConfig::getRegion, "");
luw.set(ObjectUtil.isNull(config.getExt1()), SysOssConfig::getExt1, "");
luw.set(ObjectUtil.isNull(config.getRemark()), SysOssConfig::getRemark, "");
luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId());
boolean flag = baseMapper.update(config, luw) > 0;
if (flag) {
// 从数据库查询完整的数据做缓存
config = baseMapper.selectById(config.getOssConfigId());
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
}
return flag;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SysOssConfig entity) {
if (StringUtils.isNotEmpty(entity.getConfigKey()) && !checkConfigKeyUnique(entity)) {
throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!");
}
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) {
throw new ServiceException("系统内置, 不可删除!");
}
}
List<SysOssConfig> list = CollUtil.newArrayList();
for (Long configId : ids) {
SysOssConfig config = baseMapper.selectById(configId);
list.add(config);
}
boolean flag = baseMapper.deleteByIds(ids) > 0;
if (flag) {
list.forEach(sysOssConfig ->
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey()));
}
return flag;
}
/**
* 判断configKey是否唯一
*/
private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) {
long ossConfigId = ObjectUtils.notNull(sysOssConfig.getOssConfigId(), -1L);
SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysOssConfig>()
.select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey)
.eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey()));
if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) {
return false;
}
return true;
}
/**
* 启用禁用状态
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int updateOssConfigStatus(SysOssConfigBo bo) {
SysOssConfig sysOssConfig = BeanUtil.toBean(bo, SysOssConfig.class);
int row = baseMapper.update(null, new LambdaUpdateWrapper<SysOssConfig>()
.set(SysOssConfig::getStatus, "1"));
row += baseMapper.updateById(sysOssConfig);
if (row > 0) {
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey());
}
return row;
}
}

View File

@@ -0,0 +1,263 @@
package org.dromara.resource.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enums.AccessPolicyType;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.resource.domain.SysOss;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssVo;
import org.dromara.resource.mapper.SysOssMapper;
import org.dromara.resource.service.ISysOssService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 文件上传 服务层实现
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService {
private final SysOssMapper baseMapper;
/**
* 查询OSS对象存储列表
*
* @param bo OSS对象存储分页查询对象
* @param pageQuery 分页查询实体类
* @return 结果
*/
@Override
public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
List<SysOssVo> filterResult = result.getRecords().stream().map(this::matchingUrl).collect(Collectors.toList());
result.setRecords(filterResult);
return TableDataInfo.build(result);
}
/**
* 根据一组 ossIds 获取对应的 SysOssVo 列表
*
* @param ossIds 一组文件在数据库中的唯一标识集合
* @return 包含 SysOssVo 对象的列表
*/
@Override
public List<SysOssVo> listByIds(Collection<Long> ossIds) {
List<SysOssVo> list = new ArrayList<>();
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
for (Long id : ossIds) {
SysOssVo vo = ossService.getById(id);
if (ObjectUtil.isNotNull(vo)) {
try {
list.add(this.matchingUrl(vo));
} catch (Exception ignored) {
// 如果oss异常无法连接则将数据直接返回
list.add(vo);
}
}
}
return list;
}
/**
* 根据一组 ossIds 获取对应文件的 URL 列表
*
* @param ossIds 以逗号分隔的 ossId 字符串
* @return 以逗号分隔的文件 URL 字符串
*/
@Override
public String selectUrlByIds(String ossIds) {
List<String> list = new ArrayList<>();
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
SysOssVo vo = ossService.getById(id);
if (ObjectUtil.isNotNull(vo)) {
try {
list.add(this.matchingUrl(vo).getUrl());
} catch (Exception ignored) {
// 如果oss异常无法连接则将数据直接返回
list.add(vo.getUrl());
}
}
}
return String.join(StringUtils.SEPARATOR, list);
}
private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
lqw.orderByAsc(SysOss::getOssId);
return lqw;
}
/**
* 根据 ossId 从缓存或数据库中获取 SysOssVo 对象
*
* @param ossId 文件在数据库中的唯一标识
* @return SysOssVo 对象,包含文件信息
*/
@Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
@Override
public SysOssVo getById(Long ossId) {
return baseMapper.selectVoById(ossId);
}
/**
* 文件下载方法,支持一次性下载完整文件
*
* @param ossId OSS对象ID
* @param response HttpServletResponse对象用于设置响应头和向客户端发送文件内容
*/
@Override
public void download(Long ossId, HttpServletResponse response) throws IOException {
SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
if (ObjectUtil.isNull(sysOss)) {
throw new ServiceException("文件数据不存在!");
}
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
OssClient storage = OssFactory.instance(sysOss.getService());
storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong);
}
/**
* 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库
*
* @param file 要上传的 MultipartFile 对象
* @return 上传成功后的 SysOssVo 对象,包含文件信息
* @throws ServiceException 如果上传过程中发生异常,则抛出 ServiceException 异常
*/
@Override
public SysOssVo upload(MultipartFile file) {
String originalfileName = file.getOriginalFilename();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient storage = OssFactory.instance();
UploadResult uploadResult;
try {
uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
} catch (IOException e) {
throw new ServiceException(e.getMessage());
}
// 保存文件信息
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
}
/**
* 上传文件到对象存储服务,并保存文件信息到数据库
*
* @param file 要上传的文件对象
* @return 上传成功后的 SysOssVo 对象,包含文件信息
*/
@Override
public SysOssVo upload(File file) {
String originalfileName = file.getName();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient storage = OssFactory.instance();
UploadResult uploadResult = storage.uploadSuffix(file, suffix);
// 保存文件信息
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
}
private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) {
SysOss oss = new SysOss();
oss.setUrl(uploadResult.getUrl());
oss.setFileSuffix(suffix);
oss.setFileName(uploadResult.getFilename());
oss.setOriginalName(originalfileName);
oss.setService(configKey);
baseMapper.insert(oss);
SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
return this.matchingUrl(sysOssVo);
}
/**
* 新增OSS对象存储
*
* @param bo SysOssBo 对象,包含待插入的数据
* @return 插入操作是否成功的布尔值
*/
@Override
public Boolean insertByBo(SysOssBo bo) {
SysOss oss = BeanUtil.toBean(bo, SysOss.class);
boolean flag = baseMapper.insert(oss) > 0;
if (flag) {
bo.setOssId(oss.getOssId());
}
return flag;
}
/**
* 删除OSS对象存储
*
* @param ids OSS对象ID串
* @param isValid 判断是否需要校验
* @return 结果
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 做一些业务上的校验,判断是否需要校验
}
List<SysOss> list = baseMapper.selectByIds(ids);
for (SysOss sysOss : list) {
OssClient storage = OssFactory.instance(sysOss.getService());
storage.delete(sysOss.getUrl());
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 桶类型为 private 的URL 修改为临时URL时长为120s
*
* @param oss OSS对象
* @return oss 匹配Url的OSS对象
*/
private SysOssVo matchingUrl(SysOssVo oss) {
OssClient storage = OssFactory.instance(oss.getService());
// 仅修改桶类型为 private 的URL临时URL时长为120s
if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
}
return oss;
}
}

View File

@@ -0,0 +1,34 @@
# Tomcat
server:
port: 9204
# Spring
spring:
application:
# 应用名称
name: ruoyi-resource
profiles:
# 环境配置
active: @profiles.active@
--- # nacos 配置
spring:
cloud:
nacos:
# nacos 服务地址
server-addr: @nacos.server@
username: @nacos.username@
password: @nacos.password@
discovery:
# 注册组
group: @nacos.discovery.group@
namespace: ${spring.profiles.active}
config:
# 配置组
group: @nacos.config.group@
namespace: ${spring.profiles.active}
config:
import:
- optional:nacos:application-common.yml
- optional:nacos:datasource.yml
- optional:nacos:${spring.application.name}.yml

View File

@@ -0,0 +1,10 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
_
(_)
_ __ _ _ ___ _ _ _ ______ _ __ ___ ___ ___ _ _ _ __ ___ ___
| '__| | | |/ _ \| | | | |______| '__/ _ \/ __|/ _ \| | | | '__/ __/ _ \
| | | |_| | (_) | |_| | | | | | __/\__ \ (_) | |_| | | | (_| __/
|_| \__,_|\___/ \__, |_| |_| \___||___/\___/ \__,_|_| \___\___|
__/ |
|___/

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 日志输出格式 -->
<property name="console.log.pattern"
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<include resource="logback-common.xml" />
<include resource="logback-logstash.xml" />
<!-- 开启 skywalking 日志收集 -->
<include resource="logback-skylog.xml" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>

View File

@@ -0,0 +1,3 @@
java包使用 `.` 分割 resource 目录使用 `/` 分割
<br>
此文件目的 防止文件夹粘连找不到 `xml` 文件

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.resource.mapper.SysOssConfigMapper">
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.resource.mapper.SysOssMapper">
</mapper>