幽冥大陆(三十五)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. 增加并发安全控制(如全局缓冲区的互斥锁)
相关推荐
寻星探路6 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
不老刘6 小时前
LiveKit 本地部署全流程指南(含 HTTPS/WSS)
golang·实时音视频·livekit
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
曹牧8 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate