最近写了个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;
}
}
最后是系统运行后的效果图: