人脸考勤签到进阶篇

news2024/11/19 23:18:34

目录

签到业务流程说明

一、需求介绍

二、如何获取地理信息?

三、如何判定某地区新冠疫情的风险等级?

开通腾讯位置服务

二、腾讯位置服务SDK

把定位坐标转换成真实地址

一、获取定位坐标

uni.authorize(OBJECT)

二、编辑签到页面

在Docker中安装人脸识别镜像

安装Docker程序

导入人脸识别镜像

运行人脸识别程序

一、创建Docker容器

二、运行人脸识别程序

三、接口调用

实现人脸签到(持久层) 

一、维护员工人脸模型数据

二、保存签到记录

实现人脸签到(业务层)

一、判断签到用户是否存在人脸模型

查询签到所在地区新冠疫情风险等级

一、利用本地宝查询地区风险等级

二、编写持久层代码

三、补充签到业务层代码

发送疫情高风险地区告警邮件

一、为什么要采用异步发送邮件?

二、导入Email邮件库

三、设置SMTP服务器信息

二、实现异步发送邮件

实现人脸签到(Web层)

一、设置上传图片存储的路径

二、编辑Controller类 

创建新员工人脸模型数据(业务层) 

一、编写抽象方法

二、编写创建人脸模型方法

创建新员工人脸模型数据(Web层)

实现人脸签到(移动端)


签到业务流程说明

一、需求介绍

        Emos系统的人脸签到模块包含的功能非常丰富,不仅仅只有人脸识别的签到功能,而且还可以根据用户签到时候的地理定位,计算出该地区是 新冠疫情 的 高风险 还是 低风险 地区。如果员工是在疫情高风险地区签到的,Emos系统会立即向公司人事部门发送告警邮件。

 

二、如何获取地理信息?

        微信小程序提供了获取地理定位的接口方法,我们调用该方法就能获取到地理坐标。但是我们得到的仅仅是坐标而已,我们还需要把地理坐标转换成地址信息,例如什么省份、什么城市、什么街道等等。

        腾讯位置服务提供了把地理坐标转换成地址这个功能,只需要我们注册之后就可以免费使用了。并且还提供了JS调用接口,我们在小程序中可以很简单的把地理坐标转换成地址信息。

三、如何判定某地区新冠疫情的风险等级?

        本地宝这个网站提供了新冠疫情地区风险等级的查询,我们输入自己的地址,就能看到具体的风险等级。

        既然我们已经把地理坐标转换成了地址信息,那么就可以根据地址信息去查询风险等级了。但是本地宝并没有提供Web接口让我们调用,所以我们只能URL地址传参的方式获取本地宝返回的响应。而且响应的内容是HTML,我们还要从HTML中解析出我们想要的风险等级信息。

开通腾讯位置服务

一、开通腾讯位置服务步骤

        因为Emos签到流程中要获取用户当前所在地址的信息,所以需要把定位坐标缓存成地址,恰好腾讯位置服务提供了这个功能。所以我们按照提示开通这个服务即可,该服务对开发者来说是免费的,所以我们可以放心使用。 

        首先我们用浏览器访问 腾讯位置服务 官网,然后在页面的右上角点击注册按钮,并且填写注册信息。 

        在 应用管理  〉我的应用 栏目中,可以看到已经创建的密钥。如果是新注册的用户,这里没有任何密钥,需要你自己创建一个新的密钥。

        根据提示填写密钥的信息。密钥创建成功之后,你要把密钥字符串记录下来,在小程序开发当中会用到。

        把该密钥和咱们的小程序关联在一起,在界面中填写小程序的授权ID。

二、腾讯位置服务SDK

        腾讯位置服务提供了多种SDK程序包,其中的JavaScript版本的SDK适用于微信小程序,所以我们下载这个SDK包。 

        登陆微信公众平台里面,在“开发管理” -> “开发设置”中设置request合法域名,添加https://apis.map.qq.com 。

        在小程序项目中,创建 lib 目录,把SDK文件放入其中。

把定位坐标转换成真实地址

一、获取定位坐标

可以通过用户授权API来判断用户是否给应用授予定位权限。

uni.authorize(OBJECT)

uni.authorize({
    scope: 'scope.userLocation',
    success() {
        uni.getLocation()
    }
})

注意:scope.userLocation 权限需要在 manifest.json 配置 permission 

        微信小程序提供了定位接口,只需要我们调用方法即可。uni-app框架的uni对象里面也封装了地理定位的方法,我们来看一下。

