2023年3月

Antlr4 简介


ANTLR(全名:ANother Tool for Language Recognition)是基于LL(*)算法实现的语法解析器生成器(parser generator),用Java语言编写,使用自上而下(top-down)的递归下降LL剖析器方法。由旧金山大学的Terence Parr博士等人于1989年开始发展。

antlr4 是一款强大的语法分析器生成工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。它被广泛的应用于学术领域和工业生产实践,是众多语言、工具和框架的基石。Twitter 搜索使用 antlr 进行语法分析,hadoop 生态系统中的 Hive、Pig、数据仓库和分析系统所使用的语言都用到了 antlr,Lex Machina 将 antlr 用于分析法律文本,oracle 公司在 sql 开发者 IDE 和迁移工具中使用了 antlr,NetBeans 公司的 IDE 使用 antlr 来解析 c++,Hibernate 对象-关系映射框架 ORM 使用 antlr 来处理 HQL 语言。

Antlr4 使用


1. 下载最新版Antlr4

https://www.antlr.org/download/antlr-4-12.0-complete.jar
(这里下载得是12版本的Antlr4)

2. 下载JDK11

因为Antlr4版本4.12使用的JDK最低使用11版本的,所以这里我们下载JDK11,下载链接:
https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html

3. 将下载的antlr4 jar包移动到JDK目录中

JDK一般安装在C:\Program Files\Java,新建文件夹:antlr,将antlr-4.12.0-complete.jar放入该文件夹中,如下图:


image

4. 设置antlr4命令:

新建antlr4.bat
输入
@ECHO OFF
"C:\Program Files\Java\jdk-11.0.17\bin\java" -jar "%~dp0antlr-4.12.0-complete.jar"

保存使用CMD执行下脚本,查看到如下图则证明antlr可以正常工作

image

新建grun.bat,这个是Antlr4自带的用来检查自定义的语法树是否正确的工具
输入
@ECHO OFF
"C:\Program Files\Java\jdk-11.0.17\bin\java" -cp "%~dp0antlr-4.12.0-complete.jar" org.antlr.v4.gui.TestRig %*

保存使用CMD执行下脚本,查看到如下图则证明antlr工具可以正常工作

image

上述操作做完文件目录如下图

image

5. 设置环境变量

  1. 设置系统变量CLASSPATH:.C:\Program Files\Java\jdk-11.0.17\bin(注意开头有个点)
  2. 设置系统变量JAVA_HOME:C:\Program Files\Java\jdk-11.0.17
  3. 在PATH中添加:%JAVA_HOME%\bin

设置好之后打开CMD,输入Java-version和Javac-version,验证Java环境是否正确


image

image

这里有的小伙伴输入java-version可以执行,输入javac-version不能执行,可以看下环境变量设置的是否正确,环境变量正确,可以重启电脑,有时候环境变量重启之后才会生效

到此,使用antlr4的环境就配置好了。接下来,就是学习使用antlr4。距离成功已经打开了大门!!!

背景:

1、C/S系统架构

2、前端 Extjs

3、后台C#

4、数据库SQL

前端通过ajAx请求与后台通信。

前端应用页面统一继承入口类  BasePage

应用页面

