commit 144c1788f7ee85e77a9adbc4dfa0fce959c3ffe8 Author: Hsy <32729842@qq.com> Date: Thu Feb 20 17:32:53 2025 +0800 init 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/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..db33b65 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,18 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://110.40.44.75:3306 + + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/hk.iml b/.idea/hk.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/hk.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..05e09cb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..56782ca --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/doc/api.api b/doc/api.api new file mode 100644 index 0000000..13d5a0c --- /dev/null +++ b/doc/api.api @@ -0,0 +1,103 @@ +syntax = "v1" + +info ( + title: "type title here" + desc: "type desc here" + author: "DevTTL" + email: "admin@devttl.com" + version: "v0.1" +) + +type Order172 { + OrderNo string `json:"OrderNo"` // Y 合作方订单号 + OrderNo172 string `json:"OrderNo172"` // Y 172平台订单号,请求头sign里加密的是这个号码。 + OrderStatus string `json:"OrderStatus"` // Y 订单状态:已发货,已完成,审核不通过,已取消,已撤单 + ThirdPhone string `json:"ThirdPhone"` // N 办理号码 + ExpressName string `json:"ExpressName"` // N 物流公司 + ExpressCode string `json:"ExpressCode"` // N 物流单号 + CardStatus string `json:"CardStatus"` // N 激活状态:已激活,未激活,可能为Null + ActiveTime string `json:"ActiveTime"` // N 激活时间格式yyyy-MM-dd HH:mm:ss,可能为Null + Remark string `json:"Remark"` // N 备注失败原因 +} + +type PushReq { + RequidId string `json:"RequidId"` // Y 推送请求唯一ID + Data Order172 `json:"Data"` // Y 返回订单信息 +} + +type Push172Resp { + Code int64 `json:"code",options=-1|0` // Y 响应码 -1错误 0正常 + Msg string `json:"message"` // Y 错误信息 +} + +type GoodsItem { + Id int64 `json:"id"` // Y 商品ID + Name string `json:"name"` // Y 商品信息 + MainPic string `json:"mainPic"` // Y 主图 + DisableAge string `json:"disableAge"` // Y 年龄限制 + UniFlow string `json:"uniFlow"` // Y 通用流量 + DirFlow string `json:"dirFlow"` // Y 定向流量 + TalkTime string `json:"talkTime"` // Y 通话时长 + Remarks string `json:"remarks"` // Y 备注 +} + +type ( + GoodsListReq { + Type string `json:"type":"type,default=0"` // Y 运营商 0:全部 1:电信 2:联通 3:移动 4:光电 + } + GoodsListResp { + Data []GoodsItem `json:"data"` + } +) + +type ( + GoodsDetailsReq { + Id int64 `path:"id"` + } + GoodsDetailsResp { + Data GoodsItem `json:"data"` // Y 商品详情 + } +) + +type ( + TiktokReq { + VideoUrl string `from:"videoUrl"` // 视频地址 + } + TiktokResp { + 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"` // 结果 + } +) + +@server ( + group: api + prefix: api + middleware: RealIPMiddleware +) +service api { + @handler tiktokInfo + post /tiktok/getVideoInfo (TiktokReq) returns (TiktokResp) + + @handler order172 + post /push/order172 (PushReq) returns (Push172Resp) + + @handler goodsList + get /goods/list (GoodsListReq) returns (GoodsListResp) + + @handler goodsDetails + get /goods/:id (GoodsDetailsReq) returns (GoodsDetailsResp) +} + diff --git a/doc/sql/config_api.sql b/doc/sql/config_api.sql new file mode 100644 index 0000000..b96c5e9 --- /dev/null +++ b/doc/sql/config_api.sql @@ -0,0 +1,8 @@ +CREATE TABLE `config_api` +( + `id` bigint auto_increment NOT NULL COMMENT '主键ID', + `type` varchar(255) NOT NULL COMMENT '接口类型 0:172', + `key` varchar(20) DEFAULT NULL COMMENT 'key', + `secret` varchar(100) DEFAULT NULL COMMENT '密钥', + primary key (id) +) COMMENT ='API配置表'; \ No newline at end of file diff --git a/doc/sql/goods_info.sql b/doc/sql/goods_info.sql new file mode 100644 index 0000000..6a8dd78 --- /dev/null +++ b/doc/sql/goods_info.sql @@ -0,0 +1,28 @@ +CREATE TABLE `goods_info` +( + `id` bigint NOT NULL auto_increment COMMENT '主键ID', + `type` char(1) NOT NULL default '0' COMMENT '运营商 0:未标注 1:电信 2:联通 3:移动 4:光电', + `name` varchar(255) NOT NULL COMMENT '商品名', + `main_pic` varchar(1000) NOT NULL COMMENT '主图', + `little_picture` varchar(1000) DEFAULT '' COMMENT '详情图', + `netAddr` VARCHAR(255) DEFAULT '' COMMENT '商品套餐资料介绍地址', + `area` VARCHAR(10) DEFAULT '' COMMENT '归属地', + + `uni_flow` VARCHAR(10) DEFAULT '' COMMENT '通用流量', + `dir_flow` VARCHAR(10) DEFAULT '' COMMENT '定向流量', + `talk_time` VARCHAR(10) DEFAULT '' COMMENT '通话时间', + + `disable_area` VARCHAR(1000) DEFAULT '' COMMENT '禁发地区', + `disable_age` VARCHAR(100) DEFAULT '' COMMENT '年龄限制', + `disable_contract` VARCHAR(10) DEFAULT '' COMMENT '合约期', + `notes` text COMMENT '商品说明', + + `api_id` bigint DEFAULT NULL COMMENT '三方接口', + `api_product_id` bigint DEFAULT NULL COMMENT '三方商品id', + `number_sel` char(1) NOT NULL DEFAULT '0' COMMENT '是否选号 0:不支持 1:收货地不是归属地 2:收货地是归属地', + `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态 0:上架 1:下架', + `create_time` datetime NOT NULL default current_timestamp() comment '创建时间', + `update_time` datetime NOT NULL default current_timestamp() on update current_timestamp() comment '更新时间', + `remarks` VARCHAR(255) DEFAULT NULL COMMENT '备注', + primary key (id) +) COMMENT '商品列表 '; \ No newline at end of file diff --git a/doc/sql/goods_status.sql b/doc/sql/goods_status.sql new file mode 100644 index 0000000..aa65fef --- /dev/null +++ b/doc/sql/goods_status.sql @@ -0,0 +1,8 @@ +CREATE TABLE `goods_status` +( + `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id', + `api_id` int(255) DEFAULT NULL COMMENT '三方接口', + `api_product_id` int(255) DEFAULT NULL COMMENT '三方商品id', + `status` int(10) DEFAULT NULL COMMENT '商品状态 0=上架 1=下架', + primary key (id) +) COMMENT '商品状态'; \ No newline at end of file diff --git a/doc/sql/oder_info.sql b/doc/sql/oder_info.sql new file mode 100644 index 0000000..dcef490 --- /dev/null +++ b/doc/sql/oder_info.sql @@ -0,0 +1,22 @@ +CREATE TABLE `oder_info` +( + `id` bigint NOT NULL COMMENT '订单id', + `oder_id` varchar(50) NOT NULL COMMENT '订单id', + `goods_id` varchar(50) NOT NULL COMMENT '下单产品ID', + `name` varchar(30) NOT NULL COMMENT '姓名', + `id_card` varchar(18) NOT NULL COMMENT '身份证号', + `phone` varchar(15) NOT NULL COMMENT '手机号码', + `province` varchar(20) NOT NULL COMMENT '省', + `city` varchar(20) NOT NULL COMMENT '城市', + `area` varchar(20) NOT NULL COMMENT '区/县/镇', + `address` varchar(100) NOT NULL COMMENT '详细地址', + `status` char(1) NOT NULL DEFAULT '0' COMMENT '发货处理状态:0=未处理,1=已下单 2=已发货 3已完成 4失败', + `api_id` varchar(255) DEFAULT NULL COMMENT '接口来源', + `api_oder_id` varchar(255) DEFAULT NULL COMMENT '第三方订单', + `third_phone` varchar(255) DEFAULT NULL COMMENT '用户办理的号码', + `card_status` char(1) DEFAULT '0' COMMENT '号码状态', + `create_time` datetime NOT NULL default current_timestamp() comment '创建时间', + `update_time` datetime NOT NULL default current_timestamp() on update current_timestamp() comment '更新时间', + `remarks` varchar(255) DEFAULT NULL COMMENT '备注', + primary key (id) +) COMMENT '订单列表'; \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ef62465 --- /dev/null +++ b/go.mod @@ -0,0 +1,53 @@ +module hk + +go 1.23.4 + +require ( + github.com/robfig/cron/v3 v3.0.1 + github.com/zeromicro/go-zero v1.8.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openzipkin/zipkin-go v0.4.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c4b71ee --- /dev/null +++ b/go.sum @@ -0,0 +1,136 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zeromicro/go-zero v1.8.0 h1:4g/8VW+fOyM51HZYPeI3mXIZdEX+Fl6SsdYX2H5PYw4= +github.com/zeromicro/go-zero v1.8.0/go.mod h1:xDBF+/iDzj30zPvu6HNUIbpz1J6+/g3Sx9D/DytJfss= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +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= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/model/config_api/configapimodel.go b/model/config_api/configapimodel.go new file mode 100644 index 0000000..cc43f78 --- /dev/null +++ b/model/config_api/configapimodel.go @@ -0,0 +1,50 @@ +package config_api + +import ( + "context" + "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ ConfigApiModel = (*customConfigApiModel)(nil) + +type ( + // ConfigApiModel is an interface to be customized, add more methods here, + // and implement the added methods in customConfigApiModel. + ConfigApiModel interface { + configApiModel + withSession(session sqlx.Session) ConfigApiModel + FindList(ctx context.Context) ([]ConfigApi, error) + } + + customConfigApiModel struct { + *defaultConfigApiModel + } +) + +func (m *customConfigApiModel) FindList(ctx context.Context) ([]ConfigApi, error) { + + query := fmt.Sprintf("select %s from %s", configApiRows, m.table) + var resp []ConfigApi + err := m.conn.QueryRowsCtx(ctx, &resp, query) + switch err { + case nil: + return resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + +} + +// NewConfigApiModel returns a model for the database table. +func NewConfigApiModel(conn sqlx.SqlConn) ConfigApiModel { + return &customConfigApiModel{ + defaultConfigApiModel: newConfigApiModel(conn), + } +} + +func (m *customConfigApiModel) withSession(session sqlx.Session) ConfigApiModel { + return NewConfigApiModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/model/config_api/configapimodel_gen.go b/model/config_api/configapimodel_gen.go new file mode 100644 index 0000000..be8b2a7 --- /dev/null +++ b/model/config_api/configapimodel_gen.go @@ -0,0 +1,87 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.6 + +package config_api + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + configApiFieldNames = builder.RawFieldNames(&ConfigApi{}) + configApiRows = strings.Join(configApiFieldNames, ",") + configApiRowsExpectAutoSet = strings.Join(stringx.Remove(configApiFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + configApiRowsWithPlaceHolder = strings.Join(stringx.Remove(configApiFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + configApiModel interface { + Insert(ctx context.Context, data *ConfigApi) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*ConfigApi, error) + Update(ctx context.Context, data *ConfigApi) error + Delete(ctx context.Context, id int64) error + } + + defaultConfigApiModel struct { + conn sqlx.SqlConn + table string + } + + ConfigApi struct { + Id int64 `db:"id"` // 主键ID + Type string `db:"type"` // 接口类型 0:172 + Key sql.NullString `db:"key"` // key + Secret sql.NullString `db:"secret"` // 密钥 + } +) + +func newConfigApiModel(conn sqlx.SqlConn) *defaultConfigApiModel { + return &defaultConfigApiModel{ + conn: conn, + table: "`config_api`", + } +} + +func (m *defaultConfigApiModel) Delete(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultConfigApiModel) FindOne(ctx context.Context, id int64) (*ConfigApi, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", configApiRows, m.table) + var resp ConfigApi + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultConfigApiModel) Insert(ctx context.Context, data *ConfigApi) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?)", m.table, configApiRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Type, data.Key, data.Secret) + return ret, err +} + +func (m *defaultConfigApiModel) Update(ctx context.Context, data *ConfigApi) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, configApiRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.Type, data.Key, data.Secret, data.Id) + return err +} + +func (m *defaultConfigApiModel) tableName() string { + return m.table +} diff --git a/model/config_api/vars.go b/model/config_api/vars.go new file mode 100644 index 0000000..00ee11e --- /dev/null +++ b/model/config_api/vars.go @@ -0,0 +1,5 @@ +package config_api + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/model/goods_info/goodsinfomodel.go b/model/goods_info/goodsinfomodel.go new file mode 100644 index 0000000..ee98015 --- /dev/null +++ b/model/goods_info/goodsinfomodel.go @@ -0,0 +1,54 @@ +package goods_info + +import ( + "context" + "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ GoodsInfoModel = (*customGoodsInfoModel)(nil) + +type ( + // GoodsInfoModel is an interface to be customized, add more methods here, + // and implement the added methods in customGoodsInfoModel. + GoodsInfoModel interface { + goodsInfoModel + withSession(session sqlx.Session) GoodsInfoModel + GetGoodsIndex(ctx context.Context, value string) ([]GoodsInfo, error) + } + + customGoodsInfoModel struct { + *defaultGoodsInfoModel + } +) + +func (m *customGoodsInfoModel) GetGoodsIndex(ctx context.Context, value string) ([]GoodsInfo, error) { + var resp []GoodsInfo + var err error + if len(value) > 1 || value != "0" { + query := fmt.Sprintf("select %s from %s where status = 1 and type = ? order by id desc", goodsInfoRows, m.table) + err = m.conn.QueryRowsCtx(ctx, &resp, query, value) + } else { + query := fmt.Sprintf("select %s from %s where status = 1 order by id desc", goodsInfoRows, m.table) + err = m.conn.QueryRowsCtx(ctx, &resp, query) + } + switch err { + case nil: + return resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +// NewGoodsInfoModel returns a model for the database table. +func NewGoodsInfoModel(conn sqlx.SqlConn) GoodsInfoModel { + return &customGoodsInfoModel{ + defaultGoodsInfoModel: newGoodsInfoModel(conn), + } +} + +func (m *customGoodsInfoModel) withSession(session sqlx.Session) GoodsInfoModel { + return NewGoodsInfoModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/model/goods_info/goodsinfomodel_gen.go b/model/goods_info/goodsinfomodel_gen.go new file mode 100644 index 0000000..c2876af --- /dev/null +++ b/model/goods_info/goodsinfomodel_gen.go @@ -0,0 +1,105 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.6 + +package goods_info + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + goodsInfoFieldNames = builder.RawFieldNames(&GoodsInfo{}) + goodsInfoRows = strings.Join(goodsInfoFieldNames, ",") + goodsInfoRowsExpectAutoSet = strings.Join(stringx.Remove(goodsInfoFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + goodsInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(goodsInfoFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + goodsInfoModel interface { + Insert(ctx context.Context, data *GoodsInfo) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*GoodsInfo, error) + Update(ctx context.Context, data *GoodsInfo) error + Delete(ctx context.Context, id int64) error + } + + defaultGoodsInfoModel struct { + conn sqlx.SqlConn + table string + } + + GoodsInfo struct { + Id int64 `db:"id"` // 主键ID + Type string `db:"type"` // 运营商 0:未标注 1:电信 2:联通 3:移动 4:光电 + Name string `db:"name"` // 商品名 + MainPic string `db:"main_pic"` // 主图 + LittlePicture string `db:"little_picture"` // 详情图 + NetAddr string `db:"netAddr"` // 商品套餐资料介绍地址 + Area string `db:"area"` // 归属地 + UniFlow string `db:"uni_flow"` // 通用流量 + DirFlow string `db:"dir_flow"` // 定向流量 + TalkTime string `db:"talk_time"` // 通话时间 + DisableArea string `db:"disable_area"` // 禁发地区 + DisableAge string `db:"disable_age"` // 年龄限制 + DisableContract string `db:"disable_contract"` // 合约期 + Notes sql.NullString `db:"notes"` // 商品说明 + ApiId sql.NullInt64 `db:"api_id"` // 三方接口 + ApiProductId sql.NullInt64 `db:"api_product_id"` // 三方商品id + NumberSel string `db:"number_sel"` // 是否选号 0:不支持 1:收货地不是归属地 2:收货地是归属地 + Status string `db:"status"` // 状态 0:上架 1:下架 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + Remarks sql.NullString `db:"remarks"` // 备注 + } +) + +func newGoodsInfoModel(conn sqlx.SqlConn) *defaultGoodsInfoModel { + return &defaultGoodsInfoModel{ + conn: conn, + table: "`goods_info`", + } +} + +func (m *defaultGoodsInfoModel) Delete(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultGoodsInfoModel) FindOne(ctx context.Context, id int64) (*GoodsInfo, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", goodsInfoRows, m.table) + var resp GoodsInfo + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultGoodsInfoModel) Insert(ctx context.Context, data *GoodsInfo) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, goodsInfoRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Type, data.Name, data.MainPic, data.LittlePicture, data.NetAddr, data.Area, data.UniFlow, data.DirFlow, data.TalkTime, data.DisableArea, data.DisableAge, data.DisableContract, data.Notes, data.ApiId, data.ApiProductId, data.NumberSel, data.Status, data.Remarks) + return ret, err +} + +func (m *defaultGoodsInfoModel) Update(ctx context.Context, data *GoodsInfo) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, goodsInfoRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.Type, data.Name, data.MainPic, data.LittlePicture, data.NetAddr, data.Area, data.UniFlow, data.DirFlow, data.TalkTime, data.DisableArea, data.DisableAge, data.DisableContract, data.Notes, data.ApiId, data.ApiProductId, data.NumberSel, data.Status, data.Remarks, data.Id) + return err +} + +func (m *defaultGoodsInfoModel) tableName() string { + return m.table +} diff --git a/model/goods_info/vars.go b/model/goods_info/vars.go new file mode 100644 index 0000000..f7b0b75 --- /dev/null +++ b/model/goods_info/vars.go @@ -0,0 +1,5 @@ +package goods_info + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/model/goods_status/goodsstatusmodel.go b/model/goods_status/goodsstatusmodel.go new file mode 100644 index 0000000..5233937 --- /dev/null +++ b/model/goods_status/goodsstatusmodel.go @@ -0,0 +1,92 @@ +package goods_status + +import ( + "context" + "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "strings" +) + +var _ GoodsStatusModel = (*customGoodsStatusModel)(nil) + +type ( + // GoodsStatusModel is an interface to be customized, add more methods here, + // and implement the added methods in customGoodsStatusModel. + GoodsStatusModel interface { + goodsStatusModel + withSession(session sqlx.Session) GoodsStatusModel + DeleteByApiId(ctx context.Context, id int64) error + FindUpList(ctx context.Context, apiId int64) ([]int64, error) + UpdateDownByIds(ctx context.Context, ids []int64) error + FindNotIdOne(ctx context.Context, apiId int64) (int64, error) + } + + customGoodsStatusModel struct { + *defaultGoodsStatusModel + } +) + +func (m *customGoodsStatusModel) FindNotIdOne(ctx context.Context, apiId int64) (int64, error) { + query := fmt.Sprintf("select gs.`api_product_id` from %s gs left join `goods_info` gi on gs.`api_id` = gi.`api_id` where gs.`api_id` = ? and gs.`api_product_id` not in (select gs.api_product_id from `goods_status` gs join `goods_info` gi on gs.`api_id` = gi.`api_id` and gi.api_product_id = gs.api_product_id where gs.`api_id` = ?) limit 1", m.table) + var resp int64 + err := m.conn.QueryRowCtx(ctx, &resp, query, apiId, apiId) + switch err { + case nil: + return resp, nil + case sqlx.ErrNotFound: + return 0, ErrNotFound + default: + return 0, err + } +} + +func (m *customGoodsStatusModel) UpdateDownByIds(ctx context.Context, ids []int64) error { + // 动态构造 IN 子句中的占位符 + placeholders := make([]string, len(ids)) + for i := range ids { + placeholders[i] = "?" + } + query := fmt.Sprintf("update %s set `status` = 0 where `id` in (%s)", m.table, strings.Join(placeholders, ",")) + _, err := m.conn.ExecCtx(ctx, query, convertToInterface(ids)...) + return err +} + +func (m *customGoodsStatusModel) FindUpList(ctx context.Context, apiId int64) ([]int64, error) { + query := fmt.Sprintf("select g.`id` from %s g join `config_api` c on g.`api_id` = c.id where g.status = 0 and c.id = ?", m.table) + var resp []int64 + err := m.conn.QueryRowsCtx(ctx, &resp, query, apiId) + switch err { + case nil: + return resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *customGoodsStatusModel) DeleteByApiId(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `api_id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +// NewGoodsStatusModel returns a model for the database table. +func NewGoodsStatusModel(conn sqlx.SqlConn) GoodsStatusModel { + return &customGoodsStatusModel{ + defaultGoodsStatusModel: newGoodsStatusModel(conn), + } +} + +func (m *customGoodsStatusModel) withSession(session sqlx.Session) GoodsStatusModel { + return NewGoodsStatusModel(sqlx.NewSqlConnFromSession(session)) +} + +// convertToInterface 将 int64 切片转换为接口切片 +func convertToInterface(ids []int64) []interface{} { + result := make([]interface{}, len(ids)) + for i, id := range ids { + result[i] = id + } + return result +} diff --git a/model/goods_status/goodsstatusmodel_gen.go b/model/goods_status/goodsstatusmodel_gen.go new file mode 100644 index 0000000..2c29817 --- /dev/null +++ b/model/goods_status/goodsstatusmodel_gen.go @@ -0,0 +1,87 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.6 + +package goods_status + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + goodsStatusFieldNames = builder.RawFieldNames(&GoodsStatus{}) + goodsStatusRows = strings.Join(goodsStatusFieldNames, ",") + goodsStatusRowsExpectAutoSet = strings.Join(stringx.Remove(goodsStatusFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + goodsStatusRowsWithPlaceHolder = strings.Join(stringx.Remove(goodsStatusFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + goodsStatusModel interface { + Insert(ctx context.Context, data *GoodsStatus) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*GoodsStatus, error) + Update(ctx context.Context, data *GoodsStatus) error + Delete(ctx context.Context, id int64) error + } + + defaultGoodsStatusModel struct { + conn sqlx.SqlConn + table string + } + + GoodsStatus struct { + Id int64 `db:"id"` // id + ApiId sql.NullInt64 `db:"api_id"` // 三方接口 + ApiProductId sql.NullInt64 `db:"api_product_id"` // 三方商品id + Status sql.NullInt64 `db:"status"` // 商品状态 0=上架 1=下架 + } +) + +func newGoodsStatusModel(conn sqlx.SqlConn) *defaultGoodsStatusModel { + return &defaultGoodsStatusModel{ + conn: conn, + table: "`goods_status`", + } +} + +func (m *defaultGoodsStatusModel) Delete(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultGoodsStatusModel) FindOne(ctx context.Context, id int64) (*GoodsStatus, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", goodsStatusRows, m.table) + var resp GoodsStatus + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultGoodsStatusModel) Insert(ctx context.Context, data *GoodsStatus) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?)", m.table, goodsStatusRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.ApiId, data.ApiProductId, data.Status) + return ret, err +} + +func (m *defaultGoodsStatusModel) Update(ctx context.Context, data *GoodsStatus) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, goodsStatusRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.ApiId, data.ApiProductId, data.Status, data.Id) + return err +} + +func (m *defaultGoodsStatusModel) tableName() string { + return m.table +} diff --git a/model/goods_status/vars.go b/model/goods_status/vars.go new file mode 100644 index 0000000..0a806da --- /dev/null +++ b/model/goods_status/vars.go @@ -0,0 +1,5 @@ +package goods_status + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/model/order_info/oderinfomodel.go b/model/order_info/oderinfomodel.go new file mode 100644 index 0000000..97edd22 --- /dev/null +++ b/model/order_info/oderinfomodel.go @@ -0,0 +1,29 @@ +package order_info + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var _ OderInfoModel = (*customOderInfoModel)(nil) + +type ( + // OderInfoModel is an interface to be customized, add more methods here, + // and implement the added methods in customOderInfoModel. + OderInfoModel interface { + oderInfoModel + withSession(session sqlx.Session) OderInfoModel + } + + customOderInfoModel struct { + *defaultOderInfoModel + } +) + +// NewOderInfoModel returns a model for the database table. +func NewOderInfoModel(conn sqlx.SqlConn) OderInfoModel { + return &customOderInfoModel{ + defaultOderInfoModel: newOderInfoModel(conn), + } +} + +func (m *customOderInfoModel) withSession(session sqlx.Session) OderInfoModel { + return NewOderInfoModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/model/order_info/oderinfomodel_gen.go b/model/order_info/oderinfomodel_gen.go new file mode 100644 index 0000000..61560e9 --- /dev/null +++ b/model/order_info/oderinfomodel_gen.go @@ -0,0 +1,102 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.6 + +package order_info + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + oderInfoFieldNames = builder.RawFieldNames(&OderInfo{}) + oderInfoRows = strings.Join(oderInfoFieldNames, ",") + oderInfoRowsExpectAutoSet = strings.Join(stringx.Remove(oderInfoFieldNames, "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + oderInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(oderInfoFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + oderInfoModel interface { + Insert(ctx context.Context, data *OderInfo) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*OderInfo, error) + Update(ctx context.Context, data *OderInfo) error + Delete(ctx context.Context, id int64) error + } + + defaultOderInfoModel struct { + conn sqlx.SqlConn + table string + } + + OderInfo struct { + Id int64 `db:"id"` // 订单id + OderId string `db:"oder_id"` // 订单id + GoodsId string `db:"goods_id"` // 下单产品ID + Name string `db:"name"` // 姓名 + IdCard string `db:"id_card"` // 身份证号 + Phone string `db:"phone"` // 手机号码 + Province string `db:"province"` // 省 + City string `db:"city"` // 城市 + Area string `db:"area"` // 区/县/镇 + Address string `db:"address"` // 详细地址 + Status string `db:"status"` // 发货处理状态:0=未处理,1=已下单 2=已发货 3已完成 4失败 + ApiId sql.NullString `db:"api_id"` // 接口来源 + ApiOderId sql.NullString `db:"api_oder_id"` // 第三方订单 + ThirdPhone sql.NullString `db:"third_phone"` // 用户办理的号码 + CardStatus string `db:"card_status"` // 号码状态 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + Remarks sql.NullString `db:"remarks"` // 备注 + } +) + +func newOderInfoModel(conn sqlx.SqlConn) *defaultOderInfoModel { + return &defaultOderInfoModel{ + conn: conn, + table: "`oder_info`", + } +} + +func (m *defaultOderInfoModel) Delete(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultOderInfoModel) FindOne(ctx context.Context, id int64) (*OderInfo, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", oderInfoRows, m.table) + var resp OderInfo + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultOderInfoModel) Insert(ctx context.Context, data *OderInfo) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, oderInfoRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Id, data.OderId, data.GoodsId, data.Name, data.IdCard, data.Phone, data.Province, data.City, data.Area, data.Address, data.Status, data.ApiId, data.ApiOderId, data.ThirdPhone, data.CardStatus, data.Remarks) + return ret, err +} + +func (m *defaultOderInfoModel) Update(ctx context.Context, data *OderInfo) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, oderInfoRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.OderId, data.GoodsId, data.Name, data.IdCard, data.Phone, data.Province, data.City, data.Area, data.Address, data.Status, data.ApiId, data.ApiOderId, data.ThirdPhone, data.CardStatus, data.Remarks, data.Id) + return err +} + +func (m *defaultOderInfoModel) tableName() string { + return m.table +} diff --git a/model/order_info/vars.go b/model/order_info/vars.go new file mode 100644 index 0000000..d386e26 --- /dev/null +++ b/model/order_info/vars.go @@ -0,0 +1,5 @@ +package order_info + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/pkg/middleware/getRealIP.go b/pkg/middleware/getRealIP.go new file mode 100644 index 0000000..467642b --- /dev/null +++ b/pkg/middleware/getRealIP.go @@ -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 +} diff --git a/pkg/util/172/172.go b/pkg/util/172/172.go new file mode 100644 index 0000000..92a5203 --- /dev/null +++ b/pkg/util/172/172.go @@ -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 +} diff --git a/pkg/util/172/172_test.go b/pkg/util/172/172_test.go new file mode 100644 index 0000000..5f2c1de --- /dev/null +++ b/pkg/util/172/172_test.go @@ -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("请输入正确的代理手机号") + } +} diff --git a/pkg/util/172/api_result.go b/pkg/util/172/api_result.go new file mode 100644 index 0000000..5bfdb02 --- /dev/null +++ b/pkg/util/172/api_result.go @@ -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)) +} diff --git a/pkg/util/172/black_user.go b/pkg/util/172/black_user.go new file mode 100644 index 0000000..c9733dc --- /dev/null +++ b/pkg/util/172/black_user.go @@ -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 +} diff --git a/pkg/util/172/goods_v2.go b/pkg/util/172/goods_v2.go new file mode 100644 index 0000000..7602ede --- /dev/null +++ b/pkg/util/172/goods_v2.go @@ -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 +} diff --git a/pkg/util/172/pick_number.go b/pkg/util/172/pick_number.go new file mode 100644 index 0000000..2ff0b5e --- /dev/null +++ b/pkg/util/172/pick_number.go @@ -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 +} diff --git a/pkg/util/httpUtil/post_request.go b/pkg/util/httpUtil/post_request.go new file mode 100644 index 0000000..c3d9d01 --- /dev/null +++ b/pkg/util/httpUtil/post_request.go @@ -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 +} diff --git a/pkg/util/httpUtil/post_request_test.go b/pkg/util/httpUtil/post_request_test.go new file mode 100644 index 0000000..8655968 --- /dev/null +++ b/pkg/util/httpUtil/post_request_test.go @@ -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) +} diff --git a/pkg/util/strUtil/strUtil.go b/pkg/util/strUtil/strUtil.go new file mode 100644 index 0000000..92434fd --- /dev/null +++ b/pkg/util/strUtil/strUtil.go @@ -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) +} diff --git a/pkg/util/tiktok/tiktok.go b/pkg/util/tiktok/tiktok.go new file mode 100644 index 0000000..38dc601 --- /dev/null +++ b/pkg/util/tiktok/tiktok.go @@ -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 +} diff --git a/pkg/util/tiktok/tiktok_test.go b/pkg/util/tiktok/tiktok_test.go new file mode 100644 index 0000000..f7dbad2 --- /dev/null +++ b/pkg/util/tiktok/tiktok_test.go @@ -0,0 +1,7 @@ +package tiktok + +import "testing" + +func TestTikTok(test *testing.T) { + GetTiktokVideoInfo("https://www.tiktok.com/@baotramthieu/video/6887554215598689538") +} diff --git a/service/api/Dockerfile b/service/api/Dockerfile new file mode 100644 index 0000000..ca45876 --- /dev/null +++ b/service/api/Dockerfile @@ -0,0 +1,33 @@ +FROM golang:alpine as builder +# 设置工作目录 +WORKDIR /hk +# 将当前目录内容拷到工作目录 (相对路径) +COPY ../../../ . + +# 配置 golang 环境 +RUN go env -w GO111MODULE=on \ + && go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct \ + && go mod tidy \ + && go build -o main ./service/api/api.go + +# 使用更小的基础镜像 +FROM alpine:latest + +# 设置工作目录 +WORKDIR /hk +ENV WORK_PATH /hk + +WORKDIR ${WORK_PATH} + +# 从构建阶段复制二进制文件 +COPY --from=builder ${WORK_PATH}/main . +COPY --from=0 ${WORK_PATH}/service/api/etc/api.yaml ./config/ + +# 暴露端口 +EXPOSE 8888 + +# 如果不使用上面的方式需要打开下面这行注释 +# ENTRYPOINT ./main -c config.docker.toml + +############################################### +CMD ["./main","-f","./config/api.yaml"] \ No newline at end of file diff --git a/service/api/api.go b/service/api/api.go new file mode 100644 index 0000000..46d5e32 --- /dev/null +++ b/service/api/api.go @@ -0,0 +1,34 @@ +package main + +import ( + "flag" + "fmt" + "hk/service/api/internal/cmd" + "hk/service/api/internal/config" + "hk/service/api/internal/handler" + "hk/service/api/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +var configFile = flag.String("f", "service/api/etc/api.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + server := rest.MustNewServer(c.RestConf) + + defer server.Stop() + + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + cmd.Execute(ctx) + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/service/api/etc/api.yaml b/service/api/etc/api.yaml new file mode 100644 index 0000000..eedc39c --- /dev/null +++ b/service/api/etc/api.yaml @@ -0,0 +1,22 @@ +Name: HK +Host: 0.0.0.0 +Port: 8888 + +# 控制go-zero提供的中间件是否启用 +# Middlewares: +# Breaker: false +# Shedding: false +# Metrics: false +Log: + Stat: false + +Mysql: + DataSource: root:G6fB2okoPstKvA6R@tcp(110.40.44.75:3306)/hk?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + +Auth: + AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl + AccessExpire: 86400 + +Redis: + Host: 192.168.1.13:6379 + Type: node \ No newline at end of file diff --git a/service/api/internal/cmd/config_api_list.go b/service/api/internal/cmd/config_api_list.go new file mode 100644 index 0000000..612a24c --- /dev/null +++ b/service/api/internal/cmd/config_api_list.go @@ -0,0 +1,145 @@ +package cmd + +import ( + "context" + "database/sql" + "fmt" + "hk/model/config_api" + "hk/model/goods_info" + "hk/model/goods_status" + _73 "hk/pkg/util/172" + "hk/pkg/util/strUtil" + "strconv" + "time" +) + +func configApiList() { + configApiInfo, err := svcCtx.ConfigApiModel.FindList(context.Background()) + if err != nil { + fmt.Println(err) + } + if configApiInfo == nil { + return + } + for _, item := range configApiInfo { + switch item.Type { + case "0": + getGoodsStatus(item) + updateGoodsStatus(item) + } + } +} + +func configApiListv1() { + configApiInfo, err := svcCtx.ConfigApiModel.FindList(context.Background()) + if err != nil { + fmt.Println(err) + } + if configApiInfo == nil { + return + } + for _, item := range configApiInfo { + switch item.Type { + case "0": + addGoodsInfo(item) + } + } +} + +func addGoodsInfo(configApi config_api.ConfigApi) { + ids, err := svcCtx.GoodsStatusModel.FindNotIdOne(context.Background(), configApi.Id) + if err != nil || ids <= 0 { + return + } + + req := _73.GoodsReq{ + ProductID: strconv.FormatInt(ids, 10), + BaseParams: _73.BaseParams{ + UserID: strUtil.ValidString(configApi.Key), + UserSign: strUtil.ValidString(configApi.Secret), + }, + } + + result, err := _73.GetProductV2(req) + if err != nil { + return + } + + if result.Code != 0 || len(result.Data) <= 0 { + return + } + data := result.Data[0] + status := "0" + if data.Flag { + status = "1" + } + goodsStatus := goods_info.GoodsInfo{ + Name: data.ProductName, + Type: "0", + MainPic: data.MainPic, + LittlePicture: data.LittlePicture, + NetAddr: data.NetAddr, + Area: data.Area, + DisableArea: data.DisableArea, + DisableAge: "", + DisableContract: "", + Notes: sql.NullString{}, + ApiId: strUtil.Int64ToNullInt64(configApi.Id), + ApiProductId: strUtil.Int64ToNullInt64(data.ProductID), + NumberSel: strconv.Itoa(data.NumberSel), + Status: status, + CreateTime: time.Now(), + UpdateTime: time.Now(), + Remarks: sql.NullString{}, + } + svcCtx.GoodsInfoModel.Insert(context.Background(), &goodsStatus) +} + +func updateGoodsStatus(item config_api.ConfigApi) { + ids, err := svcCtx.GoodsStatusModel.FindUpList(context.Background(), item.Id) + if err != nil { + return + } + if len(ids) <= 0 { + return + } + svcCtx.GoodsStatusModel.UpdateDownByIds(context.Background(), ids) +} + +func getGoodsStatus(configApi config_api.ConfigApi) { + req := _73.GoodsReq{ + ProductID: "", + BaseParams: _73.BaseParams{ + UserID: strUtil.ValidString(configApi.Key), + UserSign: strUtil.ValidString(configApi.Secret), + }, + } + + result, err := _73.GetProduct(req) + if err != nil { + return + } + + if result.Code == 0 { + var goodsStatusList []goods_status.GoodsStatus + if len(result.Data) > 0 { + err = svcCtx.GoodsStatusModel.DeleteByApiId(context.Background(), configApi.Id) + if err != nil { + return + } + } + for _, item := range result.Data { + var status int64 = 0 + if item.Flag == "上架中" { + status = 1 + } + goodsStatus := goods_status.GoodsStatus{ + ApiId: strUtil.Int64ToNullInt64(configApi.Id), + ApiProductId: strUtil.Int64ToNullInt64(item.ProductID), + Status: strUtil.Int64ToNullInt64(status), + } + svcCtx.GoodsStatusModel.Insert(context.Background(), &goodsStatus) + goodsStatusList = append(goodsStatusList, goodsStatus) + } + } +} diff --git a/service/api/internal/cmd/cronx/time.go b/service/api/internal/cmd/cronx/time.go new file mode 100644 index 0000000..4c6fa3f --- /dev/null +++ b/service/api/internal/cmd/cronx/time.go @@ -0,0 +1,46 @@ +package cronx + +// 每5秒 +func Every5s() string { + return "*/5 * * * * *" +} + +// 每10秒 +func Every10s() string { + return "*/10 * * * * *" +} + +// 每分钟 +func EveryMinute() string { + return "0 */1 * * * *" +} + +// 每五分钟 +func EveryFiveMinute() string { + return "0 */5 * * * *" +} + +// 每十分钟 +func EveryTenMinute() string { + return "0 */10 * * * *" +} + +// 每半小时 +func EveryHalfHour() string { + return "0 0,30 * * * *" +} + +// EveryHour 每小时 +func EveryHour() string { + return "0 0 * * * *" +} + +// 每几分钟执行 1,2,3,26 +func Hourly(m string) string { + return "0 " + m + " * * * *" +} + +// 每天几点执行 +func Daily(h string) string { + return "0 0 " + h + " * * *" +} diff --git a/service/api/internal/cmd/root.go b/service/api/internal/cmd/root.go new file mode 100644 index 0000000..8ef06e8 --- /dev/null +++ b/service/api/internal/cmd/root.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + "github.com/robfig/cron/v3" + "hk/service/api/internal/svc" +) + +var svcCtx *svc.ServiceContext + +func Execute(ctx *svc.ServiceContext) { + svcCtx = ctx + c := cron.New(cron.WithSeconds()) + + ScheduleRun(c) + fmt.Println("定时任务启动...") + go c.Start() + defer c.Stop() + //select {} +} diff --git a/service/api/internal/cmd/schedule.go b/service/api/internal/cmd/schedule.go new file mode 100644 index 0000000..39d10f1 --- /dev/null +++ b/service/api/internal/cmd/schedule.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/robfig/cron/v3" + "hk/service/api/internal/cmd/cronx" +) + +func ScheduleRun(c *cron.Cron) { + c.AddFunc(cronx.EveryHour(), configApiList) + c.AddFunc(cronx.EveryTenMinute(), configApiListv1) +} diff --git a/service/api/internal/config/config.go b/service/api/internal/config/config.go new file mode 100644 index 0000000..728434e --- /dev/null +++ b/service/api/internal/config/config.go @@ -0,0 +1,21 @@ +package config + +import "github.com/zeromicro/go-zero/rest" + +type Config struct { + rest.RestConf + + Auth struct { + AccessSecret string + AccessExpire int64 + } + + Mysql struct { + DataSource string + } + + Redis struct { + Host string + Type string + } +} diff --git a/service/api/internal/handler/api/goodsdetailshandler.go b/service/api/internal/handler/api/goodsdetailshandler.go new file mode 100644 index 0000000..6754e5c --- /dev/null +++ b/service/api/internal/handler/api/goodsdetailshandler.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "hk/service/api/internal/logic/api" + "hk/service/api/internal/svc" + "hk/service/api/internal/types" +) + +func GoodsDetailsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GoodsDetailsReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := api.NewGoodsDetailsLogic(r.Context(), svcCtx) + resp, err := l.GoodsDetails(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/service/api/internal/handler/api/goodslisthandler.go b/service/api/internal/handler/api/goodslisthandler.go new file mode 100644 index 0000000..09938d0 --- /dev/null +++ b/service/api/internal/handler/api/goodslisthandler.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "hk/service/api/internal/logic/api" + "hk/service/api/internal/svc" + "hk/service/api/internal/types" +) + +func GoodsListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GoodsListReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := api.NewGoodsListLogic(r.Context(), svcCtx) + resp, err := l.GoodsList(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/service/api/internal/handler/api/order172handler.go b/service/api/internal/handler/api/order172handler.go new file mode 100644 index 0000000..ae0476d --- /dev/null +++ b/service/api/internal/handler/api/order172handler.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "hk/service/api/internal/logic/api" + "hk/service/api/internal/svc" + "hk/service/api/internal/types" +) + +func Order172Handler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.PushReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := api.NewOrder172Logic(r.Context(), svcCtx) + resp, err := l.Order172(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/service/api/internal/handler/api/tiktokinfohandler.go b/service/api/internal/handler/api/tiktokinfohandler.go new file mode 100644 index 0000000..e5492c9 --- /dev/null +++ b/service/api/internal/handler/api/tiktokinfohandler.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "hk/service/api/internal/logic/api" + "hk/service/api/internal/svc" + "hk/service/api/internal/types" +) + +func TiktokInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.TiktokReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := api.NewTiktokInfoLogic(r.Context(), svcCtx) + resp, err := l.TiktokInfo(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/service/api/internal/handler/routes.go b/service/api/internal/handler/routes.go new file mode 100644 index 0000000..f7dd4cb --- /dev/null +++ b/service/api/internal/handler/routes.go @@ -0,0 +1,44 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.7.6 + +package handler + +import ( + "net/http" + + api "hk/service/api/internal/handler/api" + "hk/service/api/internal/svc" + + "github.com/zeromicro/go-zero/rest" +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.RealIPMiddleware}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/goods/:id", + Handler: api.GoodsDetailsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/goods/list", + Handler: api.GoodsListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/push/order172", + Handler: api.Order172Handler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/tiktok/getVideoInfo", + Handler: api.TiktokInfoHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api"), + ) +} diff --git a/service/api/internal/logic/api/goodsdetailslogic.go b/service/api/internal/logic/api/goodsdetailslogic.go new file mode 100644 index 0000000..898bdb5 --- /dev/null +++ b/service/api/internal/logic/api/goodsdetailslogic.go @@ -0,0 +1,48 @@ +package api + +import ( + "context" + "hk/pkg/util/strUtil" + + "hk/service/api/internal/svc" + "hk/service/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GoodsDetailsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGoodsDetailsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GoodsDetailsLogic { + return &GoodsDetailsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GoodsDetailsLogic) GoodsDetails(req *types.GoodsDetailsReq) (resp *types.GoodsDetailsResp, err error) { + if req.Id <= 0 { + return nil, nil + } + goods, err := l.svcCtx.GoodsInfoModel.FindOne(l.ctx, req.Id) + if err != nil || goods == nil || goods.Status == "0" { + return nil, err + } + resp = &types.GoodsDetailsResp{ + Data: types.GoodsItem{ + Id: goods.Id, + Name: goods.Name, + MainPic: goods.MainPic, + DisableAge: goods.DisableAge, + UniFlow: goods.UniFlow, + DirFlow: goods.DirFlow, + TalkTime: goods.TalkTime, + Remarks: strUtil.ValidString(goods.Remarks), + }, + } + return resp, nil +} diff --git a/service/api/internal/logic/api/goodslistlogic.go b/service/api/internal/logic/api/goodslistlogic.go new file mode 100644 index 0000000..145ca69 --- /dev/null +++ b/service/api/internal/logic/api/goodslistlogic.go @@ -0,0 +1,50 @@ +package api + +import ( + "context" + "hk/pkg/util/strUtil" + + "hk/service/api/internal/svc" + "hk/service/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GoodsListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGoodsListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GoodsListLogic { + return &GoodsListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GoodsListLogic) GoodsList(req *types.GoodsListReq) (resp *types.GoodsListResp, err error) { + goods, err := l.svcCtx.GoodsInfoModel.GetGoodsIndex(l.ctx, req.Type) + if err != nil || len(goods) == 0 { + return nil, err + } + var goodsList []types.GoodsItem + for _, item := range goods { + good := types.GoodsItem{ + Id: item.Id, + Name: item.Name, + MainPic: item.MainPic, + DisableAge: item.DisableAge, + UniFlow: item.UniFlow, + DirFlow: item.DirFlow, + TalkTime: item.TalkTime, + Remarks: strUtil.ValidString(item.Remarks), + } + goodsList = append(goodsList, good) + } + resp = &types.GoodsListResp{ + Data: goodsList, + } + return resp, nil +} diff --git a/service/api/internal/logic/api/order172logic.go b/service/api/internal/logic/api/order172logic.go new file mode 100644 index 0000000..11404e0 --- /dev/null +++ b/service/api/internal/logic/api/order172logic.go @@ -0,0 +1,30 @@ +package api + +import ( + "context" + + "hk/service/api/internal/svc" + "hk/service/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type Order172Logic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewOrder172Logic(ctx context.Context, svcCtx *svc.ServiceContext) *Order172Logic { + return &Order172Logic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *Order172Logic) Order172(req *types.PushReq) (resp *types.Push172Resp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/service/api/internal/logic/api/tiktokinfologic.go b/service/api/internal/logic/api/tiktokinfologic.go new file mode 100644 index 0000000..f41b181 --- /dev/null +++ b/service/api/internal/logic/api/tiktokinfologic.go @@ -0,0 +1,53 @@ +package api + +import ( + "context" + "hk/pkg/util/tiktok" + + "hk/service/api/internal/svc" + "hk/service/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type TiktokInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewTiktokInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TiktokInfoLogic { + return &TiktokInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *TiktokInfoLogic) TiktokInfo(req *types.TiktokReq) (resp *types.TiktokResp, err error) { + if len(req.VideoUrl) <= 0 { + return nil, nil + } + tiktokInfo, err := tiktok.GetTiktokVideoInfo(req.VideoUrl) + if err != nil || tiktokInfo == nil { + return nil, nil + } + resp = &types.TiktokResp{ + AuthorID: tiktokInfo.AuthorID, + AuthorAvatar: tiktokInfo.AuthorAvatar, + AuthorUniqueID: tiktokInfo.AuthorUniqueID, + AuthorNickname: tiktokInfo.AuthorNickname, + VideoID: tiktokInfo.VideoID, + VideoTitle: tiktokInfo.VideoTitle, + OriginCover: tiktokInfo.OriginCover, + DynamicCover: tiktokInfo.DynamicCover, + DestinationURL: tiktokInfo.DestinationURL, + WatermarkVideoURL: tiktokInfo.WatermarkVideoURL, + MusicURL: tiktokInfo.MusicURL, + OriginalURL: tiktokInfo.OriginalURL, + DownloaderURL: tiktokInfo.DownloaderURL, + CreateTime: tiktokInfo.CreateTime, + Result: true, + } + return resp, nil +} diff --git a/service/api/internal/middleware/realipmiddleware.go b/service/api/internal/middleware/realipmiddleware.go new file mode 100644 index 0000000..fb32431 --- /dev/null +++ b/service/api/internal/middleware/realipmiddleware.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "context" + "hk/pkg/middleware" + "net/http" +) + +type RealIPMiddleware struct { +} + +func NewRealIPMiddleware() *RealIPMiddleware { + return &RealIPMiddleware{} +} + +func (m *RealIPMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ipaddress := middleware.GetRealIP(r) + ctx := context.WithValue(r.Context(), "ip", ipaddress) + next(w, r.WithContext(ctx)) + } +} diff --git a/service/api/internal/svc/servicecontext.go b/service/api/internal/svc/servicecontext.go new file mode 100644 index 0000000..248683b --- /dev/null +++ b/service/api/internal/svc/servicecontext.go @@ -0,0 +1,29 @@ +package svc + +import ( + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest" + "hk/model/config_api" + "hk/model/goods_info" + "hk/model/goods_status" + "hk/service/api/internal/config" + "hk/service/api/internal/middleware" +) + +type ServiceContext struct { + Config config.Config + RealIPMiddleware rest.Middleware + ConfigApiModel config_api.ConfigApiModel + GoodsStatusModel goods_status.GoodsStatusModel + GoodsInfoModel goods_info.GoodsInfoModel +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config: c, + RealIPMiddleware: middleware.NewRealIPMiddleware().Handle, + ConfigApiModel: config_api.NewConfigApiModel(sqlx.NewMysql(c.Mysql.DataSource)), + GoodsStatusModel: goods_status.NewGoodsStatusModel(sqlx.NewMysql(c.Mysql.DataSource)), + GoodsInfoModel: goods_info.NewGoodsInfoModel(sqlx.NewMysql(c.Mysql.DataSource)), + } +} diff --git a/service/api/internal/types/types.go b/service/api/internal/types/types.go new file mode 100644 index 0000000..9a753f3 --- /dev/null +++ b/service/api/internal/types/types.go @@ -0,0 +1,75 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.7.6 + +package types + +type GoodsDetailsReq struct { + Id int64 `path:"id"` +} + +type GoodsDetailsResp struct { + Data GoodsItem `json:"data"` // Y 商品详情 +} + +type GoodsItem struct { + Id int64 `json:"id"` // Y 商品ID + Name string `json:"name"` // Y 商品信息 + MainPic string `json:"mainPic"` // Y 主图 + DisableAge string `json:"disableAge"` // Y 年龄限制 + UniFlow string `json:"uniFlow"` // Y 通用流量 + DirFlow string `json:"dirFlow"` // Y 定向流量 + TalkTime string `json:"talkTime"` // Y 通话时长 + Remarks string `json:"remarks"` // Y 备注 +} + +type GoodsListReq struct { + Type string `json:"type":"type,default=0"` // Y 运营商 0:全部 1:电信 2:联通 3:移动 4:光电 +} + +type GoodsListResp struct { + Data []GoodsItem `json:"data"` +} + +type Order172 struct { + OrderNo string `json:"OrderNo"` // Y 合作方订单号 + OrderNo172 string `json:"OrderNo172"` // Y 172平台订单号,请求头sign里加密的是这个号码。 + OrderStatus string `json:"OrderStatus"` // Y 订单状态:已发货,已完成,审核不通过,已取消,已撤单 + ThirdPhone string `json:"ThirdPhone"` // N 办理号码 + ExpressName string `json:"ExpressName"` // N 物流公司 + ExpressCode string `json:"ExpressCode"` // N 物流单号 + CardStatus string `json:"CardStatus"` // N 激活状态:已激活,未激活,可能为Null + ActiveTime string `json:"ActiveTime"` // N 激活时间格式yyyy-MM-dd HH:mm:ss,可能为Null + Remark string `json:"Remark"` // N 备注失败原因 +} + +type Push172Resp struct { + Code int64 `json:"code",options=-1|0` // Y 响应码 -1错误 0正常 + Msg string `json:"message"` // Y 错误信息 +} + +type PushReq struct { + RequidId string `json:"RequidId"` // Y 推送请求唯一ID + Data Order172 `json:"Data"` // Y 返回订单信息 +} + +type TiktokReq struct { + VideoUrl string `json:"videoUrl"` // 视频地址 +} + +type TiktokResp 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"` // 结果 +}