
代码
package cyberphp_dynamic
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unsafe"
)
/*
#cgo LDFLAGS: -L./dll -lproRFLV102024 -lproRFLP50202501
#include <stdlib.h>
#include <stdint.h>
// 声明DLL函数原型
// proRFLV102024.dll
extern int GetDLLVersion(char* sDllVer);
extern void CloseUSB();
extern int Buzzer(uint8_t fUSB, int t);
extern int ReadCard(uint8_t fUSB, uint8_t* Buffer);
extern int ReadCard_v10(uint8_t fUSB, uint8_t* Buffer);
extern int ReadCardID_T5557(uint8_t fUSB, uint8_t* Buffer);
extern int GuestCard(uint8_t fUSB, int dlsCoID, uint8_t CardNo, uint8_t dai, uint8_t llock, uint8_t pdoors, char* BDate, char* EDate, char* RoomNo, uint8_t* CardHexStr);
extern int GuestCard_原始(int d12, int dlsCoID, int CardNo, int dai, int LLock, int pdoors, const char* BDate, const char* EDate, const char* RoomNo, char* cardHexStr);
extern int LimitCard(uint8_t fUSB, int dlsCoID, uint8_t CardNo, uint8_t dai, const char* BDate, const char* LCardNo, const char* CardHexStr);
extern int CardErase(uint8_t fUSB, int dlsCoID, uint8_t* cardHexStr);
extern int CardErase_V10(int d12, int dlsCoID, char* CardNo);
extern int hex_a(const char* hex, char* asc, int hLen);
extern int a_hex(const char* asc, char* hex, int aLen);
extern int GetCardTypeByCardDataStr(const char* cardHexStr, char* CardType);
extern int GetGuestLockNoByCardDataStr(int dlsCoID, const char* cardHexStr, char* LockNo);
extern int GetGuestETimeByCardDataStr(int dlsCoID, uint8_t* cardHexStr, uint8_t* eTime);
extern int ReadRecord(uint8_t fUSB, char* bufData);
extern int GetOpenRecordByDataStr(const char* DataStr, char* sOpen);
// proRFLP50202501.dll
extern int GetDLLVersion_P50(char* sDllVer);
extern int initializeUSB_P50(int d12);
extern void CloseUSB_P50(int d12);
extern int Buzzer_P50(uint8_t fUSB, int t);
extern int CardErase_P50(int d12, int dlsCoID, char* CardNo);
extern int GuestCard_P50(uint8_t fUSB, int dlsCoID, uint8_t CardNo, uint8_t dai, uint8_t llock, uint8_t pdoors, char* BDate, char* EDate, char* RoomNo, uint8_t* CardHexStr);
extern int GuestCard_原始_P50(int d12, int dlsCoID, int CardNo, int dai, int LLock, int pdoors, const char* BDate, const char* EDate, const char* RoomNo, char* cardHexStr);
extern int GetGuestLockNoByCardDataStr_P50(int dlsCoID, const char* cardHexStr, char* LockNo);
extern int initializeUSB_V10(int d12);
// 包装函数,解决CGo类型转换问题
static int wrap_initializeUSB_V10(int d12) {
return initializeUSB_V10(d12);
}
static int wrap_initializeUSB_P50(int d12) {
return initializeUSB_P50(d12);
}
static int wrap_Buzzer(uint8_t fUSB, int t) {
return Buzzer(fUSB, t);
}
static int wrap_Buzzer_P50(uint8_t fUSB, int t) {
return Buzzer_P50(fUSB, t);
}
*/
import "C"
// 全局常量和变量
const (
bufCardSize = 129 // 128 + 1
bufCardV10Size = 201 // 200 + 1
)
var (
cardData [128]byte
idPhotoSavePath string
bufCard [bufCardSize]byte // 全局读卡缓冲区
bufCardV10 [bufCardV10Size]byte // V10版本读卡缓冲区
)
// NameValueCollection 模拟C#的NameValueCollection
type NameValueCollection map[string]string
// ClCyberWinAPPProtocolPackage 模拟协议解析类
type ClCyberWinAPPProtocolPackage struct {
data map[string]string
}
// NewClCyberWinAPPProtocolPackage 创建协议解析实例
func NewClCyberWinAPPProtocolPackage() *ClCyberWinAPPProtocolPackage {
return &ClCyberWinAPPProtocolPackage{
data: make(map[string]string),
}
}
// FormatString 解析协议字符串(这里假设参数是URL编码格式,可根据实际协议调整)
func (c *ClCyberWinAPPProtocolPackage) FormatString(param string) {
// 示例:解析 "hotelsign=123&lockno=456" 格式
pairs := strings.Split(param, "&")
for _, pair := range pairs {
kv := strings.SplitN(pair, "=", 2)
if len(kv) == 2 {
c.data[kv[0]] = kv[1]
}
}
}
// Get 获取协议参数
func (c *ClCyberWinAPPProtocolPackage) Get(key string) string {
return c.data[key]
}
// Start 对应C#的start方法
func Start(obj NameValueCollection) string {
param1 := obj["param1"]
_ = param1 // 未使用,保持兼容
return "随机预安装插件"
}
// Status 对应C#的status方法
func Status(obj NameValueCollection) string {
// 调用蜂鸣器(fUSB=1, 时长50ms)
C.wrap_Buzzer(C.uint8_t(1), C.int(50))
return "当你听到设备蜂鸣器,说明设备已经连接"
}
// CheckingOut 退房(注销卡片)
func CheckingOut(obj NameValueCollection) string {
result := "注销卡片"
param := obj["param"]
// 解析协议
clApp := NewClCyberWinAPPProtocolPackage()
clApp.FormatString(param)
WriteLog("酒店智能门锁", "P50", "注销,"+param)
hotelSignStr := clApp.Get("hotelsign")
hotelSign, err := strconv.Atoi(hotelSignStr)
if err != nil {
return result + ":酒店标识格式错误"
}
// 初始化USB设备(1=proUSB)
st := C.wrap_initializeUSB_P50(C.int(1))
if st != 0 {
ShowMessageBox("设备打开失败", "错误")
return "打开端口失败"
}
defer C.CloseUSB_P50(C.int(1)) // 确保关闭设备
// 注销卡片
cardNoBuf := make([]byte, 100) // 字符串缓冲区
st = C.CardErase_P50(
C.int(1),
C.int(hotelSign),
(*C.char)(unsafe.Pointer(&cardNoBuf[0])),
)
if st != 0 {
msg := fmt.Sprintf("注销失败\n错误码: %d", st)
ShowMessageBox(msg, "提示")
return result + ":注销失败" + strconv.Itoa(int(st))
}
return result + ":成功"
}
// CheckingIn 入住(发卡)
func CheckingIn(obj NameValueCollection) string {
result := "酒店入住发卡"
param := obj["param"]
// 解析协议
clApp := NewClCyberWinAPPProtocolPackage()
clApp.FormatString(param)
WriteLog("酒店智能门锁", "P50", "入住,"+param)
lockNo := clApp.Get("lockno")
hotelSignStr := clApp.Get("hotelsign")
checkOutTime := clApp.Get("checkingouttime")
// 验证锁号长度
if len(lockNo) < 6 {
ShowMessageBox("锁号长度错误="+lockNo, "提示")
return ""
}
hotelSign, err := strconv.Atoi(hotelSignStr)
if err != nil {
return result + ":酒店标识格式错误"
}
// 初始化USB设备
st := C.wrap_initializeUSB_P50(C.int(1))
if st != 0 {
ShowMessageBox("设备打开失败", "错误")
return "打开端口失败"
}
defer C.CloseUSB_P50(C.int(1))
// 生成时间字符串
checkInTime := time.Now().Format("060102150405") // yyMMddHHmmss
// 格式化退房时间(假设输入格式为yyMMddHHmm,保持原样)
// 发卡参数
dai := 1 // DAI值
llock := 1 // 反锁标志
cardHexStr := make([]byte, 500) // 卡片数据缓冲区
// 调用发卡函数
st = C.GuestCard_原始_P50(
C.int(1),
C.int(hotelSign),
C.int(0),
C.int(dai),
C.int(llock),
C.int(0),
C.CString(checkInTime),
C.CString(checkOutTime),
C.CString(lockNo),
(*C.char)(unsafe.Pointer(&cardHexStr[0])),
)
if st != 0 {
msg := fmt.Sprintf("调用发卡函数失败\n错误码: %d", st)
ShowMessageBox(msg, "提示")
return result + "调用发卡函数失败"
}
return result + "制卡成功V2024" + lockNo
}
// GetSign 读取卡片标识
func GetSign(obj NameValueCollection) string {
// 读卡
if !RdCardV10() {
return "读卡失败"
}
// 调用获取标识函数
return CyberWinLocakAPP_GetSign(bufCardV10[:])
}
// ReadCardInfo 读取房卡信息
func ReadCardInfo(obj NameValueCollection) string {
result := "酒店入住发卡"
param := obj["param"]
// 解析协议
clApp := NewClCyberWinAPPProtocolPackage()
clApp.FormatString(param)
WriteLog("酒店智能门锁", "P50", "读卡,"+param)
hotelSignStr := clApp.Get("hotelsign")
hotelSign, err := strconv.Atoi(hotelSignStr)
if err != nil {
return buildCardInfoJSON("3", hotelSignStr, "酒店标识格式错误", "", "", "", "", "")
}
// 模拟卡数据(实际应从读卡获取)
cardDataHex := "551501C1011B4D9D1B0601036707CB2C07D30000000000000000000000000000000000325CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
lockNoBuf := make([]byte, 50)
// 调用DLL获取卡信息
st := C.GetGuestLockNoByCardDataStr_P50(
C.int(hotelSign),
C.CString(cardDataHex),
(*C.char)(unsafe.Pointer(&lockNoBuf[0])),
)
status := "4"
message := "未知"
lockNo := ""
physicalNo := ""
checkInTime := ""
checkOutTime := ""
llock := ""
switch st {
case -4:
message = "空白卡或者已经注销的卡片,返回值:" + strconv.Itoa(int(st))
status = "4"
ShowMessageBox(message, "提示")
case -3:
message = "非本酒店卡,酒店标识不匹配,返回值:" + strconv.Itoa(int(st))
status = "3"
ShowMessageBox(message, "提示")
case -2:
message = "没有有效卡片,返回值:" + strconv.Itoa(int(st))
status = "3"
ShowMessageBox(message, "提示")
case 0:
// 解析返回数据
lockNo = C.GoStringN((*C.char)(unsafe.Pointer(&lockNoBuf[0])), 6)
checkInTime = C.GoStringN((*C.char)(unsafe.Pointer(&lockNoBuf[6])), 12)
checkOutTime = C.GoStringN((*C.char)(unsafe.Pointer(&lockNoBuf[18])), 12)
physicalNo = C.GoStringN((*C.char)(unsafe.Pointer(&lockNoBuf[32])), 8)
llock = C.GoStringN((*C.char)(unsafe.Pointer(&lockNoBuf[30])), 1)
message = "读取成功"
status = "9"
case 1:
message = "连接发卡器失败,返回值:" + strconv.Itoa(int(st))
status = "1"
ShowMessageBox(message, "提示")
default:
message = "未知返回值:" + strconv.Itoa(int(st))
ShowMessageBox(message, "提示")
}
return buildCardInfoJSON(status, hotelSignStr, message, lockNo, physicalNo, checkInTime, checkOutTime, llock)
}
// WriteLog 日志写入函数
func WriteLog(captureType, logType, content string) {
// 构建日志路径
exePath, _ := os.Executable()
exeDir := filepath.Dir(exePath)
logDir := filepath.Join(exeDir, "log", captureType, time.Now().Format("2006-01-02"))
// 创建目录
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Printf("创建日志目录失败: %v\n", err)
return
}
logPath := filepath.Join(logDir, logType+"_log.log")
// 写入日志
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("打开日志文件失败: %v\n", err)
return
}
defer file.Close()
logContent := fmt.Sprintf("==============================\n%s<<<<<<<<<<<<<<<<<<<<<<<<<<\n%s\n\n",
time.Now().Format("2006-01-02 15:04:05"), content)
_, _ = file.WriteString(logContent)
}
// RdCard 读卡(旧版本)
func RdCard() bool {
// 设置等待光标(Go无全局光标控制,可忽略或使用GUI库实现)
st := C.ReadCard(C.uint8_t(1), (*C.uint8_t)(unsafe.Pointer(&bufCard[0])))
if st != 0 {
if st == 1 {
ShowMessageBox("请放一张卡在发卡器上面,\n确保 门锁软件 可以正常发卡,然后调试接口", "读卡失败(返回值=1)")
} else {
ShowMessageBox(fmt.Sprintf("读卡失败\n错误码: %d", st), "提示")
}
return false
}
// 验证卡数据
cardDataStr := string(bufCard[:])
if len(cardDataStr) < 6 {
ShowMessageBox("发卡器的感应区无卡", "提示")
return false
}
if Copy(bufCard[:], 25, 8) == "FFFFFFFF" {
ShowMessageBox("发卡器的感应区无卡", "提示")
return false
}
return true
}
// RdCardV10 V10版本读卡
func RdCardV10() bool {
st := C.ReadCard_v10(C.uint8_t(1), (*C.uint8_t)(unsafe.Pointer(&bufCardV10[0])))
if st != 0 {
ShowMessageBox(fmt.Sprintf("读卡失败\n错误码: %d", st), "提示")
return false
}
return true
}
// Copy 模拟C#的Copy函数(从字节数组截取字符串)
func Copy(data []byte, start, length int) string {
// 转换为字符串
fullStr := string(data)
// 调整索引(C#是1开始,Go是0开始)
startIdx := start - 1
if startIdx < 0 {
startIdx = 0
}
endIdx := startIdx + length
if endIdx > len(fullStr) {
endIdx = len(fullStr)
}
return fullStr[startIdx:endIdx]
}
// CyberWinLocakAPP_GetSign 获取卡片标识
func CyberWinLocakAPP_GetSign(bufCard []byte) string {
// 读卡数据转换为字符串
fullStr := string(bufCard)
// 检查是否为空白卡
if Copy(bufCard, 25, 8) == "FFFFFFFF" {
ShowMessageBox("此卡是空白卡,请换一张能开门的卡", "提示")
return "此卡是空白卡,请换一张能开门的卡"
}
// 计算酒店标识
s := Copy(bufCard, 11, 4)
i, _ := strconv.ParseInt(s, 16, 64)
i = i % 16384
s2 := Copy(bufCard, 9, 2)
i2, _ := strconv.ParseInt(s2, 16, 64)
i += i2 * 65536
// 最终计算
iFinal := i2*65536 + (i % 16383)
return strconv.FormatInt(iFinal, 10)
}
// ShowMessageBox 模拟MessageBox(Go无原生GUI,这里用控制台输出+弹窗库备选)
func ShowMessageBox(message, title string) {
// 方案1:控制台输出(适合后台服务)
fmt.Printf("[%s] %s\n", title, message)
// 方案2:使用第三方GUI库(需要额外安装)
// 示例:github.com/andlabs/ui
// ui.MsgBox(ui.NullWindow(), title, message)
}
// buildCardInfoJSON 构建卡片信息JSON字符串
func buildCardInfoJSON(status, hotelSign, message, lockNo, physicalNo, checkInTime, checkOutTime, llock string) string {
data := map[string]string{
"status": status,
"hotelsign": hotelSign,
"message": message,
"lockno": lockNo,
"physical_no": physicalNo,
"checkingintime": checkInTime,
"checkingouttime": checkOutTime,
"llock": llock,
}
jsonBytes, _ := json.Marshal(data)
return string(jsonBytes)
}
// 以下是DLL导入的辅助函数(根据实际DLL函数名调整)
// 注意:需要确保DLL文件放在正确路径,且函数名和参数匹配
func init() {
// 可选:初始化时加载DLL版本信息
var verBuf [256]byte
C.GetDLLVersion((*C.char)(unsafe.Pointer(&verBuf[0])))
fmt.Printf("DLL版本: %s\n", C.GoString((*C.char)(unsafe.Pointer(&verBuf[0]))))
}
关键说明和注意事项:
-
DLL 导入处理:
- 使用 CGo 导入 Windows DLL,需要在系统中安装对应 DLL 文件
- 需将 DLL 文件放在
./dll目录下,或调整#cgo LDFLAGS中的路径 - 函数原型必须与 DLL 导出完全一致(参数类型、顺序、调用约定)
-
类型转换:
- C# 的
byte[]对应 Go 的[]byte - C# 的
StringBuilder对应 Go 的[]byte(作为字符串缓冲区) - C# 的
int对应 Go 的int32(通过 C.int 转换) - 字符串使用
C.CString和C.GoString进行 C/Go 互转
- C# 的
-
缺失功能替代:
- WinForm 相关 :
MessageBox用控制台输出替代,如需 GUI 可使用第三方库(如github.com/andlabs/ui) - Cursor 控制:Go 无全局光标控制,直接忽略
- NameValueCollection :用
map[string]string模拟
- WinForm 相关 :
-
协议解析:
ClCyberWinAPPProtocolPackage类模拟了 C# 的协议解析逻辑,如需支持复杂协议(如 JSON、XML),需根据实际格式修改FormatString方法
-
编译说明:
- 必须在 Windows 环境下编译(因为依赖 Windows DLL)
- 编译命令:
GOOS=windows GOARCH=amd64 go build -o cyberphp_dynamic.exe - 确保安装了 MinGW-w64(提供 C 编译器)
-
日志路径:
- 日志保存路径为可执行文件所在目录下的
log文件夹,与 C# 版本保持一致
- 日志保存路径为可执行文件所在目录下的
-
未实现部分:
- 部分 DLL 函数(如
LimitCard、ReadRecord等)未完全实现,如需使用可参考已实现函数的模式扩展 hex_a、a_hex等进制转换函数未验证,需根据实际 DLL 功能测试
- 部分 DLL 函数(如
后续优化建议:
- 使用
github.com/andlabs/ui或github.com/lxn/walk实现真正的 GUI 弹窗 - 增加错误处理的完整性(如 DLL 加载失败、文件写入失败等)
- 支持更多协议格式(如 JSON 参数)
- 增加配置文件支持(如 DLL 路径、日志路径等)
- 实现资源自动释放(如 CString 分配的内存)
- 增加并发安全控制(如全局缓冲区的互斥锁)