package utils import ( "bytes" "crypto/md5" "encoding/hex" "fmt" "github.com/gin-gonic/gin" "io" "log" "math/rand" "mime/multipart" "net/http" "net/url" "sort" "strings" "time" "unicode/utf8" ) // GenerateEmployeeID 生成工号 (5位数字,不足补零) func GenerateEmployeeID(seq int) string { return fmt.Sprintf("%05d", seq) } // GenerateUsername 生成用户名 (init_工号) func GenerateUsername(employeeID string) string { return "dl_" + employeeID } // GeneratePoNo 生成采购单号: PO + 年月日时分秒 + 随机数 func GeneratePoNo() string { return fmt.Sprintf("PO%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateTaskNo 生成任务编号: TK + 年月日时分秒 + 随机数 func GenerateTaskNo() string { return fmt.Sprintf("TK%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateTaskDetailNo 生成批次号: TD + 年月日时分秒 + 随机数 func GenerateTaskDetailNo() string { return fmt.Sprintf("TD%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateExcelImportBatchNo 生成Excel表格入库批次号: TE(TableExcel) + 年月日时分秒 + 随机数 func GenerateExcelImportBatchNo() string { return fmt.Sprintf("TE%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateReceivingNo 生成入库单号: RC + 年月日时分秒 + 随机数 func GenerateReceivingNo() string { return fmt.Sprintf("RC%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateSalesNo 生成销售单号: SO + 年月日时分秒 + 随机数 func GenerateSalesNo() string { return fmt.Sprintf("SO%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateShippingNo 生成发货单号: SH + 年月日时分秒 + 随机数 func GenerateShippingNo() string { return fmt.Sprintf("SH%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateStockCheckNo 生成盘库调整单号: SC + 年月日时分秒 + 随机数 func GenerateStockCheckNo() string { return fmt.Sprintf("SC%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateReturnNo 生成退货单号: RT + 年月日时分秒 + 随机数 func GenerateReturnNo() string { return fmt.Sprintf("RT%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateOutNo 生成出库单号: OB + 年月日时分秒 + 随机数 func GenerateOutNo() string { return fmt.Sprintf("OB%s%04d", time.Now().Format("20060102150405"), GenerateRandomNumber(4)) } // GenerateRandomNumber 生成指定位数的随机数字 func GenerateRandomNumber(digits int) int { if digits <= 0 { return 0 } end := 1 for i := 0; i < digits; i++ { end *= 10 } return rand.Intn(end) } // ParsePublicationTime 支持的格式:"2013-12", "2013", "2013-12-25", "2013 年 12 月" 等 func ParsePublicationTime(timeStr string) int64 { if timeStr == "" { return 0 } // 清理字符串中的空格和特殊字符 timeStr = strings.TrimSpace(timeStr) timeStr = strings.ReplaceAll(timeStr, "年", "-") timeStr = strings.ReplaceAll(timeStr, "月", "") timeStr = strings.TrimSpace(timeStr) // 定义多种可能的日期格式 formats := []string{ "2006-1", // "2013-12" "2006-01", // "2013-12" (补零) "2006", // "2013" "2006-1-2", // "2013-12-25" "2006-01-02", // "2013-12-25" (补零) } var t time.Time var err error for _, format := range formats { t, err = time.ParseInLocation(format, timeStr, time.Local) if err == nil { break } } // 如果所有格式都失败,返回 0 if err != nil { fmt.Printf("出版时间解析失败:%s\n", timeStr) return 0 } return t.Unix() } // GetClientIP 获取客户端IP func GetClientIP(c *gin.Context) string { ip := c.ClientIP() if ip == "::1" { ip = "127.0.0.1" } return ip } type UserInfo struct { ID int64 Role int64 Username string AboutID int64 Fid int64 } func GetUserInfo(c *gin.Context) UserInfo { return UserInfo{ ID: c.GetInt64("id"), Role: c.GetInt64("role"), Username: c.GetString("username"), AboutID: c.GetInt64("about_id"), Fid: c.GetInt64("fid"), } } func Ternary[T any](condition bool, trueVal, falseVal T) T { if condition { return trueVal } return falseVal } // SignParams 对请求参数进行MD5签名,用于接口安全校验 // 参数 params: 待签名的原始参数键值对 // 返回值: 计算出的签名字符串(大写MD5) func SignParams(params map[string]string) string { // 定义签名密钥(实际使用中应从配置或环境变量获取,避免硬编码) SecretKey := "jRQdCh52Z55Kzh1hADaA2ZtdTKetj2PXk60Tz5Yc0iz9aD8Wafbk7CwAZ8cz69A9zb9caZ3k9dnR3Ys06J5nYFPrZ0xE9p6TY8DCD538ryiRjW81YTPmk41tCEnXizPh" // 创建过滤后的参数映射表,用于存放需要参与签名的参数 filteredParams := make(map[string]string) // 遍历原始参数,过滤掉空值、sign字段和sign_type字段 for k, v := range params { // 如果参数值不为空,且参数名不是sign,且参数名不是sign_type,则保留 if v != "" && k != "sign" && k != "sign_type" { filteredParams[k] = v } } // 提取过滤后参数的所有键名,用于排序 keys := make([]string, 0, len(filteredParams)) for k := range filteredParams { keys = append(keys, k) } // 对键名进行字典序排序(保证签名结果的一致性) sort.Strings(keys) // 使用strings.Builder高效拼接字符串 var builder strings.Builder // 遍历排序后的键名,按"key=value"格式拼接,并用"&"连接 for i, k := range keys { // 从第二个参数开始,前面添加"&"分隔符 if i > 0 { builder.WriteString("&") } builder.WriteString(k) // 写入参数名 builder.WriteString("=") // 写入等号 builder.WriteString(filteredParams[k]) // 写入参数值 } // 将拼接好的参数字符串末尾加上"&key=密钥",形成待签名字符串 signStr := builder.String() + "&key=" + SecretKey // 计算待签名字符串的MD5哈希值 hash := md5.Sum([]byte(signStr)) // 将哈希结果转换为十六进制字符串,并转为大写形式返回 return strings.ToUpper(hex.EncodeToString(hash[:])) } // SubmitMultiBody 发送多个 body 字段 func SubmitMultiBody(url string, taskID string, bodyList []string, sign string) (string, error) { body := &bytes.Buffer{} writer := multipart.NewWriter(body) // 发送多个独立 body(业务用) for _, b := range bodyList { _ = writer.WriteField("body", b) } _ = writer.WriteField("task_id", taskID) _ = writer.WriteField("sign", sign) writer.Close() req, _ := http.NewRequest("POST", url, body) req.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) return string(respBody), nil } // SubmitFormData 提交表单数据 // @param url 请求地址 // @param params 表单数据 // @return error 错误信息 func SubmitFormData(url string, params map[string]string) (string, error) { body := &bytes.Buffer{} writer := multipart.NewWriter(body) for key, value := range params { err := writer.WriteField(key, value) if err != nil { return "", fmt.Errorf("write field error: %v", err) } } writer.Close() req, err := http.NewRequest("POST", url, body) if err != nil { return "", fmt.Errorf("create request error: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("send request error: %v", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read response error: %v", err) } return string(respBody), nil } // SubmitFormDataWithTimeout 提交表单数据(application/x-www-form-urlencoded) func SubmitFormDataWithTimeout(requestURL string, params map[string]string, timeoutSeconds int) (string, error) { form := url.Values{} for key, value := range params { form.Set(key, value) } log.Printf("[旺店通推送] 发送form-urlencoded:\n%s", form.Encode()) req, err := http.NewRequest("POST", requestURL, strings.NewReader(form.Encode())) if err != nil { return "", fmt.Errorf("create request error: %v", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") timeout := time.Duration(timeoutSeconds) * time.Second if timeout <= 0 { timeout = 30 * time.Second } client := &http.Client{Timeout: timeout} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("send request error: %v", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read response error: %v", err) } return string(respBody), nil } // WangdianSign 旺店通接口签名算法 // 与PHP SDK保持一致,使用UTF-8字符长度(iconv_strlen)而非字节长度 func WangdianSign(params map[string]string, appSecret string) string { keys := make([]string, 0, len(params)) for k := range params { if k == "sign" { continue } keys = append(keys, k) } sort.Strings(keys) var builder strings.Builder for idx, key := range keys { value := params[key] keyLen := utf8.RuneCountInString(key) valLen := utf8.RuneCountInString(value) builder.WriteString(fmt.Sprintf("%02d", keyLen)) builder.WriteString("-") builder.WriteString(key) builder.WriteString(":") builder.WriteString(fmt.Sprintf("%04d", valLen)) builder.WriteString("-") builder.WriteString(value) if idx < len(keys)-1 { builder.WriteString(";") } } builder.WriteString(appSecret) hash := md5.Sum([]byte(builder.String())) return hex.EncodeToString(hash[:]) }