diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/cai/app/FileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/cai/app/FileController.java index 444dc9f8..f3bd7c14 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/cai/app/FileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/cai/app/FileController.java @@ -1,22 +1,26 @@ package com.ruoyi.web.controller.cai.app; +import cn.dev33.satoken.annotation.SaIgnore; import cn.hutool.core.util.ObjectUtil; import com.ruoyi.cai.dto.FileResp; +import com.ruoyi.cai.dto.app.UploadFileResp; +import com.ruoyi.cai.enums.SystemConfigEnum; +import com.ruoyi.cai.manager.SystemConfigManager; +import com.ruoyi.cai.util.OfflineTokenManager; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.helper.LoginHelper; import com.ruoyi.system.domain.vo.SysOssVo; import com.ruoyi.system.service.ISysOssService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jdk.nashorn.internal.ir.annotations.Ignore; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @RestController @@ -27,6 +31,8 @@ public class FileController { @Autowired private ISysOssService iSysOssService; + @Autowired + private SystemConfigManager systemConfigManager; @Log(title = "OSS对象存储", businessType = BusinessType.INSERT) @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -49,6 +55,43 @@ public class FileController { return R.ok(resp); } + @GetMapping("/uploadFileSign") + public R uploadFileSign() throws Exception { + String uploadFileDomain = systemConfigManager.getSystemConfig(SystemConfigEnum.UPLOAD_FILE_DOMAIN); + String s = OfflineTokenManager.generateToken(LoginHelper.getUserId() + ""); + UploadFileResp resp = new UploadFileResp(); + resp.setDomain(uploadFileDomain); + resp.setToken(s); + resp.setHttpUrl(uploadFileDomain+"/api/file/uploadImageV2"); + return R.ok(resp); + } + + @Log(title = "OSS对象存储", businessType = BusinessType.INSERT) + @PostMapping(value = "/uploadImageV2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "上传图片类型的文件", + parameters = { + @Parameter(name = "file", description = "文件", required = true), + @Parameter(name = "type", description = "业务类型,dynamic=动态图片,user=用户相册,头像,im=聊天,common=其他", required = false) + }) + @SaIgnore + public R uploadImageV1(@RequestPart("file") MultipartFile file, + String type, String token) { + boolean success = OfflineTokenManager.validateToken(token); + if(!success){ + return R.fail("上传失败,token校验失败"); + } + log.error("上传文件图片类型 type={}",type); + if (ObjectUtil.isNull(file)) { + return R.fail("上传文件不能为空"); + } + SysOssVo oss = iSysOssService.upload(file); + FileResp resp = new FileResp(); + resp.setUrl(oss.getUrl()); + resp.setPath(oss.getFileName()); + resp.setOriginalName(oss.getOriginalName()); + return R.ok(resp); + } + @Log(title = "OSS对象存储", businessType = BusinessType.INSERT) @PostMapping(value = "/uploadImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/dto/app/UploadFileResp.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/dto/app/UploadFileResp.java new file mode 100644 index 00000000..0c3aba40 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/dto/app/UploadFileResp.java @@ -0,0 +1,12 @@ +package com.ruoyi.cai.dto.app; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class UploadFileResp implements Serializable { + private String token; + private String domain; + private String httpUrl; +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/enums/SystemConfigEnum.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/enums/SystemConfigEnum.java index 4e58cea2..84db0111 100644 --- a/ruoyi-cai/src/main/java/com/ruoyi/cai/enums/SystemConfigEnum.java +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/enums/SystemConfigEnum.java @@ -143,6 +143,7 @@ public enum SystemConfigEnum { V12_XIAOCHENGXU_ORG_ID("gh_62790d4f9c57", "V12德商小程序原始id",SystemConfigGroupEnum.SYSTEM), V12_XIAOCHENGXU_PATH("pages/zf/index?", "V12德商小程序页面路径",SystemConfigGroupEnum.SYSTEM), V12_WX_APP_ID("wxae39c7eed3221d26", "微信开放平台ID",SystemConfigGroupEnum.SYSTEM), + UPLOAD_FILE_DOMAIN("http://127.0.0.1:8080", "文件上传域名服务器",SystemConfigGroupEnum.SYSTEM), // 七牛云 ?imageView2/2/w/120/h/120 // 腾讯云 ?thumbnail=120y120&imageView IM_ICON_SUFFIX("?thumbnail=120y120&imageView", "im头像后缀",SystemConfigGroupEnum.SYSTEM), diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/OfflineTokenManager.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/OfflineTokenManager.java new file mode 100644 index 00000000..f91b4eb9 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/OfflineTokenManager.java @@ -0,0 +1,117 @@ +package com.ruoyi.cai.util; + +import com.esotericsoftware.minlog.Log; +import lombok.extern.slf4j.Slf4j; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class OfflineTokenManager { + + // 加密密钥(离线系统需保持一致,建议16位字符) + private static final String SECRET_KEY = "7ZxQ9kL2pF4sT5mR"; // 16位密钥 + // 有效期:2小时(单位:毫秒) + private static final long VALID_DURATION = 3 * 60 * 60 * 1000; + + /** + * 生成包含时间戳的token + * @param userId 用户标识(可替换为其他业务唯一标识) + * @return 加密后的token + * @throws Exception 加密异常 + */ + public static String generateToken(String userId) throws Exception { + // 获取当前时间戳(毫秒) + long timestamp = System.currentTimeMillis(); + // 拼接用户ID和时间戳(格式:userId:timestamp) + String content = userId + ":" + timestamp; + // 加密并返回Base64编码的token + return encrypt(content); + } + + /** + * 校验token有效性 + * @param token 待校验的token + * @return 校验结果:true-有效,false-无效 + */ + public static boolean validateToken(String token) { + try { + // 解密token + String decryptedContent = decrypt(token); + // 解析内容(格式:userId:timestamp) + String[] parts = decryptedContent.split(":", 2); + if (parts.length != 2) { + return false; // 格式错误 + } + + // 提取时间戳并校验 + long tokenTimestamp = Long.parseLong(parts[1]); + long currentTime = System.currentTimeMillis(); + // 检查是否在有效期内(允许一定时间误差,避免时钟细微差异) +// return currentTime - tokenTimestamp <= VALID_DURATION && currentTime >= tokenTimestamp; + return currentTime - tokenTimestamp <= VALID_DURATION; + } catch (Exception e) { + // 解密失败或格式错误均视为无效 + log.error("解析失败问题",e); + return false; + } + } + + /** + * 从有效token中提取用户ID + * @param token 已通过校验的token + * @return 用户ID + * @throws Exception 解析异常 + */ + public static String getUserIdFromToken(String token) throws Exception { + String decryptedContent = decrypt(token); + String[] parts = decryptedContent.split(":", 2); + return parts[0]; + } + + // AES加密 + private static String encrypt(String content) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encrypted); + } + + // AES解密 + private static String decrypt(String token) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec); + byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(token)); + return new String(decrypted, StandardCharsets.UTF_8); + } + + // 测试示例 + public static void main(String[] args) throws Exception { + // 生成token +// String userId = "system_user_123"; +// String token = generateToken(userId); +// System.out.println("生成的token: " + token); + + // 校验token(立即校验) + boolean isValid = validateToken("Kor2eFRylHp+3tP6xevf7fCeH0TvBJsXFPuxHWtFkss="); + System.out.println("token是否有效: " + isValid); + + // 提取用户ID +// if (isValid) { +// String extractedUserId = getUserIdFromToken(token); +// System.out.println("从token中提取的用户ID: " + extractedUserId); +// } + + // 模拟2小时后校验(实际场景中无需此代码,仅作测试) + // Thread.sleep(VALID_DURATION + 1000); + // boolean isExpired = validateToken(token); + // System.out.println("2小时后token是否有效: " + isExpired); + } +}