first commit
This commit is contained in:
commit
74fee5dbd9
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
13
.idea/getErpSendPublishing.iml
generated
Normal file
13
.idea/getErpSendPublishing.iml
generated
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true">
|
||||||
|
<buildTags>
|
||||||
|
<option name="os" value="linux" />
|
||||||
|
</buildTags>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="GoDfaErrorMayBeNotNil" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<functions>
|
||||||
|
<function importPath="getErpSendPublishing/utils/dbConnectUtil" name="InitDB" />
|
||||||
|
</functions>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/getErpSendPublishing.iml" filepath="$PROJECT_DIR$/.idea/getErpSendPublishing.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
Linux/getErpSendPubishing
Normal file
BIN
Linux/getErpSendPubishing
Normal file
Binary file not shown.
BIN
Linux/getErpSendPubishing-0518
Normal file
BIN
Linux/getErpSendPubishing-0518
Normal file
Binary file not shown.
57
cache/cache.go
vendored
Normal file
57
cache/cache.go
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// cache.go
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var redisClient *redis.Client
|
||||||
|
|
||||||
|
func NewRedisClient() *redis.Client {
|
||||||
|
if redisClient == nil {
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "", // 无密码
|
||||||
|
DB: 0, // 默认数据库
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := redisClient.Ping(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
panic("Redis连接失败: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSecondCacheObject 设置二级缓存
|
||||||
|
func SetSecondCacheObject(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||||
|
client := NewRedisClient()
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Set(ctx, key, jsonData, expiration).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecondCacheObject 获取二级缓存
|
||||||
|
func GetSecondCacheObject(ctx context.Context, key string, result interface{}) error {
|
||||||
|
client := NewRedisClient()
|
||||||
|
|
||||||
|
data, err := client.Get(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal([]byte(data), result)
|
||||||
|
}
|
||||||
23
config.ini
Normal file
23
config.ini
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[Db1]
|
||||||
|
User = zhishu
|
||||||
|
Password = XsRR4K3ATizyc5BK
|
||||||
|
Host = 146.56.227.42
|
||||||
|
Port = 3306
|
||||||
|
[http]
|
||||||
|
Addr = 127.0.0.1:51368
|
||||||
|
[Task]
|
||||||
|
User = root
|
||||||
|
Password = Long6166@@
|
||||||
|
Host = nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com
|
||||||
|
Port = 26247
|
||||||
|
|
||||||
|
[Db]
|
||||||
|
User = root
|
||||||
|
Password = Long6166@@
|
||||||
|
Host = nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com
|
||||||
|
Port = 26247
|
||||||
|
|
||||||
|
[Data]
|
||||||
|
data_count=1000
|
||||||
|
pool_size=30
|
||||||
|
limit_size=10
|
||||||
646
controller/moveRepeat.go
Normal file
646
controller/moveRepeat.go
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"getErpSendPublishing/utils"
|
||||||
|
"getErpSendPublishing/utils/dbConnectUtil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoodsFormRequest 定义接收的表单数据结构
|
||||||
|
type GoodsFormRequest struct {
|
||||||
|
ShopID int64 `form:"shopid" binding:"required,min=1"` // 店铺ID,必须大于0
|
||||||
|
GoodsID int64 `form:"goodsid" binding:"required,min=1"` // 商品ID,必须大于0
|
||||||
|
ISBN string `form:"isbn" binding:"required"` // ISBN,必须提供
|
||||||
|
}
|
||||||
|
|
||||||
|
// CenterBookBatchItem 批量提交的单个商品数据结构
|
||||||
|
type CenterBookBatchItem struct {
|
||||||
|
ISBN string `json:"isbn"`
|
||||||
|
TotalPrice string `json:"totalPrice"`
|
||||||
|
ImgBigUrl string `json:"imgBigUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoodsController 控制器结构
|
||||||
|
type GoodsController struct {
|
||||||
|
// 可以在这里注入服务层依赖
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchProcessCenterBooks 批量处理中心图书数据
|
||||||
|
func BatchProcessCenterBooks(ctx *gin.Context) {
|
||||||
|
// 1. 直接获取参数
|
||||||
|
shopIDStr := ctx.PostForm("shopid")
|
||||||
|
|
||||||
|
// 2. 空值检查
|
||||||
|
if shopIDStr == "" {
|
||||||
|
log.Printf("[WARN] shopid参数为空 (路径: %s, IP: %s)",
|
||||||
|
ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换并验证shopid格式
|
||||||
|
shopID, err := strconv.ParseInt(shopIDStr, 10, 64)
|
||||||
|
if err != nil || shopID <= 0 {
|
||||||
|
log.Printf("[WARN] 无效的shopid: %s (错误: %v)", shopIDStr, err)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid必须为正整数",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 解析JSON请求体中的图书列表
|
||||||
|
var bookItems []CenterBookBatchItem
|
||||||
|
if err := ctx.BindJSON(&bookItems); err != nil {
|
||||||
|
log.Printf("[WARN] JSON解析失败: %v (路径: %s, IP: %s)",
|
||||||
|
err, ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "JSON格式错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 检查图书列表是否为空
|
||||||
|
if len(bookItems) == 0 {
|
||||||
|
log.Printf("[WARN] 图书列表为空 (shopid: %d)", shopID)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "图书列表不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 使用全局的Redis连接池(避免每次创建新连接)
|
||||||
|
redisClient := utils.RedisTwoForParseFormData
|
||||||
|
if redisClient == nil {
|
||||||
|
log.Printf("[ERROR] Redis连接未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 构建Redis key
|
||||||
|
redisKey := fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
// 8. 去重处理:遍历图书列表,移除重复的ISBN
|
||||||
|
var deduplicatedItems []CenterBookBatchItem
|
||||||
|
var skippedItems []CenterBookBatchItem
|
||||||
|
|
||||||
|
for _, item := range bookItems {
|
||||||
|
// 检查ISBN是否为空
|
||||||
|
if item.ISBN == "" {
|
||||||
|
log.Printf("[WARN] 发现空的ISBN,跳过处理 (shopid: %d)", shopID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成MD5加密的ISBN
|
||||||
|
hash := md5.Sum([]byte(item.ISBN))
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
|
||||||
|
// 检查是否存在重复数据
|
||||||
|
exists, err := redisClient.HGet(redisKey, encryptedIsbn)
|
||||||
|
if err == nil && exists != "" {
|
||||||
|
// 存在重复数据,记录并跳过
|
||||||
|
log.Printf("[INFO] 检测到重复数据: shopid=%d, isbn=%s", shopID, item.ISBN)
|
||||||
|
skippedItems = append(skippedItems, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非重复数据,添加到去重后列表
|
||||||
|
deduplicatedItems = append(deduplicatedItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 如果没有有效数据,返回错误
|
||||||
|
if len(deduplicatedItems) == 0 {
|
||||||
|
log.Printf("[INFO] 所有数据均为重复数据,无有效处理项 (shopid: %d)", shopID)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"message": "所有数据均为重复数据",
|
||||||
|
"success": false,
|
||||||
|
"data": gin.H{
|
||||||
|
"total": len(bookItems),
|
||||||
|
"skipped": len(skippedItems),
|
||||||
|
"processed": 0,
|
||||||
|
"original_items": bookItems,
|
||||||
|
"deduplicated_items": deduplicatedItems,
|
||||||
|
"skipped_items": skippedItems,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 关键成功日志
|
||||||
|
log.Printf("[INFO] 批量处理图书数据: shopid=%d, 总数=%d, 去重后=%d, 跳过=%d",
|
||||||
|
shopID, len(bookItems), len(deduplicatedItems), len(skippedItems))
|
||||||
|
|
||||||
|
// 11. 返回结果
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"success": true,
|
||||||
|
"data": gin.H{
|
||||||
|
"shopid": shopID,
|
||||||
|
"total": len(bookItems),
|
||||||
|
"deduplicated_count": len(deduplicatedItems),
|
||||||
|
"skipped_count": len(skippedItems),
|
||||||
|
"original_items": bookItems,
|
||||||
|
"deduplicated_items": deduplicatedItems,
|
||||||
|
"skipped_items": skippedItems,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateGoodsRequest 业务层面的验证
|
||||||
|
func (c *GoodsController) validateGoodsRequest(ctx context.Context, req *GoodsFormRequest) error {
|
||||||
|
// 基础验证
|
||||||
|
if req.ShopID <= 0 {
|
||||||
|
return fmt.Errorf("店铺ID必须大于0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GoodsID <= 0 {
|
||||||
|
return fmt.Errorf("商品ID必须大于0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ISBN == "" {
|
||||||
|
return fmt.Errorf("ISBN不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务限制(根据实际情况调整)
|
||||||
|
if req.ShopID > 1000000000 {
|
||||||
|
return fmt.Errorf("店铺ID超出系统限制")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GoodsID > 1000000000 {
|
||||||
|
return fmt.Errorf("商品ID超出系统限制")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISBN长度限制
|
||||||
|
if len(req.ISBN) > 20 {
|
||||||
|
return fmt.Errorf("ISBN长度超出限制")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFormData 表单解析
|
||||||
|
func ParseFormData(ctx *gin.Context) {
|
||||||
|
// 1. 直接获取参数
|
||||||
|
shopIDStr := ctx.PostForm("shopid")
|
||||||
|
isbn := ctx.PostForm("isbn")
|
||||||
|
|
||||||
|
// 2. 空值检查(移除了 goodsid)
|
||||||
|
if shopIDStr == "" || isbn == "" {
|
||||||
|
log.Printf("[WARN] 参数为空 (路径: %s, IP: %s)",
|
||||||
|
ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid和isbn不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换并验证数字格式(只保留 shopid 验证)
|
||||||
|
shopID, err := strconv.ParseInt(shopIDStr, 10, 64)
|
||||||
|
if err != nil || shopID <= 0 {
|
||||||
|
log.Printf("[WARN] 无效的shopid: %s (错误: %v)", shopIDStr, err)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid必须为正整数",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用全局的Redis连接池(避免每次创建新连接)
|
||||||
|
redisClient := utils.RedisTwoForParseFormData
|
||||||
|
if redisClient == nil {
|
||||||
|
log.Printf("[ERROR] Redis连接未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 生成MD5加密的ISBN
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
|
||||||
|
//fmt.Println("encryptedIsbn:", encryptedIsbn)
|
||||||
|
|
||||||
|
// 6. 构建Redis key
|
||||||
|
redisKey := fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
// 7. 检查是否存在重复数据
|
||||||
|
exists, err := redisClient.HGet(redisKey, encryptedIsbn)
|
||||||
|
if err == nil && exists != "" {
|
||||||
|
// 存在重复数据
|
||||||
|
log.Printf("[INFO] 检测到重复数据: shopid=%d, isbn=%s", shopID, isbn)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"message": "已有重复数据",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 关键成功日志
|
||||||
|
log.Printf("[INFO] 表单数据解析成功: shopid=%d, isbn=%s", shopID, isbn)
|
||||||
|
|
||||||
|
// 9. 返回结果
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"shopid": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
},
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParseFormData 处理包含更多参数的表单数据
|
||||||
|
func NewParseFormData(ctx *gin.Context) {
|
||||||
|
// 1. 直接获取参数
|
||||||
|
shopIDStr := ctx.PostForm("shopId")
|
||||||
|
isbn := ctx.PostForm("isbn")
|
||||||
|
shopType := ctx.PostForm("shopType")
|
||||||
|
price := ctx.PostForm("price")
|
||||||
|
condition := ctx.PostForm("condition")
|
||||||
|
|
||||||
|
// 2. 空值检查
|
||||||
|
if shopIDStr == "" || isbn == "" {
|
||||||
|
log.Printf("[WARN] 参数为空 (路径: %s, IP: %s)",
|
||||||
|
ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopId和isbn不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换并验证数字格式
|
||||||
|
shopID, err := strconv.ParseInt(shopIDStr, 10, 64)
|
||||||
|
if err != nil || shopID <= 0 {
|
||||||
|
log.Printf("[WARN] 无效的shopId: %s (错误: %v)", shopIDStr, err)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopId必须为正整数",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用全局的Redis连接池(避免每次创建新连接)
|
||||||
|
redisClient := utils.RedisTwoForParseFormData
|
||||||
|
if redisClient == nil {
|
||||||
|
log.Printf("[ERROR] Redis连接未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 构建Redis key
|
||||||
|
redisKey := fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
// 6. 根据shopType生成不同的加密key
|
||||||
|
var encryptedKey string
|
||||||
|
switch shopType {
|
||||||
|
case "1", "5":
|
||||||
|
// shopType为1或5时,使用ISBN的MD5
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedKey = fmt.Sprintf("%x", hash)
|
||||||
|
case "2":
|
||||||
|
// shopType为2时,拼接isbn:price:condition后MD5
|
||||||
|
combinedKey := fmt.Sprintf("%s:%s:%s", isbn, price, condition)
|
||||||
|
hash := md5.Sum([]byte(combinedKey))
|
||||||
|
encryptedKey = fmt.Sprintf("%x", hash)
|
||||||
|
default:
|
||||||
|
// 其他情况默认使用ISBN的MD5
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedKey = fmt.Sprintf("%x", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 检查是否存在重复数据
|
||||||
|
exists, err := redisClient.HGet(redisKey, encryptedKey)
|
||||||
|
if err == nil && exists != "" {
|
||||||
|
// 存在重复数据
|
||||||
|
log.Printf("[INFO] 检测到重复数据: shopId=%d, isbn=%s, shopType=%s", shopID, isbn, shopType)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"success": true,
|
||||||
|
"data": gin.H{
|
||||||
|
"deduplicated": false,
|
||||||
|
"isRepeat": true,
|
||||||
|
"shopId": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
"shopType": shopType,
|
||||||
|
"price": price,
|
||||||
|
"condition": condition,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 关键成功日志
|
||||||
|
log.Printf("[INFO] 数据检查完成: shopId=%d, isbn=%s, shopType=%s, price=%s, condition=%s",
|
||||||
|
shopID, isbn, shopType, price, condition)
|
||||||
|
|
||||||
|
// 9. 返回结果
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"success": true,
|
||||||
|
"data": gin.H{
|
||||||
|
"deduplicated": true,
|
||||||
|
"isRepeat": false,
|
||||||
|
"shopId": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
"shopType": shopType,
|
||||||
|
"price": price,
|
||||||
|
"condition": condition,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultipleStores 闲鱼多点店铺去重
|
||||||
|
func MultipleStores(ctx *gin.Context) {
|
||||||
|
// 1. 直接获取参数
|
||||||
|
shopIDsStr := ctx.PostForm("shopIds")
|
||||||
|
isbn := ctx.PostForm("isbn")
|
||||||
|
|
||||||
|
// 2. 空值检查
|
||||||
|
if shopIDsStr == "" || isbn == "" {
|
||||||
|
log.Printf("[WARN] 参数为空 (路径: %s, IP: %s)",
|
||||||
|
ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopIds和isbn不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 使用全局的Redis连接池(避免每次创建新连接)
|
||||||
|
redisClient := utils.RedisTwoForParseFormData
|
||||||
|
if redisClient == nil {
|
||||||
|
log.Printf("[ERROR] Redis连接未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 生成MD5加密的ISBN
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
|
||||||
|
// 5. 解析shopIds数组(支持逗号分隔,兼容JSON数组格式如 [id1,id2,id3])
|
||||||
|
// 去除可能的首尾方括号
|
||||||
|
shopIDsStr = strings.TrimSpace(shopIDsStr)
|
||||||
|
shopIDsStr = strings.TrimPrefix(shopIDsStr, "[")
|
||||||
|
shopIDsStr = strings.TrimSuffix(shopIDsStr, "]")
|
||||||
|
|
||||||
|
shopIDStrs := strings.Split(shopIDsStr, ",")
|
||||||
|
|
||||||
|
// 6. 遍历每个shopId,逐个检查是否存在重复数据
|
||||||
|
for _, idStr := range shopIDStrs {
|
||||||
|
idStr = strings.TrimSpace(idStr)
|
||||||
|
|
||||||
|
// 跳过空字符串
|
||||||
|
if idStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换并验证shopId格式
|
||||||
|
shopID, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || shopID <= 0 {
|
||||||
|
log.Printf("[WARN] 无效的shopId: %s (错误: %v)", idStr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Redis key
|
||||||
|
redisKey := fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
// 检查是否存在重复数据
|
||||||
|
exists, err := redisClient.HGet(redisKey, encryptedIsbn)
|
||||||
|
if err == nil && exists != "" {
|
||||||
|
// 存在重复数据,立即返回不继续查询
|
||||||
|
log.Printf("[INFO] 检测到重复数据: shopId=%d, isbn=%s", shopID, isbn)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"message": "已有重复数据",
|
||||||
|
"success": false,
|
||||||
|
"data": gin.H{
|
||||||
|
"shopId": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 关键成功日志
|
||||||
|
log.Printf("[INFO] 表单数据解析成功: shopIds=%s, isbn=%s", shopIDsStr, isbn)
|
||||||
|
|
||||||
|
// 8. 返回结果
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"shopIds": shopIDsStr,
|
||||||
|
"isbn": isbn,
|
||||||
|
},
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SingleShopMultipleStores(ctx *gin.Context) {
|
||||||
|
// 1. 直接获取参数
|
||||||
|
shopIDStr := ctx.PostForm("shopId")
|
||||||
|
isbn := ctx.PostForm("isbn")
|
||||||
|
|
||||||
|
// 2. 空值检查(移除了 goodsid)
|
||||||
|
if shopIDStr == "" || isbn == "" {
|
||||||
|
log.Printf("[WARN] 参数为空 (路径: %s, IP: %s)",
|
||||||
|
ctx.FullPath(), ctx.ClientIP())
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid和isbn不能为空",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换并验证数字格式(只保留 shopid 验证)
|
||||||
|
shopID, err := strconv.ParseInt(shopIDStr, 10, 64)
|
||||||
|
if err != nil || shopID <= 0 {
|
||||||
|
log.Printf("[WARN] 无效的shopid: %s (错误: %v)", shopIDStr, err)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "shopid必须为正整数",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用全局的Redis连接池(避免每次创建新连接)
|
||||||
|
redisClient := utils.RedisTwoForParseFormData
|
||||||
|
if redisClient == nil {
|
||||||
|
log.Printf("[ERROR] Redis连接未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 使用全局MySQL连接池(单例,在main.go启动时初始化)
|
||||||
|
db := dbConnectUtil.DB
|
||||||
|
if db == nil {
|
||||||
|
log.Printf("[ERROR] 全局MySQL连接池未初始化")
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 生成MD5加密的ISBN
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
|
||||||
|
// 7. 查询当前店铺的mall_id
|
||||||
|
var mallID string
|
||||||
|
err = db.QueryRow(`
|
||||||
|
SELECT mall_id
|
||||||
|
FROM t_shop
|
||||||
|
WHERE shop_type LIKE '%5%'
|
||||||
|
AND del_flag LIKE '%0%'
|
||||||
|
AND id = ?`, shopID).Scan(&mallID)
|
||||||
|
if err != nil {
|
||||||
|
// mall_id未找到,不做去重处理,直接返回成功
|
||||||
|
log.Printf("[INFO] 未找到店铺mall_id: shopid=%d, err=%v", shopID, err)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"shopid": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
},
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 根据mall_id查询所有关联店铺id
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT id FROM t_shop
|
||||||
|
WHERE shop_type LIKE '%5%'
|
||||||
|
AND del_flag LIKE '%0%'
|
||||||
|
AND mall_id = ?`, mallID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] 查询关联店铺失败: mall_id=%s, err=%v", mallID, err)
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "系统内部错误",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// 9. 遍历所有关联店铺id,参考ParseFormData第5步之后的逻辑检查重复
|
||||||
|
var relatedShopIDs []int64
|
||||||
|
for rows.Next() {
|
||||||
|
var relatedShopID int64
|
||||||
|
if err := rows.Scan(&relatedShopID); err != nil {
|
||||||
|
log.Printf("[WARN] 扫描关联店铺id失败: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
relatedShopIDs = append(relatedShopIDs, relatedShopID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, relatedShopID := range relatedShopIDs {
|
||||||
|
// 构建Redis key
|
||||||
|
redisKey := fmt.Sprintf("%d", relatedShopID)
|
||||||
|
|
||||||
|
// 检查是否存在重复数据
|
||||||
|
exists, err := redisClient.HGet(redisKey, encryptedIsbn)
|
||||||
|
if err == nil && exists != "" {
|
||||||
|
// 存在重复数据,立即返回不继续查询
|
||||||
|
log.Printf("[INFO] 检测到重复数据: shopId=%d, isbn=%s (关联mall_id=%s)", relatedShopID, isbn, mallID)
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"message": "已有重复数据",
|
||||||
|
"success": false,
|
||||||
|
"data": gin.H{
|
||||||
|
"shopId": relatedShopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
"mallId": mallID,
|
||||||
|
"relatedShopCount": len(relatedShopIDs),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 关键成功日志
|
||||||
|
log.Printf("[INFO] 多点店铺去重检查完成: shopid=%d, isbn=%s, mall_id=%s, 关联店铺数=%d",
|
||||||
|
shopID, isbn, mallID, len(relatedShopIDs))
|
||||||
|
|
||||||
|
// 11. 返回结果
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"shopid": shopID,
|
||||||
|
"isbn": isbn,
|
||||||
|
"mallId": mallID,
|
||||||
|
"relatedShopCount": len(relatedShopIDs),
|
||||||
|
"relatedShopIds": relatedShopIDs,
|
||||||
|
},
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
BIN
dll/csv.dll
Normal file
BIN
dll/csv.dll
Normal file
Binary file not shown.
BIN
getErpSendPublishing.rar
Normal file
BIN
getErpSendPublishing.rar
Normal file
Binary file not shown.
BIN
getErpSendPublishing_linux
Normal file
BIN
getErpSendPublishing_linux
Normal file
Binary file not shown.
58
go.mod
Normal file
58
go.mod
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
module getErpSendPublishing
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.11.0
|
||||||
|
github.com/go-ini/ini v1.67.0
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
|
github.com/shopspring/decimal v1.4.0
|
||||||
|
github.com/tealeg/xlsx/v3 v3.3.13
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/frankban/quicktest v1.14.6 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/google/btree v1.0.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
|
||||||
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
|
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
|
)
|
||||||
143
go.sum
Normal file
143
go.sum
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||||
|
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
|
||||||
|
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||||
|
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tealeg/xlsx/v3 v3.3.13 h1:Zk1Stj11MGRnOYI1st6av/Z2lIXp/jFZomrSWSeJLmY=
|
||||||
|
github.com/tealeg/xlsx/v3 v3.3.13/go.mod h1:KV4FTFtvGy0TBlOivJLZu/YNZk6e0Qtk7eOSglWksuA=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
116
main.go
Normal file
116
main.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
|
||||||
|
handler "getErpSendPublishing/controller"
|
||||||
|
"getErpSendPublishing/utils"
|
||||||
|
"getErpSendPublishing/utils/dbConnectUtil"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
RedisDb struct {
|
||||||
|
Addr string `yaml:"addr"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
db int `yaml:"db"`
|
||||||
|
} `yaml:"redis-db"`
|
||||||
|
|
||||||
|
RedisDbTwo struct {
|
||||||
|
Addr string `yaml:"addr"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
db int `yaml:"db"`
|
||||||
|
} `yaml:"redis-dbTwo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置文件
|
||||||
|
func loadConfig() error {
|
||||||
|
configFile, err := os.ReadFile("config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取配置文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(configFile, &config); err != nil {
|
||||||
|
return fmt.Errorf("解析配置文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 加载配置
|
||||||
|
if err := loadConfig(); err != nil {
|
||||||
|
log.Fatalf("加载配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("config.RedisDb%v", config.RedisDb)
|
||||||
|
// 初始化默认Redis(用于任务存储)- 本地Redis
|
||||||
|
utils.InitRedis(config.RedisDb.Addr, config.RedisDb.Password, config.RedisDb.db)
|
||||||
|
|
||||||
|
// 初始化缓存Redis(用于商品图片缓存)- 远程Redis
|
||||||
|
utils.InitCacheRedis(config.RedisDbTwo.Addr, config.RedisDbTwo.Password, config.RedisDbTwo.db)
|
||||||
|
|
||||||
|
//// 初始化第二个Redis连接(库7,用于Header数据查询)
|
||||||
|
if err := utils.InitRedisTwo(); err != nil {
|
||||||
|
log.Fatalf("RedisTwo初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
//log.Println("RedisTwo初始化成功(连接到103.236.74.207:6379,库7)")
|
||||||
|
|
||||||
|
// 初始化专门用于ParseFormData函数的Redis连接(库13,用于查重)
|
||||||
|
if err := utils.InitRedisTwoForParseFormData(); err != nil {
|
||||||
|
log.Fatalf("RedisTwoForParseFormData初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("RedisTwoForParseFormData初始化成功(连接到36.212.1.63:6379,库13)")
|
||||||
|
|
||||||
|
// 初始化全局MySQL连接池(单例,被所有查重接口复用,防止大量连接同时创建)
|
||||||
|
if _, err := dbConnectUtil.InitDB("zhishu", "XsRR4K3ATizyc5BK", "146.56.227.42", 3306); err != nil {
|
||||||
|
log.Fatalf("全局MySQL连接初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("全局MySQL连接池初始化成功(zhishu@146.56.227.42:3306),最大连接数=20")
|
||||||
|
|
||||||
|
// 初始化Gin
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// 注册路由
|
||||||
|
|
||||||
|
//查重
|
||||||
|
router.POST("/api/goods/simple", handler.ParseFormData)
|
||||||
|
|
||||||
|
//批量查重
|
||||||
|
router.POST("/api/goods/batchDeduplication", handler.BatchProcessCenterBooks)
|
||||||
|
|
||||||
|
//新 - 单挑查重
|
||||||
|
router.POST("/api/goods/newParseFormData", handler.NewParseFormData)
|
||||||
|
|
||||||
|
//新 - 闲鱼多店铺去重
|
||||||
|
router.POST("/api/goods/goofish/multipleStores", handler.MultipleStores)
|
||||||
|
|
||||||
|
//新 - 闲鱼多店去重,本地确认多店铺
|
||||||
|
router.POST("/api/goods/goofish/singleShopMultipleStores", handler.SingleShopMultipleStores)
|
||||||
|
|
||||||
|
// 启动HTTP服务器
|
||||||
|
go func() {
|
||||||
|
log.Println("HTTP服务器启动在 :8182")
|
||||||
|
if err := router.Run(":8182"); err != nil {
|
||||||
|
log.Fatal("HTTP服务器启动失败:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待中断信号
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
<-sigChan
|
||||||
|
log.Println("接收到中断信号,正在关闭服务...")
|
||||||
|
|
||||||
|
log.Println("服务已关闭")
|
||||||
|
}
|
||||||
BIN
parseFormDataTest.exe
Normal file
BIN
parseFormDataTest.exe
Normal file
Binary file not shown.
16
temp/mian.go
Normal file
16
temp/mian.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
isbn := "9787107333316"
|
||||||
|
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
|
||||||
|
log.Println(encryptedIsbn)
|
||||||
|
}
|
||||||
71
test_redis_pool.go
Normal file
71
test_redis_pool.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"getErpSendPublishing/utils"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 初始化Redis连接池
|
||||||
|
if err := utils.InitRedisTwoForParseFormData(); err != nil {
|
||||||
|
log.Fatalf("Redis初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("Redis连接池初始化成功")
|
||||||
|
|
||||||
|
// 模拟高频请求
|
||||||
|
const concurrentRequests = 100
|
||||||
|
const totalRequests = 1000
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(totalRequests)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for i := 0; i < totalRequests; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
reqBody := fmt.Sprintf("shopid=123&isbn=ISBN%d", i)
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := http.Post("http://localhost:8182/api/goods/simple", "application/x-www-form-urlencoded", strings.NewReader(reqBody))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("请求失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
_, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("读取响应失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每100个请求打印一次进度
|
||||||
|
if (i+1)%100 == 0 {
|
||||||
|
log.Printf("已完成 %d 个请求", i+1)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
|
||||||
|
// 控制并发数
|
||||||
|
if (i+1)%concurrentRequests == 0 {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有请求完成
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// 计算执行时间
|
||||||
|
duration := time.Since(start)
|
||||||
|
log.Printf("完成 %d 个请求,耗时: %v", totalRequests, duration)
|
||||||
|
log.Printf("平均每个请求耗时: %v", duration/time.Duration(totalRequests))
|
||||||
|
}
|
||||||
22
utils/ApiResponse.go
Normal file
22
utils/ApiResponse.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// ApiResponse 标准 API 响应结构(核心)
|
||||||
|
type ApiResponse struct {
|
||||||
|
Code int `json:"code"` // 业务状态码(非 HTTP 状态码)
|
||||||
|
Message string `json:"message"` // 提示信息(友好、明确)
|
||||||
|
Data any `json:"data"` // 返回数据(任意类型,可为 nil)
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// (可选)预定义业务状态码常量(根据项目调整)
|
||||||
|
const (
|
||||||
|
CodeSuccess = 200 // 成功
|
||||||
|
CodeParamMissing = 40001 // 参数缺失
|
||||||
|
CodeParamInvalid = 40002 // 参数无效(格式/类型错误)
|
||||||
|
CodeUnauthorized = 40101 // 未授权(Token 失效/无权限)
|
||||||
|
CodeForbidden = 40301 // 禁止访问(权限不足)
|
||||||
|
CodeNotFound = 40401 // 资源不存在
|
||||||
|
CodeInternalError = 50001 // 服务器内部错误(如数据库异常)
|
||||||
|
)
|
||||||
447
utils/PageQuery.go
Normal file
447
utils/PageQuery.go
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageQuery 分页查询实体类
|
||||||
|
type PageQuery struct {
|
||||||
|
PageSize *int `json:"pageSize" form:"pageSize"` // 分页大小
|
||||||
|
PageNum *int `json:"pageNum" form:"pageNum"` // 当前页数
|
||||||
|
OrderByColumn string `json:"orderByColumn" form:"orderByColumn"` // 排序列
|
||||||
|
IsAsc string `json:"isAsc" form:"isAsc"` // 排序的方向desc或者asc
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
DefaultPageNum int // 当前记录起始索引 默认值
|
||||||
|
DefaultPageSize int // 每页显示记录数 默认值
|
||||||
|
MaxPageSize int // 最大分页大小限制
|
||||||
|
MinSize int // 最小分页大小
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPageQuery 创建新的PageQuery实例
|
||||||
|
func NewPageQuery() *PageQuery {
|
||||||
|
return &PageQuery{
|
||||||
|
DefaultPageNum: 1,
|
||||||
|
DefaultPageSize: 20,
|
||||||
|
MaxPageSize: 1000,
|
||||||
|
MinSize: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParams 设置分页参数
|
||||||
|
func (p *PageQuery) WithParams(pageNum, pageSize *int) *PageQuery {
|
||||||
|
p.PageNum = pageNum
|
||||||
|
p.PageSize = pageSize
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOrder 设置排序参数
|
||||||
|
func (p *PageQuery) WithOrder(orderByColumn, isAsc string) *PageQuery {
|
||||||
|
p.OrderByColumn = orderByColumn
|
||||||
|
p.IsAsc = isAsc
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildSQL 构建分页查询的SQL片段
|
||||||
|
// 返回: ORDER BY 子句, LIMIT OFFSET 子句, 参数值, 错误
|
||||||
|
func (p *PageQuery) BuildSQL() (orderBy string, limitOffset string, args []interface{}, err error) {
|
||||||
|
// 设置分页参数
|
||||||
|
pageNum := p.getPageNum()
|
||||||
|
pageSize := p.getPageSize()
|
||||||
|
|
||||||
|
// 验证分页参数
|
||||||
|
if pageNum <= 0 {
|
||||||
|
pageNum = p.DefaultPageNum
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = p.DefaultPageSize
|
||||||
|
}
|
||||||
|
// 限制最大分页大小
|
||||||
|
if pageSize > p.MaxPageSize {
|
||||||
|
return "", "", nil, errors.New(fmt.Sprintf("分页大小不能超过 %d", p.MaxPageSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算偏移量
|
||||||
|
offset := (pageNum - 1) * pageSize
|
||||||
|
|
||||||
|
// 构建排序条件
|
||||||
|
if p.OrderByColumn != "" && p.IsAsc != "" {
|
||||||
|
orderBy, err = p.buildOrder()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
if orderBy != "" {
|
||||||
|
orderBy = "ORDER BY " + orderBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分页条件
|
||||||
|
if pageSize > 0 {
|
||||||
|
limitOffset = "LIMIT ? OFFSET ?"
|
||||||
|
args = []interface{}{pageSize, offset}
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderBy, limitOffset, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildCountSQL 构建统计总数的SQL
|
||||||
|
func (p *PageQuery) BuildCountSQL(baseSQL string) (string, []interface{}) {
|
||||||
|
// 将SELECT语句转换为COUNT语句
|
||||||
|
lowerSQL := strings.ToLower(baseSQL)
|
||||||
|
|
||||||
|
// 移除ORDER BY子句(对于COUNT不需要排序)
|
||||||
|
if idx := strings.Index(lowerSQL, "order by"); idx != -1 {
|
||||||
|
baseSQL = baseSQL[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将SELECT ... FROM 替换为 COUNT(*)
|
||||||
|
selectIdx := strings.Index(lowerSQL, "select")
|
||||||
|
fromIdx := strings.Index(lowerSQL, "from")
|
||||||
|
|
||||||
|
if selectIdx != -1 && fromIdx != -1 {
|
||||||
|
countSQL := fmt.Sprintf("SELECT COUNT(*) %s", baseSQL[fromIdx:])
|
||||||
|
return countSQL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法处理,返回原样
|
||||||
|
return baseSQL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPage 构建分页对象(返回通用的分页结构)
|
||||||
|
func (p *PageQuery) BuildPage() (*PageParam, error) {
|
||||||
|
// 设置合理的默认值
|
||||||
|
if p.DefaultPageNum <= 0 {
|
||||||
|
p.DefaultPageNum = 1
|
||||||
|
}
|
||||||
|
if p.DefaultPageSize <= 0 {
|
||||||
|
p.DefaultPageSize = 20
|
||||||
|
}
|
||||||
|
if p.MaxPageSize <= 0 {
|
||||||
|
p.MaxPageSize = 1000 // 设置默认最大值
|
||||||
|
}
|
||||||
|
|
||||||
|
pageNum := p.getPageNum()
|
||||||
|
pageSize := p.getPageSize()
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
if pageNum <= 0 {
|
||||||
|
pageNum = p.DefaultPageNum
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = p.DefaultPageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加最小分页大小限制
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = p.DefaultPageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageSize > p.MaxPageSize {
|
||||||
|
// 提供更友好的错误信息
|
||||||
|
return nil, fmt.Errorf("分页大小 %d 超过最大限制 %d", pageSize, p.MaxPageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (pageNum - 1) * pageSize
|
||||||
|
|
||||||
|
return &PageParam{
|
||||||
|
PageNum: pageNum,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Offset: offset,
|
||||||
|
OrderBy: p.OrderByColumn,
|
||||||
|
IsAsc: p.IsAsc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirstNum 获取起始记录索引
|
||||||
|
func (p *PageQuery) GetFirstNum() int {
|
||||||
|
pageNum := p.getPageNum()
|
||||||
|
pageSize := p.getPageSize()
|
||||||
|
return (pageNum - 1) * pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPageNum 获取页码
|
||||||
|
func (p *PageQuery) GetPageNum() int {
|
||||||
|
return p.getPageNum()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPageSize 获取分页大小
|
||||||
|
func (p *PageQuery) GetPageSize() int {
|
||||||
|
return p.getPageSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPageNum 获取页码(内部方法)
|
||||||
|
func (p *PageQuery) getPageNum() int {
|
||||||
|
if p.PageNum == nil || *p.PageNum <= 0 {
|
||||||
|
return p.DefaultPageNum
|
||||||
|
}
|
||||||
|
return *p.PageNum
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPageSize 获取分页大小(内部方法)
|
||||||
|
func (p *PageQuery) getPageSize() int {
|
||||||
|
if p.PageSize == nil || *p.PageSize <= 0 {
|
||||||
|
return p.DefaultPageSize
|
||||||
|
}
|
||||||
|
return *p.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOrder 构建排序条件
|
||||||
|
func (p *PageQuery) buildOrder() (string, error) {
|
||||||
|
orderBy := p.escapeOrderBySql(p.OrderByColumn)
|
||||||
|
orderBy = p.toUnderScoreCase(orderBy)
|
||||||
|
|
||||||
|
// 兼容前端排序类型
|
||||||
|
p.IsAsc = strings.ReplaceAll(p.IsAsc, "ascending", "asc")
|
||||||
|
p.IsAsc = strings.ReplaceAll(p.IsAsc, "descending", "desc")
|
||||||
|
|
||||||
|
// 分割排序字段和排序方式
|
||||||
|
orderByArr := strings.Split(orderBy, ",")
|
||||||
|
isAscArr := strings.Split(p.IsAsc, ",")
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if len(isAscArr) != 1 && len(isAscArr) != len(orderByArr) {
|
||||||
|
return "", errors.New("排序参数有误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建排序SQL
|
||||||
|
var orderList []string
|
||||||
|
for i, orderByStr := range orderByArr {
|
||||||
|
var isAscStr string
|
||||||
|
if len(isAscArr) == 1 {
|
||||||
|
isAscStr = isAscArr[0]
|
||||||
|
} else {
|
||||||
|
isAscStr = isAscArr[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证排序方向
|
||||||
|
if strings.ToLower(isAscStr) == "asc" {
|
||||||
|
orderList = append(orderList, orderByStr+" ASC")
|
||||||
|
} else if strings.ToLower(isAscStr) == "desc" {
|
||||||
|
orderList = append(orderList, orderByStr+" DESC")
|
||||||
|
} else {
|
||||||
|
return "", errors.New("排序参数有误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(orderList, ", "), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeOrderBySql 转义排序SQL防止SQL注入
|
||||||
|
func (p *PageQuery) escapeOrderBySql(orderBy string) string {
|
||||||
|
// 移除危险的SQL关键字
|
||||||
|
dangerousKeywords := []string{
|
||||||
|
"select", "insert", "update", "delete", "drop", "truncate",
|
||||||
|
"union", "join", "or", "and", "--", "#", "/*", "*/",
|
||||||
|
";", "'", "\"", "`",
|
||||||
|
}
|
||||||
|
|
||||||
|
orderBy = strings.ToLower(orderBy)
|
||||||
|
for _, keyword := range dangerousKeywords {
|
||||||
|
orderBy = strings.ReplaceAll(orderBy, keyword, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只允许字母、数字、下划线、点、逗号和空格
|
||||||
|
var safeStr strings.Builder
|
||||||
|
for _, r := range orderBy {
|
||||||
|
if unicode.IsLetter(r) || unicode.IsDigit(r) ||
|
||||||
|
r == '_' || r == '.' || r == ',' || r == ' ' {
|
||||||
|
safeStr.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(safeStr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUnderScoreCase 驼峰转下划线
|
||||||
|
func (p *PageQuery) toUnderScoreCase(str string) string {
|
||||||
|
if str == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var result strings.Builder
|
||||||
|
runes := []rune(str)
|
||||||
|
|
||||||
|
for i, r := range runes {
|
||||||
|
if unicode.IsUpper(r) {
|
||||||
|
if i > 0 && unicode.IsLower(runes[i-1]) {
|
||||||
|
result.WriteRune('_')
|
||||||
|
}
|
||||||
|
result.WriteRune(unicode.ToLower(r))
|
||||||
|
} else {
|
||||||
|
result.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageParam 分页参数结构
|
||||||
|
type PageParam struct {
|
||||||
|
PageNum int `json:"pageNum"`
|
||||||
|
PageSize int `json:"pageSize"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
OrderBy string `json:"orderBy"`
|
||||||
|
IsAsc string `json:"isAsc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageResult 分页结果结构
|
||||||
|
type PageResult[T any] struct {
|
||||||
|
PageNum int `json:"pageNum"`
|
||||||
|
PageSize int `json:"pageSize"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
TotalPage int `json:"totalPage"`
|
||||||
|
List []T `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPageResult 构建分页结果
|
||||||
|
func BuildPageResult[T any](list []T, total int64, pageNum, pageSize int) *PageResult[T] {
|
||||||
|
totalPage := 0
|
||||||
|
if pageSize > 0 {
|
||||||
|
totalPage = int((total + int64(pageSize) - 1) / int64(pageSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PageResult[T]{
|
||||||
|
PageNum: pageNum,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Total: total,
|
||||||
|
TotalPage: totalPage,
|
||||||
|
List: list,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPage 执行分页查询(通用方法)
|
||||||
|
func QueryPage[T any](db *sql.DB, baseQuery string, args []interface{}, pageQuery *PageQuery) (*PageResult[T], error) {
|
||||||
|
// 构建分页SQL
|
||||||
|
orderBy, limitOffset, pageArgs, err := pageQuery.BuildSQL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整查询SQL
|
||||||
|
finalSQL := baseQuery
|
||||||
|
if orderBy != "" {
|
||||||
|
finalSQL += " " + orderBy
|
||||||
|
}
|
||||||
|
if limitOffset != "" {
|
||||||
|
finalSQL += " " + limitOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并参数
|
||||||
|
finalArgs := args
|
||||||
|
if pageArgs != nil {
|
||||||
|
finalArgs = append(finalArgs, pageArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
rows, err := db.Query(finalSQL, finalArgs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// 解析结果集
|
||||||
|
var list []T
|
||||||
|
for rows.Next() {
|
||||||
|
var item T
|
||||||
|
// 注意:这里需要根据实际结构使用rows.Scan
|
||||||
|
// 这里是一个通用占位,实际使用需要具体实现
|
||||||
|
list = append(list, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询总数
|
||||||
|
countSQL, countArgs := pageQuery.BuildCountSQL(baseQuery)
|
||||||
|
var total int64
|
||||||
|
if countArgs == nil {
|
||||||
|
countArgs = args
|
||||||
|
}
|
||||||
|
err = db.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分页结果
|
||||||
|
pageNum := pageQuery.GetPageNum()
|
||||||
|
pageSize := pageQuery.GetPageSize()
|
||||||
|
return BuildPageResult(list, total, pageNum, pageSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
func ExampleUsage() {
|
||||||
|
// 1. 创建PageQuery
|
||||||
|
pageQuery := NewPageQuery()
|
||||||
|
pageSize := 10
|
||||||
|
pageNum := 1
|
||||||
|
pageQuery.WithParams(&pageNum, &pageSize).
|
||||||
|
WithOrder("createTime,id", "desc,asc")
|
||||||
|
|
||||||
|
// 2. 获取数据库连接
|
||||||
|
var db *sql.DB // 假设已初始化
|
||||||
|
|
||||||
|
// 3. 构建基础查询SQL
|
||||||
|
baseSQL := "SELECT id, name, price, create_time FROM goods WHERE status = ?"
|
||||||
|
args := []interface{}{1}
|
||||||
|
|
||||||
|
// 4. 获取分页SQL片段
|
||||||
|
orderBy, limitOffset, pageArgs, err := pageQuery.BuildSQL()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 构建完整SQL
|
||||||
|
finalSQL := baseSQL
|
||||||
|
if orderBy != "" {
|
||||||
|
finalSQL += " " + orderBy
|
||||||
|
}
|
||||||
|
if limitOffset != "" {
|
||||||
|
finalSQL += " " + limitOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 合并参数
|
||||||
|
finalArgs := append(args, pageArgs...)
|
||||||
|
|
||||||
|
// 7. 执行查询
|
||||||
|
rows, err := db.Query(finalSQL, finalArgs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// 8. 查询总数
|
||||||
|
countSQL, countArgs := pageQuery.BuildCountSQL(baseSQL)
|
||||||
|
if countArgs == nil {
|
||||||
|
countArgs = args
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
err = db.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 解析结果
|
||||||
|
var goods []Goods
|
||||||
|
for rows.Next() {
|
||||||
|
var g Goods
|
||||||
|
err := rows.Scan(&g.ID, &g.Name, &g.Price, &g.CreateTime)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
goods = append(goods, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 构建分页结果
|
||||||
|
result := BuildPageResult(goods, total, pageNum, pageSize)
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Goods 示例结构体
|
||||||
|
type Goods struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Price float64 `db:"price"`
|
||||||
|
CreateTime string `db:"create_time"`
|
||||||
|
}
|
||||||
74
utils/dbConnectUtil/dbConnectUtil.go
Normal file
74
utils/dbConnectUtil/dbConnectUtil.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package dbConnectUtil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB 数据库连接池
|
||||||
|
// 定义两个全局变量分别存储不同的数据库连接
|
||||||
|
var (
|
||||||
|
DB *sql.DB // 第一个数据库连接
|
||||||
|
DBErp *sql.DB // 第二个数据库连接
|
||||||
|
DBTask *sql.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitDB 初始化数据库连接
|
||||||
|
func InitDB(username, password, host string, port int) (*sql.DB, error) {
|
||||||
|
log.Printf("开始初始化数据库连接")
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/zhishu?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
username, password, host, port)
|
||||||
|
|
||||||
|
log.Printf("数据库连接参数: %s", dsn)
|
||||||
|
log.Print("开始连接数据库")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
DB, err = sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return DB, fmt.Errorf("数据库连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
err = DB.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return DB, fmt.Errorf("数据库连接测试失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置连接池参数
|
||||||
|
DB.SetMaxOpenConns(20)
|
||||||
|
DB.SetMaxIdleConns(10)
|
||||||
|
DB.SetConnMaxLifetime(time.Hour)
|
||||||
|
return DB, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDBTask(username, password, host string, port int) (*sql.DB, error) {
|
||||||
|
log.Printf("开始初始化数据库连接")
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/task?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
username, password, host, port)
|
||||||
|
|
||||||
|
log.Printf("数据库连接参数: %s", dsn)
|
||||||
|
log.Print("开始连接数据库")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
DBTask, err = sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return DBTask, fmt.Errorf("数据库连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
err = DBTask.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return DBTask, fmt.Errorf("数据库连接测试失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置连接池参数
|
||||||
|
DBTask.SetMaxOpenConns(20)
|
||||||
|
DBTask.SetMaxIdleConns(10)
|
||||||
|
DBTask.SetConnMaxLifetime(time.Hour)
|
||||||
|
log.Printf("Task数据库连接初始化成功")
|
||||||
|
return DBTask, nil
|
||||||
|
}
|
||||||
379
utils/iniConfigUtil.go
Normal file
379
utils/iniConfigUtil.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
// Package utils 提供配置文件加载工具,支持从INI文件加载配置到结构体
|
||||||
|
// 支持类型: int, string, bool, float, time.Duration 及其切片类型
|
||||||
|
// 特性:
|
||||||
|
// - 支持默认值标签 `default`
|
||||||
|
// - 支持INI路径标签 `ini:"section.key"`
|
||||||
|
// - 支持时间单位转换标签 `multiplier`
|
||||||
|
// - 自动处理切片类型(逗号分隔)
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-ini/ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadConfig 从INI文件加载配置到结构体
|
||||||
|
// 参数:
|
||||||
|
//
|
||||||
|
// configPtr: 指向配置结构体的指针(必须是结构体指针)
|
||||||
|
// filename: INI配置文件的路径
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
//
|
||||||
|
// error: 加载成功返回nil,失败返回ConfigError详细错误信息
|
||||||
|
//
|
||||||
|
// 用法说明:
|
||||||
|
// 1. 定义配置结构体,使用标签声明INI映射关系和默认值
|
||||||
|
// 2. 调用LoadConfig(&config, "config.ini")
|
||||||
|
// 3. 检查错误并应用配置
|
||||||
|
//
|
||||||
|
// 示例结构体:
|
||||||
|
//
|
||||||
|
// type AppConfig struct {
|
||||||
|
// Port int `ini:"server.port" default:"8080"`
|
||||||
|
// Timeout time.Duration `ini:"server.timeout" multiplier:"1s"`
|
||||||
|
// Features []string `ini:"server.features"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
func LoadConfig(configPtr interface{}, filename string) error {
|
||||||
|
// 验证输入必须是指针
|
||||||
|
if reflect.TypeOf(configPtr).Kind() != reflect.Ptr {
|
||||||
|
return &ConfigError{Message: "configPtr必须是指向结构体的指针"} // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证输入必须指向结构体
|
||||||
|
configValue := reflect.ValueOf(configPtr).Elem()
|
||||||
|
// 确保输入是结构体
|
||||||
|
if configValue.Kind() != reflect.Struct {
|
||||||
|
return &ConfigError{Message: "configPtr必须指向结构体"} // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值(如果结构体有默认值)
|
||||||
|
setDefaultValues(configValue)
|
||||||
|
|
||||||
|
// 检查配置文件是否存在
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
log.Printf("配置文件不存在: %s, 使用默认值", filename) // 打印信息
|
||||||
|
return nil // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载INI文件
|
||||||
|
iniCfg, err := ini.Load(filename)
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return &ConfigError{Message: "加载配置文件失败", Cause: err} // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射配置到结构体
|
||||||
|
if err := mapConfig(iniCfg, configValue); err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊类型(如time.Duration)
|
||||||
|
processSpecialTypes(configValue)
|
||||||
|
|
||||||
|
// 返回成功
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDefaultValues 设置结构体字段的默认值
|
||||||
|
// 遍历结构体字段,检测`default`标签并设置初始值
|
||||||
|
// setDefaultValues 设置结构体字段的默认值
|
||||||
|
func setDefaultValues(configValue reflect.Value) {
|
||||||
|
// 获取结构体类型
|
||||||
|
configType := configValue.Type()
|
||||||
|
|
||||||
|
// 遍历字段
|
||||||
|
for i := 0; i < configType.NumField(); i++ {
|
||||||
|
// 获取字段信息
|
||||||
|
field := configType.Field(i)
|
||||||
|
// 获取字段值
|
||||||
|
fieldValue := configValue.Field(i)
|
||||||
|
|
||||||
|
// 如果字段是结构体,递归处理
|
||||||
|
if fieldValue.Kind() == reflect.Struct {
|
||||||
|
setDefaultValues(fieldValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果字段已经设置了值,跳过
|
||||||
|
if !fieldValue.IsZero() {
|
||||||
|
continue // 跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有默认值标签
|
||||||
|
if defaultValue, ok := field.Tag.Lookup("default"); ok {
|
||||||
|
setValueFromString(fieldValue, defaultValue) // 设置字段值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapConfig 将INI配置映射到结构体字段
|
||||||
|
// 解析`ini`标签获取section和key,读取对应配置值
|
||||||
|
func mapConfig(iniCfg *ini.File, configValue reflect.Value) error {
|
||||||
|
// 获取结构体类型
|
||||||
|
configType := configValue.Type()
|
||||||
|
|
||||||
|
// 遍历字段
|
||||||
|
for i := 0; i < configType.NumField(); i++ {
|
||||||
|
field := configType.Field(i) // 获取字段信息
|
||||||
|
fieldValue := configValue.Field(i) // 获取字段值
|
||||||
|
|
||||||
|
// 如果字段是结构体,递归处理
|
||||||
|
if fieldValue.Kind() == reflect.Struct {
|
||||||
|
if err := mapConfig(iniCfg, fieldValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取INI标签
|
||||||
|
iniTag, ok := field.Tag.Lookup("ini")
|
||||||
|
// 如果没有INI标签,跳过这个字段
|
||||||
|
if !ok || iniTag == "" {
|
||||||
|
continue // 跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析INI键名(支持section.key格式)
|
||||||
|
sectionName, keyName := parseIniTag(iniTag)
|
||||||
|
|
||||||
|
// 获取INI值
|
||||||
|
section, err := iniCfg.GetSection(sectionName)
|
||||||
|
// 如果section不存在
|
||||||
|
if err != nil {
|
||||||
|
continue // 跳过这个字段
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取INI键值
|
||||||
|
key, err := section.GetKey(keyName)
|
||||||
|
// 如果key不存在
|
||||||
|
if err != nil {
|
||||||
|
continue //跳过这个字段
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置结构体字段值
|
||||||
|
if err := setFieldValue(fieldValue, key); err != nil {
|
||||||
|
// 返回错误
|
||||||
|
return &ConfigError{
|
||||||
|
Message: "设置字段值失败", // 返回错误信息
|
||||||
|
Cause: err, // 返回错误原因
|
||||||
|
Field: field.Name, // 返回字段名称
|
||||||
|
Tag: iniTag, // 返回标签
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIniTag 解析INI标签格式
|
||||||
|
// 输入: "section.key" 格式的字符串
|
||||||
|
// 返回: (section名称, key名称)
|
||||||
|
func parseIniTag(tag string) (section, key string) {
|
||||||
|
// 默认section
|
||||||
|
section = "DEFAULT"
|
||||||
|
// 默认key
|
||||||
|
key = tag
|
||||||
|
|
||||||
|
// 检查是否有section前缀
|
||||||
|
if parts := regexp.MustCompile(`^(\w+)\.(\w+)$`).FindStringSubmatch(tag); len(parts) == 3 {
|
||||||
|
section = parts[1] // 设置section
|
||||||
|
key = parts[2] // 设置key
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回section和key
|
||||||
|
return section, key
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFieldValue 根据INI键值设置结构体字段值
|
||||||
|
// 自动处理基础类型和time.Duration类型转换
|
||||||
|
func setFieldValue(fieldValue reflect.Value, key *ini.Key) error {
|
||||||
|
// 检查字段类型
|
||||||
|
switch fieldValue.Kind() {
|
||||||
|
case reflect.String: // 字符串类型
|
||||||
|
fieldValue.SetString(key.String()) // 设置字段值
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
|
||||||
|
// 特殊处理time.Duration类型
|
||||||
|
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||||
|
duration, err := time.ParseDuration(key.String()) // 解析字符串为time.Duration
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
fieldValue.SetInt(int64(duration)) // 设置字段值
|
||||||
|
} else {
|
||||||
|
intValue, err := key.Int() // 获取整数值
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
fieldValue.SetInt(int64(intValue)) // 设置字段值
|
||||||
|
}
|
||||||
|
case reflect.Bool: // 布尔类型
|
||||||
|
boolValue, err := key.Bool() // 获取布尔值
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
fieldValue.SetBool(boolValue) // 设置字段值
|
||||||
|
case reflect.Float32, reflect.Float64: // 浮点类型
|
||||||
|
floatValue, err := key.Float64() // 获取浮点值
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
fieldValue.SetFloat(floatValue) // 设置字段值
|
||||||
|
case reflect.Slice: // 切片类型
|
||||||
|
return setSliceValue(fieldValue, key) // 处理切片类型字段
|
||||||
|
default: // 其他类型
|
||||||
|
return &ConfigError{Message: "不支持的字段类型", FieldType: fieldValue.Type().String()} // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSliceValue 设置切片类型字段值
|
||||||
|
// 将逗号分隔的字符串解析为指定类型的切片
|
||||||
|
func setSliceValue(fieldValue reflect.Value, key *ini.Key) error {
|
||||||
|
// 获取切片元素类型
|
||||||
|
sliceType := fieldValue.Type().Elem()
|
||||||
|
// 获取逗号分隔的值
|
||||||
|
values := key.Strings(",")
|
||||||
|
|
||||||
|
// 创建新切片
|
||||||
|
slice := reflect.MakeSlice(fieldValue.Type(), len(values), len(values))
|
||||||
|
|
||||||
|
// 遍历值
|
||||||
|
for i, val := range values {
|
||||||
|
elemValue := reflect.New(sliceType).Elem() // 创建新元素
|
||||||
|
|
||||||
|
// 根据切片元素类型设置值
|
||||||
|
switch sliceType.Kind() {
|
||||||
|
case reflect.String: // 字符串类型
|
||||||
|
elemValue.SetString(val) // 设置字段值
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
|
||||||
|
intVal, err := strconv.ParseInt(val, 10, 64) // 解析字符串为整数
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
elemValue.SetInt(intVal) // 设置字段值
|
||||||
|
case reflect.Float32, reflect.Float64: // 浮点类型
|
||||||
|
floatVal, err := strconv.ParseFloat(val, 64) // 解析字符串为浮点数
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
elemValue.SetFloat(floatVal) // 设置字段值
|
||||||
|
case reflect.Bool: // 布尔类型
|
||||||
|
boolVal, err := strconv.ParseBool(val) // 解析字符串为布尔值
|
||||||
|
// 处理错误
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回错误
|
||||||
|
}
|
||||||
|
elemValue.SetBool(boolVal) // 设置字段值
|
||||||
|
default:
|
||||||
|
return &ConfigError{Message: "不支持的切片元素类型", FieldType: sliceType.String()} // 返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
slice.Index(i).Set(elemValue) // 设置切片元素
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置切片字段值
|
||||||
|
fieldValue.Set(slice)
|
||||||
|
// 返回成功
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setValueFromString 从字符串解析值到结构体字段(用于默认值)
|
||||||
|
// 支持基础类型转换,不处理复杂类型
|
||||||
|
func setValueFromString(fieldValue reflect.Value, valueStr string) {
|
||||||
|
switch fieldValue.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
fieldValue.SetString(valueStr)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if intValue, err := strconv.ParseInt(valueStr, 10, 64); err == nil {
|
||||||
|
fieldValue.SetInt(intValue)
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
if boolValue, err := strconv.ParseBool(valueStr); err == nil {
|
||||||
|
fieldValue.SetBool(boolValue)
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if floatValue, err := strconv.ParseFloat(valueStr, 64); err == nil {
|
||||||
|
fieldValue.SetFloat(floatValue)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
// 切片类型需要特殊处理,这里简化处理
|
||||||
|
default:
|
||||||
|
// 保留原panic调用,但修改提示信息
|
||||||
|
panic("未处理的默认值类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSpecialTypes 处理特殊类型转换
|
||||||
|
// 当前支持time.Duration的倍数转换(使用multiplier标签)
|
||||||
|
// processSpecialTypes 处理特殊类型转换
|
||||||
|
func processSpecialTypes(configValue reflect.Value) {
|
||||||
|
// 获取结构体类型
|
||||||
|
configType := configValue.Type()
|
||||||
|
|
||||||
|
// 遍历结构体字段
|
||||||
|
for i := 0; i < configType.NumField(); i++ {
|
||||||
|
field := configType.Field(i) // 获取字段
|
||||||
|
fieldValue := configValue.Field(i) // 获取字段值
|
||||||
|
|
||||||
|
// 如果字段是结构体,递归处理
|
||||||
|
if fieldValue.Kind() == reflect.Struct {
|
||||||
|
processSpecialTypes(fieldValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理time.Duration类型的倍数转换
|
||||||
|
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||||
|
// 检查是否存在multiplier标签
|
||||||
|
if multiplier, ok := field.Tag.Lookup("multiplier"); ok {
|
||||||
|
// 解析multiplier标签
|
||||||
|
if mult, err := time.ParseDuration(multiplier); err == nil {
|
||||||
|
duration := time.Duration(fieldValue.Int()) // 将字段值转换为time.Duration
|
||||||
|
fieldValue.SetInt(int64(duration * mult)) // 将字段值设置为转换后的时间
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigError 自定义配置错误类型
|
||||||
|
// 包含错误原因、字段信息和原始错误
|
||||||
|
type ConfigError struct {
|
||||||
|
Message string
|
||||||
|
Cause error
|
||||||
|
Field string
|
||||||
|
FieldType string
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实现error接口,提供详细错误信息
|
||||||
|
func (e *ConfigError) Error() string {
|
||||||
|
msg := "配置错误: " + e.Message
|
||||||
|
if e.Field != "" {
|
||||||
|
msg += " [字段: " + e.Field + "]"
|
||||||
|
}
|
||||||
|
if e.FieldType != "" {
|
||||||
|
msg += " [类型: " + e.FieldType + "]"
|
||||||
|
}
|
||||||
|
if e.Tag != "" {
|
||||||
|
msg += " [标签: " + e.Tag + "]"
|
||||||
|
}
|
||||||
|
if e.Cause != nil {
|
||||||
|
msg += " - " + e.Cause.Error()
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
24
utils/json_util.go
Normal file
24
utils/json_util.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JsonUtil JSON工具类
|
||||||
|
type JsonUtil struct{}
|
||||||
|
|
||||||
|
var Json = &JsonUtil{}
|
||||||
|
|
||||||
|
// TransferToJson 对象转JSON字符串
|
||||||
|
func (j *JsonUtil) TransferToJson(obj interface{}) string {
|
||||||
|
data, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonToObject JSON字符串转对象
|
||||||
|
func (j *JsonUtil) JsonToObject(jsonStr string, obj interface{}) error {
|
||||||
|
return json.Unmarshal([]byte(jsonStr), obj)
|
||||||
|
}
|
||||||
47
utils/map_util.go
Normal file
47
utils/map_util.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapUtils Map工具类
|
||||||
|
type MapUtils struct{}
|
||||||
|
|
||||||
|
var Map = &MapUtils{}
|
||||||
|
|
||||||
|
// ConvertToObject Map转对象
|
||||||
|
func (m *MapUtils) ConvertToObject(data map[string]interface{}, obj interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(jsonData, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString 从map获取字符串
|
||||||
|
func (m *MapUtils) GetString(data map[string]interface{}, key string) string {
|
||||||
|
if val, ok := data[key]; ok && val != nil {
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 从map获取int64
|
||||||
|
func (m *MapUtils) GetInt64(data map[string]interface{}, key string) int64 {
|
||||||
|
if val, ok := data[key]; ok && val != nil {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case string:
|
||||||
|
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
return int64(v)
|
||||||
|
case int:
|
||||||
|
return int64(v)
|
||||||
|
case int64:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
90
utils/redisConnectUtil/redisConnectUtil.go
Normal file
90
utils/redisConnectUtil/redisConnectUtil.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package redisConnectUtil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
RedisClient *redis.Client
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitRedis 初始化Redis连接
|
||||||
|
func InitRedis(host, password string, port, db int) error {
|
||||||
|
// 如果Redis客户端已经存在且连接正常,直接返回
|
||||||
|
if RedisClient != nil {
|
||||||
|
_, err := RedisClient.Ping(ctx).Result()
|
||||||
|
if err == nil {
|
||||||
|
log.Println("Redis连接已存在且正常")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 连接异常,重新初始化
|
||||||
|
log.Println("Redis连接异常,重新初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||||
|
Password: password,
|
||||||
|
DB: db,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("开始连接Redis.... Addr: %s:%d, password:%s, db:%d", host, port, password, db)
|
||||||
|
// 测试连接
|
||||||
|
_, err := RedisClient.Ping(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Redis连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Redis连接成功")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRedis 关闭Redis连接
|
||||||
|
func CloseRedis() {
|
||||||
|
if RedisClient != nil {
|
||||||
|
RedisClient.Close()
|
||||||
|
log.Println("Redis连接已关闭")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireLock 获取分布式锁
|
||||||
|
func AcquireLock(lockKey string, timeout time.Duration) (bool, error) {
|
||||||
|
// 检查Redis客户端是否已初始化
|
||||||
|
if RedisClient == nil {
|
||||||
|
// 初始化Redis连接
|
||||||
|
err := InitRedis("36.212.20.113", "j8nZ4jra2E", 7963, 13)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("Redis连接初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取锁,设置过期时间防止死锁
|
||||||
|
result, err := RedisClient.SetNX(ctx, lockKey, "1", timeout).Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("获取Redis锁失败: %v", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseLock 释放分布式锁
|
||||||
|
func ReleaseLock(lockKey string) error {
|
||||||
|
// 检查Redis客户端是否已初始化
|
||||||
|
if RedisClient == nil {
|
||||||
|
// 初始化Redis连接
|
||||||
|
err := InitRedis("36.212.20.113", "j8nZ4jra2E", 7963, 13)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Redis连接初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := RedisClient.Del(ctx, lockKey).Result()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("释放Redis锁失败: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
359
utils/redis_util.go
Normal file
359
utils/redis_util.go
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisUtils Redis工具类
|
||||||
|
type RedisUtils struct {
|
||||||
|
client *redis.Client
|
||||||
|
ctx context.Context
|
||||||
|
processes map[string]*os.Process // 存储进程句柄的映射
|
||||||
|
processesLock sync.Mutex // 保护进程映射的锁
|
||||||
|
}
|
||||||
|
|
||||||
|
var Redis *RedisUtils // 默认Redis(用于任务存储)
|
||||||
|
var CacheRedis *RedisUtils // 缓存Redis(用于商品图片缓存)
|
||||||
|
|
||||||
|
// InitRedis 初始化默认Redis(用于任务存储)
|
||||||
|
func InitRedis(addr, password string, db int) {
|
||||||
|
Redis = &RedisUtils{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: addr,
|
||||||
|
Password: password,
|
||||||
|
DB: db,
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
processes: make(map[string]*os.Process),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitCacheRedis 初始化缓存Redis(用于商品图片缓存)
|
||||||
|
func InitCacheRedis(addr, password string, db int) {
|
||||||
|
CacheRedis = &RedisUtils{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: addr,
|
||||||
|
Password: password,
|
||||||
|
DB: db,
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecondCacheObject 获取缓存对象(秒级缓存)
|
||||||
|
func (r *RedisUtils) GetSecondCacheObject(key string) (string, error) {
|
||||||
|
return r.client.Get(r.ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSecondCacheObject 设置缓存对象(秒级缓存)
|
||||||
|
func (r *RedisUtils) SetSecondCacheObject(key, value string) error {
|
||||||
|
// 默认缓存24小时
|
||||||
|
return r.client.Set(r.ctx, key, value, 24*time.Hour).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelCacheObject 删除缓存
|
||||||
|
func (r *RedisUtils) DelCacheObject(key string) error {
|
||||||
|
return r.client.Del(r.ctx, key).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务存储相关方法
|
||||||
|
|
||||||
|
// SaveTaskToRedis 保存任务到Redis
|
||||||
|
func (r *RedisUtils) SaveTaskToRedis(taskKey string, task interface{}) error {
|
||||||
|
taskJSON, err := json.Marshal(task)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化任务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Hash存储任务信息,设置24小时过期
|
||||||
|
err = r.client.HSet(r.ctx, "tasks", taskKey, taskJSON).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("保存任务到Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时将任务ID添加到待处理任务队列
|
||||||
|
err = r.client.LPush(r.ctx, "pending_tasks", taskKey).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("添加到待处理队列失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置过期时间
|
||||||
|
r.client.Expire(r.ctx, "tasks", 24*time.Hour)
|
||||||
|
r.client.Expire(r.ctx, "pending_tasks", 24*time.Hour)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveRunningTaskToRedis 保存运行任务到Redis
|
||||||
|
func (r *RedisUtils) SaveRunningTaskToRedis(runningTask interface{}) error {
|
||||||
|
taskJSON, err := json.Marshal(runningTask)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化运行任务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一键
|
||||||
|
taskKey := fmt.Sprintf("running_task_%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
// 保存到运行任务Hash中
|
||||||
|
err = r.client.HSet(r.ctx, "running_tasks", taskKey, taskJSON).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("保存运行任务到Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时将任务键添加到待执行队列
|
||||||
|
err = r.client.LPush(r.ctx, "pending_running_tasks", taskKey).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("添加到待执行运行任务队列失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置过期时间
|
||||||
|
r.client.Expire(r.ctx, "running_tasks", 24*time.Hour)
|
||||||
|
r.client.Expire(r.ctx, "pending_running_tasks", 24*time.Hour)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSaveRunningTasksToRedis 批量保存运行任务到Redis(使用List类型队列)
|
||||||
|
func (r *RedisUtils) BatchSaveRunningTasksToRedis(tasks []interface{}) error {
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用pipeline批量操作
|
||||||
|
pipe := r.client.Pipeline()
|
||||||
|
|
||||||
|
// 用于收集所有唯一的队列名
|
||||||
|
queueNames := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, task := range tasks {
|
||||||
|
taskJSON, err := json.Marshal(task)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认队列名
|
||||||
|
queueName := "pending_running_tasks"
|
||||||
|
|
||||||
|
// 尝试从JSON中提取字段以生成唯一的队列名
|
||||||
|
var taskMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal(taskJSON, &taskMap); err == nil {
|
||||||
|
|
||||||
|
// 先尝试从外层提取 data 字段
|
||||||
|
dataStr, hasData := taskMap["data"].(string)
|
||||||
|
if hasData && dataStr != "" {
|
||||||
|
// 解析 data 字段中的 JSON
|
||||||
|
var dataMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(dataStr), &dataMap); err == nil {
|
||||||
|
|
||||||
|
// 从 data 中提取字段
|
||||||
|
shopID, hasShopID := dataMap["shopId"].(string)
|
||||||
|
taskID, hasTaskID := dataMap["taskId"].(string)
|
||||||
|
taskType, hasTaskType := dataMap["taskType"].(string)
|
||||||
|
|
||||||
|
// 提取 csvFileNameTimestamp
|
||||||
|
timestamp := ""
|
||||||
|
if ts, hasTimestamp := dataMap["csvFileNameTimestamp"].(string); hasTimestamp {
|
||||||
|
timestamp = ts
|
||||||
|
} else {
|
||||||
|
// 生成时间戳:年月日时分秒
|
||||||
|
timestamp = time.Now().Format("20060102150405")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果能从 data 中提取到所有必需字段,使用特定队列名
|
||||||
|
if hasShopID && hasTaskID && hasTaskType {
|
||||||
|
// 队列名格式: shopId_taskId_年月日时分秒_tasktype_1
|
||||||
|
queueName = fmt.Sprintf("%s_%s_%s_%s_1",
|
||||||
|
shopID,
|
||||||
|
taskID,
|
||||||
|
timestamp,
|
||||||
|
taskType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 收集队列名
|
||||||
|
queueNames[queueName] = true
|
||||||
|
|
||||||
|
// 将任务JSON推入List队列左侧(LPUSH)
|
||||||
|
pipe.LPush(r.ctx, queueName, taskJSON)
|
||||||
|
|
||||||
|
// 设置队列过期时间为24小时
|
||||||
|
pipe.Expire(r.ctx, queueName, 24*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行批量操作
|
||||||
|
_, err := pipe.Exec(r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个队列调用SendPublishing程序处理任务
|
||||||
|
for queueName := range queueNames {
|
||||||
|
go func(qName string) {
|
||||||
|
if _, err := r.callSendPublishing(qName); err != nil {
|
||||||
|
log.Printf("调用SendPublishing程序失败: %v, 队列: %s", err, qName)
|
||||||
|
}
|
||||||
|
}(queueName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallSendPublishing 调用SendPublishing程序处理任务(公开方法)
|
||||||
|
// 返回进程句柄和错误
|
||||||
|
func (r *RedisUtils) CallSendPublishing(qName string) (*os.Process, error) {
|
||||||
|
return r.callSendPublishing(qName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// callSendPublishing 调用SendPublishing程序处理任务(内部方法)
|
||||||
|
// 返回进程句柄和错误
|
||||||
|
func (r *RedisUtils) callSendPublishing(qName string) (*os.Process, error) {
|
||||||
|
log.Printf("准备启动SendPublishing程序,队列: %s", qName)
|
||||||
|
|
||||||
|
// 为每个队列名启动一个SendPublishing程序实例
|
||||||
|
// 构建SendPublishing程序路径(相对于项目根目录的父目录)
|
||||||
|
programPath := "/www/wwwroot/GetErpSendPubishing/SendPublishing"
|
||||||
|
|
||||||
|
// 构建命令行参数:传递redisKey标志
|
||||||
|
args := []string{"-redisKey", qName}
|
||||||
|
|
||||||
|
// 启动SendPublishing程序
|
||||||
|
cmd := exec.Command(programPath, args...)
|
||||||
|
|
||||||
|
// 设置输出和错误输出
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// 执行命令(不等待完成,异步执行)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Printf("启动SendPublishing程序失败: %v, 队列: %s", err, qName)
|
||||||
|
return nil, fmt.Errorf("启动程序失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取进程句柄
|
||||||
|
process := cmd.Process
|
||||||
|
|
||||||
|
// 保存进程句柄到映射中
|
||||||
|
r.processesLock.Lock()
|
||||||
|
r.processes[qName] = process
|
||||||
|
r.processesLock.Unlock()
|
||||||
|
|
||||||
|
log.Printf("SendPublishing程序已启动,PID: %d, 队列: %s", process.Pid, qName)
|
||||||
|
|
||||||
|
// 启动一个goroutine等待进程结束,自动清理映射
|
||||||
|
go func() {
|
||||||
|
err := cmd.Wait()
|
||||||
|
r.processesLock.Lock()
|
||||||
|
delete(r.processes, qName)
|
||||||
|
r.processesLock.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("SendPublishing程序异常退出,PID: %d, 队列: %s, 错误: %v", process.Pid, qName, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("SendPublishing程序正常退出,PID: %d, 队列: %s", process.Pid, qName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return process, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProcess 根据队列名称获取进程句柄
|
||||||
|
func (r *RedisUtils) GetProcess(qName string) *os.Process {
|
||||||
|
r.processesLock.Lock()
|
||||||
|
defer r.processesLock.Unlock()
|
||||||
|
return r.processes[qName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskFromRedis 从Redis获取任务
|
||||||
|
func (r *RedisUtils) GetTaskFromRedis(taskKey string) (string, error) {
|
||||||
|
return r.client.HGet(r.ctx, "tasks", taskKey).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunningTaskFromRedis 从Redis获取运行任务
|
||||||
|
func (r *RedisUtils) GetRunningTaskFromRedis(taskKey string) (string, error) {
|
||||||
|
return r.client.HGet(r.ctx, "running_tasks", taskKey).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingRunningTask 从待执行队列获取一个运行任务
|
||||||
|
func (r *RedisUtils) GetPendingRunningTask() (string, error) {
|
||||||
|
return r.client.RPop(r.ctx, "pending_running_tasks").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskCount 获取任务数量
|
||||||
|
func (r *RedisUtils) GetTaskCount() (int64, error) {
|
||||||
|
return r.client.HLen(r.ctx, "tasks").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunningTaskCount 获取运行任务数量
|
||||||
|
func (r *RedisUtils) GetRunningTaskCount() (int64, error) {
|
||||||
|
return r.client.HLen(r.ctx, "running_tasks").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient 获取底层Redis客户端
|
||||||
|
func (r *RedisUtils) GetClient() *redis.Client {
|
||||||
|
return r.client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext 获取上下文
|
||||||
|
func (r *RedisUtils) GetContext() context.Context {
|
||||||
|
return r.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Redis Get方法
|
||||||
|
func (r *RedisUtils) Get(key string) *redis.StringCmd {
|
||||||
|
return r.client.Get(r.ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Redis Set方法
|
||||||
|
func (r *RedisUtils) Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
|
||||||
|
return r.client.Set(r.ctx, key, value, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys Redis Keys方法
|
||||||
|
func (r *RedisUtils) Keys(pattern string) *redis.StringSliceCmd {
|
||||||
|
return r.client.Keys(r.ctx, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LPush Redis LPush方法
|
||||||
|
func (r *RedisUtils) LPush(key string, values ...interface{}) *redis.IntCmd {
|
||||||
|
return r.client.LPush(r.ctx, key, values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPush Redis RPush方法
|
||||||
|
func (r *RedisUtils) RPush(key string, values ...interface{}) *redis.IntCmd {
|
||||||
|
return r.client.RPush(r.ctx, key, values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LPop Redis LPop方法
|
||||||
|
func (r *RedisUtils) LPop(key string) *redis.StringCmd {
|
||||||
|
return r.client.LPop(r.ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLPop Redis BLPop方法
|
||||||
|
func (r *RedisUtils) BLPop(timeout time.Duration, keys ...string) *redis.StringSliceCmd {
|
||||||
|
return r.client.BLPop(r.ctx, timeout, keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLen Redis LLen方法
|
||||||
|
func (r *RedisUtils) LLen(key string) *redis.IntCmd {
|
||||||
|
return r.client.LLen(r.ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire Redis Expire方法
|
||||||
|
func (r *RedisUtils) Expire(key string, expiration time.Duration) *redis.BoolCmd {
|
||||||
|
return r.client.Expire(r.ctx, key, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTL Redis TTL方法
|
||||||
|
func (r *RedisUtils) TTL(key string) *redis.DurationCmd {
|
||||||
|
return r.client.TTL(r.ctx, key)
|
||||||
|
}
|
||||||
174
utils/redis_util_two.go
Normal file
174
utils/redis_util_two.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisUtilsTwo Redis工具类
|
||||||
|
type RedisUtilsTwo struct {
|
||||||
|
client *redis.Client
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
var RedisTwo *RedisUtilsTwo // 全局Redis实例(库7)
|
||||||
|
var RedisTwoForParseFormData *RedisUtilsTwo // 全局Redis实例(专门用于ParseFormData函数,库13)
|
||||||
|
|
||||||
|
// InitRedisTwo 初始化第二个Redis连接(库7)
|
||||||
|
func InitRedisTwo() error {
|
||||||
|
RedisTwo = &RedisUtilsTwo{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: "36.212.20.113:7963",
|
||||||
|
Password: "j8nZ4jra2E",
|
||||||
|
DB: 7,
|
||||||
|
// 连接池配置
|
||||||
|
PoolSize: 10, // 最大连接数
|
||||||
|
MinIdleConns: 5, // 最小空闲连接数
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
_, err := RedisTwo.client.Ping(RedisTwo.ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("连接Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitRedisTwoForParseFormData 初始化专门用于ParseFormData函数的Redis连接(库13)
|
||||||
|
func InitRedisTwoForParseFormData() error {
|
||||||
|
RedisTwoForParseFormData = &RedisUtilsTwo{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: "36.212.12.92:6379", // 为ParseFormData函数使用的Redis地址
|
||||||
|
Password: "long6166@@",
|
||||||
|
DB: 13,
|
||||||
|
// 连接池配置
|
||||||
|
PoolSize: 50, // 最大连接数,根据实际并发情况调整
|
||||||
|
MinIdleConns: 10, // 最小空闲连接数
|
||||||
|
MaxRetries: 3, // 最大重试次数
|
||||||
|
DialTimeout: 5 * time.Second, // 拨号超时时间
|
||||||
|
ReadTimeout: 3 * time.Second, // 读取超时时间
|
||||||
|
WriteTimeout: 3 * time.Second, // 写入超时时间
|
||||||
|
PoolTimeout: 4 * time.Second, // 连接池超时时间
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
_, err := RedisTwoForParseFormData.client.Ping(RedisTwoForParseFormData.ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("连接Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedisUtilsTwo 创建一个新的Redis连接实例
|
||||||
|
func NewRedisUtilsTwo(db int) (*RedisUtilsTwo, error) {
|
||||||
|
redisUtil := &RedisUtilsTwo{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: "36.212.20.113:7963",
|
||||||
|
Password: "j8nZ4jra2E",
|
||||||
|
DB: db,
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
_, err := redisUtil.client.Ping(redisUtil.ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("连接Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisUtil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedisUtilsTwoForParseFormData 为ParseFormData函数创建一个新的Redis连接实例(使用不同的Redis地址)
|
||||||
|
func NewRedisUtilsTwoForParseFormData(db int) (*RedisUtilsTwo, error) {
|
||||||
|
redisUtil := &RedisUtilsTwo{
|
||||||
|
client: redis.NewClient(&redis.Options{
|
||||||
|
Addr: "36.212.12.92:6379", // 为ParseFormData函数使用的Redis地址
|
||||||
|
Password: "long6166@@",
|
||||||
|
DB: db,
|
||||||
|
}),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
_, err := redisUtil.client.Ping(redisUtil.ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("连接Redis失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisUtil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient 获取底层Redis客户端
|
||||||
|
func (r *RedisUtilsTwo) GetClient() *redis.Client {
|
||||||
|
return r.client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext 获取上下文
|
||||||
|
func (r *RedisUtilsTwo) GetContext() context.Context {
|
||||||
|
return r.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// HGet 从Redis Hash中获取字段值
|
||||||
|
func (r *RedisUtilsTwo) HGet(key, field string) (string, error) {
|
||||||
|
return r.client.HGet(r.ctx, key, field).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HGetAll 获取Hash中的所有字段和值
|
||||||
|
func (r *RedisUtilsTwo) HGetAll(key string) (map[string]string, error) {
|
||||||
|
return r.client.HGetAll(r.ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HSet 设置Hash中的字段值
|
||||||
|
func (r *RedisUtilsTwo) HSet(key, field string, value interface{}) error {
|
||||||
|
return r.client.HSet(r.ctx, key, field, value).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists 检查key是否存在
|
||||||
|
func (r *RedisUtilsTwo) Exists(key string) (int64, error) {
|
||||||
|
return r.client.Exists(r.ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取key的值
|
||||||
|
func (r *RedisUtilsTwo) Get(key string) (string, error) {
|
||||||
|
return r.client.Get(r.ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置key的值
|
||||||
|
func (r *RedisUtilsTwo) Set(key string, value interface{}, expiration time.Duration) error {
|
||||||
|
return r.client.Set(r.ctx, key, value, expiration).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire 设置key的过期时间
|
||||||
|
func (r *RedisUtilsTwo) Expire(key string, expiration time.Duration) error {
|
||||||
|
return r.client.Expire(r.ctx, key, expiration).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HIncrBy 对Hash中的字段值进行增量操作
|
||||||
|
func (r *RedisUtilsTwo) HIncrBy(key, field string, value int64) (int64, error) {
|
||||||
|
return r.client.HIncrBy(r.ctx, key, field, value).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LIndex 通过索引获取List中的元素
|
||||||
|
func (r *RedisUtilsTwo) LIndex(key string, index int64) (string, error) {
|
||||||
|
return r.client.LIndex(r.ctx, key, index).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LRange 获取List中指定范围内的元素
|
||||||
|
func (r *RedisUtilsTwo) LRange(key string, start, stop int64) ([]string, error) {
|
||||||
|
return r.client.LRange(r.ctx, key, start, stop).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭Redis连接
|
||||||
|
func (r *RedisUtilsTwo) Close() error {
|
||||||
|
return r.client.Close()
|
||||||
|
}
|
||||||
26
utils/response.go
Normal file
26
utils/response.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JSON(c *gin.Context, httpStatus int, code int, message string, data interface{}) {
|
||||||
|
c.JSON(httpStatus, ApiResponse{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Data: data,
|
||||||
|
Timestamp: time.Now(), // 自动填充当前时间
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success 成功响应(简化版,Code=200,Message="success")
|
||||||
|
func Success(c *gin.Context, data any) {
|
||||||
|
JSON(c, http.StatusOK, CodeSuccess, "success", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 失败响应(简化版,自动匹配 HTTP 状态码)
|
||||||
|
func Error(c *gin.Context, httpStatus int, code int, message string) {
|
||||||
|
JSON(c, httpStatus, code, message, nil)
|
||||||
|
}
|
||||||
18
utils/string_util.go
Normal file
18
utils/string_util.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// StringUtils 字符串工具类
|
||||||
|
type StringUtils struct{}
|
||||||
|
|
||||||
|
var Str = &StringUtils{}
|
||||||
|
|
||||||
|
// IsEmpty 判断字符串是否为空
|
||||||
|
func (s *StringUtils) IsEmpty(str string) bool {
|
||||||
|
return strings.TrimSpace(str) == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotEmpty 判断字符串是否非空
|
||||||
|
func (s *StringUtils) IsNotEmpty(str string) bool {
|
||||||
|
return !s.IsEmpty(str)
|
||||||
|
}
|
||||||
167
utils/writeToRedis.go
Normal file
167
utils/writeToRedis.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"getErpSendPublishing/utils/redisConnectUtil"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteToRedis(shopID int64) {
|
||||||
|
// 在开头调用judgISPdd函数
|
||||||
|
if !judgISPdd(shopID) {
|
||||||
|
log.Println("此店铺不是pdd店铺")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableName := "t_running_task_" + fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
"root", "Long6166@@", "nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com", 26247, "task")
|
||||||
|
|
||||||
|
taskDb, err := sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Task数据库连接失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer taskDb.Close()
|
||||||
|
|
||||||
|
// 查询success_data字段中的isbn
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT isbn
|
||||||
|
FROM %s
|
||||||
|
WHERE isbn IS NOT NULL
|
||||||
|
AND isbn != ''
|
||||||
|
AND shop_id = ?
|
||||||
|
ORDER BY id`, tableName)
|
||||||
|
// 建议:在表 %s 上创建索引:CREATE INDEX idx_shop_id_isbn ON %s (shop_id, isbn);
|
||||||
|
|
||||||
|
rows, err := taskDb.Query(query, shopID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("查询ISBN失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// 存储ISBN列表
|
||||||
|
var isbnList []string
|
||||||
|
for rows.Next() {
|
||||||
|
var isbn string
|
||||||
|
if err := rows.Scan(&isbn); err != nil {
|
||||||
|
log.Printf("扫描ISBN失败: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isbnList = append(isbnList, isbn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Printf("遍历行时发生错误: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果judgISPdd返回true,继续执行Redis相关操作
|
||||||
|
err = redisConnectUtil.InitRedis("36.212.20.113", "j8nZ4jra2E", 7963, 13)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Redis连接失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建Redis key,使用shopID
|
||||||
|
redisKey := fmt.Sprintf("%d", shopID)
|
||||||
|
|
||||||
|
// 执行时间
|
||||||
|
execTime := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
// 并行处理MD5加密,提高性能
|
||||||
|
type encryptedISBN struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIsbnChan := make(chan encryptedISBN, len(isbnList))
|
||||||
|
|
||||||
|
// 启动多个goroutine并行处理MD5加密
|
||||||
|
for i := 0; i < 10; i++ { // 使用10个goroutine
|
||||||
|
go func(start int) {
|
||||||
|
for j := start; j < len(isbnList); j += 10 {
|
||||||
|
isbn := isbnList[j]
|
||||||
|
// 加密ISBN(使用MD5)
|
||||||
|
hash := md5.Sum([]byte(isbn))
|
||||||
|
// 将MD5 hash转换为十六进制字符串
|
||||||
|
encryptedIsbn := fmt.Sprintf("%x", hash)
|
||||||
|
encryptedIsbnChan <- encryptedISBN{value: encryptedIsbn}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集加密结果
|
||||||
|
encryptedIsbnList := make([]string, 0, len(isbnList))
|
||||||
|
for i := 0; i < len(isbnList); i++ {
|
||||||
|
encryptedIsbn := <-encryptedIsbnChan
|
||||||
|
encryptedIsbnList = append(encryptedIsbnList, encryptedIsbn.value)
|
||||||
|
}
|
||||||
|
close(encryptedIsbnChan)
|
||||||
|
|
||||||
|
// 使用Redis管道批量执行HSet操作,减少网络往返
|
||||||
|
pipe := redisConnectUtil.RedisClient.Pipeline()
|
||||||
|
|
||||||
|
// 循环加密后的ISBN列表,执行Redis写入操作
|
||||||
|
for i := 0; i < len(isbnList); i++ {
|
||||||
|
encryptedIsbn := encryptedIsbnList[i]
|
||||||
|
// 以加密后的ISBN作为子key,存储执行时间
|
||||||
|
// 这里使用哈希表存储,key是shopid,field是加密后的ISBN,value是执行时间
|
||||||
|
pipe.HSet(context.Background(), redisKey, encryptedIsbn, execTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行管道中的所有命令
|
||||||
|
_, err = pipe.Exec(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("批量存储执行时间失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("成功处理店铺 %d,存储 %d 条数据到Redis", shopID, len(isbnList))
|
||||||
|
}
|
||||||
|
|
||||||
|
func judgISPdd(shopID int64) bool {
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
"zhishu", "XsRR4K3ATizyc5BK", "146.56.227.42", 3306, "zhishu")
|
||||||
|
|
||||||
|
db, err := sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("42db连接失败: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("42db连接测试失败: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `SELECT
|
||||||
|
CASE
|
||||||
|
WHEN shop_type = '1' THEN true
|
||||||
|
ELSE false
|
||||||
|
END AS result
|
||||||
|
FROM t_shop
|
||||||
|
WHERE id = ? AND del_flag = '0';`
|
||||||
|
|
||||||
|
var result bool
|
||||||
|
err = db.QueryRow(query, shopID).Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
log.Printf("未找到对应的店铺记录,ID: %d", shopID)
|
||||||
|
} else {
|
||||||
|
log.Printf("查询失败: %v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("judgISPdd查询结果: %v", result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user