public partial classxxxxxxx :BasePage 
{
//y业务代码...... }

BasePage

public classBasePage : System.Web.UI.Page
{
//在这里实现数据权限核查

}

需求:

完成数据操作权限核查,并提醒前端

分析:

因为所有页面都继承了 BasePage 所以,现可以BasePage中加入代码。后台功能实现,因为要根据当前用户的ID 和所请求页面进行权限判断。

为提高效率可以存储过程中完成判断,并返回结果。

另外为便于管理和跟踪,判断结束后记录日志

存储过程

 
--=============================================--Author:        张立辉--Create date: 2023-3-24--Description:     系统权限控制 AOP--=============================================
create PROCEDURE [dbo].[Hztech_Aop] 
    @userID     as nvarchar(20),@URI as nvarchar(200),@result as nvarchar(20) output,@msg as nvarchar(200) outputAS
BEGIN
declare @roleid as int --角色ID
declare @location as int--DataStore/ 的位置
declare @moduleAndPage as nvarchar(200)--模块/页面及请求参数
declare @module as nvarchar(50) --模块
declare @pageAndOptype as nvarchar(100)--page
declare @page as nvarchar(100)--page
declare @optype as nvarchar(50) --optype


declare @Edit   as bit
declare  @Del   as bit
declare    @Close  as bit
declare   @Lock   as bit
declare   @Unop as bit
declare   @Export as bit
declare @pageName as nvarchar(100)declare @pass nvarchar(20)set @pass='refuse'

set @pageName='查询'
--1 通过用户id 获取角色 id
    select @roleid=isnull(roleid,0) from [xxxx_UserRoles] where UserID=@userID
    if(@roleid is null or @roleid=0)begin
        set @pass='refuse'
        set @msg='您未被授权进行任何操作。'
        set @result= 0
        gotologgend
--2 通过url 和 和角色ID 获取菜单权限
    --2.1 判断是否为 datastore请求
    set @location=charindex('xxxxxxxxxx/',@URI,0)if(@location<=0)begin
        set @msg='pass+log,非xxxxxxxxxx操作。'
        set @result= 1
        gotologgend
    --2.2 包含 DataStore/
    set   @moduleAndPage= SUBSTRING(@URI,@location+len('xxxxxxxxxxxx/'),len(@URI)-4)--解析URL
    set @module=dbo.GetSplitOfIndex(@moduleAndPage,'/',1)--模块
    set @pageAndOptype=dbo.GetSplitOfIndex(@moduleAndPage,'/',2)--@pageAndOptype
    set @page=dbo.GetSplitOfIndex(@pageAndOptype,'?',1) --yyy.aspx?optype=xxxx
    set @optype=dbo.GetSplitOfIndex(@pageAndOptype,'?',2)--optype=xxxx
    set @optype=dbo.GetSplitOfIndex(@optype,'=',2) --xxxx   或者 xxxx&p=v
    if(CHARINDEX('&',@optype,0)>0) --包含多个参数
    begin
        set @optype=dbo.GetSplitOfIndex(@optype,'&',1) --xxxx
    end
    
 
--3 判断是否需要控制权限
    /*optype  值范围

*Submit --dialog 单表提交
savePOBill 单据保存
  。。。。。。。
*/ --3.1 查询操作 if(@optype='getPobillAndDetail' or @optype='showfile' or charindex('Select',@optype,0)>0 or @optype='WorkFlowApprovePobill' --审批提交 )begin set @pass='log' set @msg='pass,非修改操作。' set @result= 1 gotologgend --3.2 获取权限 select @Edit=[CanEdit]  --其它权限省略 from [permissionView] where [Url] like '%/'+@module+'/%' and roleid=@roleid --4 判断是否有 权限 if (charindex('Submit',@optype,0)>0--dialog 单表提交 or @optype='savePOBill' --单据保存 )begin---编辑权限 if(@Edit=1)--可编辑 begin set @result= 1 end else begin set @result=0 gotologgend end   ---其它权限核查 略
    。。。。。。。。。。
else set @result=0--其它情况 拒绝 refuse if(@result=1)set @pass='pass' --5 记录日志 logg:if(@result=0)set @msg='未授权进行当前操作'+@optype insert into sys_aoplog([code],[name],[uri],[verifyresult], module,[page],optype,remark) values (@userID,@pageName,@URI,@pass,@module,@page,@optype,@msg)--6 返回结果 return @result END

前端调用存储过程进行鉴权

  //判断权限
                   bool purCheck= dataOperate.ExeAopProc(aurl, currentUser.UserID.ToString(), outpurMsg);if (!purCheck)
{
string jsonlist1 = Common.ExtAjaxRequest.GetOperateRet("0", purCheck, purMsg);
Response.Write(jsonlist1);
Response.End();
}

效果:

后台日志

收工。

@


测试环境:

操作系统: Window 10
工具:Pycharm
Python: 3.7

一、系统函数__init__() 初始化类函数

功能:

使用该
__init__()
类系统方法可以在创建类的实例化对象时,可以初始化对象 —— 传递参数(该类的全局变量),参数可以是函数对象,也可以是各种数据类型的变量
eg:

# test.py
class System_Function:
    def __init__(self, function_or_varible):
        self.function_or_varible = function_or_varible
        print("init(初始化) class System_Function: {0}\n".format(self.function_or_varible))

# 初始化类对象
init_args = 'init_test'
System_Function(init_args)  

程序输出显示:

init(初始化) class System_Function: init_test

二、系统函数__call__() 调用对象函数

call
n. 打电话,通话;喊叫,呼叫;需求,需要;号召,呼吁;执行子程序的命令;

功能:

本质是将一个类变成一个函数

Python 类中一个非常特殊的实例方法,即
__call__()
。该方法的功能类似于在类中重载
()
运算符,使得类实例对象可以像调用普通函数那样,以“
对象名()
”的形式使用。

对于可调用对象,实际上“名称()”可以理解为是“
名称.__call__()
”的简写。仍以上面程序中定义的
call_obj
实例对象为例,其最后一行代码还可以改写为如下形式:

obj.__call__("obj.__call__","__call__()")

具体代码演示:

# test.py
class System_Function:

    def __init__(self, function_or_varible):
        self.function_or_varible = function_or_varible
        print("init(初始化) class System_Function: {0}\n".format(self.function_or_varible))


	## 定义__call__方法
    def __call__(self,func,name):
        print("{0}: 调用 {1} 方法 \n".format(func,name))
        
# 初始化类对象
init_args = 'init_test'
obj = System_Function(init_args)  # 类实例化对象 object

## 像调用函数方法那样调用 类对象系统函数 __call__()
obj("obj()", "__call__()") 

obj.__call__("obj.__call__","__call__()")

程序输出显示:

init(初始化) class System_Function: init_test

obj(): 调用 __call__() 方法

obj.__call__: 调用 __call__() 方法

输出结果很有意思,对象无需加
.
(点)就可以调用类的方法,这意味着对象本身就是一个函数对象,不过这应该是类似一种
软连接
的操作,
将对象的点调用操作连接到对象括号
,这个
别名

三、系统函数__dict__类属性查询函数

功能:

在 Python 类的内部,无论是
类属性
还是
实例属性
,都是以字典(
dict
)的形式进行存储的,其中
属性名作为键
,而
值作为该键对应的值

代码参考案例:

class CLanguage:
    a = 1
    b = 2
    def __init__ (self):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
#通过类名调用__dict__
print(CLanguage.__dict__)
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)

程序输出显示:

{'__module__': '__main__', 'a': 1, 'b': 2, '__init__': <function CLanguage.__init__ at 0x0000022C69833E18>, '__dict__': <attribute '__dict__' of 'CLanguage' objects>, '__weakref__': <attribute '__weakref__' of 'CLanguage' objects>, '__doc__': None}
{'name': 'C语言中文网', 'add': 'http://c.biancheng.net'}

个人代码案例:

测试1

# test.py
class System_Function:

    def __init__(self, function_or_varible):
        self.function_or_varible = function_or_varible
        print("init(初始化) class System_Function: {0}\n".format(self.function_or_varible))


	## 定义__call__方法
    def __call__(self,func,name):
        print("{0}: 调用 {1} 方法 \n".format(func,name))
        
# 初始化类对象
init_args = 'init_test'
obj = System_Function(init_args)  # 类实例化对象 object

## 像调用函数方法那样调用 类对象系统函数 __call__()
obj("obj()", "__call__()") 

obj.__call__("obj.__call__","__call__()")

#通过类名调用__dict__
print("System_Function 类名调用系统函数__dict__,无需参数:\n",System_Function.__dict__)
print("\n")

#通过类实例对象调用 __dict__
print("类实例对象 obj 调用__dict__:\n",obj.__dict__)

