212 lines
5.7 KiB
Go
212 lines
5.7 KiB
Go
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"unsafe"
|
||
)
|
||
|
||
const outMessageSize = 2048
|
||
|
||
var (
|
||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||
procMultiByteToWide = kernel32.NewProc("MultiByteToWideChar")
|
||
)
|
||
|
||
type ReaderStatus struct {
|
||
Loaded bool `json:"loaded"`
|
||
DLLDir string `json:"dllDir,omitempty"`
|
||
Arch string `json:"arch"`
|
||
Platform string `json:"platform"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
type DLLResult struct {
|
||
Code int `json:"code"`
|
||
Success bool `json:"success"`
|
||
ReaderType string `json:"reader_type,omitempty"`
|
||
Raw string `json:"raw"`
|
||
Data any `json:"data"`
|
||
}
|
||
|
||
type cardOperations interface {
|
||
Status() ReaderStatus
|
||
ReadCard(readerType string, noPIN bool) (DLLResult, error)
|
||
HMACSM3(key, secret, timestamp, requestBody string) (DLLResult, error)
|
||
}
|
||
|
||
type DLLReader struct {
|
||
mu sync.Mutex
|
||
dll *syscall.LazyDLL
|
||
read *syscall.LazyProc
|
||
readNoPIN *syscall.LazyProc
|
||
hmac *syscall.LazyProc
|
||
dllDir string
|
||
loadErr error
|
||
}
|
||
|
||
func newDLLReader(cfg Config) *DLLReader {
|
||
r := &DLLReader{}
|
||
r.init(cfg)
|
||
return r
|
||
}
|
||
|
||
func (r *DLLReader) init(cfg Config) {
|
||
if runtime.GOOS != "windows" {
|
||
r.loadErr = errors.New("读卡 DLL 仅支持 Windows")
|
||
return
|
||
}
|
||
if runtime.GOARCH != "386" {
|
||
r.loadErr = fmt.Errorf("CardReaderDLL.dll 为 32 位,当前程序架构为 %s;请使用 build.ps1 构建 windows/386 版本", runtime.GOARCH)
|
||
return
|
||
}
|
||
|
||
r.dllDir = resolveDLLDir(cfg)
|
||
dllPath := filepath.Join(r.dllDir, "CardReaderDLL.dll")
|
||
if !fileExists(dllPath) {
|
||
r.loadErr = fmt.Errorf("未找到 CardReaderDLL.dll: %s", dllPath)
|
||
return
|
||
}
|
||
|
||
// 厂商 DLL 会按当前工作目录查找各地市的二级依赖库。
|
||
if err := os.Chdir(r.dllDir); err != nil {
|
||
r.loadErr = fmt.Errorf("切换 DLL 工作目录失败: %w", err)
|
||
return
|
||
}
|
||
r.dll = syscall.NewLazyDLL(dllPath)
|
||
if err := r.dll.Load(); err != nil {
|
||
r.loadErr = fmt.Errorf("加载 CardReaderDLL.dll 失败: %w", err)
|
||
return
|
||
}
|
||
r.read = r.dll.NewProc("ZJ_ReadCardInfo")
|
||
r.readNoPIN = r.dll.NewProc("ZJ_ReadCardInfo_NoPin")
|
||
r.hmac = r.dll.NewProc("ZJ_Hmac_SM3")
|
||
for name, proc := range map[string]*syscall.LazyProc{
|
||
"ZJ_ReadCardInfo": r.read,
|
||
"ZJ_ReadCardInfo_NoPin": r.readNoPIN,
|
||
"ZJ_Hmac_SM3": r.hmac,
|
||
} {
|
||
if err := proc.Find(); err != nil {
|
||
r.loadErr = fmt.Errorf("查找 DLL 函数 %s 失败: %w", name, err)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func resolveDLLDir(cfg Config) string {
|
||
candidates := make([]string, 0, 3)
|
||
if filepath.IsAbs(cfg.DLLDir) {
|
||
candidates = append(candidates, cfg.DLLDir)
|
||
} else {
|
||
candidates = append(candidates,
|
||
filepath.Join(cfg.AppRoot, cfg.DLLDir),
|
||
filepath.Join(cfg.AppRoot, "..", "card-read-service", "package", "DWCardReaderDLL"),
|
||
)
|
||
if cwd, err := os.Getwd(); err == nil {
|
||
candidates = append(candidates, filepath.Join(cwd, cfg.DLLDir))
|
||
}
|
||
}
|
||
for _, candidate := range candidates {
|
||
candidate, _ = filepath.Abs(candidate)
|
||
if fileExists(filepath.Join(candidate, "CardReaderDLL.dll")) {
|
||
return filepath.Clean(candidate)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
return cfg.DLLDir
|
||
}
|
||
first, _ := filepath.Abs(candidates[0])
|
||
return filepath.Clean(first)
|
||
}
|
||
|
||
func (r *DLLReader) Status() ReaderStatus {
|
||
status := ReaderStatus{Loaded: r.loadErr == nil && r.dll != nil, DLLDir: r.dllDir, Arch: runtime.GOARCH, Platform: runtime.GOOS}
|
||
if r.loadErr != nil {
|
||
status.Error = r.loadErr.Error()
|
||
}
|
||
return status
|
||
}
|
||
|
||
func (r *DLLReader) ReadCard(readerType string, noPIN bool) (DLLResult, error) {
|
||
proc := r.read
|
||
if noPIN {
|
||
proc = r.readNoPIN
|
||
}
|
||
return r.invoke(proc, readerType, readerType)
|
||
}
|
||
|
||
func (r *DLLReader) HMACSM3(key, secret, timestamp, requestBody string) (DLLResult, error) {
|
||
return r.invoke(r.hmac, "", key, secret, timestamp, requestBody)
|
||
}
|
||
|
||
func (r *DLLReader) invoke(proc *syscall.LazyProc, readerType string, args ...string) (DLLResult, error) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
if r.loadErr != nil {
|
||
return DLLResult{}, r.loadErr
|
||
}
|
||
if proc == nil {
|
||
return DLLResult{}, errors.New("读卡 DLL 尚未初始化")
|
||
}
|
||
|
||
ptrs := make([]*byte, len(args))
|
||
callArgs := make([]uintptr, 0, len(args)+1)
|
||
for i, value := range args {
|
||
ptr, err := syscall.BytePtrFromString(value)
|
||
if err != nil {
|
||
return DLLResult{}, fmt.Errorf("DLL 入参包含 NUL 字符: %w", err)
|
||
}
|
||
ptrs[i] = ptr
|
||
callArgs = append(callArgs, uintptr(unsafe.Pointer(ptrs[i])))
|
||
}
|
||
out := make([]byte, outMessageSize)
|
||
callArgs = append(callArgs, uintptr(unsafe.Pointer(&out[0])))
|
||
|
||
r1, _, _ := proc.Call(callArgs...)
|
||
runtime.KeepAlive(ptrs)
|
||
runtime.KeepAlive(out)
|
||
code := int(int32(r1))
|
||
raw, decodeErr := decodeGBK(bytes.TrimRight(out, "\x00"))
|
||
if decodeErr != nil {
|
||
raw = strings.ToValidUTF8(string(bytes.TrimRight(out, "\x00")), "<22>")
|
||
}
|
||
raw = strings.TrimSpace(raw)
|
||
var data any
|
||
if raw != "" {
|
||
if jsonErr := json.Unmarshal([]byte(raw), &data); jsonErr != nil {
|
||
data = raw
|
||
}
|
||
}
|
||
return DLLResult{Code: code, Success: code == 0, ReaderType: readerType, Raw: raw, Data: data}, nil
|
||
}
|
||
|
||
func decodeGBK(src []byte) (string, error) {
|
||
if len(src) == 0 {
|
||
return "", nil
|
||
}
|
||
const codePageGBK = 936
|
||
size, _, callErr := procMultiByteToWide.Call(
|
||
codePageGBK, 0, uintptr(unsafe.Pointer(&src[0])), uintptr(len(src)), 0, 0,
|
||
)
|
||
if size == 0 {
|
||
return "", fmt.Errorf("GBK 长度转换失败: %v", callErr)
|
||
}
|
||
wide := make([]uint16, int(size))
|
||
written, _, callErr := procMultiByteToWide.Call(
|
||
codePageGBK, 0, uintptr(unsafe.Pointer(&src[0])), uintptr(len(src)),
|
||
uintptr(unsafe.Pointer(&wide[0])), size,
|
||
)
|
||
if written == 0 {
|
||
return "", fmt.Errorf("GBK 转 UTF-16 失败: %v", callErr)
|
||
}
|
||
return syscall.UTF16ToString(wide[:int(written)]), nil
|
||
}
|