文章目录
签到业务流程说明
Emos系统的人脸签到模块包含的功能非常丰富,不仅仅只有人脸识别的签到功能,而且还可以根据用户签到时候的地理定位,计算出该地区是 新冠疫情 的 高风险 还是 低风险 地区。如果员工是在疫情高风险地区签到的,Emos系统会立即向公司人事部门发送告警邮件。
一、获取定位地址
uni.showLoading({
title: '签到中请稍后'
});
setTimeout(function() {
uni.hideLoading();
}, 30000);
//获取地理定位
uni.getLocation({
type: 'wgs84',
success: function(resp) {
let latitude = resp.latitude;
let longitude = resp.longitude;
}
})
var QQMapWX = require('../../lib/qqmap-wx-jssdk.min.js');
var qqmapsdk;
onLoad: function() {
qqmapsdk = new QQMapWX({
key: 'KSFBZ-####-####-####-37KUE-W3FLZ'
});
},
qqmapsdk.reverseGeocoder({
location: {
latitude: latitude;
longitude: longitude
},
success: function(resp){
let address = resp.result.address;
let addressComponent = resp.result.address_component;
let nation = addressComponent.nation;
let province = addressComponent.province;
let city = addressComponent.city;
let district = addressComponent.district;
}
})
二、在Docker中安装人脸识别镜像
三、运行人脸识别程序
四、实现人脸签到(持久层)
<select id="searchFaceModel" parameterType="int" resultType="String">
SELECT face_model FROM tb_face_model
WHERE user_id = #{userId}
</select>
<insert id="insert" resultType="TbfaceModel">
INSERT INTO tb_face_model
SET user_id = #{userId}
face_model = #{faceModel}
</insert>
<delete id="deleteFaceModel" parameterType="int">
DELETE FROM tb_face_model
WHERE user_Id = #{userId}
</delete>
五、实现人脸签到(业务层)
1.判断签到用户是否存在人脸模型
emos:
face:
createFaceModelUrl: http://192.168.0.101/create_face_model
checkinUrl:http://192.168.0.101/checkin
创建表单,接受小程序提交的签到数据
@Date
@ApiModel
public class CheckinForm{
private String address;
private String country;
private String province;
private String city;
private String district;
}
public void checkin(HashMap param){
Date d1 = DateUtil.date();
Date d2 = DateUtil.parse(DateUtil.today()+""+constants.attendanceTime);
Date d2 = DateUtil.parse(DateUtil.today()+""+attendanceEndTime);
int status = 1;
if(d1.compareTo(d2) <= 0){
status = 1;
}
if(d1.compareTo(d2)>0 && d1.compareTo(d3)<0){
status = 2;
}
int userId = (Integer)param.get("userId");
String faceModel = faceModelDao.searchFaceModel(userId);
if(faceMode == null){
throw new EmosException("不存在人脸模型");
}else{
HttpRequest request = HttpUtil.createPost(checkinUrl);
request.form("photo", FileUtil.file(path), "targetModel", faceModel);
HttpResponse response = request.execute();
if(response.getStatus() != 200){
log.error("人脸识别服务异常");
throw new EmosException("人脸识别服务异常");
}
String body = response.body();
if("无法识别出人脸".equals(body) || "照片中存在多张人脸".equals(body)){
hrow new EmosException(body);
}else if("False".equals(body)){
throw new EmosException("签到无效,非本人签到");
}else if ("True".equals(body)) {
//TODO 这里要获取签到地区新冠疫情风险等级
//TODO 保存签到记录
}
}
六、查询签到所在地区新冠疫情风险等级
1.利用本地宝查询地区风险等级
在浏览器输入URL连接:
http://m.code.bendibao.com/news/yqdengji/?qu=()
URL地址要传入两个参数: 城市编码 和 区县 。
城市编码可以从 tb_city 表中查询到,其中的code字段就是城市对应的编号
<select id="searchCode" parameter="String" resultType="String">
SELECT code FROM tb_city WHERE city = #{city}
</select>
//查询疫情风险等级
int risk=1;
String city= (String) param.get("city");
String district=(String) param.get("district");
String address= (String) param.get("address");
String country= (String) param.get("country");
String province= (String) param.get("province")
if(!StrUtil.isBlank(city) && !StrUtil.isBlank(district)){
String code = cityDao.searchCode(city);
try{
String url = "http:/m."+code+".bendibao.com/news/yqdengji/?qu="+district;
Document document = Jsoup.connect(url).get();
Elements elements = document.getElementsByClass("list-content");
if(elements.size()>0){
Element element = elements.get(0);
String result = element.select("p:last-child").text();
if(result.equals("高风险")){
risk=3;
//发送警告邮件
}else if("中风险".equals(result)){
risk = 2;
}
}
}catch(Exception e){
log.error("执行异常",e);
throw new EmosException("获取风险等级失败");
}
}
//保存签到记录
TbCheckin entity=new TbCheckin();
entity.setUserId(userId);
entity.setAddress(address);
entity.setCountry(country);
entity.setProvince(province);
entity.setCity(city);
entity.setDistrict(district);
entity.setStatus((byte) status);
entity.setRisk(risk);
entity.setDate(DateUtil.today());
entity.setCreateTime(d1);
checkinDao.insert(entity)
七、发送疫情高风险地区警告邮件
1.为什么要采用异步发送邮件?
因为在签到过程中,执行人脸识别和查询疫情风险等级,都比较消耗时间。如果发送邮件再做成同步执行的,势必导致签到执行时间过长,影响用户体验。由于要把签到结果保存到签到表,所以人脸识别和疫情风险等级查询必须是同步执行的。发送邮件跟保存签到数据没有直接关联,所以做成异步并行执行的程序更好一些,这样也能缩短用户签到时候等待的时间。
2.导入Email邮件库
org.springframework.boot spring-boot-starter-mail ## 3.设置SMTP服务器信息 ## 4.实现异步发送 在主类开启@EnableAsync 声明java线程池@Configuration
public class ThreadPoolConfig{
@Bean("AsyncTaskExecutor")
public AsyncTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(8);
// 设置最大线程数
executor.setMaxPoolSize(16);
// 设置队列容量
executor.setQueueCapacity(32);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("task-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Component
@Scope("prototype")
public class EmailTask implements Serializable{
@Autowired
private JavaMailSender javaMailSender;
@Value("${emos.email.system}")
private String mailbox;
@Async
public void sendAsync(SimpleMailMessage message){
message.setFrom(mailbox);
javaMailSender.send(message);
}
}
<select id="searchNameAndDept" parameterType="int" resultType="HashMap">
SELECT u.name d.dept_name
FROM tb_user u LEFT JOIN tb_dept d ON u.dept_id=d.id
WHERE u.id = #{userId} AND u.status = 1
</select>
编写发送警告邮件的代码
HashMap<String,String> map = userDao.searchNameAndDept(userId);
String name = map.get("name");
String deptName = map.get("dept_name");
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(hrEmail);
message.setSubject("员工"+name+"身处高风险疫情地区警告");
message.setText(deptName+"员工"+name+","+DateUtil.format(new Date(),"yyyy年MM月DD日") + "处于" + address + ",属于新冠疫情高风险地区,请及时与该员工联系,核实情况!");
emailTask.sendAsync(message);
八、实现人脸签到(Web)
1.设置图片上传路径
因为签到自拍照是临时使用,所以不需要存储在腾讯云对象存储中,我们只需要在本地找个文件夹存放这些签到照片,签到业务执行完,就立即删除该文件即可。
在 application.yml 文件中,设置图片存放路径
emos:
image-folder:G:/emos/image
在主类中添加初始化代码,项目启动时候自动创建图片文件夹
@Value("${emos.image-folder}")
private String imageFolder;
@PostConstruct
public void init(){
....
new File(imageFolder).mkdirs();
}
@RequestMapping("/checkin")
@RestController
@Api("签到模块Web接口")
@Slf4j
public class CheckinController{
@Value("${emos.image-folder}")
private String imageFolder;
@PostMapping("/checkin")
public R checkin(@Valid CheckinForm form ,@ReqeusetParam("photo") MutipartFile file @RequestHeader("token") String token ){
if(file == null){
return R.error("没有上传文件");
}
String fileName = file.getOriginalFilename().toLowerCase();
String path = imageFolder+"/"+fileName;
if(!fileName.endsWith(".jpg")){
FileUtil.del(path);
return R.error("必须提交JPG格式图片");
}else{
try{
file.transferTo(Paths.get(path));
HashMap param = new HashMap();
param.put("userId",userId);
param.put("path",path);
param.put("district",district);
param.put("address",form.getAddress);
param.put("country",country);
param.put("province",province);
checkinService.checkin(param);
return R.ok("签到成功");
}catch(IOException e){
log.error(e.getMessage());
throw new EmosException("保存图片错误");
}finally{
FileUtil.del(path);
}
}
}}
九、创建新员工人脸模型数据(业务层)
如果用户是第一次签到,checkin方法检测到数据库中没有该员工的人脸模型数据,移动端会收到异常消息,所以要重新发送HTTP请求,让后端项目用签到照片创建人脸模型数据。所以我们先来把创建人脸模型的业务层抽象方法声明一下。
public void createFaceModel(int userId,String path){
HttpRequest request = HttpUtil.createPost(createFaceModelUrl);
request.form("photo",FileUtil.file(path));
HttpResponse response = request.execute();
String body = response.body();
if ("无法识别出人脸".equals(body) || "照片中存在多张人脸".equals(body)) {
throw new EmosException(body);
} else{
TbFaceModel entity = new TbFaceModel();
entity.setUserId(userId);
entity.setFaceModel(body);
faceModelDao.insert(entity);
}
}
十、创建新员工人脸模型数据(Web层)
@PostMapping("/createFaceModel")
public R createFaceModel(@RequestParam("photo") MultipartFile file,@RequestHeader("token") String token){
int userId = jwtUtil.getUserId(token);
if(file == null){
return R.error("没有上传文件");
}
String fileName = file.getOriginalFilename().toLowerCase();
String path = imageFolder+"/"+fileName;
if(!fileNmae.endWith(".jpg")){
return R.error("必须提交jpg格图片");
}else {
try{
file.transferTo(Paths.get(path));
checkService.createFaceModel(userId,path);
return R.ok("人脸建模成功");
}catch(IOException e){
log.error(e.getMessage());
throw new EmosException("保存图片错误");
}finally{
FileUtil.del(path);
}
}
}
十一、实现人脸签到(移动端)
1.封装Ajax请求路径
Vue.prototype.url = {
……
checkin: baseUrl + "/checkin/checkin",
createFaceModel: baseUrl + "/checkin/createFaceModel",
validCanCheckIn: baseUrl + "/checkin/validCanCheckIn",
}
2.上传照片执行签到
uni.uploadFile({
url: that.url.checkin,
filePath: that.photoPath,
name: "photo",
formDate:{
address:address,
country: nation,
province: province,
city: city,
district: district
},
success.function(resp){
if(resp.statusCode = 500 && resp.data == "不存在人脸模型"){
uni.hideLoading();
uni.showModal({
title:'提示信息',
content:'EMOS系统中不存在你的人脸识别模型,是否用当前这张照片作为人脸识别模
型?',
success:function(res){
if(res.confirm){
uni.uploadFile({
url:that.url.createFaceModel,
filePath:that.photoPath,
name:'photo',
header:{
token:uni.getStorageSync('token')
},
success: function(resp){
if(resp.statusCode == 500){
uni.showToast({
title:resp.data,
icon:'none'
});
}
else if(resp.statusCode == 200){
uni.showToast({
title:'人脸建模成功',
icon:'none'
});
}
}
});
}
}
});
}
else if(resp.statusCode == 200){
let data = JSON.parse(reps.data);
let code = data.code;
let msg = data.msg;
if(code == 200){
uni.hideLoading();
uni.showToast({
title:'签到成功',
complete:function(){
//跳转签到结果统计页面
}
})
}
}
else if (resp.statusCode == 500) {
uni.showToast({
title: resp.data,
icon: 'none'
});
}
}
})
3.检查当天是否可以签到
1. onShow: function() {
2. let that = this;
3. that.ajax(that.url.validCanCheckIn, 'GET', null, function(resp) {
4. let msg = resp.data.msg;
5. if (msg != '可以考勤') {
6. setTimeout(function() {
7. uni.showToast({
8. title: msg,
9. icon: 'none'
10. });
11. }, 1000);
12. that.canCheckin = false;
13. }
14. });
15. }