程序输出显示:

init(初始化) class System_Function: init_test

obj(): 调用 __call__() 方法

obj.__call__: 调用 __call__() 方法

System_Function 类名调用系统函数__dict__,无需参数:
 {'__module__': '__main__', '__init__': <function System_Function.__init__ at 0x000001EA556B1378>, '__call__': <function System_Function.__call__ at 0x000001EA556B17B8>, '__dict__': <attribute '__dict__' of 'System_Function' objects>, '__weakref__': <attribute '__weakref__' of 'System_Function' objects>, '__doc__': None}


类实例对象 obj 调用__dict__:
 {'function_or_varible': 'init_test'}

测试2

# test.py
class System_Function:

    def __init__(self,function_or_varible):
        self.function_or_varible = function_or_varible
        print("init(初始化) class System_Function: {0}\n".format(self.function_or_varible))
    ## 定义__call__方法
    def __call__(self,func,name):
        self.func = func
        self.name = name
        print("{0}: 调用 {1} 方法 \n".format(self.func,self.name))
        
# 初始化类对象
init_args = 'init_test'
obj = System_Function(init_args)  # 类实例化对象 object

## 像调用函数方法那样调用 类对象系统函数 __call__()
obj("obj()", "__call__()") 

obj.__call__("obj.__call__","__call__()")

#通过类名调用__dict__
print("System_Function 类名调用系统函数__dict__,无需参数:\n",System_Function.__dict__)
print("\n")
#通过类实例对象调用 __dict__
print("类实例对象 obj 调用__dict__:\n",obj.__dict__)

程序输出显示:

init(初始化) class System_Function: init_test

obj(): 调用 __call__() 方法

obj.__call__: 调用 __call__() 方法

System_Function 类名调用系统函数__dict__,无需参数:
 {'__module__': '__main__', '__init__': <function System_Function.__init__ at 0x0000019AF6801378>, '__call__': <function System_Function.__call__ at 0x0000019AF68017B8>, '__dict__': <attribute '__dict__' of 'System_Function' objects>, '__weakref__': <attribute '__weakref__' of 'System_Function' objects>, '__doc__': None}


类实例对象 obj 调用__dict__:
 {'function_or_varible': 'init_test', 'func': 'obj.__call__', 'name': '__call__()'}

需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用
__dict__
,会输出该由类中所有类属性组成的字典(
未赋予实例化的所有属性——函数名
);而使用类的实例对象调用
__dict__
,会输出由类中所有实例属性组成的字典(
包括所有传过去的形参参数
)。

对比
测试1

测试2
的输出结果,可以知道当使用类的实例化对象去调用
__dict__
系统函数时,会自动调用类的
self
实例,
self
关键字代表了类的所有的实例,每一个类实例化都会有的一个属性

四、系统函数__str__()描述类信息函数

功能:

如果定义了该函数,当
print
当前实例化对象的时候,会返回该函数的
return
信息 可用于定义当前类
__str__()
方法定义的描述信息

  1. 参数:无
  2. 返回值:一般返回对于该类的描述信息

代码演示:

class Test(object):
    def __str__(self):
        return '这是关于这个类的描述'
        
test = Test()
print(test)

运行结果:
在这里插入图片描述

五、系统函数__getattr__()调用类未定义之信息反馈函数

功能:

当调用的属性或者方法不存在时,会返回该方法(
__getattr__()
)定义的信息

class Test(object):
    def __getattr__(self, key):
        print('这个key:{}不存在'.format(key))
test = Test()
test.a

运行结果:
在这里插入图片描述

六、系统函数__setattr__()拦截类未定义属性及值的函数

功能:

拦截当前类中不存在的属性与值

代码演示:

class Test(object):
    def __setattr__(self,key,value):
        if key not in self.__dict__:
            self.__dict__[key] = value
t = Test()
t.name ='dewei'
t.name

运行结果:
在这里插入图片描述

参考链接:
Python类的高级函数

七、其他的系统函数

此处贴上链接,就不一一详解了。
Python类特殊成员(属性和方法)


  • 实现Redis的database层(核心层:处理命令并返回)
  • https://github.com/csgopher/go-redis
  • 本文涉及以下文件:
    dict:定义字典的一些方法
    sync_dict:实现dict
    db:分数据库
    command:定义指令
    ping,keys,string:指令的具体处理逻辑
    database:单机版数据库

datastruct/dict/dict.go

type Consumer func(key string, val interface{}) bool

type Dict interface {
   Get(key string) (val interface{}, exists bool)
   Len() int
   Put(key string, val interface{}) (result int)
   PutIfAbsent(key string, val interface{}) (result int)
   PutIfExists(key string, val interface{}) (result int)
   Remove(key string) (result int)
   ForEach(consumer Consumer)
   Keys() []string
   RandomKeys(limit int) []string
   RandomDistinctKeys(limit int) []string
   Clear()
}

Dict接口:Redis数据结构的接口。这里我们使用sync.Map作为字典的实现,如果想用别的数据结构,换一个实现即可
Consumer:遍历字典所有的键值对,返回值是布尔,true继续遍历,false停止遍历

datastruct/dict/sync_dict.go

type SyncDict struct {
   m sync.Map
}

func MakeSyncDict() *SyncDict {
   return &SyncDict{}
}

func (dict *SyncDict) Get(key string) (val interface{}, exists bool) {
   val, ok := dict.m.Load(key)
   return val, ok
}

func (dict *SyncDict) Len() int {
   length := 0
   dict.m.Range(func(k, v interface{}) bool {
      length++
      return true
   })
   return length
}

func (dict *SyncDict) Put(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   dict.m.Store(key, val)
   if existed {
      return 0
   }
   return 1
}

func (dict *SyncDict) PutIfAbsent(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   if existed {
      return 0
   }
   dict.m.Store(key, val)
   return 1
}

func (dict *SyncDict) PutIfExists(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   if existed {
      dict.m.Store(key, val)
      return 1
   }
   return 0
}

