commit 3665a21003041b5e231c70d23a21eb003de52d70 Author: renzhiyuan <465386466@qq.com> Date: Fri May 29 14:18:26 2026 +0800 1 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/entity.go b/entity.go new file mode 100644 index 0000000..6250e36 --- /dev/null +++ b/entity.go @@ -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"` +} diff --git a/func.go b/func.go new file mode 100644 index 0000000..1a99494 --- /dev/null +++ b/func.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e3f75ca --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module l_short_utl_request + +go 1.26.2 diff --git a/option.go b/option.go new file mode 100644 index 0000000..62d5c88 --- /dev/null +++ b/option.go @@ -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) + } +} diff --git a/request.go b/request.go new file mode 100644 index 0000000..42642ca --- /dev/null +++ b/request.go @@ -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 +} diff --git a/short_url.go b/short_url.go new file mode 100644 index 0000000..8c2eaae --- /dev/null +++ b/short_url.go @@ -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, + } +} diff --git a/short_url_test.go b/short_url_test.go new file mode 100644 index 0000000..68fabe9 --- /dev/null +++ b/short_url_test.go @@ -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) +}