1. 程式人生 > >【128】Spring Boot 1 實現瀏覽器拍照上傳功能

【128】Spring Boot 1 實現瀏覽器拍照上傳功能

最近寫了個DEMO,實現瀏覽器拍照並上傳圖片的功能。框架用了Spring Boot 1.5.17.RELEASE,Java版本是8 。我把程式碼傳到了碼雲上,專案地址:https://gitee.com/zhangchao19890805/csdnBlog

Git專案中的 blog128 資料夾就是這次的演示專案。整個專案使用 Maven 構建。前端使用了 Spring Boot 1 預設整合的 Thymeleaf 模板引擎。後端使用了RESTful 風格的 API。使用瀏覽器拍照功能的時候,推薦使用火狐瀏覽器。如果使用谷歌瀏覽器,需要注意新版本的谷歌瀏覽器可能需要 https 協議的網站域名。

下面說一下關鍵程式碼。

HtmlController.java 用於跳轉到 index.html

package zhangchao.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class
HtmlController { @RequestMapping(value="/" ,method=RequestMethod.GET) public String index(Model model) { model.addAttribute("userId", 2); return "index"; } }

index.html 前端頁面,位於src/main/resources/templates/ 資料夾下。

<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"
/>
<title>拍照上傳</title> <link th:href="@{styles/index.css}" rel="stylesheet" type="text/css"/> </head> <body> <div class="index__tips" id="index__tips"></div> <div class="index__title">請將您的臉部正面放置於下方提示區域,並點選“拍照”按鈕。</div> <div class="index__video-panel"> <!-- 顯示攝像頭拍攝到的視訊 --> <video id="index__video" class="index__video"></video> <!-- 視訊上的提示框 --> <svg width="200" height="200" class="index__video-over"> <!-- 克萊因藍 #002FA7 --> <polyline points="5,50 5,5 50,5" fill-opacity="0" style="stroke:#002FA7;stroke-width:10"/> <polyline points="5,150 5,195 50,195" fill-opacity="0" style="stroke:#002FA7;stroke-width:10"/> <polyline points="155,5 195,5 195,45" fill-opacity="0" style="stroke:#002FA7;stroke-width:10"/> <polyline points="155,195 195,195 195,155" fill-opacity="0" style="stroke:#002FA7;stroke-width:10"/> </svg> <!-- 視訊上的拍照按鈕 --> <div class="index__video-over-button" onclick="indexObj.uploadImg()">拍照並上傳</div> </div> <!-- 使用者ID --> <input type="hidden" th:value="${userId}" id="index__user-id"/> <!-- 用於給video標籤截圖的畫布 --> <canvas id="index__canvas" style="display:none;"></canvas> <script th:src="@{styles/axios.min.js}"></script> <script th:src="@{styles/index.js}"></script> </body> </html>

index.css 位於src/main/resources/static/styles/ 資料夾下。

@CHARSET "UTF-8";

.index__tips{
	width: 100%;
	color: red;
	font-size: 16px;
	font-family: "microsoft yahei";
	text-align: center;
}

.index__title{
	font-size: 20px;
	font-family: "microsoft yahei";
	margin: 0 auto;
	width: auto;
	text-align: center;
	padding: 10px 0 15px 0;
}

.index__video-panel{
	width: 450px;
	height: 300px;
	padding:0;border:0;
	z-index: 0;
	margin: 0 auto;
}

.index__video{
	width:450px;
	height:300px;
	position: relative;
	z-index: 0;
	/* float:left; */
}

.index__video-over{
	/* float:left; */
	position: relative;
	z-index:1;
	margin:-250px 0 0 125px;
}

.index__video-over-button{
	position: relative;
	z-index:1;
	color: white;
	background-color:#002FA7;
	width: 100px;
	height: 30px;
	line-height: 30px;
	margin:-40px 0 0 175px;
	text-align: center;
	cursor: pointer;
}

index.js 位於src/main/resources/static/styles/ 資料夾下。

/**
 * 拍照上傳圖片
 */
var indexObj = {};

indexObj.tips = "沒有檢測到裝置,請確保開啟攝像頭。";

// begin 顯示攝像頭的錄影。
navigator.getUserMedia = navigator.getUserMedia ||
		navigator.webkitGetUserMedia ||
		navigator.mozGetUserMedia;

if (navigator.getUserMedia) {
	navigator.getUserMedia({ audio: true, video: { width: 800, height: 450 } },
	function(stream) {
		var video = document.getElementById("index__video");
		video.srcObject = stream;
		console.log("stream active", stream)
		video.onloadedmetadata = function(e) {
			video.play();
		};
	},
	function(err) {
		console.log("The following error occurred: " + err.name);
		document.getElementById("index__tips").innerHTML = indexObj.tips;
	}
);
} else {
	console.log("getUserMedia not supported");
	document.getElementById("index__tips").innerHTML = indexObj.tips;
}
// end 顯示攝像頭的錄影。