func (dict *SyncDict) Remove(key string) (result int) {
   _, existed := dict.m.Load(key)
   dict.m.Delete(key)
   if existed {
      return 1
   }
   return 0
}

func (dict *SyncDict) ForEach(consumer Consumer) {
   dict.m.Range(func(key, value interface{}) bool {
      consumer(key.(string), value)
      return true
   })
}

func (dict *SyncDict) Keys() []string {
   result := make([]string, dict.Len())
   i := 0
   dict.m.Range(func(key, value interface{}) bool {
      result[i] = key.(string)
      i++
      return true
   })
   return result
}

func (dict *SyncDict) RandomKeys(limit int) []string {
   result := make([]string, limit)
   for i := 0; i < limit; i++ {
      dict.m.Range(func(key, value interface{}) bool {
         result[i] = key.(string)
         return false
      })
   }
   return result
}

func (dict *SyncDict) RandomDistinctKeys(limit int) []string {
   result := make([]string, limit)
   i := 0
   dict.m.Range(func(key, value interface{}) bool {
      result[i] = key.(string)
      i++
      if i == limit {
         return false
      }
      return true
   })
   return result
}

func (dict *SyncDict) Clear() {
   *dict = *MakeSyncDict()
}

使用sync.Map实现Dict接口

database/db.go

type DB struct {
	index int
	data  dict.Dict
}

type ExecFunc func(db *DB, args [][]byte) resp.Reply

type CmdLine = [][]byte

func makeDB() *DB {
	db := &DB{
		data: dict.MakeSyncDict(),
	}
	return db
}

func (db *DB) Exec(c resp.Connection, cmdLine [][]byte) resp.Reply {
	cmdName := strings.ToLower(string(cmdLine[0]))
	cmd, ok := cmdTable[cmdName]
	if !ok {
		return reply.MakeErrReply("ERR unknown command '" + cmdName + "'")
	}
	if !validateArity(cmd.arity, cmdLine) {
		return reply.MakeArgNumErrReply(cmdName)
	}
	fun := cmd.executor
	return fun(db, cmdLine[1:]) // 把 set k v 中的set切掉
}

func validateArity(arity int, cmdArgs [][]byte) bool {
	argNum := len(cmdArgs)
	if arity >= 0 {
		return argNum == arity
	}
	return argNum >= -arity
}

func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
	raw, ok := db.data.Get(key)
	if !ok {
		return nil, false
	}
	entity, _ := raw.(*database.DataEntity)
	return entity, true
}

func (db *DB) PutEntity(key string, entity *database.DataEntity) int {
	return db.data.Put(key, entity)
}

func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
	return db.data.PutIfExists(key, entity)
}

func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
	return db.data.PutIfAbsent(key, entity)
}

func (db *DB) Remove(key string) {
	db.data.Remove(key)
}

func (db *DB) Removes(keys ...string) (deleted int) {
	deleted = 0
	for _, key := range keys {
		_, exists := db.data.Get(key)
		if exists {
			db.Remove(key)
			deleted++
		}
	}
	return deleted
}

func (db *DB) Flush() {
	db.data.Clear()
}

实现Redis中的分数据库
ExecFunc:所有Redis的指令都写成这样的类型
validateArity方法:

  • 定长
    :set k v => arity=3;
  • 变长
    :exists k1 k2 k3 ... => arity=-2,表示参数>=2个

database/command.go

var cmdTable = make(map[string]*command)

type command struct {
   executor ExecFunc
   arity    int 
}

func RegisterCommand(name string, executor ExecFunc, arity int) {
   name = strings.ToLower(name)
   cmdTable[name] = &command{
      executor: executor,
      arity:    arity,
   }
}

command:每一个command结构体都是一个指令,例如ping,keys等等
arity:参数数量
cmdTable:记录所有指令和command结构体的关系
RegisterCommand:注册指令的实现,在程序

database/ping.go

func Ping(db *DB, args [][]byte) resp.Reply {
    if len(args) == 0 {
        return &reply.PongReply{}
    } else if len(args) == 1 {
        return reply.MakeStatusReply(string(args[0]))
    } else {
        return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
    }
}

func init() {
    RegisterCommand("ping", Ping, 1)
}

init方法:在启动程序时就会调用这个方法,用于初始化

database/keys.go

func execDel(db *DB, args [][]byte) resp.Reply {
   keys := make([]string, len(args))
   for i, v := range args {
      keys[i] = string(v)
   }

   deleted := db.Removes(keys...)
   return reply.MakeIntReply(int64(deleted))
}

func execExists(db *DB, args [][]byte) resp.Reply {
   result := int64(0)
   for _, arg := range args {
      key := string(arg)
      _, exists := db.GetEntity(key)
      if exists {
         result++
      }
   }
   return reply.MakeIntReply(result)
}

func execFlushDB(db *DB, args [][]byte) resp.Reply {
   db.Flush()
   return &reply.OkReply{}
}

func execType(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   entity, exists := db.GetEntity(key)
   if !exists {
      return reply.MakeStatusReply("none")
   }
   switch entity.Data.(type) {
   case []byte:
      return reply.MakeStatusReply("string")
   }
   return &reply.UnknownErrReply{}
}

func execRename(db *DB, args [][]byte) resp.Reply {
   if len(args) != 2 {
      return reply.MakeErrReply("ERR wrong number of arguments for 'rename' command")
   }
   src := string(args[0])
   dest := string(args[1])
   
   entity, ok := db.GetEntity(src)
   if !ok {
      return reply.MakeErrReply("no such key")
   }
   db.PutEntity(dest, entity)
   db.Remove(src)
   return &reply.OkReply{}
}

func execRenameNx(db *DB, args [][]byte) resp.Reply {
   src := string(args[0])
   dest := string(args[1])

   _, exist := db.GetEntity(dest)
   if exist {
      return reply.MakeIntReply(0)
   }

   entity, ok := db.GetEntity(src)
   if !ok {
      return reply.MakeErrReply("no such key")
   }
   db.Removes(src, dest)
   db.PutEntity(dest, entity)
   return reply.MakeIntReply(1)
}

