Files
xk-hy-transit-go/internal/hyfile/local.go
2026-05-22 08:06:07 +08:00

150 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package hyfile
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
var invalidFilenameChars = regexp.MustCompile(`[\\/:*?"<>|]`)
// PDFNameInput 监管处方 PDF 文件名组成字段。
type PDFNameInput struct {
PatientName string
StoreName string
PrescriptionNo string
At time.Time
}
// BuildPDFFilename 生成 {患者}{诊所} {处方号} {时间}.pdf
func BuildPDFFilename(in PDFNameInput) string {
patient := sanitizeFilename(strings.TrimSpace(in.PatientName))
if patient == "" || patient == "未知患者" {
patient = "未知患者"
}
store := sanitizeFilename(strings.TrimSpace(in.StoreName))
prefix := patient
if store != "" && store != "未知患者" {
prefix = patient + store
}
if prefix == "" {
prefix = "未知患者"
}
no := sanitizeFilename(strings.TrimSpace(in.PrescriptionNo))
if no == "" {
no = "未知处方号"
}
at := in.At
if at.IsZero() {
at = time.Now()
}
stamp := at.Format("2006-01-02 15-04-05")
base := fmt.Sprintf("%s %s %s.pdf", prefix, no, stamp)
base = sanitizeFilename(strings.TrimSuffix(base, ".pdf")) + ".pdf"
return base
}
// SavePDFLocal 将 PDF 保存到 {程序目录}/pdf/{YYYY-MM-DD}/{患者}{诊所} {处方号} {时间}.pdf
func SavePDFLocal(pdf []byte, in PDFNameInput) (string, error) {
if len(pdf) == 0 {
return "", fmt.Errorf("empty pdf bytes")
}
root := resolveProgramDir()
at := in.At
if at.IsZero() {
at = time.Now()
}
dateDir := at.Format("2006-01-02")
baseName := BuildPDFFilename(in)
dir := filepath.Join(root, "pdf", dateDir)
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("mkdir %s: %w", dir, err)
}
fullPath := filepath.Join(dir, baseName)
fullPath, err := uniquePath(fullPath)
if err != nil {
return "", err
}
if err := os.WriteFile(fullPath, pdf, 0o644); err != nil {
return "", fmt.Errorf("write pdf: %w", err)
}
return fullPath, nil
}
// ProgramDir 返回 PDF 落盘根目录(与 SavePDFLocal 一致)。
func ProgramDir() string {
return resolveProgramDir()
}
// resolveProgramDir 返回可执行文件所在目录go run 时使用当前工作目录。
func resolveProgramDir() string {
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
if strings.Contains(dir, "go-build") {
if wd, err := os.Getwd(); err == nil && wd != "" {
return wd
}
}
return dir
}
if wd, err := os.Getwd(); err == nil && wd != "" {
return wd
}
return "."
}
func sanitizeFilename(name string) string {
s := invalidFilenameChars.ReplaceAllString(name, "_")
s = strings.TrimSpace(s)
if s == "" {
return ""
}
return s
}
// ParseTimeString 解析处方开具时间等字符串(供 pdf-test 与 sync 共用)。
func ParseTimeString(s string) time.Time {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}
}
layouts := []string{
"2006-01-02 15:04:05",
"2006-01-02 15:04",
time.RFC3339,
"2006-01-02",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, s, time.Local); err == nil {
return t
}
}
return time.Time{}
}
func uniquePath(path string) (string, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return path, nil
} else if err != nil {
return "", err
}
ext := filepath.Ext(path)
base := strings.TrimSuffix(path, ext)
for i := 2; i < 1000; i++ {
candidate := fmt.Sprintf("%s_%d%s", base, i, ext)
if _, err := os.Stat(candidate); os.IsNotExist(err) {
return candidate, nil
} else if err != nil {
return "", err
}
}
return "", fmt.Errorf("too many duplicate filenames for %s", path)
}