uni.getLocation(OBJECT)     

        获取当前的地理位置和速度。 在微信小程序中,当用户离开应用后,此接口无法调用,除非申请后台持续定位权限;当用户点击“显示在聊天顶部”时,此接口可继续调用。 

// 示例
uni.getLocation({
    type: 'wgs84',
    success: function (res) {
        console.log('当前位置的经度:' + res.longitude);
        console.log('当前位置的纬度:' + res.latitude);
    }
});

二、编辑签到页面

我们首先要获取用户签到时的地理定位

uni.showLoading({
    title: '签到中请稍后' 3. });
    setTimeout(function() { 5. uni.hideLoading();
}, 30000);
//获取地理定位
uni.getLocation({
    type: 'wgs84',
    success: function(resp) {
        let latitude = resp.latitude;
        let longitude = resp.longitude;
    }
})

接下来我们根据定位坐标,换算成真实地址,先引用腾讯位置SDK文件 

var QQMapWX = require('../../lib/qqmap-wx-jssdk.min.js');
var qqmapsdk; 

然后在 onLoad() 生命周期函数中,初始化 qqmapsdk 对象 

onLoad: function() { 
    qqmapsdk = new QQMapWX({
        key: 'KSFBZ-####-####-####-37KUE-W3FLZ'
    });
},

编写JS代码把GPS坐标转换成地址 