func execKeys(db *DB, args [][]byte) resp.Reply {
   pattern := wildcard.CompilePattern(string(args[0]))
   result := make([][]byte, 0)
   db.data.ForEach(func(key string, val interface{}) bool {
      if pattern.IsMatch(key) {
         result = append(result, []byte(key))
      }
      return true
   })
   return reply.MakeMultiBulkReply(result)
}

func init() {
   RegisterCommand("Del", execDel, -2)
   RegisterCommand("Exists", execExists, -2)
   RegisterCommand("Keys", execKeys, 2)
   RegisterCommand("FlushDB", execFlushDB, -1)
   RegisterCommand("Type", execType, 2)
   RegisterCommand("Rename", execRename, 3)
   RegisterCommand("RenameNx", execRenameNx, 3)
}

keys.go实现以下指令:
execDel:del k1 k2 k3 ...
execExists:exist k1 k2 k3 ...
execFlushDB:flushdb
execType:type k1
execRename:rename k1 k2
execRenameNx:renamenx k1 k2
execKeys:keys(依赖lib包的工具类wildcard.go)

database/string.go

func execGet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   bytes, err := db.getAsString(key)
   if err != nil {
      return err
   }
   if bytes == nil {
      return &reply.NullBulkReply{}
   }
   return reply.MakeBulkReply(bytes)
}

func (db *DB) getAsString(key string) ([]byte, reply.ErrorReply) {
   entity, ok := db.GetEntity(key)
   if !ok {
      return nil, nil
   }
   bytes, ok := entity.Data.([]byte)
   if !ok {
      return nil, &reply.WrongTypeErrReply{}
   }
   return bytes, nil
}

func execSet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]
   entity := &database.DataEntity{
      Data: value,
   }
   db.PutEntity(key, entity)
   return &reply.OkReply{}
}

func execSetNX(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]
   entity := &database.DataEntity{
      Data: value,
   }
   result := db.PutIfAbsent(key, entity)
   return reply.MakeIntReply(int64(result))
}

func execGetSet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]

   entity, exists := db.GetEntity(key)
   db.PutEntity(key, &database.DataEntity{Data: value})
   if !exists {
      return reply.MakeNullBulkReply()
   }
   old := entity.Data.([]byte)
   return reply.MakeBulkReply(old)
}

func execStrLen(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   entity, exists := db.GetEntity(key)
   if !exists {
      return reply.MakeNullBulkReply()
   }
   old := entity.Data.([]byte)
   return reply.MakeIntReply(int64(len(old)))
}

func init() {
   RegisterCommand("Get", execGet, 2)
   RegisterCommand("Set", execSet, -3)
   RegisterCommand("SetNx", execSetNX, 3)
   RegisterCommand("GetSet", execGetSet, 3)
   RegisterCommand("StrLen", execStrLen, 2)
}

string.go实现以下指令:
execGet:get k1
execSet:set k v
execSetNX:setnex k v
execGetSet:getset k v 返回旧值
execStrLen:strlen k

database/database.go

type Database struct {
   dbSet []*DB
}

func NewDatabase() *Database {
   mdb := &Database{}
   if config.Properties.Databases == 0 {
      config.Properties.Databases = 16
   }
   mdb.dbSet = make([]*DB, config.Properties.Databases)
   for i := range mdb.dbSet {
      singleDB := makeDB()
      singleDB.index = i
      mdb.dbSet[i] = singleDB
   }
   return mdb
}

func (mdb *Database) Exec(c resp.Connection, cmdLine [][]byte) (result resp.Reply) {
   defer func() {
      if err := recover(); err != nil {
         logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
      }
   }()

   cmdName := strings.ToLower(string(cmdLine[0]))
   if cmdName == "select" {
      if len(cmdLine) != 2 {
         return reply.MakeArgNumErrReply("select")
      }
      return execSelect(c, mdb, cmdLine[1:])
   }
   dbIndex := c.GetDBIndex()
   selectedDB := mdb.dbSet[dbIndex]
   return selectedDB.Exec(c, cmdLine)
}

func execSelect(c resp.Connection, mdb *Database, args [][]byte) resp.Reply {
   dbIndex, err := strconv.Atoi(string(args[0]))
   if err != nil {
      return reply.MakeErrReply("ERR invalid DB index")
   }
   if dbIndex >= len(mdb.dbSet) {
      return reply.MakeErrReply("ERR DB index is out of range")
   }
   c.SelectDB(dbIndex)
   return reply.MakeOkReply()
}

func (mdb *Database) Close() {
}

func (mdb *Database) AfterClientClose(c resp.Connection) {
}

Database:一组db的集合
Exec:执行切换db指令或者其他指令
execSelect方法:选择db(指令:select 2)

resp/handler/handler.go

import (
	database2 "go-redis/database"
)

func MakeHandler() *RespHandler {
   var db database.Database
   db = database2.NewDatabase()
   return &RespHandler{
      db: db,
   }
}

修改实现协议层handler的database实现

架构小结

TCP层服务TCP的连接,然后将连接交给RESP协议层的handler,handler监听客户端的连接,将指令解析后发给管道,管道转给database层(database/database.go),核心层根据命令类型执行不同的方法,然后返回。

SDK介绍

概述

设备网络SDK是基于设备私有网络通信协议开发的,为嵌入式网络硬盘录像机、NVR、网络摄像机、网络球机、视频服务器、解码器、报警主机、网络存储等产品服务的配套模块,用于远程访问和控制设备软件的二次开发。

功能

图像预览, 文件回放和下载, 云台控制, 布防/撤防, 语音对讲, 日志管理, 解码卡, 远程升级, 远程重启/关闭, 格式化硬盘, 参数配置(系统配置, 通道配置, 串口配置, 报警配置, 用户配置), 多路解码器, 智能设备功能和获取设备能力集等。

