golang实现Medium平台OAuth2 API集成插件库Medium的使用

Golang实现Medium平台OAuth2 API集成插件库Medium的使用

Medium SDK for Go

这个仓库包含了用于将Medium的OAuth2 API集成到Go应用中的开源SDK。

安装

go get github.com/Medium/medium-sdk-go

使用

创建一个客户端,然后调用相关命令。

package main

import (
	medium "github.com/medium/medium-sdk-go"
	"log"
)

func main() {
	// 访问 https://medium.com/me/applications 获取你的applicationId和applicationSecret
	m := medium.NewClient("YOUR_APPLICATION_ID", "YOUR_APPLICATION_SECRET")

	// 构建用于获取授权码的URL
	url := m.GetAuthorizationURL("secretstate", "https://yoursite.com/callback/medium",
        medium.ScopeBasicProfile, medium.ScopePublishPost)

	// (将用户发送到授权URL以获取授权码)

	// 将授权码交换为访问令牌
	at, err := m.ExchangeAuthorizationCode("YOUR_AUTHORIZATION_CODE", "https://yoursite.com/callback/medium")
	if err != nil {
		log.Fatal(err)
	}

	// 成功交换后,访问令牌会自动设置在客户端上
	// 但如果你已经有令牌,可以直接设置
	m.AccessToken = at.AccessToken

	// 如果你有自签发的访问令牌,可以跳过上述步骤
	// 直接创建一个新客户端
	m2 := medium.NewClientWithAccessToken("SELF_ISSUED_ACCESS_TOKEN")

	// 获取由访问令牌标识的用户资料详情
	// 空字符串表示当前用户,否则需要指定用户ID(65个字符的字母数字字符串)
	u, err := m2.GetUser("")
	if err != nil {
		log.Fatal(err)
	}

	// 创建草稿文章
	p, err := m.CreatePost(medium.CreatePostOptions{
		UserID:        u.ID,
		Title:         "Title",
		Content:       "<h2>Title</h2><p>Content</p>",
		ContentFormat: medium.ContentFormatHTML,
		PublishStatus: medium.PublishStatusDraft,
	})
	if err != nil {
		log.Fatal(err)
	}

	// 当访问令牌过期时,使用刷新令牌获取新的令牌
	nt, err := m.ExchangeRefreshToken(at.RefreshToken)
	if err != nil {
		log.Fatal(err)
	}

	// 确认一切正常。p.URL包含创建的文章位置
	log.Println(url, at, u, p, nt)
}

贡献

欢迎提出问题、评论、错误报告和拉取请求。如果你之前没有为Medium项目贡献过代码,请前往Open Source Project并填写OCLA(应该非常简单)。

作者

Jamie Talbot
Dan Pupius
Andrew Bonventre

许可证

版权所有 2015 A Medium Corporation

根据Apache License Version 2.0授权。详细信息请参阅附带的LICENSE文件。


更多关于golang实现Medium平台OAuth2 API集成插件库Medium的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现Medium平台OAuth2 API集成插件库Medium的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang实现Medium平台OAuth2 API集成

Medium提供了一个OAuth2 API,允许开发者集成Medium的发布功能到自己的应用中。下面我将介绍如何使用Golang实现Medium平台的OAuth2集成。

准备工作

  1. 首先需要在Medium开发者页面注册你的应用,获取Client ID和Client Secret。
  2. 设置回调URL(redirect_uri)。

实现步骤

1. 安装必要的依赖

go get golang.org/x/oauth2

2. 配置OAuth2客户端

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/medium"
)

var (
	oauth2Config = &oauth2.Config{
		ClientID:     os.Getenv("MEDIUM_CLIENT_ID"),
		ClientSecret: os.Getenv("MEDIUM_CLIENT_SECRET"),
		RedirectURL:  "http://localhost:8080/callback",
		Scopes:       []string{"basicProfile", "publishPost"},
		Endpoint:     medium.Endpoint,
	}
	
	// 随机字符串,用于防止CSRF攻击
	oauthStateString = "random"
)

3. 实现授权处理