qqmapsdk.reverseGeocoder({
    location: { 
        latitude: latitude, 
        longitude: longitude
    },
    success: function(resp) { 
        // console.log(resp.result);
        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中安装人脸识别镜像

安装Docker程序

执行下面的指令,稍等片刻,Docker程序就安装好了 

yum install docker -y 

管理Docker程序的命令也非常简单,如下:

service docker start

service docker stop

service docker restart

导入人脸识别镜像

把 face.tar.gz 文件上传到CentOS系统 

把镜像导入Docker环境

#导入镜像文件

docker load < face.tar.gz

#查看安装的镜像

docker images

#删除镜像

docker rmi face

运行人脸识别程序

一、创建Docker容器

        上节课我们在Docker中安装了人脸识别镜像,因为人脸识别程序是用Python写的,而且需要很多依赖库,安装起来非常麻烦,所以我就把依赖环境和人脸识别程序封装成Docker镜像,只要你在本地Docker上面导入镜像,创建出容器,就能运行Python人脸程序了。 

把 demo.tar 文件上传到Linux根目录,然后解压缩 

tar -xvf demo.tar

        解压缩之后,demo文件夹中就包含了人脸识别Python程序,我们只需要把demo文件夹挂载到Docker容器,那么在容器中就能访问Linux主机的demo文件夹了。下面开始创建容器,映射端口号,挂载目录。 

#创建容器,把容器3000端口映射到宿主机3000端口,把/demo映射到宿主机的/demo

docker run -d -it -p 3000:3000 -v /demo:/demo --name node face

#查看容器运行状态

docker ps -a 

#进入到node容器

docker exec -it node bash

二、运行人脸识别程序

进入到node容器之后,然后进入 /demo 目录,运行人脸识别程序

cd /demo

#把Python程序挂起到后台运行

nohup python3 -c "from app import app;" > log.out 2>&1 &

ps -aux

kill -9 进程ID

三、接口调用

人脸识别程序程序结合了Flask框架,提供Web接口,具体如下 

1. 创建人脸模型数据 

        当Emos系统的MySQL数据库中不存在签到员工的人脸模型数据,这时候应该调用人脸识别程序的Web接口,上传照片文件,然后由Python程序识别照片中的人脸,返回人脸模型数据。Java系统接收到人脸模型数据之后,把数据保存在MySQL数据表里面。 

接口名称:/create_face_model 

请求类型:POST 

传入参数:icode

返回结果:人脸模型数据 

2. 执行人脸签到识别 

接口名称:/checkin 

请求类型:POST 

传入参数:icode 

返回结果:人脸识别结果

实现人脸签到(持久层) 

一、维护员工人脸模型数据

在 TbFaceModelDao.xml 文件中添加SQL语句 

    <select id="searchFaceModel" parameterType="int" resultType="String">
        SELECT face_model FROM tb_face_model
        WHERE user_id=#{userId}
    </select>
    <insert id="insert" parameterType="com.example.emos.wx.db.pojo.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>

在 TbFaceModelDao.java 接口中添加DAO方法  

@Mapper
public interface TbFaceModelDao {
    public String searchFaceModel(int userId);
    public void insert(TbFaceModel faceModel);
    public int deleteFaceModel(int userId);
}

二、保存签到记录

在 TbCheckinDao.xml 文件中添加INSERT语句

  <insert id="insert" parameterType="com.example.emos.wx.db.pojo.TbCheckin">
    INSERT INTO tb_checkin
    SET user_id=#{userId},
    <if test="address!=null">
      address=#{address},
    </if>
    <if test="country!=null">
      country=#{country},
    </if>
    <if test="province!=null">
      province=#{province},
    </if>
    <if test="city!=null">
      city=#{city},
    </if>
    <if test="district!=null">
      district=#{district},
    </if>
    status=#{status},
    <if test="risk!=null">
      risk=#{risk},
    </if>
    date=#{date},
    create_time=#{createTime}
  </insert>

在 TbCheckinDao.java 中添加抽象方法 

@Mapper
public interface TbCheckinDao {
    ……
    public void insert(TbCheckin entity);
}

实现人脸签到(业务层)

一、判断签到用户是否存在人脸模型

在 application.yml 文件中,添加值注入信息 

emos:
  ……
  face:
    createFaceModelUrl: http://CentOS的IP地址:3000/create_face_model
    checkinUrl: http://CentOS的IP地址:3000/checkin
  code: HelloWorld

创建 CheckinForm.java 表单类,接收小程序提交的签到数据 

@Data
@ApiModel
public class CheckinForm {
    private String address;
    private String country;
    private String province;
    private String city;
    private String district;
}

在 CheckinService.java 接口中添加抽象的签到方法 

public interface CheckinService {
    ……
    public void checkin(HashMap param);
}

在 CheckinServiceImpl.java 中实现抽象方法

@Service
@Scope("prototype")
@Slf4j
public class CheckinServiceImpl implements CheckinService {
    @Autowired
    private TbFaceModelDao faceModelDao;

    @Value("${emos.face.checkinUrl}")
    private String checkinUrl;

    @Autowired
    private SystemConstants constants;

    @Value("${emos.code}")
    private String code;

    @Override
    public void checkin(HashMap param) {
        Date d1=DateUtil.date();
        Date d2=DateUtil.parse(DateUtil.today()+" "+constants.attendanceTime);
        Date d3=DateUtil.parse(DateUtil.today()+" "+constants.attendanceEndTime);
        int status=1;
        if(d1.compareTo(d2)<=0){
            status=1;
        }
        else if(d1.compareTo(d2)>0&&d1.compareTo(d3)<0){
            status=2;
        }
        else{
            throw new EmosException("超出考勤时间段,无法考勤");
        }
        int userId= (Integer) param.get("userId");
        String faceModel=faceModelDao.searchFaceModel(userId);
        if(faceModel==null){
            throw new EmosException("不存在人脸模型");
        }
        else{
            String path=(String)param.get("path");
            HttpRequest request= HttpUtil.createPost(checkinUrl);
            request.form("photo", FileUtil.file(path),"targetModel",faceModel);
            request.form("code",code);
            HttpResponse response=request.execute();
            if(response.getStatus()!=200){
                log.error("人脸识别服务异常");
                throw new EmosException("人脸识别服务异常");
            }
            String body=response.body();
            if("无法识别出人脸".equals(body)||"照片中存在多张人脸".equals(body)){
                throw new EmosException(body);
            }
            else if("False".equals(body)){
                throw new EmosException("签到无效,非本人签到");
            }
            else if("True".equals(body)){
                //TODO 查询疫情风险等级
                //TODO 保存签到记录
            }
        }
    }
}

查询签到所在地区新冠疫情风险等级

@Data
public class TbCheckin implements Serializable {

    private String date;

    private Date createTime;
}

延伸:date字段是日期类型,createTime字段是Datetime类型。Java中没有Datetime类型,所以映射时用了日期类型-Date类。数据表中date类型就是date类型,保存的数据就是日期不包含时间。如果映射成Java中的日期类型,Java中日期类型还会有小时分钟秒毫秒,这些信息不应该存在。所以一个正确的ORM映射,就是把数据表中date类型字段映射到Java的string变量上。这样就只保存了日期数据,并不包含小时分钟秒毫秒之类的。

一、利用本地宝查询地区风险等级

        本地宝H5网页提供了新冠疫情风险等级查询,在网页上面直接输入地区,就能查询到疫情的风险等级。 

        Java程序想要查询用户签到地区的风险等级,不能到页面里面点来点去的,所以我们要用URL传参的方式,把地址信息传入本地宝的H5页面。 

        你可以在浏览器地址栏填写下方的URL连接,就能查询到北京市西城区当前的新冠疫情风险等级。

        http://m.bj.bendibao.com/news/yqdengji/?qu=西城区

从上面的案例推断,URL地址要传入两个参数: 城市编码 和 区县 。 

城市编码可以从 tb_city 表中查询到,其中的code字段就是城市对应的编号。

 

        我们可以用小程序提交过来的签到城市,然后到 tb_city 表中根据城市名称查询到城市编号。接下来,就可以把参数添加到URL上面。 

        我们想要提取查询到的风险等级结果应该怎么办呢?这个很简单,用Java程序解析本地宝HTML页面的标签,提取我们想要的结果信息即可。在Java领域中 jsoup 提供了解析HTML标签的功能,所以我们要在Java项目中引入 jsoup 库。 

在 pom.xml 文件中添加 jsoup 依赖,然后重新reload项目

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.13.1</version>
</dependency>

二、编写持久层代码

在 TbCityDao.xml 文件中添加查询语句 

<select id="searchCode" parameterType="String" resultType="String"> 
    SELECT code
    FROM tb_city
    WHERE city = #{city}
</select>

在 TbCityDao.java 接口中添加抽象方法 

@Mapper
public interface TbCityDao { 
    public String searchCode(String city);
}

三、补充签到业务层代码

在 CheckinServiceImpl.java 文件中继续补充查询疫情风险等级的代码 

@Autowired
private TbCityDao cityDao;

@Override
public void checkin(HashMap param) {
    ……
    String faceModel=faceModelDao.searchFaceModel(userId);
        if(faceModel==null){
            throw new EmosException("不存在人脸模型");
        }
        else{
            ……
            if("无法识别出人脸".equals(body)||"照片中存在多张人脸".equals(body)){
                throw new EmosException(body);
            }
            else if("False".equals(body)){
                throw new EmosException("签到无效,非本人签到");
            }
            else if("True".equals(body)){
                //查询疫情风险等级
                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();
                            // result="高风险";
                            if("高风险".equals(result)){
                                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);
            }
        }
    }
}

发送疫情高风险地区告警邮件

一、为什么要采用异步发送邮件?

        因为在签到过程中,执行人脸识别和查询疫情风险等级,都比较消耗时间。如果发送邮件再做成同步执行的,势必导致签到执行时间过长,影响用户体验。由于要把签到结果保存到签到表,所以人脸识别和疫情风险等级查询必须是同步执行的。发送邮件跟保存签到数据没有直接关联,所以做成异步并行执行的程序更好一些,这样也能缩短用户签到时候等待的时间。

 

二、导入Email邮件库

编辑 pom.xml 文件,添加依赖库 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

三、设置SMTP服务器信息

        发送邮件是通过SMTP服务器来完成的,所以我们要配置一下SMTP服务器的连接信息。这里我以163的SMTP服务器为例,并且提前已经开启了163邮箱的SMTP功能。 

spring:
    ……
    mail:
        default-encoding: UTF-8
        host: smtp.163.com
        username: *************@163.com
        password: 此处是密码

        接下来我们把系统内的常用邮箱声明一下,以后会用到这些邮箱往外发送邮件,或者给这些邮箱发送内部邮件。例如,员工签到地点是疫情高风险地区,那么就应该向HR邮箱发送邮件,告知人事总监有员工需要隔离。 

emos:
    ……
    email:
        system: *********@163.com
        hr: **********@qq.com

二、实现异步发送邮件

在SpringBoot项目中开启异步多线程非常简单,只需要下面几个步骤即可。 

在主类上面开启 @EnableAsync 注解 

……
@EnableAsync
public class EmosWxApiApplication { 
    ……
} 

在 com.example.emos.wx.config 中创建 ThreadPoolConfig 类,声明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;

    }
}

