【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;
	}
}

最后是系统运行后的效果图:
在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
系统功能1、管理员管理:根据不同角色设置不同的管理权限;2、小区管理,管理多个小区资料,新增、修改、删除、摄像头管理等功能;3、小区摄像头管理:摄像头的新增、修改及删除功能;4、居民管理:居民资料新增,修改,删除,Excel批量导入,导出,居民人脸采集;5、访客登记:访客的新增,修改,删除,进入登记,离开登记,查询等功能;6、人脸识别:居民出入小区人脸识别功能实现,使用腾讯AI人脸识别技术实现;7、出入记录:居民出入小区的人脸识别记录查询;8、小区地图:所有小区在地图的分布情况,使用百度地图实现;9、使用Echarts技术实现小区人员分类统计(柱状)图表;10、菜单管理:新增、修改、删除菜单功能(包括目录,菜单,按钮)11、角色管理:新增、修改、删除角色(系统角色、普通角色)12、系统日志:记录了系统中所有操作的日志,方便发现问题,查找原因;运行环境:  1、JDK1.8及以上版本  2、Tomcat 8.5及以上版本  3、MySql 5.7及以上版本  4、Redis开发工具:  1、前端开发工具:Visual Studio Code  2、后端开发工具:Intellij IDEA使用技术:  1、Vue2.x+ElementUI(前端)  2、Springboot+MyBatisPlus+Redis+Shiro+Swagger(后端)  3、人脸识别技术(腾讯AI)  4、MySql数据库技术  5、Redis缓存技术  6、百度地图  7、Echarts图表技术  8、POI Excel导入导出技术  9、Shiro权限控制:菜单管理,角色管理,权限管理(按钮及用户级别权限)  10、 Swagger接口配置管理,接口文档管理技术  11、Token单点技术(一个用户不能同时在多个设备登录使用)  12、前后端分离跨域设置等技术

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值