| 層次 | 技術棧 | 職責 |
| 前端展示 | Bootstrap 3 | 響應式佈局、模態框、表單樣式 |
| 圖片剪裁 | Cropper.js | 圖片預覽、拖拽選區、旋轉縮放、坐標輸出 |
| 後端控制 | SpringMVC | 文件接收、參數綁定、業務編排、JSON 響應 |
| 圖像處理 | Java AWT (ImageIO) | 旋轉、剪裁、縮放、水印、格式轉換 |
一、前端頁面:模態框內集成 Cropper
核心結構:<form> 包裹文件輸入 + 隱藏域(剪裁坐標 x,y,width,height,rotate),Cropper 綁定 <div class="avatar-wrapper"> 容器,預覽區實時同步。
<!-- 簡化關鍵片段 -->
<div class="container" id="crop-avatar">
<div class="avatar-view" title="點擊更換頭像">
<img src="images/default.jpg" alt="Avatar" />
</div>
<div class="modal fade" id="avatar-modal">
<form class="avatar-form" action="/vip/image/updateuserlogo" method="post" enctype="multipart/form-data">
<div class="modal-body">
<div class="avatar-upload">
<input type="hidden" class="avatar-src" name="avatar_src" />
<input type="hidden" class="avatar-data" name="avatar_data" />
<label for="avatarInput">頭像上傳</label>
<input type="file" class="avatar-input" id="avatarInput" name="avatar_file" accept="image/*" />
</div>
<div class="row">
<div class="col-md-9"><div class="avatar-wrapper"></div></div>
<div class="col-md-3">
<div class="avatar-preview preview-md"><label>100×100</label></div>
<div class="avatar-preview preview-sm"><label>50×50</label></div>
<div class="avatar-preview preview-xs"><label>30×30</label></div>
</div>
</div>
<!-- 隱藏域:Cropper 實時寫入剪裁參數 -->
<input type="hidden" name="x" id="dataX" />
<input type="hidden" name="y" id="dataY" />
<input type="hidden" name="width" id="dataWidth" />
<input type="hidden" name="height" id="dataHeight" />
<input type="hidden" name="degree" id="dataRotate" />
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary avatar-save">確定</button>
</div>
</form>
</div>
</div>
二、Cropper 初始化與交互邏輯
$(function () {
var $image = $('#image'); // avatar-wrapper 內動態生成的 img
var options = {
aspectRatio: 1, // 頭像通常 1:1
viewMode: 1, // 限制選區在圖片內
preview: '.avatar-preview', // 綁定多尺寸預覽區
crop: function (e) { // 實時回寫隱藏域
$('#dataX').val(Math.round(e.detail.x));
$('#dataY').val(Math.round(e.detail.y));
$('#dataWidth').val(Math.round(e.detail.width));
$('#dataHeight').val(Math.round(e.detail.height));
$('#dataRotate').val(e.detail.rotate);
}
};
// 文件選擇 → 讀取為 Blob URL → 替換 Cropper 圖片
$('#avatarInput').on('change', function () {
var file = this.files[0];
if (!/^image\/\w+$/.test(file.type)) return alert('請選擇圖片');
var blobURL = URL.createObjectURL(file);
$image.one('built.cropper', function () { URL.revokeObjectURL(blobURL); })
.cropper('reset').cropper('replace', blobURL);
});
// 旋轉按鈕委託
$('.docs-buttons').on('click', '[data-method]', function () {
var data = $(this).data();
$image.cropper(data.method, data.option);
});
$image.cropper(options);
});
三、後端控制器:SpringMVC 接收 MultipartFile + 剪裁參數
@Controller
@RequestMapping("/{vip}/image")
public class ImageAction {
@Resource private IImageService imageService;
@Resource private UUserService userService;
@Resource private BSiteService siteService;
@RequestMapping("/updateuserlogo")
@ResponseBody
public IImageDto updateUserLogo(
@PathVariable String vip,
@RequestParam Integer x, @RequestParam Integer y,
@RequestParam Integer width, @RequestParam Integer height,
@RequestParam Integer degree,
@RequestParam("avatar_file") MultipartFile imageFile,
HttpServletRequest request) {
IImageDto dto = new IImageDto();
if (imageFile == null || imageFile.isEmpty()) return dto;
try {
// 1. 構建存儲路徑:/upload/image/{userId}/{yyyy}/{MM}/{dd}/
String realPath = request.getServletContext().getRealPath("/");
UUserDto user = (UUserDto) request.getSession().getAttribute("user");
String relPath = "upload/image/" + user.getUserId() + "/"
+ DateUtils.getYear(new Date()) + "/"
+ DateUtils.getMonth(new Date()) + "/"
+ DateUtils.getDay(new Date()) + "/";
File dir = new File(realPath + relPath);
if (!dir.exists()) dir.mkdirs();
// 2. 保存原圖
String fileName = System.currentTimeMillis() + ".jpg";
File srcFile = new File(dir, fileName);
imageFile.transferTo(srcFile);
// 3. 圖像處理管道:旋轉+剪裁 → 多規格縮放
String base = realPath + relPath + System.currentTimeMillis();
boolean ok = true;
ok &= ImageUtils.cutAndRotateImage(srcFile.getPath(), base + "_s.jpg", x, y, width, height, degree);
ok &= ImageUtils.scale2(base + "_s.jpg", base + "_s_200.jpg", 200, 200, true);
ok &= ImageUtils.scale2(base + "_s.jpg", base + "_s_100.jpg", 100, 100, true);
ok &= ImageUtils.scale2(base + "_s.jpg", base + "_s_50.jpg", 50, 50, true);
ok &= ImageUtils.scale2(base + "_s.jpg", base + "_s_30.jpg", 30, 30, true);
ok &= ImageUtils.scale2(srcFile.getPath(), base + "_200.jpg", 200, 200, true);
if (ok) {
// 4. 持久化記錄 & 更新用戶/站點頭像
dto.setUUserDto(user);
dto.setName(fileName);
dto.setUrl(relPath + fileName);
dto.setPreviewUrl(relPath + fileName.replace(".jpg", "_200.jpg"));
dto.setExt3(relPath + fileName.replace(".jpg", "_s_200.jpg"));
dto.setExt4(relPath + fileName.replace(".jpg", "_s_100.jpg"));
// ... 設置其他字段:時間、寬高、客戶端信息、狀態等
int id = imageService.addImage(dto);
if (id > 0) {
user.setPortrait(relPath + fileName.replace(".jpg", "_s_30.jpg"));
userService.updateUser(user);
dto.setLoginFlag("1");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
}
四、ImageUtils 核心工具類:AWT 圖像處理全集
封裝 ImageIO、BufferedImage、AffineTransform,提供:
- 旋轉:
Rotate(Image, angle) 計算外接矩形,避免裁切;cutAndRotateImage 組合旋轉+剪裁。
- 剪裁:
ImageReadParam.setSourceRegion(Rectangle) 直接解碼目標區域,內存效率高。
- 縮放:
scale2 支持等比縮放 + 白邊填充(bb=true),生成多規格頭像(200/100/50/30)。
- 水印:圖片水印(透明度 AlphaComposite)+ 文字水印(字體/顏色/位置)。
- 工具方法:獲取寬高、格式轉換、灰度化。
// 關鍵方法簽名
public static boolean cutAndRotateImage(String src, String dest, int x, int y, int w, int h, int degree);
public static boolean scale2(String src, String dest, int targetW, int targetH, boolean padWhite);
public static BufferedImage Rotate(Image src, int angle); // 返回旋轉後完整圖像
public static boolean pressImage(String watermark, String src, String dest, int x, int y, float alpha);
public static boolean pressText(String text, String src, String dest, String font, int style, Color color, int size, int x, int y, float alpha);
五、關鍵注意點與優化建議
- 前端剪裁坐標 基於 Cropper 顯示尺寸,若原圖極大需在後端按比例換算,或前端限制上傳尺寸(
max-width)。
- 大文件上傳:配置
MultipartResolver 的 maxUploadSize,必要時分片上傳/直傳 OSS。
- 圖片格式統一:統一轉 JPEG 輸出,避免 PNG 透明通道導致白邊/黑邊問題。
- 異步化:圖像處理耗時,建議異步隊列處理,前端輪詢/websocket 推送結果。
- 安全:校驗 Content-Type、魔數、文件大小;存儲路徑隨機化,防遍歷。
- 上一篇文章: 設計模式精講:橋接模式—— 讓抽象與實現獨立演化
- 下一篇文章: Java 線程池核心指南:從 Executors 到自定義配置的最佳實踐
Copyright ©
程式碼之家 All Rights Reserved