使用Go解析HTTP返回数据为struct并存入数据库的操作
简介
之前初级版本的博客:使用Go处理SDK返回的嵌套层级数据并将所需字段存入数据库(一)
之前的这篇博客介绍了如何去处理HTTP请求获取到的响应数据以及转换成map的思路,但是前面那种方法太繁琐了,这里给出优化版本的使用方案以及具体说明。
本优化版本大体的思路为:将HTTP返回的响应转为json结构的string类型数据,然后将json数据转为结构体,最后使用GORM将结构体中的数据写入到MySQL中去!
具体的实现过程如下:首先当然是发送HTTP请求获取响应数据,获取到的响应数据最开始是以字节流的形式存在的,接着将这些字节流形式的数据转换为string类型的数据,从这里开始就与上次的方式有了本质的区别了(之前的方式是转换为map类型)!最后我们将得到的string类型的数据根据返回的格式存入跟已经定义好的与返回格式相同层级结构的结构体中即可!
Go发送HTTP请求获取数据
首先我们需要获取数据,使用Go给特定的URL发送请求的代码如下(注意这里我将结果解析成了string类型):
// base
func baseRqeuestString(args ...string) string {
// token requestMethod url 三个参数是有顺序的!
token := args[0]
requestMethod := args[1]
url := args[2]
client := &http.Client{}
// get请求
req, err := http.NewRequest(requestMethod, url, nil)
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
// 在请求头中加入校验的token
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
log.Fatal(err)
} defer resp.Body.Close()
returnStr, err := ParseResponseString(resp)
//fmt.Println("responseStr>>> " + returnStr) // HTTP字节流转为str的结果
return returnStr
}
// 解析http请求返回的内容 ———— 转换为 string
func ParseResponseString(response *http.Response) (string, error) {
//var result map[string]interface{}
body, err := ioutil.ReadAll(response.Body) // response.Body 是一个数据流
return string(body), err // 将 io数据流转换为string类型返回!
}
得到的结果格式如下
{
"request_status": "SUCCESS",
"request_id": "xxx",
"paging": {},
"adsquads": [
{
"sub_request_status": "SUCCESS",
"adsquad": {
"id": "xx-6b5b-4exx82-a3c2-xx",
"updated_at": "2020-10-05T23:14:44.884Z",
"created_at": "2019-09-16T10:21:34.662Z",
"name": "x Ad, om, x, 23-35+",
"status": "ACTIVE",
"campaign_id": "xxx-xx-44ba-xx-xxx",
"type": "SNAP_ADS",
"targeting": {
"regulated_content": false,
"geos": [
{
"country_code": "om"
}
],
},
"targeting_reach_status": "VALID",
"placement": "UNSUPPORTED",
"billing_event": "IMPRESSION",
}
},
{
"sub_request_status": "SUCCESS",
"adsquad": {
"id": "xx-d717-49f2-x-xx",
"updated_at": "2020-10-05T23:15:47.741Z",
"created_at": "2019-09-18T07:19:26.661Z",
"name": "App Install, kw, Male, 18-35+",
"status": "ACTIVE",
"campaign_id": "xx-d4fe-4a85-878b-xx",
"type": "SNAP_ADS",
"targeting": {
"regulated_content": false,
"geos": [
{
"country_code": "kw"
}
],
},
"targeting_reach_status": "VALID",
}
},
......
]
}
注意理解这个结果的格式十分重要!
定义结构体
我们接下来的工作就是将这个json字符串转换为结构体,然后将数据存入数据库中!
定义结构体主要是为了2个方面的工作:一个是获取json转换结构体的结果,另外就是根据结构体中存的字段将我们需要的数据写入到数据库中。
定义的结构体以及其对应的成员函数如下:
// 存数据库
type AdSetSub struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
CampaignId string `json:"campaign_id"`
CreateTime int64 `json:"-"`
UpdateTime int64 `json:"-"`
AdType string `json:"type"`
AccountId string
}
// json解析 比存入数据库的字段多,因此做组合处理
type AdSet struct {
AdSetSub
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// json解析嵌套格式用到下面2个结构体 ———— 跟HTTP返回的结构保持一致
type AdSetSnapResponse struct {
RequestStatus string `json:"request_status"`
RequestId string `json:"request_id"`
Paging interface{} `json:"paging"`
Adsquads []*AdsquadsSubResponse `json:"adsquads"` // 注意这里的字段要与返回的字段对应上!
}
type AdsquadsSubResponse struct {
Adsquad *AdSet `json:"adsquad"` // 注意这里的字段要与返回的字段对应上!
}
// 根据GORM设置表名 注意用的是AdSetSub
func (a AdSetSub) TableName() string{
return "adset"
}
// AdSet结构体中数据处理
func (a *AdSet) Format(accountId string){
// 时间字符串转时间戳
createTimeStamp, err1 := Timestr2Timestamp(a.CreatedAt)
updateTimeStamp, err2 := Timestr2Timestamp(a.UpdatedAt)
if err1 != nil{
fmt.Println("AdSet转换时间戳异常")
}else{
a.CreateTime = createTimeStamp
}
if err2 != nil{
fmt.Println("AdSet转换时间戳异常")
}else{
a.UpdateTime = updateTimeStamp
}
// 账号ID
a.AccountId = accountId
}
json数据解析为结构体的函数
// 获取某一个account下的adsquad的信息
// 处理账户下的AdSet数据写入数据库中 )———— 主体函数中调用它传入对应参数即可,注意db是gorm的链接对象
func handleAdSetToMySQL(accountId, token string, db *gorm.DB) (insertAdSet int64){
// 得到存放着adset地址的切片
adsets := getAccountAdsquads(baseRqeuestString,accountId,token)
fmt.Println("len_adsets>>> ",len(adsets))
insertAdSet = int64(len(adsets))
for _, adsetObj := range adsets{
// 注意这里的 adsetObj 其实是指针,需要用 *号 获取到具体结构体!
// First方法中要的就是指针,所以不用 &符号获取指针了
err := db.Where("id=?",(*adsetObj).ID).First(adsetObj).Error
if err != nil{ // 写入数据库前做一下字段的格式化或者某些字段的单独处理
(*adsetObj).Format(accountId) // 写入数据库 注意这里用的是 AdSetSub结构体
db.Create(adsetObj.AdSetSub)
}
}
return
}
func getAccountAdsquads(f func(...string) string, accountID string, token string) []*AdSet {
requestMethod := "GET"
url := "https://adsapi.snapchat.com/v1/adaccounts/" + accountID + "/adsquads"
ret := f(token, requestMethod, url)
//fmt.Println("Adsquads 返回的字符串响应>>> ",ret)
result := NewAdSetFromJsonString(ret)
return result
}
// json 转结构体 AdSet
func NewAdSetFromJsonString(str string) []*AdSet{ // 注意 json解析成struct的时候 用这两个结构体解析!
var a []*AdSet
var response AdSetSnapResponse
err := json.NewDecoder(strings.NewReader(str)).Decode(&response)
if err != nil{
fmt.Println("AdSet json转结构体出错!err>>> ",err)
}
// 遍历结果,根据上面的结构将一个个结构体数据存入切片中
for _, AdsetSub := range response.Adsquads{
a = append(a, AdsetSub.Adsquad)
}
return a // 最终返回的是一个切片,里面的元素是json转换为结构体对应的指针
}
主体函数中GORM的简单使用
// 初始化db
db, err := gorm.Open("mysql", "root:123@(localhost)/gorm1?charset=utf8mb4&parseTime=True&loc=Local")
//db, err := gorm.Open("mysql", "aduser:adpm@(39.98.79.195:3309)/ads_server_db?charset=utf8")
//fmt.Printf("db>>> %T, %v
",db, db)
if err != nil {
fmt.Println("err>>> ", err)
// panic
panic(err)
}
// defer
defer db.Close()
// 1 GORM 获取单独的数据
// 从ownership表中获取token及其他的数据
var ownerObj OwnerShip
ownerRet := db.Where("name=?","snap_chat_test").Find(&ownerObj)
if ownerRet.Error != nil{
panic(ownerRet.Error)
}
// 注意 结果放在了 ownerObj 这个结构体里面了!
fmt.Println("refresh_token>>> ",ownerObj.RefreshToken)
// 2 GORM 获取多条数据
// 获取所有账号的ID
var accountsList []AdAccountSub
accRet := db.Find(&accountsList)
if accRet.Error != nil{
panic(accRet.Error)
}
// 注意结果都放在了 accountsList这个切片里面了!
fmt.Println("accountsList>>> ",accountsList)
。。。。。。
时间字符串转时间戳的方法
// 时间字符串转时间戳
func Timestr2Timestamp(str string) (int64, error) {
return Timestr2TimestampBasic(str, "", nil)
}
func Timestr2TimestampBasic(str string, format string, loc *time.Location) (int64, error) {
t, err := Timestr2TimeBasic(str, format, loc)
if err != nil {
return -1., err
}
return (int64(t.UnixNano()) * 1) / 1e6, nil // 时间为毫秒级别,这里是1e6
}
func Timestr2TimeBasic(value string, resultFormat string, resultLoc *time.Location) (time.Time, error) {
/**
- params
value: 转换内容字符串
resultFormat: 结果时间格式
resultLoc: 结果时区
*/
resultLoc = getLocationDefault(resultLoc)
useFormat := []string{ // 可能的转换格式
BINano, BIMicro, BIMil, BISec, BICST, BIUTC, BIDate, BITime,
time.RFC3339,
time.RFC3339Nano,
}
var t time.Time
for _, usef := range useFormat {
tt, error := time.ParseInLocation(usef, value, resultLoc)
t = tt
if error != nil {
continue
}
break
}
if t == getTimeDefault(resultLoc) {
return t, errors.New("时间字符串格式错误")
}
if resultFormat == "" {
// 时间为毫秒级别
resultFormat = "2006-01-02 15:04:05.000"
}
st := t.Format(resultFormat)
fixedt, _ := time.ParseInLocation(resultFormat, st, resultLoc)
return fixedt, nil
}
// 获取time默认值, 造一个错误
func getTimeDefault(loc *time.Location) time.Time {
loc = getLocationDefault(loc)
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "", loc)
return t
}
func getLocationDefault(loc *time.Location) *time.Location {
if loc == nil {
loc, _ = time.LoadLocation("Local")
}
return loc
}