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
|
|||
|
|
}
|