345 lines
9.7 KiB
Go
345 lines
9.7 KiB
Go
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[:])
|
||
}
|