02_使用 AES 算法實現文件加密上傳至阿里云、解密下載
一、文件上傳下載接口 controller 層
@RestController
@RequestMapping("/api/common/file")
@Api(tags = "公共文件上傳")
@AllArgsConstructor
@Slf4j
public class FileV2Controller {private final OssUtil ossUtil;/*** 上傳單個加密文件并返回新文件名* @param file* @return*/@PostMapping("/uploadfile/encrypt")@ApiOperation("上傳單個加密文件")public CommonResult<String> fileUploadEncrypt(@RequestParam("file") MultipartFile file) {try {//獲取文件對應的字節數組byte[] originalBytes = file.getBytes();//這里是密鑰String secretKey = "1234567890123456"; // 記得換成你自己的密鑰//為文件生成一個新文件名 前面為UUID 后面為 文件后綴 (如原始文件名為 test.txt 加密后文件名 為 0x12.txt)String newFileName = UUID.randomUUID() + getFileSuffix(file.getOriginalFilename());//加密上傳 原始文件字節數組 新文件名 密鑰ossUtil.uploadEncryptedFile(originalBytes, newFileName, secretKey);//返回文件名return CommonResult.success(newFileName);} catch (Exception e) {log.error("加密上傳文件失敗", e);return CommonResult.failed("上傳失敗");}}/**** @param fileName* @return*/@GetMapping("/downloadfile/decrypt")@ApiOperation("下載單個加密文件")public ResponseEntity<byte[]> fileDownloadDecrypt(@RequestParam("fileName") String fileName) {try {//這里是密鑰String secretKey = "1234567890123456"; // 記得換成你自己的密鑰//解密下載 文件名 密鑰byte[] decryptedBytes = ossUtil.downloadDecryptedFile(fileName ,secretKey);// 設置響應頭部,通知瀏覽器下載文件HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDisposition(ContentDisposition.attachment().filename(fileName).build());// 返回字節流,直接下載return new ResponseEntity<>(decryptedBytes, headers, HttpStatus.OK);} catch (Exception e) {log.error("解密下載文件失敗", e);return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}}// 工具方法,提取后綴private String getFileSuffix(String filename) {return filename.substring(filename.lastIndexOf("."));}@GetMapping("/previewfile/decrypt")@ApiOperation("預覽單個加密文件")public ResponseEntity<byte[]> filePreviewDecrypt(@RequestParam("fileName") String fileName) {try {// 這里是密鑰 后續可以通過配置文件進行管理String secretKey = "1234567890123456"; // 記得換成你自己的密鑰// 解密下載 文件名 密鑰byte[] decryptedBytes = ossUtil.downloadDecryptedFile(fileName, secretKey);// 獲取文件類型(可根據實際情況調整)String fileExtension = getFileSuffix(fileName).toLowerCase();// 設置響應頭,根據文件類型進行調整HttpHeaders headers = new HttpHeaders();// 根據不同文件類型設置Content-Typeif (fileExtension.equals(".jpg") || fileExtension.equals(".jpeg") || fileExtension.equals(".png") || fileExtension.equals(".gif")) {// 圖片文件headers.setContentType(MediaType.IMAGE_JPEG); // 也可以根據具體類型選擇} else if (fileExtension.equals(".pdf")) {// PDF文件headers.setContentType(MediaType.APPLICATION_PDF);} else if (fileExtension.equals(".txt")) {// 文本文件headers.setContentType(MediaType.TEXT_PLAIN);} else {// 默認二進制流headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);}// 設置文件名,確保瀏覽器使用傳遞的文件名headers.setContentDisposition(ContentDisposition.inline().filename(fileName).build());// 返回字節流,直接預覽return new ResponseEntity<>(decryptedBytes, headers, HttpStatus.OK);} catch (Exception e) {log.error("解密預覽文件失敗", e);return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}}
}
二、AESUtil 工具類
/*** AES加密解密工具類*/
public class AESUtil {// 定義使用的加密算法(這里是AES)private static final String ALGORITHM = "AES";/*** AES加密** @param data 原始數據(要加密的內容,比如文件內容的byte數組)* @param secretKey 密鑰(必須是16字節長度的字符串,比如 "1234567890abcdef")* @return 加密后的數據(byte數組)* @throws Exception 拋出任何加密異常*/public static byte[] encrypt(byte[] data, String secretKey) throws Exception {// 1. 創建一個AES專用的密鑰對象(把字符串密鑰變成加密用的key)SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);// 2. 創建Cipher對象,指定用AES算法Cipher cipher = Cipher.getInstance(ALGORITHM);// 3. 初始化加密模式cipher.init(Cipher.ENCRYPT_MODE, keySpec);// 4. 執行加密操作,并返回加密后的數據return cipher.doFinal(data);}/*** AES解密** @param data 加密后的數據(byte數組)* @param secretKey 密鑰(加密和解密必須用同一個密鑰)* @return 解密后的原始數據(byte數組)* @throws Exception 拋出任何解密異常*/public static byte[] decrypt(byte[] data, String secretKey) throws Exception {// 1. 創建一個AES專用的密鑰對象SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);// 2. 創建Cipher對象,指定用AES算法Cipher cipher = Cipher.getInstance(ALGORITHM);// 3. 初始化解密模式cipher.init(Cipher.DECRYPT_MODE, keySpec);// 4. 執行解密操作,并返回解密后的數據return cipher.doFinal(data);}
}
三、OSS 工具類
@Component
public class OssUtil {@Value("${aliyun.oss.endpoint}")private String endpoint;@Value("${aliyun.oss.accessKeyId}")private String accessKeyId;@Value("${aliyun.oss.accessKeySecret}")private String accessKeySecret;@Value("${aliyun.oss.bucketName}")private String bucketName;//文件存儲目錄private String filedir = "xxx/";// 上傳文件(指定文件名)private boolean uploadFile2OSS(InputStream instream, String fileName) {boolean success = false;try {// 創建上傳Object的MetadataObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(instream.available());objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");objectMetadata.setContentDisposition("inline;filename=" + fileName);// 創建 OSSClient 實例OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);// 上傳文件PutObjectResult putResult = ossClient.putObject(bucketName, filedir + fileName, instream, objectMetadata);// 如果上傳沒有異常,設置 success 為 truesuccess = true;} catch (IOException e) {e.printStackTrace();} finally {try {if (instream != null) {instream.close();}} catch (IOException e) {e.printStackTrace();}}return success;}/*** 通過指定文件名從 OSS 下載文件* @param fileName 文件名* @return 文件的 InputStream*/public InputStream downloadFileFromOSS(String fileName) {InputStream inputStream = null;try {// 創建 OSSClient 對象OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);// 創建 GetObjectRequest 請求對象GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, filedir + fileName);// 獲取文件對象OSSObject ossObject = ossClient.getObject(getObjectRequest);// 獲取文件的 InputStreaminputStream = ossObject.getObjectContent();} catch (Exception e) {e.printStackTrace();}return inputStream;}/*** 上傳加密后的文件** @param bytes 文件原始內容(未加密)* @param fileName 要保存的文件名* @param secretKey 16位AES密鑰* @return 文件訪問地址*/public Boolean uploadEncryptedFile(byte[] bytes, String fileName, String secretKey) {try {// 1. 加密 生成原始文件內容加密后的 字節數組byte[] encryptedBytes = AESUtil.encrypt(bytes, secretKey);// 2. 生成流 通過加密后字節數組 轉為一個字節輸入流ByteArrayInputStream inputStream = new ByteArrayInputStream(encryptedBytes);// 3. 上傳 上傳到ossBoolean result = this.uploadFile2OSS(inputStream, fileName);return result;} catch (Exception e) {e.printStackTrace();return null;}}/*** 下載并解密文件** @param fileName 文件名* @param secretKey 16位AES密鑰* @return 解密后的原始字節數組*/public byte[] downloadDecryptedFile(String fileName, String secretKey) {try {// 1. 獲取文件輸入流(加密后的內容)InputStream encryptedInputStream = this.downloadFileFromOSS(fileName);if (encryptedInputStream == null) {return null;}// 2. 將輸入流轉為字節數組ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = encryptedInputStream.read(buffer)) != -1) {baos.write(buffer, 0, bytesRead);}byte[] encryptedBytes = baos.toByteArray();// 3. 解密return AESUtil.decrypt(encryptedBytes, secretKey);} catch (Exception e) {e.printStackTrace();return null;}}
}