Java根据SDK二次开发海康人脸设备DS-K1T673M

前言: 咨询过很多次海康的官方客服,看了操作手册,然后再分析了集成的SDK文档,总结了以下的代码以及结论。

功能:我实现的二维码核验的功能

步骤:

1.设备初始化

2.加载日志

3.设备进行登录

4.配置门禁参数开启远程核验功能和相关参数

5.事件查询

6.设备通道布防,设备回调函数,事件监听报警

7.根据业务远程控门

具体业务需要具体实现,我的思路以及代码仅供参考

上代码:

文件先放进项目中

还要将两个类放入项目中,这个SDK里面有我就不提供了,需要的话私信我

导入依赖:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>examples</artifactId>
            <version>0.2.3</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jna</groupId>
            <artifactId>jna</artifactId>
            <version>3.0.9</version>
        </dependency>
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.13.0</version>
        </dependency>
        <!--工具类依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>6.1.1</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArguments>
                        <extdirs>${project.basedir}/src/main/resources/lib</extdirs>
                    </compilerArguments>
                </configuration>
            </plugin>
        </plugins>
    </build>

初始化登录代码:

package com.ruoyi.verify.config;

import com.ruoyi.verify.Commom.osSelect;
import com.ruoyi.verify.SDKDome.HCNetSDK;
import com.sun.jna.Native;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

/**
 * 设备初始化
 */
@Configuration
@Slf4j
public class InitConfig{
    public static HCNetSDK hCNetSDK = null;
    //用户句柄
    public static int lUserID = -1;
    //设备字符集
    public static int iCharEncodeType = 0;

    /**
     * 根据不同操作系统选择不同的库文件和库路径
     *
     * @return
     */
    private static boolean createSDKInstance() {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                String strDllPath = "";
                try {
                    //System.setProperty("jna.debug_load", "true");
                    if (osSelect.isWindows()) {
                        //win系统加载库路径
                        strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";
                    } else if (osSelect.isLinux()) {
                        //Linux系统加载库路径
                        strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";
                    }
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
                } catch (Exception ex) {
                    log.info("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    public static int creatUserId() throws Exception {
        //开始设备初始化操作
        if (hCNetSDK == null) {
            if (!createSDKInstance()) {
                log.info("Load SDK fail");
                return -1;
            }
        }
        //linux系统建议调用以下接口加载组件库
        if (osSelect.isLinux()) {
            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
            //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
            String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
            String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";

            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
            ptrByteArray1.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());

            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
            ptrByteArray2.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());

            String strPathCom = System.getProperty("user.dir") + "/lib/";
            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
            struComPath.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
        }

        /**初始化*/
        hCNetSDK.NET_DVR_Init();

        /**加载日志*/
        boolean i = hCNetSDK.NET_DVR_SetLogToFile(3, "./sdklog", false);


        /**登录*/
        int lUserID = login_V40("设备IP", "admin", "设备登录密码", (short) 8000);
        return lUserID;
    }

        /**
         * 设备登录
         *
         * @param ipadress IP地址
         * @param user     用户名
         * @param psw      密码
         * @param port     端口,默认8000
         */
        public static int login_V40(String ipadress, String user, String psw, short port) {
            //注册
            //设备登录信息
            HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
            //设备ip地址
            String m_sDeviceIP = ipadress;
            m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
            System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());
            //设备用户名
            String m_sUsername = user;
            m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
            System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());
            //设备密码
            String m_sPassword = psw;
            m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
            System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());
            //sdk端口
            m_strLoginInfo.wPort = port;
            //是否异步登录:0- 否,1- 是
            m_strLoginInfo.bUseAsynLogin = false;
            m_strLoginInfo.write();
            //设备信息
            HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();
            lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);

            if (lUserID == -1) {
                log.info("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
                return -1;
            } else {
                log.info("登录成功!");
                m_strDeviceInfo.read();
                iCharEncodeType = m_strDeviceInfo.byCharEncodeType;
                return lUserID;
            }
        }
}

配置门禁参数类

package com.ruoyi.verify.config;

import com.ruoyi.verify.SDKDome.HCNetSDK;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

/**
 * 获取门禁参数配置类
 */
@Slf4j
@Configuration
public class DoorParam{