// 线程池对象自动注册给Spring项目了。

在 com.example.emos.wx.task 中创建 EmailTask 类,定义线程任务

@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);
        // message.setCc(mailbox); // 抄送给自己
        javaMailSender.send(message);
    }
}

// @Component
// @Scope("prototype")
// Serializable
// @Async
// 都是必须的

查询员工的姓名和部门名称,在 TbUserDao.xml 文件中声明查询语句 

<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>

在 TbUserDao 接口中定义抽象方法 

public HashMap searchNameAndDept(int userId);

定义值注入变量,用来接收人员隔离告警邮件 

@Value("${emos.email.hr}") 
private String hrEmail; 

@Autowired
private EmailTask emailTask; 

@Autowired
private TbUserDao userDao;

编写发送告警邮件的代码 

HashMap<String,String> map=userDao.searchNameAndDept(userId);
String name = map.get("name");
String deptName = map.get("dept_name");
deptName = deptName != null ? deptName : "";
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层)

一、设置上传图片存储的路径

        因为签到自拍照是临时使用,所以不需要存储在腾讯云对象存储中,我们只需要在本地找个文件夹存放这些签到照片,签到业务执行完,就立即删除该文件即可。 

在 application.yml 文件中,设置图片存放路径

emos:
    ……
    image-folder: D:/emos/image

