幽冥大陆(三十五)S18酒店门锁SDK go语言——东方仙盟筑基期

代码

复制代码
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]))))
}

关键说明和注意事项:

  1. DLL 导入处理

    • 使用 CGo 导入 Windows DLL,需要在系统中安装对应 DLL 文件
    • 需将 DLL 文件放在./dll目录下,或调整#cgo LDFLAGS中的路径
    • 函数原型必须与 DLL 导出完全一致(参数类型、顺序、调用约定)
  2. 类型转换

    • C# 的byte[]对应 Go 的[]byte
    • C# 的StringBuilder对应 Go 的[]byte(作为字符串缓冲区)
    • C# 的int对应 Go 的int32(通过 C.int 转换)
    • 字符串使用C.CStringC.GoString进行 C/Go 互转
  3. 缺失功能替代

    • WinForm 相关MessageBox用控制台输出替代,如需 GUI 可使用第三方库(如github.com/andlabs/ui
    • Cursor 控制:Go 无全局光标控制,直接忽略
    • NameValueCollection :用map[string]string模拟
  4. 协议解析

    • ClCyberWinAPPProtocolPackage类模拟了 C# 的协议解析逻辑,如需支持复杂协议(如 JSON、XML),需根据实际格式修改FormatString方法
  5. 编译说明

    • 必须在 Windows 环境下编译(因为依赖 Windows DLL)
    • 编译命令:GOOS=windows GOARCH=amd64 go build -o cyberphp_dynamic.exe
    • 确保安装了 MinGW-w64(提供 C 编译器)
  6. 日志路径

    • 日志保存路径为可执行文件所在目录下的log文件夹,与 C# 版本保持一致
  7. 未实现部分

    • 部分 DLL 函数(如LimitCardReadRecord等)未完全实现,如需使用可参考已实现函数的模式扩展
    • hex_aa_hex等进制转换函数未验证,需根据实际 DLL 功能测试

后续优化建议:

  1. 使用github.com/andlabs/uigithub.com/lxn/walk实现真正的 GUI 弹窗
  2. 增加错误处理的完整性(如 DLL 加载失败、文件写入失败等)
  3. 支持更多协议格式(如 JSON 参数)
  4. 增加配置文件支持(如 DLL 路径、日志路径等)
  5. 实现资源自动释放(如 CString 分配的内存)
  6. 增加并发安全控制(如全局缓冲区的互斥锁)
相关推荐
卸任1 小时前
解密Flex布局:为什么flex:1仍会导致内容溢出
前端·css·flexbox
前端一课1 小时前
第 28 题:async / await 的原理是什么?为什么说它是 Promise 的语法糖?(详细版)
前端·面试
前端一课2 小时前
第 28 题:手写 async/await(Generator 自动执行器原理)
前端·面试
前端一课2 小时前
第 33 题:浏览器渲染流程(Reflow 重排、Repaint 重绘、Composite 合成)*
前端·面试
前端一课2 小时前
前端面试第 34 题:事件循环(Event Loop)—— 必考高频题
前端·面试
前端一课2 小时前
第 26 题:Vue2 和 Vue3 的响应式原理有什么区别?为什么 Vue3 要用 Proxy 替代 defineProperty?
前端·面试
前端一课2 小时前
第 30 题:模块化原理(CommonJS vs ESModule)
前端·面试
前端一课2 小时前
第 31 题:Tree Shaking 原理与常见失效原因(高频 + 难点 + 面试必考)
前端·面试
前端一课2 小时前
第 27 题:Promise 实现原理(含手写 Promise)
前端·面试