下载

https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10
1678439082400.png

对接指南

以java为例

由于我司提供的设备网络SDK是封装的动态链接库(Windows的dll或者Linux的so),各种开发语言对接SDK,都是通过加载动态库链接,调用动态库中的接口实现功能模块对接,因此,设备网络SDK的对接不区分开发语言,而且对接的流程和对应的接口都是通用的,各种语言调用动态库的方式有所不同。本文重点介绍java开发语言如何对接设备网络SDK。目前我司提供的java语言开发的demo是通过JNA的方式调用动态链接库中的接口,JNA(Java Native Access)框架是SUN公司主导开发的开源java框架,是建立在JNI的基础上的一个框架,JNA框架提供了一组java工具类用于在运行期间动态访问动态链接库(native library:如Window的dll、Linux的so),实现在java语言中调用C/C++语言封装的接口,java开发人员只需要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,而不需要编写任何Native/JNI代码,大大降低了Java调用动态链接库的开发难度。相比于JNI的方式,JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。JNA调用C/C++的过程大致如下:

1678439136114.png

集成


SpringBoot
项目为例,海康SDK版本为6.1.9.47,JNA版本为3.0.9,在windows环境使用Intellij IDEA 2022.2.3开发

初始化项目

  • 新建 SpringBoot 项目,版本 2.5.3
  • 添加pom依赖:jna,fastjson2

<dependency>
        <groupId>com.sun.jna</groupId>
        <artifactId>jna</artifactId>
        <version>3.0.9</version>
</dependency>


<dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.20</version>
</dependency>          


  • 将下载下来的海康sdk放到项目目录,并根据操作系统不同分别创建相应的文件夹
  • 若出现 HCNetSDK.NET_DVR_PREVIEWINFO.HWND找不到引用,可做如下处理:1:在ProjectStructure中引入官方示例代码中的 examples.jar ;2:将 HWND 类型修改为 int

1678772887942.png

  • 设置一个喜欢的端口
    server.port

初始化SDK

初始化SDK概述

一般的,我们希望在程序启动的时候就初始化sdk。

  • 这里使用了
    ApplicationRunner
    作为初始化入口,当程序启动成功后,将执行 Runner 做初始化
  • 为避免初始化sdk对主线程造成影响,所以
    ApplicationRunner
    需要放在线程池中
    ThreadPoolExecutor
    ,并添加try-catch处理
  • HCNetSDK是SDK示例代码中提供的一个对象,此对象继承Library,负责和更底层的C/C++库(更底层也许是C写的,这里不确定)交互,即执行 Native 调用。通过实例化此对象完成sdk依赖库的导入,并在后续业务开发中使用此对象向摄像机发布指令。
  • 涉及多操作系统平台的差异性,官方分别提供不同sdk依赖库,具体包含:win32,win64,linux32,linux64等,所以当初始化SDK的时候需要根据当前所处环境不同分别加载不同的依赖库文件
  • 上述提到的依赖库文件,在windows下就是 dll 后缀文件 , 在 linux 下就是 so 后缀文件
  • 真正执行初始化就是调用
    hCNetSDK.NET_DVR_Init()
    此方法,并可通过返回值为
    true

    false
    判断初始化是否成功。

新建AppRunner

  • AppRunner 需要实现 ApplicationRunner 接口,并将
    AppRunner作为组件放到Spring 容器中管理
  • AppRunner 中注入SdkInitService ,并在run 方法中调用 SdkInitService 的initSdk 方法实现SDK的初始化

package com.ramble.hikvisionsdkintegration;
import com.ramble.hikvisionsdkintegration.service.SdkInitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner  implements ApplicationRunner {
    @Autowired
    private SdkInitService hksdkInitService;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        hksdkInitService.initSdk();
    }
}


新建SdkInitService

  • 定义一个公开的 hCNetSDK 属性,类型为 HCNetSDK ,并在构造函数中初始化 hCNetSDK 属性的值,此值需要全局唯一,这里参照官方代码做了单例处理。HCNetSDK 是官方提供的一个接口,一般的都是直接copy到项目源代码中,你会发现,所有和设备交互的地方都是通过这个接口来完成的
  • 内部定义一个异常回调类,用来处理和设备交互的时候全局异常的处理
  • 注入 ThreadPoolExecutor 执行器,真正的初始化将放到子线程中进行
  • 定义 initSdk 方法用来执行初始化
  • 需要注意的是,构造函数中为 hCNetSDK 属性初始化值,仅仅只是为了将 sdk 所需的
    依赖库文件
    加载到运行时中,并没有真正的做初始化SDK的工作
  • 需要重点关注OSUtils中的代码,加载依赖库文件的前提是找到对应的库文件,

    的操作是在
    getLoadLibrary
    方法中管理的,这里编写的代码需要和部署时候选择的部署方式对应,否则可能会出现在windows中开发正常,部署到linux 中就报异常的问题

SdkInitService:


package com.ramble.hikvisionsdkintegration.service;
import com.ramble.hikvisionsdkintegration.sdklib.HCNetSDK;
import com.ramble.hikvisionsdkintegration.task.InitSdkTask;
import com.ramble.hikvisionsdkintegration.util.OSUtils;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Component
public class SdkInitService {
    public static HCNetSDK hCNetSDK = null;
    static FExceptionCallBack_Imp fExceptionCallBack;
    static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
        public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
            System.out.println("异常事件类型:" + dwType);
            return;
        }
    }
    public SdkInitService() {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                try {
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(OSUtils.getLoadLibrary(), HCNetSDK.class);
                } catch (Exception ex) {
                    log.error("SdkInitService-init-hCNetSDK-error");
                }
            }
        }
    }
    @Autowired
    private ThreadPoolExecutor executor;
    public void initSdk() {
        log.info("HKSDKInitService-init-coming");
        executor.execute(new InitSdkTask());
    }
}

OSUtils:


