Github地址
前言
最近有需要研究陰歷和陽歷互相轉換的問題。因此找到兩個庫carbon和solarlunar
但是感覺計算出來的總是不太放心,而且也會占用計算資源。我的想法是通過接口獲取現成的陰歷和陽歷數據,存到本地數據庫,這樣查詢的時候一步到位。
方案
我通過百度搜索
萬年歷
,抓取網頁請求得到百度的一個接口正好可以獲取萬年歷的信息,還是挺全面的。
因此我寫代碼實現了將百度萬年歷的數據獲取,然后存入數據庫的代碼。
下面是calendar.go
的代碼,主要使用gorm
創建表,以及寫入拉取的數據。
package calendar
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type (
PerpetualCalendar struct {
//Status string `json:"status"`
//T string `json:"t"`
//SetCacheTime string `json:"set_cache_time"`
Data []PerpetualCalendarData `json:"data"`
}
PerpetualCalendarData struct {
//ExtendedLocation string `json:"ExtendedLocation"`
//OriginQuery string `json:"OriginQuery"`
//SiteID int `json:"SiteId"`
//StdStg int `json:"StdStg"`
//StdStl int `json:"StdStl"`
//SelectTime int `json:"_select_time"`
//UpdateTime string `json:"_update_time"`
//Version int `json:"_version"`
//Appinfo string `json:"appinfo"`
//CambrianAppid string `json:"cambrian_appid"`
//DispType int `json:"disp_type"`
//Fetchkey string `json:"fetchkey"`
//Key string `json:"key"`
//Loc string `json:"loc"`
//Resourceid string `json:"resourceid"`
//RoleID int `json:"role_id"`
//Showlamp string `json:"showlamp"`
//Tplt string `json:"tplt"`
//URL string `json:"url"`
Almanac []PerpetualCalendarAlmanac `json:"almanac"`
}
PerpetualCalendarAlmanac struct {
Id int `gorm:"primarykey"` // 自增主鍵
Animal string `json:"animal" gorm:"column:animal;not null;size:4"` // 生肖
Suit string `json:"suit" gorm:"column:suit;not null"` // 宜
Avoid string `json:"avoid" gorm:"column:avoid;not null"` // 忌
CnDay string `json:"cnDay" gorm:"column:cnDay;not null;size:4"` // 星期
Day int `json:"day,string" gorm:"column:day;not null;uniqueIndex:Solar"` // 陽歷日
Month int `json:"month,string" gorm:"column:month;not null;uniqueIndex:Solar"` // 陽歷月
Year int `json:"year,string" gorm:"column:year;not null;uniqueIndex:Solar"` // 陽歷年
GzDate string `json:"gzDate" gorm:"column:gzDate;not null;size:8"` // 干支日
GzMonth string `json:"gzMonth" gorm:"column:gzMonth;not null;size:8"` // 干支月
GzYear string `json:"gzYear" gorm:"column:gzYear;not null;size:8"` // 干支年
IsBigMonth string `json:"isBigMonth" gorm:"-"` // json取數據,忽略gorm
IsBigMonthBool bool `gorm:"column:isBigMonth;not null;default:0"` // 是否為陰歷大月
LDate string `json:"lDate" gorm:"column:lDate;not null;size:4"` // 陰歷日,漢字
LMonth string `json:"lMonth" gorm:"column:lMonth;not null;size:4"` // 陰歷月,漢字,帶'閏'字表示閏月
LunarDate int `json:"lunarDate,string" gorm:"column:lunarDate;not null;index:Lunar"` // 陰歷日,數字
LunarMonth int `json:"lunarMonth,string" gorm:"column:lunarMonth;not null;index:Lunar"` // 陰歷月,數字
LunarYear int `json:"lunarYear,string" gorm:"column:lunarYear;not null;index:Lunar"` // 陰歷年,數字
ODate time.Time `json:"oDate" gorm:"column:oDate;not null"` // ODate.Local(),陽歷當天0點
Term string `json:"term,omitempty" gorm:"column:term;not null"` // 如'除夕','萬聖節','三伏'等
Desc string `json:"desc,omitempty" gorm:"column:desc;not null"` // 如'臘八節','下元節'等
Type string `json:"type,omitempty" gorm:"column:type;not null;size:2"` // 各種和節日有關的類型
Value string `json:"value,omitempty" gorm:"column:value;not null"` // 如'國際殘疾人日'等
Status int `json:"status,string,omitempty" gorm:"column:status;not null;default:0"` // 0 工作日,1 休假,2 上班,3 周末
}
)
func (PerpetualCalendarAlmanac) TableName() string {
return "perpetualCalendarAlmanac"
}
// GetPerpetualCalendar 返回[前一個月,本月,后一個月]的數據
func GetPerpetualCalendar(year, mouth int) ([]PerpetualCalendarAlmanac, error) {
u := url.Values{}
u.Add("tn", "wisetpl")
u.Add("format", "json")
u.Add("resource_id", "39043") // 這個用瀏覽器請求后得到的
u.Add("query", fmt.Sprintf("%d年%d月", year, mouth))
u.Add("t", strconv.FormatInt(time.Now().UnixMilli(), 10))
urls := "https://sp1.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?" + u.Encode()
resp, err := http.Get(urls) // 百度這個接口可能現在請求速度,所以可能報錯
if err != nil {
return nil, err
}
defer resp.Body.Close()
var ret PerpetualCalendar // 需要將gbk轉為utf8
err = json.NewDecoder(transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())).Decode(&ret)
if err != nil {
return nil, err
}
if len(ret.Data) != 1 { // 該數組目前只會有一個
return nil, errors.New("get Data error")
}
for i, v := range ret.Data[0].Almanac {
// 賦值大月
ret.Data[0].Almanac[i].IsBigMonthBool = v.IsBigMonth == "1"
if v.Status == 0 && (v.CnDay == "六" || v.CnDay == "日") {
ret.Data[0].Almanac[i].Status = 3 // 不是特殊類型,且為周末則賦值
}
}
return ret.Data[0].Almanac, nil
}
func SaveCalendar(dsnSrc string) error {
ts := time.Now()
defer func() {
fmt.Println(time.Since(ts))
}()
iDsn := strings.Index(dsnSrc, ":")
if iDsn < 0 {
return errors.New("dsn error")
}
var gormOpen gorm.Dialector
switch strings.ToLower(dsnSrc[:iDsn]) {
case "mysql":
gormOpen = mysql.Open(dsnSrc[iDsn+1:])
case "sqlite":
gormOpen = sqlite.Open(dsnSrc[iDsn+1:])
default:
return errors.New("just support mysql or sqlite")
}
db, err := gorm.Open(gormOpen, &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return err
}
res := db.Exec("DROP table if exists " + PerpetualCalendarAlmanac{}.TableName())
if res.Error != nil {
return res.Error
}
// 每次重建數據表
err = db.AutoMigrate(PerpetualCalendarAlmanac{})
if err != nil {
return err
}
// 起止時間按照百度萬年歷得到
start := time.Date(1900, time.February, 1, 0, 0, 0, 0, time.Local)
end := time.Date(2050, time.December, 1, 0, 0, 0, 0, time.Local)
wg := sync.WaitGroup{}
// 由於每次查詢包含前一個月,當月,下個月,因此每次都增加3個月進行查詢
for ; start.Before(end); start = start.AddDate(0, 3, 0) {
wg.Add(1)
y, m, _ := start.Date()
go func(y, m int) {
defer wg.Done()
for { // 使用協程並發請求,提高速度,出現錯誤時重試
data, err := GetPerpetualCalendar(y, m)
if err != nil {
fmt.Println("GetPerpetualCalendar", y, m, err)
continue // 報錯重試,直到成功
}
res := db.Create(&data)
if res.Error != nil {
fmt.Println("Create", y, m, res.Error)
continue // 報錯重試,直到成功
}
break
}
}(y, int(m))
}
wg.Wait()
return nil
}
下面是main.go
,根據傳入的參數,選擇是保存在mysql
還是sqlite
中。
package main
import (
calendar "interesting/perpetual_calendar"
)
func main() {
dsn := "mysql:user:pass@tcp(127.0.0.1:3306)/janbar?charset=utf8mb4&parseTime=True&loc=Local"
//dsn := "sqlite:test.db"
err := calendar.SaveCalendar(dsn)
if err != nil {
panic(err)
}
}
結果展示
如下圖所示,是存入的數據,已經為陽歷年月日創建唯一聯合索引,陰歷年月日因為存在閏月會重復,因此只創建了聯合索引。
查詢語句可以按照下面的來做。大部分屬性已經按照我的理解寫到注釋里面了,可以結合百度的萬年歷看看,展示在哪個位置吧。
查詢陽歷 2021-09-16 號的數據,結果有對應的陰歷情況
SELECT *FROM perpetualCalendarAlmanac WHERE year=2021 AND month=9 AND day=16
查詢陰歷 2021-09-03 號的數據,結果有對應陽歷的情況
SELECT *FROM perpetualCalendarAlmanac WHERE lunarYear=2021 AND lunarMonth=9 AND lunarDate=3
總結
這個接口調用,和存數據沒啥難度。主要是讓我加深對gorm等庫的使用。
當然最主要是我想實現按照陰歷定時的cron規則,結合cron庫來搞。