在主类中添加初始化代码,项目启动时候自动创建图片文件夹 

……
public class EmosWxApiApplication { 
    ……
    @Value("${emos.image-folder}") 
    private String imageFolder; 

    ……
    @PostConstruct
    public void init(){
        ……
        new File(imageFolder).mkdirs();
    }

}

二、编辑Controller类 

编辑 CheckinController.java 类,定义 checkin() 方法

@RequestMapping("/checkin")
@RestController
@Api("签到模块Web接口")
@Slf4j
public class CheckinController {

    @Value("${emos.image-folder}")
    private String imageFolder;

    @PostMapping("/checkin")
    @ApiOperation("签到")
    public R checkin(@Valid CheckinForm form,@RequestParam("photo") MultipartFile file,@RequestHeader("token") String token){
        if(file==null){
            return R.error("没有上传文件");
        }
        int userId=jwtUtil.getUserId(token);
        String fileName=file.getOriginalFilename().toLowerCase();
        if(!fileName.endsWith(".jpg")){
            return R.error("必须提交JPG格式图片");
        }
        else{
            String path=imageFolder+"/"+fileName;
            try{
                file.transferTo(Paths.get(path));
                HashMap param=new HashMap();
                param.put("userId",userId);
                param.put("path",path);
                param.put("city",form.getCity());
                param.put("district",form.getDistrict());
                param.put("address",form.getAddress());
                param.put("country",form.getCountry());
                param.put("province",form.getProvince());
                checkinService.checkin(param);
                return R.ok("签到成功");
            }catch (IOException e){
                log.error(e.getMessage(),e);
                throw new EmosException("图片保存错误");
            }
            finally {
                FileUtil.del(path);
            }

        }
    }
}
// 防止照片重名,加上时间戳
if (file != null) {
    //获取上传文件名
    fileName = file1.getOriginalFilename();
    //获取后缀名
    String sname = fileName.substring(fileName.lastIndexOf("."));
    //时间格式化格式
    SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyyMMddHHmmssSSS");
    //获取当前时间并作为时间戳
    String timeStamp=simpleDateFormat.format(new Date());
    //拼接新的文件名
    String newName ="人脸识别"+timeStamp+sname;
    //指定上传文件的路径
    String path = "F:\\" + newName;
    //上传保存
    file.transferTo(new File(path));
    //保存当前文件路径
    request.getSession().setAttribute("currFilePath", path);
}

创建新员工人脸模型数据(业务层) 

一、编写抽象方法

        如果用户是第一次签到,checkin方法检测到数据库中没有该员工的人脸模型数据,移动端会收到异常消息,所以要重新发送HTTP请求,让后端项目用签到照片创建人脸模型数据。所以我们先来把创建人脸模型的业务层抽象方法声明一下。 

在 CheckinService 接口中,声明抽象方法 

public interface CheckinService {
    ……
    public void createFaceModel(int userId, String path);
}

二、编写创建人脸模型方法

在 CheckinServiceImpl 类中,实现抽象方法 

……
public class CheckinServiceImpl implements CheckinService {
    ……
    @Value("${emos.face.createFaceModelUrl}")
    private String createFaceModelUrl;
    ……
    @Override
    public void createFaceModel(int userId, String path) {
        HttpRequest request=HttpUtil.createPost(createFaceModelUrl);
        request.form("photo",FileUtil.file(path));
        request.form("code",code);
        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层)

在 CheckinController 类中创建 createFaceModel() 方法

@RequestMapping("/checkin")
@RestController
@Api("签到模块Web接口")
@Slf4j
public class CheckinController {
    ……
    @PostMapping("/createFaceModel")
    @ApiOperation("创建人脸模型")
    public R createFaceModel(@RequestParam("photo") MultipartFile file,@RequestHeader("token") String token){
        if(file==null){
            return R.error("没有上传文件");
        }
        int userId=jwtUtil.getUserId(token);
        String fileName=file.getOriginalFilename().toLowerCase();
        if(!fileName.endsWith(".jpg")){
            return R.error("必须提交JPG格式图片");
        }
        else{
            String path=imageFolder+"/"+fileName;
            try{
                file.transferTo(Paths.get(path));
                checkinService.createFaceModel(userId,path);
                return R.ok("人脸建模成功");
            }catch (IOException e){
                log.error(e.getMessage(),e);
                throw new EmosException("图片保存错误");
            }
            finally {
                FileUtil.del(path);
            }

        }
    }
}

实现人脸签到(移动端)

        每人每天只可签到一次,调试时要删掉数据表数据。

        163邮箱反垃圾邮件级别提升,会拦截咱们项目发送邮件,推荐使用阿里邮箱个人版。