    /**
     * 配置门禁参数
     *
     * @param lUserID
     * @param szUrl 固定:"PUT /ISAPI/AccessControl/AcsCfg?format=json\r\n"
     * @return
     */
    public static String doorParam(int lUserID, String szUrl) {
        HCNetSDK.NET_DVR_XML_CONFIG_INPUT struXMLInput = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
        struXMLInput.read();
        HCNetSDK.BYTE_ARRAY stringRequest = new HCNetSDK.BYTE_ARRAY(1024);
        stringRequest.read();
        //输入ISAPI协议命令
        System.arraycopy(szUrl.getBytes(), 0, stringRequest.byValue, 0, szUrl.length());
        stringRequest.write();
        // 输入JSON数据
        String pBuf = "{\"AcsCfg\":{\"uploadCapPic\": true,\"remoteCheckDoorEnabled\": false,\"checkChannelType\": \"PrivateSDK\",\"channelIp\" : \"通道IP\"}}";
        HCNetSDK.BYTE_ARRAY lpBuf = new HCNetSDK.BYTE_ARRAY(1024);
        lpBuf.read();
        System.arraycopy(pBuf.getBytes(), 0, lpBuf.byValue, 0, pBuf.length());
        lpBuf.write();
        struXMLInput.dwSize = struXMLInput.size();
        struXMLInput.lpRequestUrl = stringRequest.getPointer();
        struXMLInput.dwRequestUrlLen = szUrl.length();
        struXMLInput.lpInBuffer = lpBuf.getPointer();
        struXMLInput.dwInBufferSize = lpBuf.byValue.length;
        struXMLInput.write();

        HCNetSDK.BYTE_ARRAY stringXMLOut = new HCNetSDK.BYTE_ARRAY(8 * 1024);
        stringXMLOut.read();
        HCNetSDK.BYTE_ARRAY struXMLStatus = new HCNetSDK.BYTE_ARRAY(1024);
        struXMLStatus.read();
        HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT struXMLOutput = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
        struXMLOutput.read();
        struXMLOutput.dwSize = struXMLOutput.size();
        struXMLOutput.lpOutBuffer = stringXMLOut.getPointer();
        struXMLOutput.dwOutBufferSize = stringXMLOut.size();
        struXMLOutput.lpStatusBuffer = struXMLStatus.getPointer();
        struXMLOutput.dwStatusSize = struXMLStatus.size();
        struXMLOutput.write();
        //配置门禁参数
        if (!InitConfig.hCNetSDK.NET_DVR_STDXMLConfig(lUserID, struXMLInput, struXMLOutput)) {
            int iErr = InitConfig.hCNetSDK.NET_DVR_GetLastError();
            System.err.println("NET_DVR_STDXMLConfig失败,错误号" + iErr + "----URL:" + szUrl);
            return null;
        } else {
            stringXMLOut.read();
            log.info("输出文本大小:" + struXMLOutput.dwReturnedXMLSize);
            //打印输出XML文本
            String strOutXML = new String(stringXMLOut.byValue).trim();
            log.info(strOutXML);
            struXMLStatus.read();
            String strStatus = new String(struXMLStatus.byValue).trim();
            log.info(strStatus);

        }

        //获取门禁参数
        szUrl = "GET /ISAPI/AccessControl/AcsCfg?format=json\r\n";
        HCNetSDK.BYTE_ARRAY stringResponse = new HCNetSDK.BYTE_ARRAY(1024);
        stringResponse.read();
        //输入ISAPI协议命令
        System.arraycopy(szUrl.getBytes(), 0, stringResponse.byValue, 0, szUrl.length());
        stringResponse.write();
        struXMLInput.lpRequestUrl = stringResponse.getPointer();
        struXMLInput.dwRequestUrlLen = szUrl.length();

        //获取时输入为空
        struXMLInput.lpInBuffer = null;
        struXMLInput.dwInBufferSize = 0;

        //分配输出内存
        HCNetSDK.BYTE_ARRAY szGetOutput = new HCNetSDK.BYTE_ARRAY(1024 * 8);
        struXMLOutput.lpOutBuffer = szGetOutput.getPointer();
        struXMLOutput.dwOutBufferSize = szGetOutput.size();

        if (!InitConfig.hCNetSDK.NET_DVR_STDXMLConfig(lUserID, struXMLInput, struXMLOutput)) {
            log.info("GET /ISAPI/AccessControl/AcsCfg?format=json failed, error code: " + InitConfig.hCNetSDK.NET_DVR_GetLastError());
            return "GET /ISAPI/AccessControl/AcsCfg?format=json failed, error code: " + InitConfig.hCNetSDK.NET_DVR_GetLastError();
        } else {
            log.info("GET /ISAPI/AccessControl/AcsCfg?format=json successfully!");
            System.out.println(szGetOutput);
        }
        return szGetOutput.toString();
    }
}

查询所有事件

package com.ruoyi.verify.config;

import com.ruoyi.verify.SDKDome.HCNetSDK;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;

