This commit is contained in:
commit
5ed11c4d2b
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type ResCommon struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShortUrlBatchCreate struct {
|
||||||
|
//批次名称
|
||||||
|
BatchName string `json:"batch_name,omitempty"`
|
||||||
|
//链接
|
||||||
|
Urls []string `json:"urls"`
|
||||||
|
// 开始时间,yyyy-mm-dd
|
||||||
|
StartTimeStr string `json:"start_time_str,omitempty"`
|
||||||
|
// 结束时间,yyyy-mm-dd,不填则永久有效
|
||||||
|
EndTimeStr string `json:"end_time_str,omitempty"`
|
||||||
|
//返回映射列表
|
||||||
|
WithBatchMap bool `json:"with_batch_map,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShortUrlBatchCreateResult struct {
|
||||||
|
Id int32 `json:"id"`
|
||||||
|
// 名称
|
||||||
|
BatchName string `json:"batch_name"`
|
||||||
|
// 批次号
|
||||||
|
BatchNo int64 `json:"batch_no"`
|
||||||
|
UrlMap map[string]string `json:"url_map,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShortUrlCreateReq struct {
|
||||||
|
BatchName string `json:"batch_name,omitempty"`
|
||||||
|
// 用户id
|
||||||
|
UserId int32 `json:"user_id,omitempty"`
|
||||||
|
// 链接
|
||||||
|
Urls []string `json:"urls,omitempty"`
|
||||||
|
// 开始时间,yyyy-mm-dd
|
||||||
|
StartTimeStr string `json:"start_time_str,omitempty"`
|
||||||
|
// 结束时间,yyyy-mm-dd,不填则永久有效
|
||||||
|
EndTimeStr string `json:"end_time_str,omitempty"`
|
||||||
|
// 结束时间,yyyy-mm-dd,不填则永久有效
|
||||||
|
WithBatchMap bool `json:"with_batch_map,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchQueryReq struct {
|
||||||
|
BatchNo int64 `json:"batch_no,omitempty"`
|
||||||
|
Id int64 `json:"id,omitempty"`
|
||||||
|
BatchName string `json:"batch_name,omitempty"`
|
||||||
|
OriginUrl string `json:"origin_url,omitempty"`
|
||||||
|
// 短链接
|
||||||
|
ShortUrl string `json:"short_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryBatchWithUrlResp struct {
|
||||||
|
// id
|
||||||
|
Id int32 `json:"id,omitempty"`
|
||||||
|
// 名称
|
||||||
|
BatchName string `json:"batch_name,omitempty"`
|
||||||
|
// 批次号
|
||||||
|
BatchNo int64 `json:"batch_no,omitempty"`
|
||||||
|
UrlMap map[string]string `json:"url_map,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 纯小写字母
|
||||||
|
func GenerateRandomLowerString(n int) string {
|
||||||
|
return GenerateRandomStringCustom(n, "abcdefghijklmnopqrstuvwxyz")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRandomStringCustom 使用自定义字符集
|
||||||
|
func GenerateRandomStringCustom(n int, charset string) string {
|
||||||
|
result := make([]byte, n)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = charset[rand.IntN(len(charset))]
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructToMap 将结构体转换为 map[string]any
|
||||||
|
func StructToMap(v any) (map[string]any, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var m map[string]any
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Option func(*ShortUrl)
|
||||||
|
|
||||||
|
// WithHost 修改请求地址
|
||||||
|
func WithHost(host string) Option {
|
||||||
|
return func(b *ShortUrl) {
|
||||||
|
b.host = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAuth(auth string) Option {
|
||||||
|
return func(b *ShortUrl) {
|
||||||
|
b.auth = fmt.Sprintf("Basic %s", auth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 请求结构体
|
||||||
|
type Request struct {
|
||||||
|
Method string `json:"method"` // 请求方法
|
||||||
|
Url string `json:"url"` // 请求url
|
||||||
|
Params map[string]string `json:"params"` // Query参数
|
||||||
|
Headers map[string]string `json:"headers"` // 请求头
|
||||||
|
Cookies map[string]string `json:"cookies"` // todo 处理 Cookies
|
||||||
|
Data map[string]string `json:"data"` // 表单格式请求数据
|
||||||
|
Json map[string]interface{} `json:"json"` // JSON格式请求数据 todo 多层 嵌套
|
||||||
|
Files map[string]string `json:"files"` // todo 处理 Files
|
||||||
|
Raw string `json:"raw"` // 原始请求数据
|
||||||
|
JsonByte []byte `json:"json_raw"` // JSON格式请求数据 todo 多层 嵌套
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应结构体
|
||||||
|
type Response struct {
|
||||||
|
StatusCode int `json:"status_code"` // 状态码
|
||||||
|
Reason string `json:"reason"` // 状态码说明
|
||||||
|
Elapsed float64 `json:"elapsed"` // 请求耗时(秒)
|
||||||
|
Content []byte `json:"content"` // 响应二进制内容
|
||||||
|
Text string `json:"text"` // 响应文本
|
||||||
|
Headers map[string]string `json:"headers"` // 响应头
|
||||||
|
Cookies map[string]string `json:"cookies"` // todo 添加响应Cookies
|
||||||
|
Request *Request `json:"request"` // 原始请求
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理请求方法
|
||||||
|
func (r *Request) getMethod() string {
|
||||||
|
return strings.ToUpper(r.Method) // 必须转为全部大写
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装URL
|
||||||
|
func (r *Request) getUrl() string {
|
||||||
|
if r.Params != nil {
|
||||||
|
urlValues := url.Values{}
|
||||||
|
Url, _ := url.Parse(r.Url) // todo 处理err
|
||||||
|
for key, value := range r.Params {
|
||||||
|
urlValues.Set(key, value)
|
||||||
|
}
|
||||||
|
Url.RawQuery = urlValues.Encode()
|
||||||
|
return Url.String()
|
||||||
|
}
|
||||||
|
return r.Url
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装请求数据
|
||||||
|
func (r *Request) getData() io.Reader {
|
||||||
|
var reqBody string
|
||||||
|
if r.Headers == nil {
|
||||||
|
r.Headers = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
if r.Raw != "" {
|
||||||
|
reqBody = r.Raw
|
||||||
|
} else if r.Data != nil {
|
||||||
|
urlValues := url.Values{}
|
||||||
|
for key, value := range r.Data {
|
||||||
|
urlValues.Add(key, value)
|
||||||
|
}
|
||||||
|
reqBody = urlValues.Encode()
|
||||||
|
r.Headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||||
|
} else if r.Json != nil {
|
||||||
|
bytesData, _ := json.Marshal(r.Json)
|
||||||
|
reqBody = string(bytesData)
|
||||||
|
r.Headers["Content-Type"] = "application/json"
|
||||||
|
} else if r.JsonByte != nil {
|
||||||
|
reqBody = string(r.JsonByte)
|
||||||
|
r.Headers["Content-Type"] = "application/json"
|
||||||
|
}
|
||||||
|
return strings.NewReader(reqBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加请求头-需要在getData后使用
|
||||||
|
func (r *Request) addHeaders(req *http.Request) {
|
||||||
|
if r.Headers != nil {
|
||||||
|
for key, value := range r.Headers {
|
||||||
|
req.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备请求
|
||||||
|
func (r *Request) prepare() *http.Request {
|
||||||
|
Method := r.getMethod()
|
||||||
|
Url := r.getUrl()
|
||||||
|
Data := r.getData()
|
||||||
|
req, _ := http.NewRequest(Method, Url, Data)
|
||||||
|
r.addHeaders(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装响应对象
|
||||||
|
func (r *Request) packResponse(res *http.Response, elapsed float64) Response {
|
||||||
|
var resp Response
|
||||||
|
resBody, _ := io.ReadAll(res.Body)
|
||||||
|
resp.Content = resBody
|
||||||
|
resp.Text = string(resBody)
|
||||||
|
resp.StatusCode = res.StatusCode
|
||||||
|
resp.Reason = strings.Split(res.Status, " ")[1]
|
||||||
|
resp.Elapsed = elapsed
|
||||||
|
resp.Headers = map[string]string{}
|
||||||
|
for key, value := range res.Header {
|
||||||
|
resp.Headers[key] = strings.Join(value, ";")
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
func (r *Request) Send() (Response, error) {
|
||||||
|
req := r.prepare()
|
||||||
|
client := &http.Client{}
|
||||||
|
start := time.Now()
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return Response{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
elapsed := time.Since(start).Seconds()
|
||||||
|
return r.packResponse(res, elapsed), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShortUrl struct {
|
||||||
|
host string
|
||||||
|
auth string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(args ...Option) *ShortUrl {
|
||||||
|
s := &ShortUrl{
|
||||||
|
host: "https://crm.1688sup.com/api",
|
||||||
|
}
|
||||||
|
for _, opt := range args {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchCreate 批量创建短链接。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - urls: 需要生成短链的原始 URL 列表,每个 URL 长度至少为 1,不能为空切片。
|
||||||
|
// - index: 外部索引标识,用于后续批量查询和分组管理。非必填,传空字符串表示不设置索引。
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - error: 创建过程中遇到的错误,如参数校验失败、存储异常等。
|
||||||
|
//
|
||||||
|
// 注意:该方法会为每个 URL 生成对应的短链映射关系,任一 URL 创建失败将返回错误并中断后续处理。
|
||||||
|
|
||||||
|
func (s *ShortUrl) BatchCreate(in *ShortUrlBatchCreate) (*ShortUrlBatchCreateResult, error) {
|
||||||
|
path := "/openapi/short_url/batch/create"
|
||||||
|
if len(in.Urls) == 0 {
|
||||||
|
return nil, errors.New("url is empty")
|
||||||
|
}
|
||||||
|
if len(in.BatchName) == 0 {
|
||||||
|
in.BatchName = fmt.Sprintf("%s_%d", GenerateRandomLowerString(3), time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJson, err := StructToMap(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := Request{
|
||||||
|
Method: "POST",
|
||||||
|
Url: s.host + path,
|
||||||
|
Json: requestJson,
|
||||||
|
Headers: s.GetHeader(),
|
||||||
|
}
|
||||||
|
resp, err := req.Send()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
var resData ShortUrlBatchCreateResult
|
||||||
|
err = CheckHttpRes(&resp, &resData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShortUrl) BatchQuery(in *BatchQueryReq) (*QueryBatchWithUrlResp, error) {
|
||||||
|
path := "/openapi/short_url/batch/query"
|
||||||
|
if in.BatchNo == 0 && in.Id == 0 && len(in.BatchName) == 0 {
|
||||||
|
return nil, errors.New("batch_no,id,batch_name必传其中一个")
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJson, err := StructToMap(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := Request{
|
||||||
|
Method: "POST",
|
||||||
|
Url: s.host + path,
|
||||||
|
Json: requestJson,
|
||||||
|
Headers: s.GetHeader(),
|
||||||
|
}
|
||||||
|
resp, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
var resData QueryBatchWithUrlResp
|
||||||
|
err = CheckHttpRes(&resp, &resData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckHttpRes(resp *Response, data interface{}) (err error) {
|
||||||
|
var resBody ResCommon
|
||||||
|
if err = json.Unmarshal(resp.Content, &resBody); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Code != 0 {
|
||||||
|
return fmt.Errorf("请求失败,err: %s", resBody.Message)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
dataByte, _ := json.Marshal(resBody.Data)
|
||||||
|
if err = json.Unmarshal(dataByte, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShortUrl) GetHeader() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": s.auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package l_short_utl_request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBatchCreate(t *testing.T) {
|
||||||
|
in := &ShortUrlBatchCreate{
|
||||||
|
BatchName: "test_query_" + time.Now().Format("20060102150405"),
|
||||||
|
Urls: []string{
|
||||||
|
"https://www.baidu.com",
|
||||||
|
"https://github.com/trending",
|
||||||
|
"https://www.sojson.com/",
|
||||||
|
},
|
||||||
|
StartTimeStr: time.Now().Format(time.DateOnly),
|
||||||
|
EndTimeStr: time.Now().AddDate(0, 0, 2).Format(time.DateOnly),
|
||||||
|
WithBatchMap: true,
|
||||||
|
}
|
||||||
|
res, err := NewClient(WithAuth("eW10OjkxMmU3MWE2NGI3ZjkzNzlkYWRkNWYzMzRhM2M3MjM2OTdiNjJmOTJkMThkZDY3NTNjZWI1MzMwOWIwNjE2NDg=")).BatchCreate(in)
|
||||||
|
t.Log(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchQuery(t *testing.T) {
|
||||||
|
// 查询批次
|
||||||
|
queryIn := &BatchQueryReq{
|
||||||
|
BatchNo: 4233953069,
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRes, err := NewClient().BatchQuery(queryIn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BatchQuery failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Query Response: %+v", queryRes)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue