<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.5.30</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
Action 需提供三個屬性(以 myFile 為例):
| 屬性 | 類型 | 說明 |
|---|---|---|
| myFile | File | 上傳的臨時文件 |
| myFileFileName | String | 原始文件名 |
| myFileContentType | String | MIME 類型 |
<form id="uploadForm" action="upload.action" method="post" enctype="multipart/form-data">
<input type="file" id="fileInput" name="myFile" accept="image/*" />
<button type="submit" id="submitBtn" disabled>上傳</button>
<img id="preview" src="#" alt="預覽" style="display:none; max-width:200px;" />
<div id="errorMsg" style="color:red; display:none;"></div>
</form>
<script>
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
const MAX_SIZE = 2 * 1024 * 1024; // 2MB
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const submitBtn = document.getElementById('submitBtn');
const errorMsg = document.getElementById('errorMsg');
fileInput.addEventListener('change', function() {
const file = this.files[0];
errorMsg.style.display = 'none';
preview.style.display = 'none';
submitBtn.disabled = true;
if (!file) return;
// 1. 類型校驗
if (!ALLOWED_TYPES.includes(file.type)) {
showError('僅支持 JPG/PNG/GIF 格式');
this.value = ''; // 清空選擇
return;
}
// 2. 大小校驗
if (file.size > MAX_SIZE) {
showError('文件大小不能超過 2MB');
this.value = '';
return;
}
// 3. 生成預覽 (FileReader)
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.style.display = 'block';
submitBtn.disabled = false;
};
reader.readAsDataURL(file);
});
function showError(msg) {
errorMsg.textContent = msg;
errorMsg.style.display = 'block';
}
</script>
<package name="upload" extends="struts-default">
<action name="upload" class="com.example.UploadAction">
<interceptor-ref name="defaultStack">
<param name="fileUpload.allowedTypes">image/jpeg,image/png,image/gif</param>
<param name="fileUpload.maximumSize">2097152</param> <!-- 2MB -->
</interceptor-ref>
<result name="success">/upload/success.jsp</result>
<result name="input">/upload/upload.jsp</result> <!-- 校驗失敗返回 -->
</action>
</package>
struts.messages.error.file.too.large=文件大小超過限制 ({1} bytes)
struts.messages.error.content.type.not.allowed=文件類型不允許: {1}
struts.messages.error.uploading=上傳失敗: {1}
@Controller
public class UploadAction extends ActionSupport {
private File myFile;
private String myFileFileName;
private String myFileContentType;
// 業務常量
private static final String UPLOAD_DIR = "/var/www/uploads";
private static final int MAX_WIDTH = 800;
private static final int MAX_HEIGHT = 600;
private static final float QUALITY = 0.8f;
@Override
public String execute() {
if (myFile == null) {
addActionError(getText("struts.messages.error.uploading", new String[]{"未選擇文件"}));
return INPUT;
}
try {
// 1. 確保目錄存在
File dir = new File(UPLOAD_DIR);
if (!dir.exists()) dir.mkdirs();
// 2. 生成唯一文件名 (UUID + 原始擴展名)
String ext = FilenameUtils.getExtension(myFileFileName);
String newFileName = UUID.randomUUID().toString() + "." + ext;
File destFile = new File(dir, newFileName);
// 3. 圖片壓縮 + 存儲
compressAndSave(myFile, destFile);
// 4. 可選:記錄數據庫、返回文件 URL 等
addActionMessage("上傳成功: " + newFileName);
return SUCCESS;
} catch (Exception e) {
log.error("上傳處理異常", e);
addActionError(getText("struts.messages.error.uploading", new String[]{e.getMessage()}));
return INPUT;
}
}
/** 使用 ImageIO + ImageWriter 進行質量壓縮 */
private void compressAndSave(File src, File dest) throws IOException {
BufferedImage original = ImageIO.read(src);
if (original == null) throw new IOException("非有效圖片文件");
// 計算縮放比例
int w = original.getWidth();
int h = original.getHeight();
double scale = Math.min((double) MAX_WIDTH / w, (double) MAX_HEIGHT / h);
if (scale >= 1.0) {
// 無需縮放,直接複製
Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
return;
}
int newW = (int) (w * scale);
int newH = (int) (h * scale);
BufferedImage resized = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = resized.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(original, 0, 0, newW, newH, null);
g2d.dispose();
// JPEG 壓縮質量控制
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(QUALITY);
try (ImageOutputStream ios = ImageIO.createImageOutputStream(dest)) {
writer.setOutput(ios);
writer.write(null, new IIOImage(resized, null, null), param);
} finally {
writer.dispose();
}
}
// Getter/Setter 省略
public void setMyFile(File myFile) { this.myFile = myFile; }
public void setMyFileFileName(String myFileFileName) { this.myFileFileName = myFileFileName; }
public void setMyFileContentType(String myFileContentType) { this.myFileContentType = myFileContentType; }
}
| 場景 | 前端攔截 | 後端攔截 (fileUpload 攔截器) |
|---|---|---|
| 未選文件點提交 | 按鈕禁用 | Action 執行,myFile 為 null,返回 INPUT |
| 選擇 .exe 文件 | 彈窗提示、清空輸入 | 返回 INPUT,顯示「文件類型不允許」 |
| 選擇 5MB 圖片 | 彈窗提示、清空輸入 | 返回 INPUT,顯示「文件大小超過限制」 |
| 正確圖片 | 顯示預覽、啟用按鈕 | 執行 execute(),壓縮存儲,返回 SUCCESS |
Struts2 文件上傳最佳實踐:
struts.xml 中聲明類型/大小限制,無硬編碼globalMessage.properties 統一錯誤文案ImageWriter.dispose()、流關閉防洩漏