        application.yml 中修改 spring.mail 和 emos.email 项

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/694211.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在 Spring Boot 中使用反向代理

如何在 Spring Boot 中使用反向代理 介绍 在分布式系统中&#xff0c;反向代理是一项非常重要的技术。通过反向代理&#xff0c;可以将客户端的请求转发到后端的多台服务器上&#xff0c;实现负载均衡和故障转移等功能。本文将介绍如何在 Spring Boot 应用中使用反向代理。 环…

微信小程序判断用户是否授权定位

1、判断用户是否授权定位 checkLocationAuth() {let that thiswx.getSetting({success: (res) > {let authSetting res.authSettingif (authSetting[scope.userLocation]) {that.getCurrentLocation()} else if (authSetting[scope.userLocation] false) {wx.showModal(…

yxcms弱口令至getshell 漏洞复现

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 环境部署02 漏洞配置03 利用方式04 修复方案 01 环境部署 &#xff08;1&#xff09;yxcms yxcms 基于 PHPMySQL 开发&#xff0c;这是一个采用轻量级 MVC 设计模式的网站管理系统。轻量级 MVC 设…

【wifi模块应用】基于路由模块SKW92A的4G转WiFi、无线中继、视频传输方案

在家用WiFi&#xff0c;外出用4G&#xff0c;已经成为时下很多人的常规操作&#xff0c;尤其在室内环境中&#xff0c;无论是办公、居家&#xff0c;还是在高铁站、飞机场&#xff0c;亦或是酒店、咖啡馆等公共场所&#xff0c;我们都会下意识地主动连上WiFi。据悉&#xff0c;…

lenovo联想Yoga 13s 2021 AMD平台ACN版(82CY)原厂预装Win11系统镜像原装OEM恢复出厂状态

LENOVO联想笔记本电脑&#xff0c;Yoga 13s 2021 AMD平台ACN版(82CY)原装出厂Windows11系统&#xff0c;恢复原厂OEM预装专用系统 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;…

星辰秘典:探索Python项目的神秘力量——贪吃蛇

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;星辰秘典&#xff1a;探索Python项目的神秘力量 &#x1f433;专栏&#xff1a;web开发&#xff08;html css js&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主…

Hive(10):Hive分桶表

1 分桶表的概念 分桶表也叫做桶表,叫法源自建表语法中bucket单词,是一种用于优化查询而设计的表类型。分桶表对应的数据文件在底层会被分解为若干个部分,通俗来说就是被拆分成若干个独立的小文件。在分桶时,要指定根据哪个字段将数据分为几桶(几个部分)。2 分桶表的规则 …

torch.cuda模块获取信息

判断GPU是否可用 import torch print(torch.cuda.is_available())如果输出True则表示可以使用GPU 输出可使用GPU的数量 import torch print(torch.cuda.device_count())输出1就表示只有一块可以用 输出GPU的名字 import torch print(torch.cuda.get_device_name())如果有…

如何在 VSCode 去调试 Redis源码

学习使用 Redis 时&#xff0c;从 GitHub 下载 Redis 源码是个不错的选择。在下载后&#xff0c;通过创建 task.json 和 launch.json 两个文件&#xff0c;可以方便地在本地进行 Redis 的编译、运行和调试。通过这些步骤&#xff0c;你可以更好地理解 Redis 的内部实现&#xf…

小程序 css flex的子元素图片被周边元素text挤压变形

flex下子元素会自动拉伸&#xff0c;第一个子元素image设置了100 * 100还是被后面的text过长文字挤压了。 解决&#xff1a; 方法1&#xff1a; 让图片不自动拉伸加上 flex:noe 方法2&#xff1a; flex下面所有的子元素都设置宽度&#xff0c;这个会比较麻烦&#xff0c;需要…

如何在Microsoft Excel中迅速冻结首行和末行

如果你正在处理一个大型电子表格&#xff0c;那么冻结某些行或列会很有用&#xff0c;这样当你滚动浏览工作表的其余部分时&#xff0c;它们会留在屏幕上。 当你在 Excel 中滚动浏览大的工作表时&#xff0c;你可能希望保留一些行或列&#xff0c;例如页眉。Excel 允许你通过以…

useReducer可配合react-redux方案

接下来 我们来看 useReducer 这个属性 配合 react-redux 就会非常好用 那么 我们编写一段这样代码 import React, { useState } from react;const ContDom () > {const [count, setCount] useState(0);return (<div>cont值{ count }<button onClick {()>{ …

DevSecOps实践:如何在研发过程中做好供应链安全

DevSecOps与供应链安全 很多企业都建立了DevOps流程&#xff0c;但安全基本还处在流程之外&#xff0c;没有融入传统DevOps流程&#xff0c;导致安全一直都是敏捷交付的瓶颈。本篇内容我们将从DvSecOps和软件供应链安全的角度来谈谈研发过程中的安全问题。 01 DevSecOps——供…

Self-Instruct 论文解读:利用大模型自己给自己生成指令数据,指令数据自动生成

总览 大规模“指令调整”的语言模型&#xff0c;即指令微调的LLM&#xff0c;已经表现出非凡的零样本能力&#xff0c;尤其是推广新任务上。 然而&#xff0c;这些模型严重依赖于人类编写的指令数据&#xff0c;而这些数据通常在数量、多样性和创造力方面受到限制&#xff0c;…

Elasticsearch:result

排序 es支持对搜索结果排序&#xff0c;默认是根据相关度算分(_score)来排序。可以排序的字段类型有&#xff1a;keyword、数值、地理坐标、日期等类型。 语法 GET /indexName/_search {"query": {"match_all": {}},"sort": [{"FIELD&quo…

视频融合平台EasyCVR迁移数据库报错1146是什么原因?该如何解决?

EasyCVR视频融合平台基于云边端协同架构&#xff0c;具有强大的数据接入、处理及分发能力。平台支持多协议接入&#xff0c;包括&#xff1a;国标GB28181、RTMP、RTSP/Onvif、海康Ehome、海康SDK、大华SDK、宇视SDK等&#xff0c;对外可分发多格式视频流&#xff0c;包括RTSP、…

SSM 开放式实验管理系统 -计算机毕设 附源码78512

SSM 开放式实验管理系统 摘 要 我国高校开放式实验管理普遍存在实验设备使用率较低、管理制度不完善,实验设备共享程度不高等诸多问题。要在更大范围推行开放式实验管理,就必须在开放式实验教学管理流程中,通过引入信息化管理加大信息技术在其中的应用,才能真正发挥这种教学模…

datax插件开发HdfsReader支持parquet

数据仓库HIVE存储数据一般采用parquet格式&#xff0c;但Alibaba datax开源版不支持parquet格式&#xff0c;在网上查了很多资料&#xff0c;写的大多不完整&#xff0c;特此总结出完整版记录一下&#xff0c;供大家参考。 操作步骤 1.从gitee 拉取datax代码&#xff0c;对hd…

一、云尚办公系统:搭建环境

云尚办公系统&#xff1a;搭建环境 B站直达【为尚硅谷点赞】: https://www.bilibili.com/video/BV1Ya411S7aT 本博文以课程相关为主发布&#xff0c;并且融入了自己的一些看法以及对学习过程中遇见的问题给出相关的解决方法。一起学习一起进步&#xff01;&#xff01;&#x…