package com.ramble.hikvisionsdkintegration.util;
import com.sun.jna.Platform;
import lombok.extern.slf4j.Slf4j;
import java.io.File;

@Slf4j
public class OSUtils {
    // 获取操作平台信息
    public static String getOsPrefix() {
        String arch = System.getProperty("os.arch").toLowerCase();
        final String name = System.getProperty("os.name");
        String osPrefix;
        if (Platform.isWindows()) {
            if ("i386".equals(arch)) {
                arch = "x86";
            }
            osPrefix = "win32-" + arch;
        } else if (Platform.isLinux()) {
            if ("x86".equals(arch)) {
                arch = "i386";
            } else if ("x86_64".equals(arch)) {
                arch = "amd64";
            }
            osPrefix = "linux-" + arch;
        } else {
            osPrefix = name.toLowerCase();
            if ("x86".equals(arch)) {
                arch = "i386";
            }
            if ("x86_64".equals(arch)) {
                arch = "amd64";
            }
            int space = osPrefix.indexOf(" ");
            if (space != -1) {
                osPrefix = osPrefix.substring(0, space);
            }
            osPrefix += "-" + arch;
        }
        return osPrefix;
    }
    public static String getOsName() {
        String osName = "";
        String osPrefix = getOsPrefix();
        if (osPrefix.toLowerCase().startsWith("win32-x86")
                || osPrefix.toLowerCase().startsWith("win32-amd64")) {
            osName = "win";
        } else if (osPrefix.toLowerCase().startsWith("linux-i386")
                || osPrefix.toLowerCase().startsWith("linux-amd64")) {
            osName = "linux";
        }
        return osName;
    }
    /**
     * 获取库文件
     * 区分win、linux
     *
     * @return
     */
    public static String getLoadLibrary() {
        if (isChecking()) {
            return null;
        }
        String userDir = System.getProperty("user.dir");
        log.info("getLoadLibrary-userDir={}", userDir);
        String loadLibrary = "";
        String library = "";
        String osPrefix = getOsPrefix();
        if (osPrefix.toLowerCase().startsWith("win32-x86")) {
            loadLibrary = System.getProperty("user.dir") + File.separator + "sdk" + File.separator + "hklibwin32" + File.separator;
            library = "HCNetSDK.dll";
        } else if (osPrefix.toLowerCase().startsWith("win32-amd64")) {
            loadLibrary = System.getProperty("user.dir") + File.separator + "sdk" + File.separator + "hklibwin64" + File.separator;
            library = "HCNetSDK.dll";
        } else if (osPrefix.toLowerCase().startsWith("linux-i386")) {
            //同 linux-amd64
            loadLibrary = "";
            library = "libhcnetsdk.so";
        } else if (osPrefix.toLowerCase().startsWith("linux-amd64")) {
            //方式一:使用系统默认的加载库路径,在系统的/usr/lib文件中加入你Java工程所需要使用的so文件,然后将HCNetSDKCom文件夹下的组件库也复制到/usr/lib目录,HCNetSDKCom文件夹中的组件库不要随意更换路径。CentOS 64位需拷贝到/usr/lib64下。
            //针对方式一,前缀就是绝对路径
            //loadLibrary = "/usr/lib64/lib/hkliblinux64/";
            //方式二:配置LD_LIBRARY_PATH环境变量加载库文件;配置/etc/ld.so.conf,加上你自己的Java工程所需要的so文件的路径
            //针对方式二,无需添加前缀,程序会从linux系统的so共享库中查找libhcnetsdk.so
            loadLibrary = "";
            library = "libhcnetsdk.so";
        }
        log.info("================= Load library Path :{} ==================", loadLibrary + library);
        return loadLibrary + library;
    }
    private static boolean checking = false;
    public static void setChecking() {
        checking = true;
    }
    public static void clearChecking() {
        checking = false;
    }
    public static boolean isChecking() {
        return checking;
    }
}


新建InitSdkTask

  • 此类实现 Runnable 接口,并重写run方法。
  • 新建一个私有属性 hCNetSDK 并赋值为 SdkInitService.hCNetSDK ,因为初始化需要用到 HCNetSDK 这个对象和设备交互,所以初始化前必须确保此对象已经创建,本例中,程序在执行 SdkInitService 构造函数的时候初始化了 HCNetSDK 对象,并放到一个全局静态变量中
  • 其实也可以不新建私有属性 hCNetSDK ,在需要用到此对象的地方 使用 SdkInitService.hCNetSDK 的方式获取也可以
  • 通过调用  hCNetSDK.NET_DVR_Init 方法执行初始化,并可以通过返回值确定是否初始化成功,初始化成功后,将可以调用业务接口向设备发送指令。
  • NET_DVR_SetConnectTime,NET_DVR_SetReconnect 是可选的,并不会对初始化SDK本身造成影响。
  • 为了避免对主程序造成影响,初始化代码将需要做 try - catch 处理

InitSdkTask:


package com.ramble.hikvisionsdkintegration.task;
import com.ramble.hikvisionsdkintegration.sdklib.HCNetSDK;
import com.ramble.hikvisionsdkintegration.service.SdkInitService;
import com.ramble.hikvisionsdkintegration.util.OSUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;