/**
 * 拍照上傳按鈕的事件響應
 */

indexObj.uploadImg = function(){
	var canvas = document.getElementById("index__canvas");
	var video = document.getElementById("index__video");
	if (0 == video.videoWidth) {
		document.getElementById("index__tips").innerHTML = indexObj.tips;
		return;
	}
	// 讓canvas和視訊一樣寬高。
	var w = video.videoWidth;
	var h = video.videoHeight;
	canvas.width = w;
	canvas.height = h;
	// 把video標籤中的畫面,畫到canvas中。
	var ctx = canvas.getContext('2d');
	ctx.drawImage(video, 0, 0, w, h);
	// 把canvas中的影象轉換成png圖片檔案的Base64字串。
	var imgStr = canvas.toDataURL('image/png').split("base64,")[1];
	// 獲得使用者ID
	var userId = document.getElementById("index__user-id").value;
	axios.post("/api/profile/upload", {"userId":userId, "imgStr": imgStr})
			.then(function(res){
				console.log(res);
				alert("上傳成功")
			}).catch(function(error){
				console.error(error);
			})
}

ProfileController.java 處理上傳圖片。圖片用Base64傳輸。

package zhangchao.web;

import java.io.File;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import zhangchao.dto.ProfileUploadDTO;
import zhangchao.externalconfig.ZhangchaoSetting;
import zhangchao.sys.result.BaseResult;
import zhangchao.sys.result.ObjectOkResult;
import zhangchao.sys.utils.Base64Utils;

/**
 * 作者
 * @author jyn
 * 
 */
@RestController
@RequestMapping("/api/profile")
@Api(tags= {"profile"})
public class ProfileController {
	
	@Autowired
	private ZhangchaoSetting zhangchaoSetting;
	
	@ApiOperation(value = "上傳拍照頭像。", notes = "上傳拍照頭像。用Base64傳輸圖片內容。", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
	@RequestMapping(value="/upload",method=RequestMethod.POST)
	public BaseResult upload(@RequestBody ProfileUploadDTO profileUploadDTO){
		ObjectOkResult r = new ObjectOkResult();
		Integer userId = profileUploadDTO.userId;
		String imgStr = profileUploadDTO.imgStr;
		String basePath = this.zhangchaoSetting.getUploadPath();
		String filePath = basePath + "/" + userId + ".png";
		Base64Utils.createFile(imgStr, new File(filePath));
		return r;
	}
}

Base64Utils.java 處理Base64 的工具類。

package zhangchao.sys.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;

public class Base64Utils {
	
	/**
	 * 根據檔案獲取Base64字串
	 * @param file 檔案物件
	 * @return
	 */
	public static String getBase64Str(File file) {
		String r = null;
		FileInputStream fis = null;
		byte[] data = null;
		try {
			fis = new FileInputStream(file);
			data = new byte[fis.available()];
			fis.read(data);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fis != null) {
					fis.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		// 防止data陣列為空。
		if (null == data || data.length == 0) {
			return null;
		}
		BASE64Encoder encoder = new BASE64Encoder();
		r = encoder.encode(data);
		return r;
	}
	
	/**
	 * 根據Base64字串建立檔案。
	 * @param base64Str Base64字串。
	 * @param file      要建立的檔案
	 * @return          true表示建立成功。false表示建立失敗。
	 */
	public static boolean createFile(String base64Str, File file){
		boolean flag = false;
		if (null == base64Str) {
			return flag;
		}
        BASE64Decoder decoder = new BASE64Decoder();
        File dir = file.getParentFile();
        FileOutputStream out = null;
       	try {
       		if (!dir.exists()) {
       			dir.mkdirs();
       		}
       		if (!file.exists()) {
				file.createNewFile();
       		}
       		byte[] b = decoder.decodeBuffer(base64Str);
       		for(int i=0;i<b.length;++i)
            {
                if(b[i]<0)
                {//調整異常資料
                    b[i]+=256;
                }
            }
            //生成檔案
            out = new FileOutputStream(file);
            out.write(b);
            
            flag = true;
		} catch (IOException e) {
			e.printStackTrace();
			flag = false;
		} finally {
			try {
				if (null != out) {
					out.flush();
					out.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return flag;
	}
}

這個專案上傳圖片存放的硬碟路徑寫在配置檔案中。檔案是application.properties 。

配置項是

zhangchao.uploadPath=E:/test

讀取配置項的Java程式碼是 ZhangchaoSetting.java 。 程式碼如下:

package zhangchao.externalconfig;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix="zhangchao")
public class ZhangchaoSetting {
	private String uploadPath;

	public String getUploadPath() {
		return uploadPath;
	}

	public void setUploadPath(String uploadPath) {
		this.uploadPath = uploadPath;
	}
}

最後是系統執行後的效果圖:
在這裡插入圖片描述