394 lines
9.7 KiB
Go
394 lines
9.7 KiB
Go
package tool
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"planA/initialization/golabl"
|
||
_type "planA/type"
|
||
"reflect"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// CheckContext 检查上下文是否取消
|
||
func CheckContext(ctx context.Context) error {
|
||
select {
|
||
case <-ctx.Done():
|
||
return ctx.Err() // 返回取消原因
|
||
default:
|
||
return nil // 上下文仍然有效
|
||
}
|
||
}
|
||
|
||
// StructToMap 使用反射将结构体转换为map[string]interfaces{}
|
||
// @param obj 需要转换的数据
|
||
// @return map[string]interface{} 转换后的数据
|
||
// @return error 错误信息
|
||
func StructToMap(obj interface{}) (map[string]interface{}, error) {
|
||
result := make(map[string]interface{})
|
||
|
||
val := reflect.ValueOf(obj)
|
||
typ := reflect.TypeOf(obj)
|
||
|
||
// 如果是指针,获取指向的值
|
||
if val.Kind() == reflect.Ptr {
|
||
val = val.Elem()
|
||
typ = typ.Elem()
|
||
}
|
||
|
||
// 确保是结构体
|
||
if val.Kind() != reflect.Struct {
|
||
return nil, fmt.Errorf("参数必须是结构体")
|
||
}
|
||
|
||
// 遍历结构体字段
|
||
for i := 0; i < val.NumField(); i++ {
|
||
field := typ.Field(i)
|
||
fieldValue := val.Field(i)
|
||
|
||
// 跳过不可导出的字段
|
||
if !field.IsExported() {
|
||
continue
|
||
}
|
||
|
||
// 获取json标签作为字段名
|
||
jsonTag := field.Tag.Get("json")
|
||
fieldName := field.Name
|
||
|
||
if jsonTag != "" {
|
||
// 处理 json tag,忽略 omitempty 等选项
|
||
if idx := strings.Index(jsonTag, ","); idx != -1 {
|
||
fieldName = jsonTag[:idx]
|
||
} else {
|
||
fieldName = jsonTag
|
||
}
|
||
|
||
// 跳过标记为 "-" 的字段
|
||
if fieldName == "-" {
|
||
continue
|
||
}
|
||
}
|
||
|
||
// 检查字段类型
|
||
switch fieldValue.Kind() {
|
||
case reflect.Struct:
|
||
// 处理结构体字段(嵌套结构体)
|
||
nestedValue := fieldValue.Interface()
|
||
|
||
// 检查是否是 time.Time 类型
|
||
if _, ok := nestedValue.(time.Time); ok {
|
||
// time.Time 类型转换为时间戳
|
||
result[fieldName] = nestedValue.(time.Time).Unix()
|
||
continue
|
||
}
|
||
|
||
// 检查是否是自定义类型
|
||
switch v := nestedValue.(type) {
|
||
case _type.TaskStatus:
|
||
// TaskStatus 转换为 int64
|
||
result[fieldName] = int64(v)
|
||
case _type.ShopMsg:
|
||
// ShopMsg 转换为 JSON 字符串
|
||
shopMsgJSON, err := json.Marshal(v)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化 ShopMsg 失败: %v", err)
|
||
}
|
||
result[fieldName] = string(shopMsgJSON)
|
||
case _type.PriceMod:
|
||
// PriceMod 转换为 JSON 字符串
|
||
priceModJSON, err := json.Marshal(v)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化 PriceMod 失败: %v", err)
|
||
}
|
||
result[fieldName] = string(priceModJSON)
|
||
default:
|
||
// 其他结构体转换为 JSON 字符串
|
||
nestedJSON, err := json.Marshal(nestedValue)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化字段 %s 失败: %v", fieldName, err)
|
||
}
|
||
result[fieldName] = string(nestedJSON)
|
||
}
|
||
|
||
default:
|
||
// 处理基础类型字段
|
||
if !fieldValue.CanInterface() {
|
||
continue
|
||
}
|
||
|
||
// 处理指针类型
|
||
if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() {
|
||
elemValue := fieldValue.Elem()
|
||
if elemValue.Kind() == reflect.Struct {
|
||
// 指针指向结构体,转换为 JSON 字符串
|
||
nestedJSON, err := json.Marshal(elemValue.Interface())
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化指针字段 %s 失败: %v", fieldName, err)
|
||
}
|
||
result[fieldName] = string(nestedJSON)
|
||
} else {
|
||
// 指针指向基础类型
|
||
result[fieldName] = elemValue.Interface()
|
||
}
|
||
} else {
|
||
// 直接存储值,但处理自定义类型
|
||
switch v := fieldValue.Interface().(type) {
|
||
case _type.TaskStatus:
|
||
// TaskStatus 转换为 int64
|
||
result[fieldName] = int64(v)
|
||
default:
|
||
result[fieldName] = fieldValue.Interface()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// SetPage 分页处理
|
||
func SetPage(pageStr string, sizeStr string) (int, int) {
|
||
// 处理页码,默认为1
|
||
page := 1
|
||
if pageStr != "" {
|
||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||
page = p
|
||
}
|
||
}
|
||
|
||
// 处理每页条数,默认为10
|
||
size := 10
|
||
if sizeStr != "" {
|
||
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 {
|
||
// 可以限制最大条数
|
||
if s > 100 {
|
||
size = 100
|
||
} else {
|
||
size = s
|
||
}
|
||
}
|
||
}
|
||
return page, size
|
||
}
|
||
|
||
// Success 成功响应
|
||
// @param httpMsg http.ResponseWriter
|
||
// @param data 返回的数据
|
||
func Success(httpMsg http.ResponseWriter, data any) {
|
||
ret := map[string]interface{}{
|
||
"code": "200",
|
||
"msg": "成功",
|
||
"data": data,
|
||
}
|
||
json.NewEncoder(httpMsg).Encode(ret)
|
||
}
|
||
|
||
// Error 错误响应
|
||
// @param httpMsg http.ResponseWriter
|
||
// @param msg 错误信息
|
||
// @param code 错误码
|
||
func Error(httpMsg http.ResponseWriter, msg string, code int) {
|
||
fmt.Println("错误:" + msg)
|
||
codeStr := strconv.FormatInt(int64(code), 10)
|
||
ret := map[string]interface{}{
|
||
"code": codeStr,
|
||
"msg": msg,
|
||
}
|
||
json.NewEncoder(httpMsg).Encode(ret)
|
||
}
|
||
|
||
// HttpBannedWordSubstitution 违禁词处理
|
||
func HttpBannedWordSubstitution(url string, reqData map[string]string) (_type.HttpBannedWordSubstitutionBookInfoRes, error) {
|
||
var resDta _type.HttpBannedWordSubstitutionBookInfoRes
|
||
|
||
// 构建带参数的 URL
|
||
reqUrl, err := BuildURLWithParams(url, reqData)
|
||
if err != nil {
|
||
return resDta, fmt.Errorf("构建URL失败: %v", err)
|
||
}
|
||
|
||
// 发送 GET请求
|
||
_, resStr, httpGetRequestErr := HttpGetRequest(reqUrl)
|
||
|
||
if httpGetRequestErr != nil {
|
||
return resDta, httpGetRequestErr
|
||
}
|
||
|
||
// 将字符串转换为结构体
|
||
jsonErr := json.Unmarshal([]byte(resStr), &resDta)
|
||
if jsonErr != nil {
|
||
return resDta, jsonErr
|
||
}
|
||
|
||
if resDta.Code != "200" {
|
||
return resDta, fmt.Errorf("请求违禁词接口错误 错误: url %s %s", reqUrl, resStr)
|
||
}
|
||
// 返回结果
|
||
return resDta, nil
|
||
}
|
||
|
||
// BuildURLWithParams 将map参数拼接到URL后面
|
||
func BuildURLWithParams(baseURL string, params map[string]string) (string, error) {
|
||
if len(params) == 0 {
|
||
return baseURL, nil
|
||
}
|
||
|
||
// 解析基础URL
|
||
parsedURL, err := url.Parse(baseURL)
|
||
if err != nil {
|
||
return "", fmt.Errorf("解析URL失败: %v", err)
|
||
}
|
||
|
||
// 获取现有的查询参数
|
||
query := parsedURL.Query()
|
||
|
||
// 添加新的参数
|
||
for key, value := range params {
|
||
query.Set(key, value)
|
||
}
|
||
|
||
// 重新编码查询参数
|
||
parsedURL.RawQuery = query.Encode()
|
||
|
||
return parsedURL.String(), nil
|
||
}
|
||
|
||
// FilterStrings 过滤掉数组中的空字符串
|
||
func FilterStrings(s []string) []string {
|
||
result := make([]string, 0, len(s))
|
||
|
||
// 固定写死要过滤的字符串
|
||
filterMap := map[string]bool{
|
||
"": true,
|
||
"图片格式错误:图片格式非jpeg,请自查图片格式": true,
|
||
}
|
||
|
||
for _, str := range s {
|
||
if !filterMap[str] {
|
||
result = append(result, str)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// JsonResponse JSON响应工具函数
|
||
// 统一处理API响应的JSON格式化和发送
|
||
// 参数:
|
||
// - w: HTTP响应写入器
|
||
// - statusCode: HTTP状态码
|
||
// - resp: API响应数据
|
||
func JsonResponse(w http.ResponseWriter, statusCode int, resp _type.APIResponse) {
|
||
// 设置响应头为JSON格式
|
||
w.Header().Set("Content-Type", "application/json")
|
||
// 设置HTTP状态码
|
||
w.WriteHeader(statusCode)
|
||
|
||
// 编码JSON并写入响应体
|
||
err := json.NewEncoder(w).Encode(resp)
|
||
if err != nil {
|
||
// 编码失败时打印错误到控制台(不中断请求)
|
||
fmt.Printf("JSON编码错误: %v\n", err)
|
||
return
|
||
}
|
||
}
|
||
|
||
// StringToArray 将字符串根据/转为数组
|
||
func StringToArray(str string) []string {
|
||
|
||
// 1. 分割字符串
|
||
parts := strings.Split(str, "/")
|
||
|
||
// 2. 创建结果切片
|
||
result := make([]string, 0, len(parts))
|
||
|
||
// 3. 遍历转换
|
||
for _, part := range parts {
|
||
result = append(result, part)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// GetTaskTypeName 获取任务类型名称
|
||
// @param taskType 任务类型
|
||
// @return string 任务类型名称
|
||
func GetTaskTypeName(taskType string) string {
|
||
switch taskType {
|
||
case "1":
|
||
return "新发布商品任务"
|
||
case "2":
|
||
return "新发布商品任务"
|
||
case "3":
|
||
return "新拉取商品任务"
|
||
}
|
||
return "未知任务类型"
|
||
}
|
||
|
||
// CleanOldFolders 删除指定目录中超过保留天数的日期文件夹
|
||
// dirPath: 要清理的目录路径
|
||
// keepDays: 保留的天数(保留最近 keepDays 天的文件夹)
|
||
func CleanOldFolders(dirPath string, keepDays int) error {
|
||
// 读取目录内容
|
||
entries, err := os.ReadDir(dirPath)
|
||
if err != nil {
|
||
return fmt.Errorf("读取目录失败: %v", err)
|
||
}
|
||
|
||
// 计算截止日期(保留最近 keepDays 天,即删除 keepDays 天之前的)
|
||
cutoffDate := time.Now().AddDate(0, 0, -keepDays)
|
||
|
||
deletedCount := 0
|
||
for _, entry := range entries {
|
||
// 只处理目录
|
||
if !entry.IsDir() {
|
||
continue
|
||
}
|
||
|
||
folderName := entry.Name()
|
||
|
||
// 尝试解析文件夹名为日期格式 (2006-01-02)
|
||
folderDate, err := time.Parse("2006-01-02", folderName)
|
||
if err != nil {
|
||
// 不是日期格式的文件夹,跳过
|
||
fmt.Printf("跳过非日期格式文件夹: %s\n", folderName)
|
||
continue
|
||
}
|
||
|
||
// 如果文件夹日期早于截止日期,则删除
|
||
if folderDate.Before(cutoffDate) {
|
||
folderPath := filepath.Join(dirPath, folderName)
|
||
fmt.Printf("删除过期文件夹: %s\n", folderPath)
|
||
|
||
err := os.RemoveAll(folderPath)
|
||
if err != nil {
|
||
fmt.Printf("删除失败 %s: %v\n", folderPath, err)
|
||
} else {
|
||
deletedCount++
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Printf("共删除 %d 个过期文件夹\n", deletedCount)
|
||
return nil
|
||
}
|
||
|
||
// GetSubscriptionExpirationTime 根据用户id获取订阅到期时间
|
||
func GetSubscriptionExpirationTime(shopId string) (int64, error) {
|
||
url := golabl.Config.FileUrl.GetSubscriptionExpirationDateUrl + "?userId=" + shopId
|
||
_, dataStr, httpGetRequestErr := HttpGetRequest(url)
|
||
if httpGetRequestErr != nil {
|
||
return 0, httpGetRequestErr
|
||
}
|
||
var getSubscriptionExpirationDateUrl _type.GetSubscriptionExpirationDateUrl
|
||
jsonErr := json.Unmarshal([]byte(dataStr), &getSubscriptionExpirationDateUrl)
|
||
if jsonErr != nil {
|
||
return 0, jsonErr
|
||
}
|
||
return getSubscriptionExpirationDateUrl.Data.ExpirationDate, nil
|
||
}
|