@Slf4j
public class InitSdkTask implements Runnable {
    /**
     * 装配 sdk 所需依赖
     */
    private static HCNetSDK hCNetSDK = SdkInitService.hCNetSDK;
    @Override
    public void run() {
        try {
            if (Objects.equals(OSUtils.getOsName(), "linux")) {
                log.info("InitSdk-is-linux");
                String userDir = System.getProperty("user.dir");
                log.info("InitSdk-userDir={}", userDir);
                String osPrefix = OSUtils.getOsPrefix();
                if (osPrefix.toLowerCase().startsWith("linux-i386")) {
                    HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
                    HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
                    //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
                    //linux 下, 库加载参考:OSUtils.getLoadLibrary()
                    String strPath1 = System.getProperty("user.dir") + "/hkliblinux32/libcrypto.so.1.1";
                    String strPath2 = System.getProperty("user.dir") + "/hkliblinux32/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());
                    //linux 下, 库加载参考:OSUtils.getLoadLibrary()
                    String strPathCom = System.getProperty("user.dir") + "/hkliblinux32/HCNetSDKCom/";
                    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());
                } else if (osPrefix.toLowerCase().startsWith("linux-amd64")) {
                    HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
                    HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
                    //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
                    //linux 下, 库加载参考:OSUtils.getLoadLibrary()
                    String strPath1 = System.getProperty("user.dir") + "/hkliblinux64/libcrypto.so.1.1";
                    String strPath2 = System.getProperty("user.dir") + "/hkliblinux64/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") + "/hkliblinux64/HCNetSDKCom/";
                    //linux 下, 库加载参考:OSUtils.getLoadLibrary()
                    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());
                } else {
                    log.info("osPrefix={}", osPrefix);
                }
            }
            //初始化sdk
            boolean isOk = hCNetSDK.NET_DVR_Init();
            hCNetSDK.NET_DVR_SetConnectTime(10, 1);
            hCNetSDK.NET_DVR_SetReconnect(100, true);
            if (!isOk) {
                log.error("=================== InitSDK init fail ===================");
            } else {
                log.info("============== InitSDK init success ====================");
            }
        } catch (Exception e) {
            log.error("InitSDK-error,e={}", e.getMessage());
            e.printStackTrace();
        }
    }
}


新建 HCNetSDK

直接从官方示例代码中copy过来即可

调用业务接口

  • 新建一个controller ,尝试调用 获取SDK状态 的接口。
  • 调用所有的业务接口之前都需要先登录

package com.ramble.hikvisionsdkintegration.controller;
import com.alibaba.fastjson2.JSON;
import com.ramble.hikvisionsdkintegration.dto.GlobalResponseEntity;
import com.ramble.hikvisionsdkintegration.sdklib.HCNetSDK;
import com.ramble.hikvisionsdkintegration.service.SdkInitService;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/test")
public class TestController {
    private static String m_sDeviceIP = "192.168.1.142";
    private static String m_sUsername = "xxx";
    private static String m_sPassword = "xxx";
    
    /**
     * 获取sdk状态
     *
     * @return {@link GlobalResponseEntity}<{@link String}>
     * 返回值举例:{"success":true,"code":"000000","message":"request successfully",
     * "data":"{\"dwRes\":[0,0,0,0,0,0,0,0,0,0],\"dwTotalAlarmChanNum\":0,\"dwTotalBroadCastNum\":0,\"dwTotalFileSearchNum\":0,\"dwTotalFormatNum\":0,
     * \"dwTotalLogSearchNum\":0,\"dwTotalLoginNum\":1,\"dwTotalPlayBackNum\":0,\"dwTotalRealPlayNum\":0,\"dwTotalSerialNum\":0,\"dwTotalUpgradeNum\":0,
     * \"dwTotalVoiceComNum\":0,\"autoRead\":true,\"autoWrite\":true,\"pointer\":{\"size\":84,\"valid\":true}}"}
     */
     
    @GetMapping("/state")
    public GlobalResponseEntity<String> getSdkState() {
        //登录
        Integer userId = login();
        log.info("userId={}", userId);
        HCNetSDK.NET_DVR_SDKSTATE sdkState = new HCNetSDK.NET_DVR_SDKSTATE();
        //获取当前SDK状态信息
        boolean result = SdkInitService.hCNetSDK.NET_DVR_GetSDKState(sdkState);
        if (result) {
            sdkState.read();
            String s = JSON.toJSONString(sdkState);
            return GlobalResponseEntity.success(s);
        } else {
            int error = SdkInitService.hCNetSDK.NET_DVR_GetLastError();
            return GlobalResponseEntity.error("获取失败,错误码为:" + error);
        }
    }
    
    
    private Integer login() {
        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
        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());
        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());
        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());
        m_strLoginInfo.wPort = Short.valueOf("8000");
        m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
        m_strLoginInfo.write();
        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息
        int loginHandler = SdkInitService.hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
        if (loginHandler == -1) {
            int errorCode = SdkInitService.hCNetSDK.NET_DVR_GetLastError();
            IntByReference errorInt = new IntByReference(errorCode);
            log.error("[HK] login fail errorCode:{}, errMsg:{}", errorCode, SdkInitService.hCNetSDK.NET_DVR_GetErrorMsg(errorInt));
            return null;
        } else {
            return loginHandler;
        }
    }
}


部署

拷贝so库文件到部署目录

所有厂家的所有版本sdk库文件均维护在项目源代码中,需要将linux库文件so文件拷贝到部署根目录,和jar文件同级

追加环境变量

通过配置
LD_LIBRARY_PATH
环境变量加载库文件,打开系统的
/etc/profile
配置文件,在最后追加so库文件所在目录:


export  LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/app/jars/hkliblinux64:/home/app/jars/hkliblinux64/HCNetSDKCom

如上所示:
● 32位就追加  hkliblinux32 目录,64位就追加 hkliblinux64 目录
● 不要忘记 HCNetSDKCom 目录也需要配置,因为里面也有so库文件。
执行source 命令,让配置生效:


source   /etc/profile

追加so库加载路径

打开
/etc/ld.so.conf
配置文件,追加so库文件所在目录


/home/app/jars/hkliblinux64
/home/app/jars/hkliblinux64/HCNetSDKCom

如上所示:
● 32位就追加  hkliblinux32 目录,64位就追加 hkliblinux64 目录。
● 不要忘记 HCNetSDKCom 目录也需要配置,因为里面也有so库文件。

执行 ldconfig 命令,让配置生效:


ldconfig

验证SDK初始化是否成功

一般来说,可以在程序初始化SDK的时候添加日志,通过日志输出判断是否初始化成功。

代码

https://gitee.com/naylor_personal/ramble-spring-boot/tree/master/hikvision-sdk-integration