init
This commit is contained in:
82
pkg/middleware/getRealIP.go
Normal file
82
pkg/middleware/getRealIP.go
Normal file
@ -0,0 +1,82 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRealIP 提取客户端真实 IP 地址
|
||||
func GetRealIP(r *http.Request) string {
|
||||
headers := []string{"x-forwarded-for", "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "X-Real-IP"}
|
||||
|
||||
// 遍历头信息,找到第一个有效 IP
|
||||
for _, header := range headers {
|
||||
ip := extractIP(r.Header.Get(header))
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 RemoteAddr,如果经过代理则是代理 IP
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err == nil && ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
// 检查是否是本地地址
|
||||
if strings.HasPrefix(ip, "127.0.0.1") || strings.HasPrefix(ip, "[::1]") {
|
||||
if externalIP, err := getExternalIP(); err == nil {
|
||||
return externalIP.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 提取 IP 地址并返回第一个非空部分
|
||||
func extractIP(ips string) string {
|
||||
if ips == "" || strings.EqualFold(ips, "unknown") {
|
||||
return ""
|
||||
}
|
||||
// 返回第一个有效 IP
|
||||
return strings.TrimSpace(strings.Split(ips, ",")[0])
|
||||
}
|
||||
|
||||
// 获取非 127.0.0.1 的局域网 IP
|
||||
func getExternalIP() (net.IP, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ip := getIPFromAddr(addr)
|
||||
if ip != nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not connected to the network")
|
||||
}
|
||||
|
||||
func getIPFromAddr(addr net.Addr) net.IP {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip != nil && !ip.IsLoopback() {
|
||||
return ip.To4()
|
||||
}
|
||||
return nil
|
||||
}
|
72
pkg/util/172/172.go
Normal file
72
pkg/util/172/172.go
Normal file
@ -0,0 +1,72 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hk/pkg/util/httpUtil"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
productsUrl = "https://haokaopenapi.lot-ml.com/api/order/GetProducts" // 商品列表查询Url
|
||||
productsV2Url = "https://haokaopenapi.lot-ml.com/api/order/GetProductsV2" // 商品信息查询Url
|
||||
pickNumberUrl = "https://haokaopenapi.lot-ml.com/api/order/PickNumber" // 商品选号Url
|
||||
blackUserUrl = "https://haokaopenapi.lot-ml.com/api/black/BlackCheckCus" // 用户黑名单Url
|
||||
blackAgentUrl = "https://haokaopenapi.lot-ml.com/api/black/BlackCheckAgent" // 代理黑名单Url
|
||||
upFileUrl = "https://haokaopenapi.lot-ml.com/api/order/UpPicFile" // 证件照上传Url
|
||||
addOrderUrl = "https://haokaopenapi.lot-ml.com/api/order/ApiToOrder" // 下单Url
|
||||
orderInfoUrl = "https://haokaopenapi.lot-ml.com/api/order/GetOrderInfo" // 订单查询Url
|
||||
)
|
||||
|
||||
type BaseParams struct {
|
||||
Timestamp string `json:"Timestamp"` // Y 时间戳
|
||||
UserID string `json:"user_id"` // Y 用户ID
|
||||
UserSign string `json:"user_sign"` // Y 密钥
|
||||
}
|
||||
|
||||
// GoodsReq 商品请求参数
|
||||
type GoodsReq struct {
|
||||
ProductID string `json:"ProductID"`
|
||||
BaseParams
|
||||
// Timestamp string `json:"Timestamp"` // Y 时间戳
|
||||
// UserID string `json:"user_id"` // Y 用户ID
|
||||
// UserSign string `json:"user_sign"` // Y 密钥
|
||||
}
|
||||
|
||||
// ProductResp 定义产品信息的结构体
|
||||
type ProductResp struct {
|
||||
ProductID int64 `json:"productID"`
|
||||
ProductName string `json:"productName"`
|
||||
Flag string `json:"flag"` // 上架中
|
||||
}
|
||||
|
||||
// GetProduct 获取上架中商品列表
|
||||
func GetProduct(params GoodsReq) (ApiResponse[[]ProductResp], error) {
|
||||
// 设置时间戳
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
params.Timestamp = timestamp
|
||||
// 生成 MD5 签名
|
||||
userSign := generateMD5SignV2(params, params.UserSign)
|
||||
|
||||
formParams := map[string]string{
|
||||
"ProductID": params.ProductID,
|
||||
"Timestamp": params.Timestamp,
|
||||
"user_sign": userSign,
|
||||
"user_id": params.UserID,
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendFormData(productsUrl, nil, formParams)
|
||||
if err != nil {
|
||||
return ApiResponse[[]ProductResp]{}, err
|
||||
}
|
||||
|
||||
// 解析 JSON 响应
|
||||
var apiResponse ApiResponse[[]ProductResp]
|
||||
err = json.Unmarshal([]byte(body), &apiResponse)
|
||||
if err != nil {
|
||||
fmt.Println("解析响应体时发生错误:", err)
|
||||
return ApiResponse[[]ProductResp]{}, err
|
||||
}
|
||||
|
||||
return apiResponse, nil
|
||||
}
|
198
pkg/util/172/172_test.go
Normal file
198
pkg/util/172/172_test.go
Normal file
@ -0,0 +1,198 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetProduct(t *testing.T) {
|
||||
key := "devttl"
|
||||
secret := "2eed544990ff898cf4fe47ef90b1ce3a"
|
||||
productID := ""
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
|
||||
// 创建请求参数
|
||||
params := GoodsReq{
|
||||
ProductID: productID,
|
||||
BaseParams: BaseParams{
|
||||
Timestamp: timestamp,
|
||||
UserID: key,
|
||||
UserSign: secret,
|
||||
},
|
||||
}
|
||||
|
||||
// 发送 POST 请求
|
||||
apiResponse, err := GetProduct(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果成功,处理 data 中的数据
|
||||
if apiResponse.Code == 0 {
|
||||
for index, item := range apiResponse.Data {
|
||||
fmt.Printf("------------------%d\n", index)
|
||||
fmt.Printf("产品ID: %v\n", item.ProductID)
|
||||
fmt.Printf("产品名称: %v\n", item.ProductName)
|
||||
fmt.Printf("产品状态: %v\n", item.Flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProductV2(t *testing.T) {
|
||||
key := "devttl"
|
||||
secret := "2eed544990ff898cf4fe47ef90b1ce3a"
|
||||
productID := "1128"
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
|
||||
// 创建请求参数
|
||||
params := GoodsReq{
|
||||
ProductID: productID,
|
||||
BaseParams: BaseParams{
|
||||
Timestamp: timestamp,
|
||||
UserID: key,
|
||||
},
|
||||
}
|
||||
|
||||
// 生成 MD5 签名
|
||||
userSign := generateMD5SignV2(params, secret)
|
||||
|
||||
params.UserSign = userSign
|
||||
|
||||
fmt.Print(params)
|
||||
|
||||
// 发送 POST 请求
|
||||
apiResponse, err := getProductV2(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果成功,处理 data 中的数据
|
||||
if apiResponse.Code == 0 {
|
||||
for index, item := range apiResponse.Data {
|
||||
fmt.Printf("------------------%d\n", index)
|
||||
fmt.Printf("产品ID: %v\n", item.ProductID)
|
||||
fmt.Printf("产品名称: %v\n", item.ProductName)
|
||||
fmt.Printf("产品主图: %v\n", item.MainPic)
|
||||
fmt.Printf("发货地区: %v\n", item.Area)
|
||||
fmt.Printf("禁发地区: %v\n", item.DisableArea)
|
||||
fmt.Printf("详情图片: %v\n", item.LittlePicture)
|
||||
fmt.Printf("详情链接: %v\n", item.NetAddr)
|
||||
fmt.Printf("产品状态: %v\n", item.Flag)
|
||||
fmt.Printf("是否选号: %v\n", item.NumberSel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPickNumber(t *testing.T) {
|
||||
key := "devttl"
|
||||
secret := "2eed544990ff898cf4fe47ef90b1ce3a"
|
||||
productID := "1128"
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
|
||||
// 创建请求参数
|
||||
params := PickNumberReq{
|
||||
ProductID: productID,
|
||||
SearchCategory: "3",
|
||||
BaseParams: BaseParams{
|
||||
UserID: key,
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
}
|
||||
|
||||
// 生成 MD5 签名
|
||||
userSign := generateMD5SignV2(params, secret)
|
||||
|
||||
params.UserSign = userSign
|
||||
|
||||
fmt.Println(params)
|
||||
|
||||
// 发送 POST 请求
|
||||
apiResponse, err := GetPickNumber(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果成功,处理 data 中的数据
|
||||
if apiResponse.Code == 0 {
|
||||
for index, item := range apiResponse.Data {
|
||||
fmt.Printf("------------------%d\n", index)
|
||||
fmt.Printf("号码: %v\n", item.Number)
|
||||
fmt.Printf("号码类型: %v\n", item.Type)
|
||||
fmt.Printf("号码ID: %v\n", item.NumberId)
|
||||
fmt.Printf("号码池ID: %v\n", item.NumberPoolId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUserBlack(t *testing.T) {
|
||||
key := "devttl"
|
||||
secret := "2eed544990ff898cf4fe47ef90b1ce3a"
|
||||
number := "15538654901"
|
||||
numberType := "1"
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
|
||||
// 创建请求参数
|
||||
params := BlackReq{
|
||||
Number: number,
|
||||
Type: numberType,
|
||||
Timestamp: timestamp,
|
||||
UserID: key,
|
||||
UserSign: secret,
|
||||
}
|
||||
|
||||
// 发送 POST 请求
|
||||
apiResponse, err := CheckBlackUser(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果成功,处理 data 中的数据
|
||||
if apiResponse.Code == 0 {
|
||||
fmt.Println("此用户是黑名单")
|
||||
}
|
||||
if apiResponse.Code == 1 {
|
||||
fmt.Println("此用户不是黑名单")
|
||||
}
|
||||
if apiResponse.Code == -1 {
|
||||
fmt.Println("请输入正确的手机号")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAgentBlack(t *testing.T) {
|
||||
key := "devttl"
|
||||
secret := "2eed544990ff898cf4fe47ef90b1ce3a"
|
||||
number := "15538654901"
|
||||
numberType := "1"
|
||||
|
||||
// 创建请求参数
|
||||
params := BlackReq{
|
||||
Number: number,
|
||||
Type: numberType,
|
||||
UserID: key,
|
||||
}
|
||||
|
||||
// 生成 MD5 签名
|
||||
userSign := generateMD5SignV2(params, secret)
|
||||
|
||||
params.UserSign = userSign
|
||||
|
||||
fmt.Println(params)
|
||||
|
||||
// 发送 POST 请求
|
||||
apiResponse, err := CheckBlackAgent(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果成功,处理 data 中的数据
|
||||
if apiResponse.Code == 0 {
|
||||
fmt.Println("此用户是黑名单代理")
|
||||
}
|
||||
if apiResponse.Code == 1 {
|
||||
fmt.Println("此用户不是黑名单代理")
|
||||
}
|
||||
if apiResponse.Code == -1 {
|
||||
fmt.Println("请输入正确的代理手机号")
|
||||
}
|
||||
}
|
70
pkg/util/172/api_result.go
Normal file
70
pkg/util/172/api_result.go
Normal file
@ -0,0 +1,70 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ApiResponse 定义响应体的结构体
|
||||
type ApiResponse[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Errs *string `json:"errs"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
// 优化后的 generateMD5Sign 方法
|
||||
func generateMD5SignV2(params interface{}, secret string) string {
|
||||
// 获取结构体中的字段信息
|
||||
var strBuilder strings.Builder
|
||||
|
||||
// 获取参数类型
|
||||
v := reflect.ValueOf(params)
|
||||
|
||||
// 遍历结构体中的字段
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// 获取字段名和值
|
||||
field := v.Type().Field(i)
|
||||
jsonTag := field.Tag.Get("json") // 获取 json 标签
|
||||
value := v.Field(i)
|
||||
// 如果字段是嵌套结构体,跳过
|
||||
if value.Kind() == reflect.Struct {
|
||||
continue
|
||||
}
|
||||
// 获取字段值
|
||||
fieldValue := value.String()
|
||||
|
||||
// 排除 user_id 和空字符串字段
|
||||
if field.Name != "UserID" && field.Name != "UserSign" {
|
||||
// 拼接字段名和值
|
||||
strBuilder.WriteString(fmt.Sprintf("%s=%s&", jsonTag, fieldValue))
|
||||
}
|
||||
}
|
||||
|
||||
// 使用反射获取字段并拼接
|
||||
timestamp := v.FieldByName("Timestamp")
|
||||
if timestamp.IsValid() {
|
||||
timestampTag, _ := v.Type().FieldByName("Timestamp")
|
||||
strBuilder.WriteString(fmt.Sprintf("%s=%s&", timestampTag.Tag.Get("json"), timestamp.String()))
|
||||
}
|
||||
userID := v.FieldByName("UserID")
|
||||
if userID.IsValid() {
|
||||
userIDTag, _ := v.Type().FieldByName("UserID")
|
||||
strBuilder.WriteString(fmt.Sprintf("%s=%s", userIDTag.Tag.Get("json"), userID.String()))
|
||||
}
|
||||
|
||||
// 拼接 secret 到最后
|
||||
strBuilder.WriteString(secret)
|
||||
|
||||
// 获取拼接后的字符串
|
||||
concatenatedStr := strBuilder.String()
|
||||
|
||||
fmt.Println(concatenatedStr)
|
||||
// 计算 MD5 哈希
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(concatenatedStr))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
69
pkg/util/172/black_user.go
Normal file
69
pkg/util/172/black_user.go
Normal file
@ -0,0 +1,69 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hk/pkg/util/httpUtil"
|
||||
)
|
||||
|
||||
// BlackReq 黑名单请求
|
||||
type BlackReq struct {
|
||||
Number string `json:"number"` // Y 手机号或身份证号
|
||||
Type string `json:"type"` // Y 查询类别 1:手机号 2:身份证号
|
||||
Timestamp string `json:"Timestamp"` // Y 时间戳
|
||||
UserID string `json:"username"` // Y 用户ID
|
||||
UserSign string `json:"sign"` // Y 密钥
|
||||
}
|
||||
|
||||
// CheckBlackUser 判断用户是否为黑名单用户
|
||||
func CheckBlackUser(params BlackReq) (ApiResponse[any], error) {
|
||||
|
||||
formParams := map[string]string{
|
||||
"number": params.Number,
|
||||
"type": params.Type,
|
||||
"Timestamp": params.Timestamp,
|
||||
"username": params.UserID,
|
||||
"sign": params.UserSign,
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendFormData(blackUserUrl, nil, formParams)
|
||||
if err != nil {
|
||||
return ApiResponse[any]{}, err
|
||||
}
|
||||
|
||||
// 解析 JSON 响应
|
||||
var apiResponse ApiResponse[any]
|
||||
err = json.Unmarshal([]byte(body), &apiResponse)
|
||||
if err != nil {
|
||||
fmt.Println("解析响应体时发生错误:", err)
|
||||
return ApiResponse[any]{}, err
|
||||
}
|
||||
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
||||
// CheckBlackAgent 判断用户是否为黑名单代理
|
||||
func CheckBlackAgent(params BlackReq) (ApiResponse[any], error) {
|
||||
|
||||
formParams := map[string]string{
|
||||
"number": params.Number,
|
||||
"type": params.Type,
|
||||
"username": params.UserID,
|
||||
"sign": params.UserSign,
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendFormData(blackAgentUrl, nil, formParams)
|
||||
if err != nil {
|
||||
return ApiResponse[any]{}, err
|
||||
}
|
||||
|
||||
// 解析 JSON 响应
|
||||
var apiResponse ApiResponse[any]
|
||||
err = json.Unmarshal([]byte(body), &apiResponse)
|
||||
if err != nil {
|
||||
fmt.Println("解析响应体时发生错误:", err)
|
||||
return ApiResponse[any]{}, err
|
||||
}
|
||||
|
||||
return apiResponse, nil
|
||||
}
|
50
pkg/util/172/goods_v2.go
Normal file
50
pkg/util/172/goods_v2.go
Normal file
@ -0,0 +1,50 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hk/pkg/util/httpUtil"
|
||||
"time"
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
// ProductV2Resp 定义商品套餐的结构
|
||||
type ProductV2Resp struct {
|
||||
ProductID int64 `json:"productID"`
|
||||
ProductName string `json:"productName"`
|
||||
MainPic string `json:"mainPic"`
|
||||
Area string `json:"area"`
|
||||
DisableArea string `json:"disableArea"`
|
||||
LittlePicture string `json:"littlepicture"`
|
||||
NetAddr string `json:"netAddr"`
|
||||
Flag bool `json:"flag"`
|
||||
NumberSel int `json:"numberSel"`
|
||||
}
|
||||
|
||||
// GetProductV2 获取单个商品信息
|
||||
func GetProductV2(params GoodsReq) (ApiResponse[[]ProductV2Resp], error) {
|
||||
// 设置时间戳
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 当前时间戳,长度为10位
|
||||
params.Timestamp = timestamp
|
||||
// 生成 MD5 签名
|
||||
userSign := generateMD5SignV2(params, params.UserSign)
|
||||
formParams := map[string]string{
|
||||
"ProductID": params.ProductID,
|
||||
"user_id": params.UserID,
|
||||
"Timestamp": params.Timestamp,
|
||||
"user_sign": userSign,
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendFormData(productsV2Url, nil, formParams)
|
||||
|
||||
// 解析 JSON 响应
|
||||
var apiResponse ApiResponse[[]ProductV2Resp]
|
||||
err = json.Unmarshal([]byte(body), &apiResponse)
|
||||
if err != nil {
|
||||
return ApiResponse[[]ProductV2Resp]{}, fmt.Errorf("解析响应体时发生错误: %v", err)
|
||||
}
|
||||
|
||||
// 返回解析后的响应
|
||||
return apiResponse, nil
|
||||
}
|
53
pkg/util/172/pick_number.go
Normal file
53
pkg/util/172/pick_number.go
Normal file
@ -0,0 +1,53 @@
|
||||
package _72
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hk/pkg/util/httpUtil"
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
type PickNumberReq struct {
|
||||
City string `json:"city"` // 市
|
||||
ProductID string `json:"prodID"` // Y 商品ID
|
||||
Province string `json:"province"` // 省
|
||||
SearchCategory string `json:"searchCategory"` // 查询分类 默认3
|
||||
SearchValue string `json:"searchValue"` // 查询关键字2-4位
|
||||
BaseParams
|
||||
}
|
||||
|
||||
type PickNumberResp struct {
|
||||
Type string `json:"type"` // 号码类型 普通
|
||||
Number string `json:"number"` // 号码
|
||||
NumberId string `json:"numberId"` // 号码ID
|
||||
NumberPoolId string `json:"numberPoolId"` // 号码池ID
|
||||
|
||||
}
|
||||
|
||||
// GetPickNumber 商品选号
|
||||
func GetPickNumber(params PickNumberReq) (ApiResponse[[]PickNumberResp], error) {
|
||||
|
||||
formParams := map[string]string{
|
||||
"city": params.City,
|
||||
"prodID": params.ProductID,
|
||||
"province": params.Province,
|
||||
"searchCategory": "3",
|
||||
"searchValue": params.SearchValue,
|
||||
"Timestamp": params.Timestamp,
|
||||
"user_id": params.UserID,
|
||||
"user_sign": params.UserSign,
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendFormData(pickNumberUrl, nil, formParams)
|
||||
|
||||
// 解析 JSON 响应
|
||||
var apiResponse ApiResponse[[]PickNumberResp]
|
||||
err = json.Unmarshal([]byte(body), &apiResponse)
|
||||
if err != nil {
|
||||
return ApiResponse[[]PickNumberResp]{}, fmt.Errorf("解析响应体时发生错误: %v", err)
|
||||
}
|
||||
|
||||
// 返回解析后的响应
|
||||
return apiResponse, nil
|
||||
}
|
140
pkg/util/httpUtil/post_request.go
Normal file
140
pkg/util/httpUtil/post_request.go
Normal file
@ -0,0 +1,140 @@
|
||||
package httpUtil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Request 封装的 POST 请求工具类
|
||||
type Request struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewRequest 创建一个新的 PostRequest 实例
|
||||
func NewRequest() *Request {
|
||||
return &Request{
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second, // 设置请求超时时间
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SendJSON 发送一个 JSON 格式的 POST 请求
|
||||
func (p *Request) SendJSON(urlPath string, headers map[string]string, body interface{}) (string, error) {
|
||||
// 将请求体转为 JSON
|
||||
requestBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法编码 JSON 请求体: %v", err)
|
||||
}
|
||||
|
||||
// 创建 HTTP 请求
|
||||
req, err := http.NewRequest("POST", urlPath, bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法创建请求: %v", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("发送请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应内容
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应体失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回响应内容
|
||||
return string(respBody), nil
|
||||
}
|
||||
|
||||
// SendFormData 发送一个 form-data 格式的 POST 请求
|
||||
func (p *Request) SendFormData(urlPath string, headers map[string]string, params map[string]string) (string, error) {
|
||||
// 将参数拼接为表单数据
|
||||
formData := url.Values{}
|
||||
for key, value := range params {
|
||||
formData.Set(key, value)
|
||||
}
|
||||
|
||||
// 创建 HTTP 请求
|
||||
req, err := http.NewRequest("POST", urlPath, strings.NewReader(formData.Encode()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法创建请求: %v", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("发送请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应内容
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应体失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回响应内容
|
||||
return string(respBody), nil
|
||||
}
|
||||
|
||||
// SendGET 发送一个 GET 请求
|
||||
func (p *Request) SendGET(urlPath string, headers map[string]string, params map[string]string) (string, error) {
|
||||
// 如果有查询参数,拼接到 URL 中
|
||||
if len(params) > 0 {
|
||||
urlValues := url.Values{}
|
||||
for key, value := range params {
|
||||
urlValues.Set(key, value)
|
||||
}
|
||||
urlPath = fmt.Sprintf("%s?%s", urlPath, urlValues.Encode())
|
||||
}
|
||||
|
||||
// 创建 HTTP GET 请求
|
||||
req, err := http.NewRequest("GET", urlPath, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法创建请求: %v", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("发送请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应内容
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应体失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回响应内容
|
||||
return string(respBody), nil
|
||||
}
|
43
pkg/util/httpUtil/post_request_test.go
Normal file
43
pkg/util/httpUtil/post_request_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package httpUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
// 创建 PostRequest 实例
|
||||
postRequest := NewRequest()
|
||||
|
||||
// 示例:发送 JSON 请求
|
||||
url := "https://example.com/api"
|
||||
headers := map[string]string{
|
||||
"Authorization": "Bearer your_token",
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
// 发送 JSON 请求
|
||||
resp, err := postRequest.SendJSON(url, headers, body)
|
||||
if err != nil {
|
||||
fmt.Println("发送请求失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("响应内容:", resp)
|
||||
|
||||
// 示例:发送 form-data 请求
|
||||
formParams := map[string]string{
|
||||
"user_id": "your_user_id",
|
||||
"product_id": "12345",
|
||||
}
|
||||
resp2, err := postRequest.SendFormData(url, headers, formParams)
|
||||
if err != nil {
|
||||
fmt.Println("发送 form-data 请求失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("响应内容:", resp2)
|
||||
}
|
58
pkg/util/strUtil/strUtil.go
Normal file
58
pkg/util/strUtil/strUtil.go
Normal file
@ -0,0 +1,58 @@
|
||||
package strUtil
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinStrings 字符串拼接
|
||||
func JoinStrings(separator string, elems ...string) string {
|
||||
return strings.Join(elems, separator)
|
||||
}
|
||||
|
||||
// ValidString sql.NullString 转 string
|
||||
func ValidString(sqlString sql.NullString) string {
|
||||
if sqlString.Valid {
|
||||
return sqlString.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StringToNullString string 转 sql.NullString
|
||||
func StringToNullString(str string) sql.NullString {
|
||||
return sql.NullString{
|
||||
String: str,
|
||||
Valid: str != "",
|
||||
}
|
||||
}
|
||||
|
||||
// ValidInt64 sql.NullInt64 转 int64
|
||||
func ValidInt64(sqlInt sql.NullInt64) int64 {
|
||||
if sqlInt.Valid {
|
||||
return sqlInt.Int64
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64ToNullInt64 StringToNullString int64 转 sql.NullInt64
|
||||
func Int64ToNullInt64(value int64) sql.NullInt64 {
|
||||
return sql.NullInt64{
|
||||
Int64: value,
|
||||
Valid: value >= 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidBool sql.NullBool 转 bool
|
||||
func ValidBool(sqlBool sql.NullBool) bool {
|
||||
if sqlBool.Valid {
|
||||
return sqlBool.Bool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchRegexp 正则匹配
|
||||
func MatchRegexp(pattern string, value string) bool {
|
||||
r := regexp.MustCompile(pattern)
|
||||
return r.MatchString(value)
|
||||
}
|
151
pkg/util/tiktok/tiktok.go
Normal file
151
pkg/util/tiktok/tiktok.go
Normal file
@ -0,0 +1,151 @@
|
||||
package tiktok
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hk/pkg/util/httpUtil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type tiktokResp struct {
|
||||
Code int64 `json:"code"`
|
||||
Data tiktokData `json:"data"`
|
||||
}
|
||||
|
||||
type tiktokData struct {
|
||||
ID string `json:"id"`
|
||||
AwesomeID string `json:"aweme_id"`
|
||||
Region string `json:"region"`
|
||||
Title string `json:"title"`
|
||||
Cover string `json:"cover"`
|
||||
AiDynamicCover string `json:"ai_dynamic_cover"` // 动态封面
|
||||
OriginCover string `json:"origin_cover"` // 静态封面
|
||||
Duration int `json:"duration"`
|
||||
Play string `json:"play"` // 无水印视频
|
||||
Wmplay string `json:"wmplay"` // 有水印视频
|
||||
Size int `json:"size"`
|
||||
WmSize int `json:"wm_size"`
|
||||
Music string `json:"music"`
|
||||
MusicInfo musicInfo `json:"music_info"`
|
||||
PlayCount int `json:"play_count"`
|
||||
DiggCount int `json:"digg_count"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
ShareCount int `json:"share_count"`
|
||||
DownloadCount int `json:"download_count"`
|
||||
CollectCount int `json:"collect_count"`
|
||||
CreateTime int `json:"create_time"`
|
||||
AnchorsExtras string `json:"anchors_extras"`
|
||||
IsAd bool `json:"is_ad"`
|
||||
CommercialVideoInfo string `json:"commercial_video_info"`
|
||||
ItemCommentSettings int `json:"item_comment_settings"`
|
||||
Author authorDTO `json:"author"`
|
||||
}
|
||||
|
||||
type musicInfo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Play string `json:"play"` // 背景音乐
|
||||
Cover string `json:"cover"`
|
||||
Author string `json:"author"`
|
||||
Original bool `json:"original"`
|
||||
Duration int `json:"duration"`
|
||||
Album string `json:"album"`
|
||||
}
|
||||
|
||||
type authorDTO struct {
|
||||
ID string `json:"id"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
|
||||
type VideoInfoResponse struct {
|
||||
AuthorID string `json:"authorId"` // 作者ID
|
||||
AuthorUniqueID string `json:"authorUniqueId"` // 作者账号
|
||||
AuthorAvatar string `json:"authorAvatar"` // 作者头像
|
||||
AuthorNickname string `json:"authorNickname"` // 作者昵称
|
||||
|
||||
VideoID string `json:"videoId"` // 视频ID
|
||||
VideoTitle string `json:"videoTitle"` // 作品描述
|
||||
OriginCover string `json:"originCover"` // 静态封面
|
||||
DynamicCover string `json:"dynamicCover"` // 动态封面
|
||||
DestinationURL string `json:"destinationUrl"` // 无水印下载地址
|
||||
WatermarkVideoURL string `json:"watermarkVideoUrl"` // 有水印下载地址
|
||||
|
||||
MusicURL string `json:"musicUrl"` // 背景音乐链接
|
||||
|
||||
OriginalURL string `json:"originalUrl"` // 原始下载链接
|
||||
DownloaderURL string `json:"downloaderUrl"` // 解析后下载地址
|
||||
CreateTime string `json:"createTime"` // 创建时间
|
||||
Result bool `json:"result"` // 结果
|
||||
}
|
||||
|
||||
func GetTiktokVideoInfo(videoUrl string) (*VideoInfoResponse, error) {
|
||||
if len(videoUrl) <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 解析视频地址
|
||||
// 定义需要匹配的前缀
|
||||
prefixes := []string{
|
||||
"https://www.tiktok.com/t/",
|
||||
"https://m.tiktok.com/v/",
|
||||
"https://vm.tiktok.com/",
|
||||
"https://vt.tiktok.com/",
|
||||
"https://dltik.com/",
|
||||
}
|
||||
|
||||
// 检查是否有匹配的前缀
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(videoUrl, prefix) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 获取视频信息
|
||||
var url = "https://tiktok-download-without-watermark.p.rapidapi.com/analysis?url=" + videoUrl
|
||||
headers := map[string]string{
|
||||
"x-rapidapi-host": "tiktok-download-without-watermark.p.rapidapi.com",
|
||||
"x-rapidapi-key": "4a5bbe21bfmshbd98e7404b74063p19ba22jsne87a37e3b675",
|
||||
}
|
||||
|
||||
body, err := httpUtil.NewRequest().SendGET(url, headers, nil)
|
||||
if err != nil || len(body) <= 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tiktokResp tiktokResp
|
||||
err = json.Unmarshal([]byte(body), &tiktokResp)
|
||||
if err != nil {
|
||||
fmt.Println("解析响应体时发生错误:", err)
|
||||
return nil, err
|
||||
}
|
||||
// 比对 statusCode 是否等于0
|
||||
if tiktokResp.Code != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var videoInfo = VideoInfoResponse{}
|
||||
|
||||
// 作者信息
|
||||
videoInfo.AuthorID = tiktokResp.Data.Author.ID
|
||||
videoInfo.AuthorAvatar = tiktokResp.Data.Author.Avatar
|
||||
videoInfo.AuthorUniqueID = tiktokResp.Data.Author.UniqueID
|
||||
videoInfo.AuthorNickname = tiktokResp.Data.Author.Nickname
|
||||
|
||||
videoInfo.VideoID = tiktokResp.Data.ID
|
||||
videoInfo.VideoTitle = tiktokResp.Data.Title
|
||||
videoInfo.OriginCover = tiktokResp.Data.OriginCover
|
||||
videoInfo.DynamicCover = tiktokResp.Data.AiDynamicCover
|
||||
videoInfo.DestinationURL = tiktokResp.Data.Play
|
||||
videoInfo.WatermarkVideoURL = tiktokResp.Data.Wmplay
|
||||
|
||||
videoInfo.MusicURL = tiktokResp.Data.MusicInfo.Play
|
||||
|
||||
videoInfo.OriginalURL = videoUrl
|
||||
videoInfo.DownloaderURL = videoUrl
|
||||
|
||||
//videoInfo.CreateTime = tiktokResp.Data.CreateTime
|
||||
|
||||
videoInfo.Result = true
|
||||
return &videoInfo, nil
|
||||
}
|
7
pkg/util/tiktok/tiktok_test.go
Normal file
7
pkg/util/tiktok/tiktok_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package tiktok
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTikTok(test *testing.T) {
|
||||
GetTiktokVideoInfo("https://www.tiktok.com/@baotramthieu/video/6887554215598689538")
|
||||
}
|
Reference in New Issue
Block a user