func handleMain(w http.ResponseWriter, r *http.Request) {
	var htmlIndex = `<html>
<body>
	<a href="/login">Medium Log In</a>
</body>
</html>`
	fmt.Fprintf(w, htmlIndex)
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	url := oauth2Config.AuthCodeURL(oauthStateString)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")
	token, err := oauth2Config.Exchange(context.Background(), code)
	if err != nil {
		fmt.Printf("oauth2Conf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	// 使用token访问Medium API
	client := oauth2Config.Client(context.Background(), token)
	resp, err := client.Get("https://api.medium.com/v1/me")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	// 处理获取到的用户信息
	// ...
	
	fmt.Fprintf(w, "登录成功!Token: %+v", token)
}

4. 发布文章到Medium

func publishPost(client *http.Client, title, content string) error {
	post := struct {
		Title     string `json:"title"`
		Content   string `json:"content"`
		Format    string `json:"format"`
		PublishStatus string `json:"publishStatus"`
	}{
		Title:     title,
		Content:   content,
		Format:    "markdown", // 或 "html"
		PublishStatus: "draft", // 或 "public"
	}

	reqBody, err := json.Marshal(post)
	if err != nil {
		return err
	}

	resp, err := client.Post(
		"https://api.medium.com/v1/users/me/posts",
		"application/json",
		bytes.NewBuffer(reqBody),
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to publish post: %s", body)
	}

	return nil
}

5. 启动HTTP服务器

func main() {
	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handleLogin)
	http.HandleFunc("/callback", handleCallback)

	fmt.Println("Server started at http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

完整示例

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/medium"
)

var (
	oauth2Config = &oauth2.Config{
		ClientID:     os.Getenv("MEDIUM_CLIENT_ID"),
		ClientSecret: os.Getenv("MEDIUM_CLIENT_SECRET"),
		RedirectURL:  "http://localhost:8080/callback",
		Scopes:       []string{"basicProfile", "publishPost"},
		Endpoint:     medium.Endpoint,
	}
	
	oauthStateString = "random"
)

func main() {
	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handleLogin)
	http.HandleFunc("/callback", handleCallback)

	fmt.Println("Server started at http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
	var htmlIndex = `<html>
<body>
	<a href="/login">Medium Log In</a>
</body>
</html>`
	fmt.Fprintf(w, htmlIndex)
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	url := oauth2Config.AuthCodeURL(oauthStateString)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")
	token, err := oauth2Config.Exchange(context.Background(), code)
	if err != nil {
		fmt.Printf("oauth2Conf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	client := oauth2Config.Client(context.Background(), token)
	
	// 获取用户信息
	resp, err := client.Get("https://api.medium.com/v1/me")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()
	
	body, _ := io.ReadAll(resp.Body)
	fmt.Fprintf(w, "User Info: %s\n", body)
	
	// 发布文章示例
	err = publishPost(client, "Hello Medium", "This is a test post from my Go application.")
	if err != nil {
		fmt.Fprintf(w, "Error publishing post: %v", err)
	} else {
		fmt.Fprintf(w, "Post published successfully!")
	}
}

func publishPost(client *http.Client, title, content string) error {
	post := struct {
		Title     string `json:"title"`
		Content   string `json:"content"`
		Format    string `json:"format"`
		PublishStatus string `json:"publishStatus"`
	}{
		Title:     title,
		Content:   content,
		Format:    "markdown",
		PublishStatus: "draft",
	}

	reqBody, err := json.Marshal(post)
	if err != nil {
		return err
	}

	resp, err := client.Post(
		"https://api.medium.com/v1/users/me/posts",
		"application/json",
		bytes.NewBuffer(reqBody),
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to publish post: %s", body)
	}

	return nil
}

注意事项

  1. 确保环境变量MEDIUM_CLIENT_IDMEDIUM_CLIENT_SECRET已正确设置
  2. 在生产环境中,应该使用更安全的随机字符串作为state参数
  3. 考虑使用会话管理来存储token
  4. 处理token过期和刷新逻辑
  5. 错误处理应该更加完善

这个示例提供了Medium OAuth2集成的基本框架,你可以根据需要扩展功能,如获取用户所有文章、更新已有文章等。

回到顶部