实现一个上传文章的命令行工具
这是一个最初的版本,使用 flag 和 ini 来实现文章上传功能。使用 flag 来解析命令行参数,使用 Ini 配置文件,记录识别码,以及 token。记录 token 的原因是因为每次启动命令行,都需要重新获取 token,为了减少 token 获取次数,在获取到 token 后,同时存储到 Ini 配置文件中。每次命令行启动,优先查看配置文件中的 token。
开发这个工具主要有以下几个技术要点:
- 从命令行获取参数
- 从配置文件中读取参数
- 读取文件内容
- 请求后端接口
我们通过分析后,需要定义以下几个参数:
- title 标题
- keyword 关键字
- filename MD 文件名
- ispub 是否公开
- islock 是否加锁
使用 Go 代码定义参数和结构体
var (
title string
keyword string
filename string
ispub int // 1 pub
islock int // 1 lock
)
func init() {
flag.StringVar(&title, "b", "", "文章题目")
flag.StringVar(&keyword, "k", "", "文章关键字")
flag.StringVar(&filename, "f", "", "MD文件")
flag.IntVar(&islock, "l", 0, "是否加锁")
flag.IntVar(&ispub, "p", 0, "是否开放")
}
func main(){
flag.Parse()
}
init() 在 main 函数前调用。需要在 main 函数中调用 flag.Parse(),这一步非常关键。之后就可以使用变量了。
配置文件样例如下:
[User]
icode = 你的icode
isecret = 你的isecret
token =
expire_at = 0
其中,icode 和 isecret 为识别码内容,从小程序端获取。
配置文件库使用gopkg.in/ini.v1
,可以读取和写入 ini 文件。
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
fmt.Println("获取系统目录错误,", err)
os.Exit(1)
}
confFile := filepath.Join(dir, "conf.ini")
cfg, err := ini.Load(confFile)
if err != nil {
fmt.Println("读取配置文件错误,", err)
os.Exit(1)
}
icode := cfg.Section("User").Key("icode").String()
isecret := cfg.Section("User").Key("isecret").String()
token := cfg.Section("User").Key("token").String()
expireAt := cfg.Section("User").Key("expire_at").MustInt64()
设计的默认配置文件存放在命令行同级目录,但是当在其它目录运行时,会报错配置文件无法找到,为了解决这个问题,需要找到命令行的目录,然后在该目录下再查找配置文件。
为了解决每次运行命令行,都需要重新获取 token,将 token 存储在 ini 配置文件中,并添加超时时间,当在有效时间内,不再重新获取 token。
// 刷新token
now := time.Now().Unix()
if now > expireAt {
token, err = getToken(icode, isecret)
if err != nil {
fmt.Println("获取token错误,", err)
os.Exit(1)
}
expireAtStr := strconv.FormatInt(now+7000, 10)
cfg.Section("User").Key("token").SetValue(token)
cfg.Section("User").Key("expire_at").SetValue(expireAtStr)
cfg.SaveTo(confFile)
}
看看 token 的获取方式,使用 net/http 标准库。
func getToken(icode string, isecret string) (string, error) {
url := fmt.Sprintf("%s/vtoken?icode=%s&isecret=%s", OPEN_URL, icode, isecret)
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result RespData
err = json.Unmarshal(data, &result)
if err != nil {
return "", err
}
if result.Code == 1 {
return result.Data, nil
} else {
return "", errors.New(result.Msg)
}
}
我一般提前编制好 Markdown 文件,然后放在某个目录,然后使用命令工具上传,在上传的时候,就需要读取文件。
filecontent, err := os.ReadFile(filename)
if err != nil {
return err
}
这里实现,简单粗暴,因为文件不会很大,所以一次性读取。当要读取很大的文件时,可以使用缓存,提高性能。
最后就是上传文章接口了。这个实现如下:
func uploadArt(token string, title string, keyword string, filename string, ispub int, islock int) error {
if token == "" {
return errors.New("token不能为空")
}
url := fmt.Sprintf("%s/upVArt?token=%s", OPEN_URL, token)
filecontent, err := os.ReadFile(filename)
if err != nil {
return err
}
snippet := string(filecontent)
reqBody := new(bytes.Buffer)
art := ArtReq{
Title: title,
Keyword: keyword,
Content: snippet,
IsPub: ispub,
IsLock: islock,
}
json.NewEncoder(reqBody).Encode(art)
req, err := http.NewRequest("POST", url, reqBody)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var result RespMsg
err = json.Unmarshal(data, &result)
if err != nil {
return err
}
if result.Code == 1 {
return nil
} else {
return errors.New(result.Msg)
}
}
上传功能已经完成。
现在有了新的需求,可以更新文章,删除文章。请看下一章如何实现?