@Slf4j
@Configuration
public class SelectEvent{
    /**
     * 根据UserID查询事件,查询的是所有事件
     * @param lUserID
     * @throws UnsupportedEncodingException
     * @throws InterruptedException
     */
    public static void searchAllEvent(int lUserID) throws UnsupportedEncodingException, InterruptedException {
        //事件条数
        int i = 0;
        HCNetSDK.NET_DVR_ACS_EVENT_COND struAcsEventCond = new HCNetSDK.NET_DVR_ACS_EVENT_COND();
        struAcsEventCond.read();
        struAcsEventCond.dwSize = struAcsEventCond.size();
        //查询全部主次类型的报警
        // 主次事件类型设为0,代表查询所有事件
        struAcsEventCond.dwMajor = 0;
        struAcsEventCond.dwMinor = 0;
        //开始时间
        struAcsEventCond.struStartTime.dwYear = 2022;
        struAcsEventCond.struStartTime.dwMonth = 4;
        struAcsEventCond.struStartTime.dwDay = 27;
        struAcsEventCond.struStartTime.dwHour = 9;
        struAcsEventCond.struStartTime.dwMinute = 0;
        struAcsEventCond.struStartTime.dwSecond = 0;
        //结束时间
        struAcsEventCond.struEndTime.dwYear = 2025;
        struAcsEventCond.struEndTime.dwMonth = 4;
        struAcsEventCond.struEndTime.dwDay = 30;
        struAcsEventCond.struEndTime.dwHour = 16;
        struAcsEventCond.struEndTime.dwMinute = 0;
        struAcsEventCond.struEndTime.dwSecond = 0;
        struAcsEventCond.wInductiveEventType = 1;
        struAcsEventCond.byPicEnable = 1; //是否带图片,0-不带图片,1-带图片
        struAcsEventCond.write();
        Pointer ptrStruEventCond = struAcsEventCond.getPointer();
        int m_lSearchEventHandle = InitConfig.hCNetSDK.NET_DVR_StartRemoteConfig(lUserID, HCNetSDK.NET_DVR_GET_ACS_EVENT, ptrStruEventCond, struAcsEventCond.size(), null, null);
        if (m_lSearchEventHandle<=-1)
        {
            log.info("NET_DVR_StartRemoteConfig调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
        }
        HCNetSDK.NET_DVR_ACS_EVENT_CFG struAcsEventCfg = new HCNetSDK.NET_DVR_ACS_EVENT_CFG();
        struAcsEventCfg.read();
        struAcsEventCfg.dwSize = struAcsEventCfg.size();
        struAcsEventCfg.write();
        Pointer ptrStruEventCfg = struAcsEventCfg.getPointer();
        while (true) {
            log.info("i=" + i);
            int dwEventSearch = InitConfig.hCNetSDK.NET_DVR_GetNextRemoteConfig(m_lSearchEventHandle, ptrStruEventCfg, struAcsEventCfg.size());
            if (dwEventSearch <= -1) {
                log.info("NET_DVR_GetNextRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
            }
            if (dwEventSearch == HCNetSDK.NET_SDK_GET_NEXT_STATUS_NEED_WAIT) {
                log.info("配置等待....");
                Thread.sleep(10);
                continue;
            } else if (dwEventSearch == HCNetSDK.NET_SDK_NEXT_STATUS__FINISH) {
                log.info("获取事件完成");
                break;
            } else if (dwEventSearch == HCNetSDK.NET_SDK_GET_NEXT_STATUS_FAILED) {
                log.info("获取事件出现异常");
                break;
            } else if (dwEventSearch == HCNetSDK.NET_SDK_GET_NEXT_STATUS_SUCCESS) {
                struAcsEventCfg.read();
                //获取的事件信息通过struAcsEventCfg结构体进行解析,
                log.info(i + "获取事件成功, 报警主类型:" + Integer.toHexString(struAcsEventCfg.dwMajor) + "报警次类型:" + Integer.toHexString(struAcsEventCfg.dwMinor) + "卡号:" + new String(struAcsEventCfg.struAcsEventInfo.byCardNo).trim());
                /**
                 * 工号有两个字段,dwEmployeeNo表示工号,当值为0时,表示无效,解析byEmployeeNo参数
                 */
                log.info("工号1:"+struAcsEventCfg.struAcsEventInfo.dwEmployeeNo);
                log.info("工号2:"+new String(struAcsEventCfg.struAcsEventInfo.byEmployeeNo));
                //人脸温度数据,需要设备和支持测温功能
                log.info("人脸温度:" + struAcsEventCfg.struAcsEventInfo.fCurrTemperature + "是否异常:" + struAcsEventCfg.struAcsEventInfo.byIsAbnomalTemperature);
                //口罩功能,需要设备支持此功能 0-保留,1-未知,2-不戴口罩,3-戴口罩
                log.info("是否带口罩:"+struAcsEventCfg.struAcsEventInfo.byMask);
                //人脸图片保存
                if (struAcsEventCfg.dwPicDataLen>0 || struAcsEventCfg.pPicData != null )
                {
                    FileOutputStream fout;
                    try {
                        /**
                         * 人脸图片保存路径
                         */
                        String filename = "..\\pic\\"+i+"_event.jpg";
                        fout = new FileOutputStream(filename);
                        //将字节写入文件
                        long offset = 0;
                        ByteBuffer buffers = struAcsEventCfg.pPicData.getByteBuffer(offset, struAcsEventCfg.dwPicDataLen);
                        byte[] bytes = new byte[struAcsEventCfg.dwPicDataLen];
                        buffers.rewind();
                        buffers.get(bytes);
                        fout.write(bytes);
                        fout.close();
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                i++;
                continue;
            }
        }
        i = 0;
        if (!InitConfig.hCNetSDK.NET_DVR_StopRemoteConfig(m_lSearchEventHandle)) {
            log.info("NET_DVR_StopRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
        } else {
            log.info("NET_DVR_StopRemoteConfig接口成功");
        }
        return ;
    }

}

通道布防,设置回调函数,实时获取事件

package com.ruoyi.verify.config;
import com.ruoyi.verify.SDKDome.HCNetSDK;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * 设置通道上传
 */
@Slf4j
@Configuration
public class UpChannel{

    // 报警回调函数实现
    public static HCNetSDK.FMSGCallBack_V31 fMSFCallBack_V31;

    /**
     * 建立布防上传通道,用于传输数据
     *
     * @param lUserID      唯一标识符
     * @param lAlarmHandle 报警处理器
     */
    public int setupAlarmChan(int lUserID, int lAlarmHandle) {
        // 根据设备注册生成的lUserID建立布防的上传通道,即数据的上传通道
        if (lUserID == -1) {
            log.info("请先注册");
            return lUserID;
        }
        if (lAlarmHandle < 0) {
            // 设备尚未布防,需要先进行布防
            if (fMSFCallBack_V31 == null) {
                fMSFCallBack_V31 = new FMSGCallBack_V31();
                Pointer pUser = null;
                if (!InitConfig.hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUser)) {
                    log.info("设置回调函数失败!", InitConfig.hCNetSDK.NET_DVR_GetLastError());
                }
            }
            // 这里需要对设备进行相应的参数设置,不设置或设置错误都会导致设备注册失败
            HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM();
            m_strAlarmInfo.dwSize = m_strAlarmInfo.size();
            // 智能交通布防优先级:0 - 一等级(高),1 - 二等级(中),2 - 三等级(低)
            m_strAlarmInfo.byLevel = 1;
            // 智能交通报警信息上传类型:0 - 老报警信息(NET_DVR_PLATE_RESULT), 1 - 新报警信息(NET_ITS_PLATE_RESULT)
            m_strAlarmInfo.byAlarmInfoType = 1;
            // 布防类型(仅针对门禁主机、人证设备):0 - 客户端布防(会断网续传),1 - 实时布防(只上传实时数据)
            m_strAlarmInfo.byDeployType = 1;
            // 抓拍,这个类型要设置为 0 ,最重要的一点设置
            m_strAlarmInfo.byFaceAlarmDetection = 0;
            m_strAlarmInfo.write();
            // 布防成功,返回布防成功的数据传输通道号
            lAlarmHandle = InitConfig.hCNetSDK.NET_DVR_SetupAlarmChan_V41(lUserID, m_strAlarmInfo);
            if (lAlarmHandle == -1) {
                log.info("设备布防失败,错误码=========={}", InitConfig.hCNetSDK.NET_DVR_GetLastError());
                // 注销 释放sdk资源
                logout(lUserID);
                return lAlarmHandle;
            } else {
                log.info("设备布防成功");
                return lAlarmHandle;
            }
        }
        return lAlarmHandle;
    }

    /**
     * 设置报警信息回调函数,根据上传的数据进行回调触发
     */
    public class FMSGCallBack_V31 implements HCNetSDK.FMSGCallBack_V31 {
        // lCommand 上传消息类型,这个是设备上传的数据类型,比如现在测试的门禁设备,回传回来的是 COMM_ALARM_ACS = 0x5002; 门禁主机报警信息
        // pAlarmer 报警设备信息
        // pAlarmInfo  报警信息 根据 lCommand 来选择接收的报警信息数据结构
        // dwBufLen 报警信息缓存大小
        // pUser  用户数据
        @Override
        public boolean invoke(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) {
            alarmDataHandle(lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser);
            return true;
        }
    }

    /**
     * 报警撤防
     *
     * @param lAlarmHandle 报警处理器
     */
    public int closeAlarmChan(int lAlarmHandle) {
        if (lAlarmHandle > -1) {
            if (InitConfig.hCNetSDK.NET_DVR_CloseAlarmChan_V30(lAlarmHandle)) {
                log.info("撤防成功");
                lAlarmHandle = -1;
                return lAlarmHandle;
            }
            return lAlarmHandle;
        }
        return lAlarmHandle;
    }

    /**
     * 接收设备上传的报警信息,进行上传数据的业务逻辑处理
     *
     * @param lCommand   上传消息类型
     * @param pAlarmer   报警设备信息
     * @param pAlarmInfo 报警信息
     * @param dwBufLen   报警信息缓存大小
     * @param pUser      用户数据
     */
    public static Pointer alarmDataHandle(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) {
        System.out.println("报警监听中================================");
        AlarmDataHandle(lCommand,pAlarmer,pAlarmInfo,dwBufLen,pUser);

        String sAlarmType = new String();
        String[] newRow = new String[3];
        //报警时间
        Date today = new Date();
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String[] sIP = new String[2];

        sAlarmType = new String("lCommand=0x") + Integer.toHexString(lCommand);
        // lCommand是传的报警类型
        switch (lCommand) {
            // 摄像头实时人脸抓拍上传
            case HCNetSDK.COMM_UPLOAD_FACESNAP_RESULT:
                // 分配存储空间
                HCNetSDK.NET_VCA_FACESNAP_RESULT strFaceSnapInfo = new HCNetSDK.NET_VCA_FACESNAP_RESULT();
                strFaceSnapInfo.write();
                Pointer pFaceSnapInfo = strFaceSnapInfo.getPointer();

                // 写入传入数据
                pFaceSnapInfo.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo.size()), 0, strFaceSnapInfo.size());
                strFaceSnapInfo.read();
                sAlarmType = sAlarmType + ":人脸抓拍上传[人脸评分:" + strFaceSnapInfo.dwFaceScore + ",抓拍人脸的方式:" + strFaceSnapInfo.struFeature.byRes4 + ",性别:" + strFaceSnapInfo.struFeature.bySex + "]";
                newRow[0] = dateFormat.format(today);
                // 报警类型
                newRow[1] = sAlarmType;
                // 报警设备IP地址
                sIP = new String(strFaceSnapInfo.struDevInfo.struDevIP.sIpV4).split("\0", 2);
                newRow[2] = sIP[0];
                log.info("人脸抓拍========{}", Arrays.toString(newRow));
                // 设置日期格式
                SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
                // new Date()为获取当前系统时间
                String time = df.format(new Date());

                // 人脸图片写文件
                File file = new File(System.getProperty("user.dir") + "\\pic1\\");
                if (!file.exists()) {
                    file.mkdir();
                }
                try {
                    FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic1\\" + time + "background.jpg");
                    if (strFaceSnapInfo.dwFacePicLen > 0) {
                        if (strFaceSnapInfo.dwFacePicLen > 0) {
                            try {
                                big.write(strFaceSnapInfo.pBuffer2.getByteArray(0, strFaceSnapInfo.dwBackgroundPicLen), 0, strFaceSnapInfo.dwBackgroundPicLen);
                                big.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                break;
            // 门禁主机类型实时人脸抓拍上传,走这里
            case HCNetSDK.COMM_ALARM_ACS:
                // 分配存储空间
                System.out.println("============ 这是门禁主机的报警信息 ============");
                HCNetSDK.NET_DVR_ACS_ALARM_INFO strFaceSnapInfo1 = new HCNetSDK.NET_DVR_ACS_ALARM_INFO();
                strFaceSnapInfo1.write();
                Pointer pFaceSnapInfo1 = strFaceSnapInfo1.getPointer();

                // 写入传入数据
                pFaceSnapInfo1.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo1.size()), 0, strFaceSnapInfo1.size());
                strFaceSnapInfo1.read();
                // 设置日期格式
                SimpleDateFormat df1 = new SimpleDateFormat("yyyyMMddHHmmss");
                // new Date()为获取当前系统时间
                String time1 = df1.format(new Date());

                // 人脸图片写文件
                File file1 = new File(System.getProperty("user.dir") + "\\pic3\\");
                if (!file1.exists()) {
                    file1.mkdir();
                }
                try {
                    FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic3\\" + time1 + ".jpg");
                    if (strFaceSnapInfo1.dwPicDataLen > 0) {
                        System.out.println("========== 图片有数据  ========");
                        if (strFaceSnapInfo1.dwPicDataLen > 0) {
                            try {
                                System.out.println("============ 图片上传成功 =============");
                                big.write(strFaceSnapInfo1.pPicData.getByteArray(0, strFaceSnapInfo1.dwPicDataLen), 0, strFaceSnapInfo1.dwPicDataLen);
                                big.close();
                                System.out.println("设备唯一编码=================" + strFaceSnapInfo1.struAcsEventInfo.byDeviceNo);
                                System.out.println("数据采集时间=================" + strFaceSnapInfo1.struTime.dwYear + strFaceSnapInfo1.struTime.dwMonth + strFaceSnapInfo1.struTime.dwDay + strFaceSnapInfo1.struTime.dwHour + strFaceSnapInfo1.struTime.dwMinute + strFaceSnapInfo1.struTime.dwSecond);
                                System.out.println("人员工号=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo);
                                System.out.println("人员姓名=================" + strFaceSnapInfo1.sNetUser);
                                System.out.println("通进类型(0:入场,1:离场)=================" + strFaceSnapInfo1.struAcsEventInfo.dwDoorNo);
                                System.out.println("图片唯一标识(工号加时间)=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo + time1 + ".jpg");
                                System.out.println("人员类型(0:白名单,1:访客,2:黑名单)=================" + strFaceSnapInfo1.struAcsEventInfo.byCardType);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                break;
            default:
                newRow[0] = dateFormat.format(today);
                // 报警类型
                newRow[1] = sAlarmType;
                // 报警设备IP地址
                sIP = new String(pAlarmer.sDeviceIP).split("\0", 2);
                newRow[2] = sIP[0];
                log.info("其他报警信息=========={}", Arrays.toString(newRow));
                break;
        }
        return pAlarmInfo;
    }

    // 抓拍图片
    public static void getDVRPic(int userId) throws IOException {
        // 设置通道号,其中 1 正常,-1不正常
        NativeLong chanLong = new NativeLong(1);
        // 返回Boolean值,判断是否获取设备能力
        HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
        if (!InitConfig.hCNetSDK.NET_DVR_GetDVRWorkState_V30(userId, devwork)) {
            System.out.println("返回设备状态失败");
        }
        // JPEG图像信息结构体
        HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA();
        jpeg.wPicSize = 2; // 设置图片的分辨率
        jpeg.wPicQuality = 2; // 设置图片质量
        IntByReference a = new IntByReference();
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
        Date date = new Date();
        int random = (int) (Math.random() * 1000);
        String fileNameString = sdf.format(date) + random + ".jpg";
        // 设置字节缓存
        ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024);
        // 抓图到文件
        boolean is = InitConfig.hCNetSDK.NET_DVR_CaptureJPEGPicture(userId, chanLong.intValue(), jpeg, fileNameString.getBytes(StandardCharsets.UTF_8));
        if (is) {
            System.out.println("图片抓取成功,返回长度:" + a.getValue());
        } else {
            System.out.println("图片抓取失败:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());

        }
    }

    /**
     * 获取实时报警的事件
     * @param lCommand
     * @param pAlarmer
     * @param pAlarmInfo
     * @param dwBufLen
     * @param pUser
     */
    public static void AlarmDataHandle(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) {
        HCNetSDK.NET_DVR_ACS_ALARM_INFO strACSInfo;
        Pointer pACSInfo;
        if (lCommand == 20482) {
            System.out.println("===============1111111111111==========");
            strACSInfo = new HCNetSDK.NET_DVR_ACS_ALARM_INFO();
            strACSInfo.write();
            pACSInfo = strACSInfo.getPointer();
            pACSInfo.write(0, pAlarmInfo.getByteArray(0, strACSInfo.size()), 0, strACSInfo.size());
            strACSInfo.read();
            System.out.println("=========22222222=============");
//            strACSInfo.byAcsEventInfoExtendV20=1;
            // 5代表主事件类型  次事件类型9对应的16进制0x09代表无此卡号
            if (strACSInfo.dwMajor == 5 && strACSInfo.dwMinor == 9) {
                String deviceIP = (new String(pAlarmer.sDeviceIP)).trim();
                String punchTime = strACSInfo.struTime.toStringTime();
                String empCode = Integer.toString(strACSInfo.struAcsEventInfo.dwEmployeeNo);
                String cardNo = (new String(strACSInfo.struAcsEventInfo.byCardNo)).trim();
                System.out.println("======设备ip:====" + deviceIP);
                System.out.println("======编号:====" + empCode);
                System.out.println("======卡号:====" + cardNo);
                System.out.println("======时间:====" + punchTime);
                HCNetSDK.NET_DVR_ACS_EVENT_INFO_EXTEND_V20 v20 = new HCNetSDK.NET_DVR_ACS_EVENT_INFO_EXTEND_V20();
                v20.write();
                Pointer pp = v20.getPointer();
                pp.write(0, strACSInfo.pAcsEventInfoExtendV20.getByteArray(0, v20.size()), 0, v20.size());
                v20.read();
//                System.out.println(v20);
                System.out.println("======体温:====" + v20.fCurrTemperature);
// 在项目中可以对接数据库,进行自己的业务逻辑操作
            }
        }
    }


    /**
     * 注销
     *
     * @param lUserID 设备注册成功唯一标识符
     */
    public void logout(int lUserID) {
        // 注销
        InitConfig.hCNetSDK.NET_DVR_Logout(lUserID);
        // 释放sdk资源
        InitConfig.hCNetSDK.NET_DVR_Cleanup();
    }
}

卡下发删除卡代码

package com.ruoyi.verify.config;

import com.ruoyi.verify.SDKDome.HCNetSDK;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.io.UnsupportedEncodingException;

import static com.ruoyi.verify.config.InitConfig.iCharEncodeType;


/**
 * 下发卡和删除卡
 */
@Slf4j
@Configuration
public class SendAndDeleteCard{

    /**
     * 卡下发
     *
     * @param lUserID             用户登录句柄
     * @param CardNo              卡号
     * @param EmployeeNo          工号 卡下发的时候可以一起下发工号
     * @param iPlanTemplateNumber 关联门计划模板,计划模板的配置可以参考卡计划模板配置模块,(下发卡前要设置好计划模板)
     * @throws UnsupportedEncodingException
     * @throws InterruptedException
     */
    public static void setOneCard(int lUserID, String CardNo, int EmployeeNo, short iPlanTemplateNumber) throws UnsupportedEncodingException, InterruptedException {
        HCNetSDK.NET_DVR_CARD_COND struCardCond = new HCNetSDK.NET_DVR_CARD_COND();
        struCardCond.read();
        struCardCond.dwSize = struCardCond.size();
        //下发一张
        struCardCond.dwCardNum = 1;
        struCardCond.write();
        Pointer ptrStruCond = struCardCond.getPointer();
        int m_lSetCardCfgHandle = InitConfig.hCNetSDK.NET_DVR_StartRemoteConfig(lUserID, HCNetSDK.NET_DVR_SET_CARD, ptrStruCond, struCardCond.size(), null, null);
        if (m_lSetCardCfgHandle == -1) {
            log.info("建立下发卡长连接失败,错误码为" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
            return;
        } else {
            log.info("建立下发卡长连接成功!");
        }
        HCNetSDK.NET_DVR_CARD_RECORD struCardRecord = new HCNetSDK.NET_DVR_CARD_RECORD();
        struCardRecord.read();
        struCardRecord.dwSize = struCardRecord.size();
        for (int i = 0; i < HCNetSDK.ACS_CARD_NO_LEN; i++) {
            struCardRecord.byCardNo[i] = 0;
        }
        for (int i = 0; i < CardNo.length(); i++) {
            struCardRecord.byCardNo[i] = CardNo.getBytes()[i];
        }
        struCardRecord.byCardType = 1; //普通卡
        struCardRecord.byLeaderCard = 0; //是否为首卡,0-否,1-是
        struCardRecord.byUserType = 0;
        struCardRecord.byDoorRight[0] = 1; //门1有权限

        struCardRecord.wCardRightPlan[0] = iPlanTemplateNumber;   //关联门计划模板,计划模板的配置可以参考卡计划模板配置模块,(下发卡前要设置好计划模板)
        struCardRecord.wCardRightPlan[1] = (short) 2;
        struCardRecord.struValid.byEnable = 1;    //卡有效期使能,下面是卡有效期从2020-1-1 11:11:11到2030-1-1 11:11:11
        struCardRecord.struValid.struBeginTime.wYear = 2020;
        struCardRecord.struValid.struBeginTime.byMonth = 1;
        struCardRecord.struValid.struBeginTime.byDay = 1;
        struCardRecord.struValid.struBeginTime.byHour = 11;
        struCardRecord.struValid.struBeginTime.byMinute = 11;
        struCardRecord.struValid.struBeginTime.bySecond = 11;
        struCardRecord.struValid.struEndTime.wYear = 2030;
        struCardRecord.struValid.struEndTime.byMonth = 1;
        struCardRecord.struValid.struEndTime.byDay = 1;
        struCardRecord.struValid.struEndTime.byHour = 11;
        struCardRecord.struValid.struEndTime.byMinute = 11;
        struCardRecord.struValid.struEndTime.bySecond = 11;
        struCardRecord.dwEmployeeNo = EmployeeNo; //工号 卡下发的时候可以一起下发工号
        if ((iCharEncodeType == 0) || (iCharEncodeType == 1) || (iCharEncodeType == 2)) {
            byte[] strCardName = "jx".getBytes("GBK");  //姓名
            for (int i = 0; i < HCNetSDK.NAME_LEN; i++) {
                struCardRecord.byName[i] = 0;
            }
            System.arraycopy(strCardName, 0, struCardRecord.byName, 0, strCardName.length);
        }
        if (iCharEncodeType == 6) {
            byte[] strCardName = "jx".getBytes("UTF-8");  //姓名
            for (int i = 0; i < HCNetSDK.NAME_LEN; i++) {
                struCardRecord.byName[i] = 0;
            }
            System.arraycopy(strCardName, 0, struCardRecord.byName, 0, strCardName.length);
        }
        struCardRecord.write();
        HCNetSDK.NET_DVR_CARD_STATUS struCardStatus = new HCNetSDK.NET_DVR_CARD_STATUS();
        struCardStatus.read();
        struCardStatus.dwSize = struCardStatus.size();
        struCardStatus.write();
        IntByReference pInt = new IntByReference(0);
        while (true) {
            int dwState = InitConfig.hCNetSDK.NET_DVR_SendWithRecvRemoteConfig(m_lSetCardCfgHandle, struCardRecord.getPointer(), struCardRecord.size(), struCardStatus.getPointer(), struCardStatus.size(), pInt);
            struCardStatus.read();
            if (dwState == -1) {
                log.info("NET_DVR_SendWithRecvRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_NEED_WAIT) {
                log.info("配置等待");
                Thread.sleep(10);
                continue;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FAILED) {
                log.info("下发卡失败, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 错误码:" + struCardStatus.dwErrorCode);
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_EXCEPTION) {
                log.info("下发卡异常, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 错误码:" + struCardStatus.dwErrorCode);
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_SUCCESS) {
                if (struCardStatus.dwErrorCode != 0) {
                    log.info("下发卡成功,但是错误码" + struCardStatus.dwErrorCode + ", 卡号:" + new String(struCardStatus.byCardNo).trim());
                } else {
                    log.info("下发卡成功, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 状态:" + struCardStatus.byStatus);
                }
                continue;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FINISH) {
                log.info("下发卡完成");
                break;
            }
        }
        if (!InitConfig.hCNetSDK.NET_DVR_StopRemoteConfig(m_lSetCardCfgHandle)) {
            log.info("NET_DVR_StopRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
        } else {
            log.info("NET_DVR_StopRemoteConfig接口成功\n");
        }
    }

    /**
     * 删除单张卡(删除单张卡号之前要先删除这张卡关联的人脸和指纹信息)
     *
     * @param lUserID 用户登录句柄
     * @param CardNo 卡号
     * @throws UnsupportedEncodingException
     * @throws InterruptedException
     */
    public static void delOneCard(int lUserID, String CardNo) throws UnsupportedEncodingException, InterruptedException {
        HCNetSDK.NET_DVR_CARD_COND struCardCond = new HCNetSDK.NET_DVR_CARD_COND();
        struCardCond.read();
        struCardCond.dwSize = struCardCond.size();
        struCardCond.dwCardNum = 1;  //删除一张卡号
        struCardCond.write();
        Pointer ptrStruCond = struCardCond.getPointer();
        int m_lDelCardCfgHandle = InitConfig.hCNetSDK.NET_DVR_StartRemoteConfig(lUserID, HCNetSDK.NET_DVR_DEL_CARD, ptrStruCond, struCardCond.size(), null, null);
        if (m_lDelCardCfgHandle == -1) {
            log.info("建立删除卡长连接失败,错误码为" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
            return;
        } else {
            log.info("建立删除卡长连接成功!");
        }
        HCNetSDK.NET_DVR_CARD_SEND_DATA struCardData = new HCNetSDK.NET_DVR_CARD_SEND_DATA();
        struCardData.read();
        struCardData.dwSize = struCardData.size();
        for (int i = 0; i < HCNetSDK.ACS_CARD_NO_LEN; i++) {
            struCardData.byCardNo[i] = 0;
        }
        for (int i = 0; i < CardNo.length(); i++) {
            struCardData.byCardNo[i] = CardNo.getBytes()[i];
        }
        struCardData.write();
        HCNetSDK.NET_DVR_CARD_STATUS struCardStatus = new HCNetSDK.NET_DVR_CARD_STATUS();
        struCardStatus.read();
        struCardStatus.dwSize = struCardStatus.size();
        struCardStatus.write();
        IntByReference pInt = new IntByReference(0);
        while (true) {
            int dwState = InitConfig.hCNetSDK.NET_DVR_SendWithRecvRemoteConfig(m_lDelCardCfgHandle, struCardData.getPointer(), struCardData.size(), struCardStatus.getPointer(), struCardStatus.size(), pInt);
            struCardStatus.read();
            if (dwState == -1) {
                log.info("NET_DVR_SendWithRecvRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_NEED_WAIT) {
                log.info("配置等待");
                Thread.sleep(10);
                continue;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FAILED) {
                log.info("删除卡失败, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 错误码:" + struCardStatus.dwErrorCode);
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_EXCEPTION) {
                log.info("删除卡异常, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 错误码:" + struCardStatus.dwErrorCode);
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_SUCCESS) {
                if (struCardStatus.dwErrorCode != 0) {
                    log.info("删除卡成功,但是错误码" + struCardStatus.dwErrorCode + ", 卡号:" + new String(struCardStatus.byCardNo).trim());
                } else {
                    log.info("删除卡成功, 卡号: " + new String(struCardStatus.byCardNo).trim() + ", 状态:" + struCardStatus.byStatus);
                }
                continue;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FINISH) {
                log.info("删除卡完成");
                break;
            }
        }
        if (!InitConfig.hCNetSDK.NET_DVR_StopRemoteConfig(m_lDelCardCfgHandle)) {
            log.info("NET_DVR_StopRemoteConfig接口调用失败,错误码:" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
        } else {
            log.info("NET_DVR_StopRemoteConfig接口成功\n");
        }
    }
}

远程控门

package com.ruoyi.verify.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

/**
 * 远程控制门开关类
 */
@Slf4j
@Configuration
public class DoorGateway{
    /**
     * 远程控门
     *
     * @param lUserID       用户登录句柄
     * @param lGatewayIndex 门禁序号(楼层编号、锁ID),从1开始,-1表示对所有门(或者梯控的所有楼层)进行操作
     * @param dwStaic       命令值:0- 关闭(对于梯控,表示受控),1- 打开(对于梯控,表示开门),2- 常开(对于梯控,表示自由、通道状态),3- 常关(对于梯控,表示禁用),4- 恢复(梯控,普通状态),5- 访客呼梯(梯控),6- 住户呼梯(梯控)
     */
    public static void controlGateway(int lUserID, int lGatewayIndex, int dwStaic) {

        boolean b_Gate = InitConfig.hCNetSDK.NET_DVR_ControlGateway(lUserID, lGatewayIndex, dwStaic);
        if (b_Gate == false) {
            log.info("NET_DVR_ControlGateway远程控门失败,错误码为" + InitConfig.hCNetSDK.NET_DVR_GetLastError());
        } else {
            log.info("远程控门成功");
        }
    }
}

代码导入之后自己重新导个包,有些代码需要稍微改动,根据自己的业务进行修改

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值