From cda459ae9ca596f199f75a51f36e495e135e4f08 Mon Sep 17 00:00:00 2001
From: daiao <358551898@qq.com>
Date: Tue, 23 Nov 2021 19:50:43 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=83=E7=89=9B=E4=BA=91rtc=E6=9C=8D?=
 =?UTF-8?q?=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...rtc_controller.go => aliyun_controller.go} |   0
 controllers/rtc/qiniu_controller.go           |  53 ++++
 controllers/rtc/qiniu_util.go                 | 140 ++++++++++
 controllers/rtc/qiu_api.go                    | 246 ++++++++++++++++++
 controllers/user/user_controller.go           |   1 +
 go.mod                                        |   1 +
 go.sum                                        |   4 +
 routers/web_routers.go                        |   4 +
 8 files changed, 449 insertions(+)
 rename controllers/rtc/{rtc_controller.go => aliyun_controller.go} (100%)
 create mode 100644 controllers/rtc/qiniu_controller.go
 create mode 100644 controllers/rtc/qiniu_util.go
 create mode 100644 controllers/rtc/qiu_api.go

diff --git a/controllers/rtc/rtc_controller.go b/controllers/rtc/aliyun_controller.go
similarity index 100%
rename from controllers/rtc/rtc_controller.go
rename to controllers/rtc/aliyun_controller.go
diff --git a/controllers/rtc/qiniu_controller.go b/controllers/rtc/qiniu_controller.go
new file mode 100644
index 0000000..57fd103
--- /dev/null
+++ b/controllers/rtc/qiniu_controller.go
@@ -0,0 +1,53 @@
+package rtc
+
+import (
+	"fmt"
+	"gowebsocket/common"
+	"gowebsocket/controllers"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/qiniu/api.v7/v7/auth"
+)
+
+var manager *Manager
+
+func init() {
+	accessKey := "dzumJl3gfsMSR3fvfABL4e0kDpo6FJmrlcuTu8TF"
+	secretKey := "aqWegU2o8tTCe0JtIVfMDdOjC3-jvuv2eWFvKQOm"
+	fmt.Println("accessKey: secretKey: ", accessKey, secretKey)
+	mac := auth.New(accessKey, secretKey)
+	manager = NewManager(mac)
+}
+
+// TODO: 获取直播流地址
+func GetRoomToken(c *gin.Context) {
+	if o := c.Request.Header.Get("Origin"); o != "" {
+		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,HEAD,PUT,DELETE,OPTIONS")
+		c.Writer.Header().Set("Access-Control-Expose-Headers", "Server,Range,Content-Length,Content-Range")
+		c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin,Range,Accept-Encoding,Referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type")
+	}
+
+	q := c.Request.URL.Query()
+	appID, roomName, userID, Permission := q.Get("app_id"), q.Get("room_name"), q.Get("user_id"), q.Get("permission")
+
+	roomAccess := RoomAccess{
+		AppID:      appID,
+		RoomName:   roomName,
+		UserID:     userID,
+		ExpireAt:   time.Now().Unix() + 3600,
+		Permission: Permission,
+	}
+
+	token, _ := manager.GetRoomToken(roomAccess)
+	url := fmt.Sprintf("https://rtc.qiniuapi.com/v3/apps/%v/rooms/%v/auth?user=%v&token=%v", roomAccess.AppID, roomAccess.RoomName, roomAccess.UserID, token)
+	fmt.Println("url: ", url)
+
+	data := gin.H{
+		"token": token,
+	}
+	controllers.Response(c, common.OK, "", data)
+	// url := fmt.Sprintf("https://rtc.qiniuapi.com/v3/apps/%v/rooms/%v/auth?user=%v&token=%v", roomAccess.AppID, roomAccess.RoomName, roomAccess.UserID, token)
+
+}
diff --git a/controllers/rtc/qiniu_util.go b/controllers/rtc/qiniu_util.go
new file mode 100644
index 0000000..f4da441
--- /dev/null
+++ b/controllers/rtc/qiniu_util.go
@@ -0,0 +1,140 @@
+package rtc
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/qiniu/api.v7/v7/auth"
+)
+
+// resInfo is httpresponse infomation
+type resInfo struct {
+	Code int
+	Err  error
+}
+
+func newResInfo() resInfo {
+	info := resInfo{}
+	return info
+}
+
+func getReqid(src *http.Header) string {
+	for k, v := range *src {
+		K := strings.Title(k)
+		if strings.Contains(K, "Reqid") {
+			return strings.Join(v, ", ")
+		}
+	}
+	return ""
+}
+
+func buildURL(path string) string {
+	if strings.Index(path, "/") != 0 {
+		path = "/" + path
+	}
+	return "https://" + RtcHost + path
+}
+
+func postReq(httpClient *http.Client, mac *auth.Credentials, url string,
+	reqParam interface{}, ret interface{}) *resInfo {
+	info := newResInfo()
+	var reqData []byte
+	var err error
+
+	switch v := reqParam.(type) {
+	case *string:
+		reqData = []byte(*v)
+	case string:
+		reqData = []byte(v)
+	case *[]byte:
+		reqData = *v
+	case []byte:
+		reqData = v
+	default:
+		reqData, err = json.Marshal(reqParam)
+	}
+
+	if err != nil {
+		info.Err = err
+		return &info
+	}
+	req, err := http.NewRequest("POST", url, bytes.NewReader(reqData))
+	if err != nil {
+		info.Err = err
+		return &info
+	}
+	req.Header.Add("Content-Type", "application/json")
+	return callReq(httpClient, req, mac, &info, ret)
+}
+
+func getReq(httpClient *http.Client, mac *auth.Credentials, url string, ret interface{}) *resInfo {
+	info := newResInfo()
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		info.Err = err
+		return &info
+	}
+	return callReq(httpClient, req, mac, &info, ret)
+}
+
+func delReq(httpClient *http.Client, mac *auth.Credentials, url string, ret interface{}) *resInfo {
+	info := newResInfo()
+	req, err := http.NewRequest("DELETE", url, nil)
+	if err != nil {
+		info.Err = err
+		return &info
+	}
+	return callReq(httpClient, req, mac, &info, ret)
+}
+
+func callReq(httpClient *http.Client, req *http.Request, mac *auth.Credentials,
+	info *resInfo, ret interface{}) (oinfo *resInfo) {
+	oinfo = info
+	accessToken, err := mac.SignRequestV2(req)
+	if err != nil {
+		info.Err = err
+		return
+	}
+	req.Header.Add("Authorization", "Qiniu "+accessToken)
+	client := httpClient
+	if client == nil {
+		client = http.DefaultClient
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		info.Err = err
+		return
+	}
+	defer resp.Body.Close()
+	info.Code = resp.StatusCode
+	reqid := getReqid(&resp.Header)
+	rebuildErr := func(msg string) error {
+		return fmt.Errorf("Code: %v, Reqid: %v, %v", info.Code, reqid, msg)
+	}
+
+	if resp.ContentLength > 2*1024*1024 {
+		err = rebuildErr(fmt.Sprintf("response is too long. Content-Length: %v", resp.ContentLength))
+		info.Err = err
+		return
+	}
+	resData, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		info.Err = rebuildErr(err.Error())
+		return
+	}
+	if info.Code != 200 {
+		info.Err = rebuildErr(string(resData))
+		return
+	}
+	if ret != nil {
+		err = json.Unmarshal(resData, ret)
+		if err != nil {
+			info.Err = rebuildErr(fmt.Sprintf("err: %v, res: %v", err, resData))
+		}
+	}
+	return
+}
diff --git a/controllers/rtc/qiu_api.go b/controllers/rtc/qiu_api.go
new file mode 100644
index 0000000..772f0ce
--- /dev/null
+++ b/controllers/rtc/qiu_api.go
@@ -0,0 +1,246 @@
+package rtc
+
+import (
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/qiniu/api.v7/v7/auth"
+)
+
+var (
+	// RtcHost 为 Qiniu RTC Server API服务域名
+	RtcHost = "rtc.qiniuapi.com"
+)
+
+// Manager 提供了 Qiniu RTC Server API 相关功能
+type Manager struct {
+	mac        *auth.Credentials
+	httpClient *http.Client
+}
+
+// MergePublishRtmp  连麦合流转推 RTMP 的配置
+// Enable: 布尔类型,用于开启和关闭所有房间的合流功能。
+// AudioOnly: 布尔类型,可选,指定是否只合成音频。
+// Height, Width: int,可选,指定合流输出的高和宽,默认为 640 x 480。
+// OutputFps: int,可选,指定合流输出的帧率,默认为 25 fps 。
+// OutputKbps: int,可选,指定合流输出的码率,默认为 1000 。
+// URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置
+// StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub 为 qn-zhibo ,配置 StreamTitle 为 $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。
+type MergePublishRtmp struct {
+	Enable      bool   `json:"enable,omitempty"`
+	AudioOnly   bool   `json:"audioOnly,omitempty"`
+	Height      int    `json:"height,omitempty"`
+	Width       int    `json:"width,omitempty"`
+	OutputFps   int    `json:"fps,omitempty"`
+	OutputKbps  int    `json:"kbps,omitempty"`
+	URL         string `json:"url,omitempty"`
+	StreamTitle string `json:"streamTitle,omitempty"`
+}
+
+// App 完整信息
+// AppID: app 的唯一标识,创建的时候由系统生成。
+type App struct {
+	AppID string `json:"appId"`
+	AppInitConf
+	MergePublishRtmp MergePublishRtmp `json:"mergePublishRtmp,omitempty"`
+	CreatedAt        time.Time        `json:"createdAt"`
+	UpdatedAt        time.Time        `json:"updatedAt"`
+}
+
+// AppInitConf 创建 App 请求参数
+// Title: app 的名称, 可选。
+// Hub: 绑定的直播 hub,可选,用于合流后 rtmp 推流。
+// MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。
+// NoAutoKickUser: bool 类型,可选,禁止自动踢人。
+type AppInitConf struct {
+	Hub            string `json:"hub,omitempty"`
+	Title          string `json:"title,omitempty"`
+	MaxUsers       int    `json:"maxUsers,omitempty"`
+	NoAutoKickUser bool   `json:"noAutoKickUser,omitempty"`
+}
+
+// MergePublishRtmpInfo 连麦合流转推 RTMP 的配置更改信息
+type MergePublishRtmpInfo struct {
+	Enable      *bool   `json:"enable,omitempty"`
+	AudioOnly   *bool   `json:"audioOnly,omitempty"`
+	Height      *int    `json:"height,omitempty"`
+	Width       *int    `json:"width,omitempty"`
+	OutputFps   *int    `json:"fps,omitempty"`
+	OutputKbps  *int    `json:"kbps,omitempty"`
+	URL         *string `json:"url,omitempty"`
+	StreamTitle *string `json:"streamTitle,omitempty"`
+}
+
+// AppUpdateInfo 更改信息
+// MergePublishRtmpInfo 连麦合流转推 RTMP 的配置更改信息
+type AppUpdateInfo struct {
+	Hub              *string               `json:"hub,omitempty"`
+	Title            *string               `json:"title,omitempty"`
+	MaxUsers         *int                  `json:"maxUsers,omitempty"`
+	NoAutoKickUser   *bool                 `json:"noAutoKickUser,omitempty"`
+	MergePublishRtmp *MergePublishRtmpInfo `json:"mergePublishRtmp,omitempty"`
+}
+
+// User 连麦房间里的用户
+type User struct {
+	UserID string `json:"userId"`
+}
+
+// NewManager 用来构建一个新的 Manager
+func NewManager(mac *auth.Credentials) *Manager {
+	httpClient := http.DefaultClient
+	return &Manager{mac: mac, httpClient: httpClient}
+}
+
+// CreateApp 新建实时音视频云
+func (r *Manager) CreateApp(appReq AppInitConf) (App, error) {
+	url := buildURL("/v3/apps")
+	ret := App{}
+	info := postReq(r.httpClient, r.mac, url, &appReq, &ret)
+	return ret, info.Err
+}
+
+// GetApp 根据 appID 获取 实时音视频云 信息
+func (r *Manager) GetApp(appID string) (App, error) {
+	url := buildURL("/v3/apps/" + appID)
+	ret := App{}
+	info := getReq(r.httpClient, r.mac, url, &ret)
+	return ret, info.Err
+}
+
+// DeleteApp 根据 appID 删除 实时音视频云
+func (r *Manager) DeleteApp(appID string) error {
+	url := buildURL("/v3/apps/" + appID)
+	info := delReq(r.httpClient, r.mac, url, nil)
+	return info.Err
+}
+
+// UpdateApp 根据 appID, App 更改实时音视频云 信息
+func (r *Manager) UpdateApp(appID string, appInfo AppUpdateInfo) (App, error) {
+	url := buildURL("/v3/apps/" + appID)
+	ret := App{}
+	info := postReq(r.httpClient, r.mac, url, &appInfo, &ret)
+	return ret, info.Err
+}
+
+// ListUser 根据 appID, roomName 获取连麦房间里在线的用户
+// appID: 连麦房间所属的 app 。
+// roomName: 操作所查询的连麦房间。
+func (r *Manager) ListUser(appID, roomName string) ([]User, error) {
+	url := buildURL("/v3/apps/" + appID + "/rooms/" + roomName + "/users")
+	users := struct {
+		Users []User `json:"users"`
+	}{}
+	info := getReq(r.httpClient, r.mac, url, &users)
+	return users.Users, info.Err
+}
+
+// KickUser 根据 appID, roomName, UserID 剔除在线的用户
+// appID: 连麦房间所属的 app 。
+// roomName: 连麦房间。
+// userID: 操作所剔除的用户。
+func (r *Manager) KickUser(appID, roomName, userID string) error {
+	url := buildURL("/v3/apps/" + appID + "/rooms/" + roomName + "/users/" + userID)
+	info := delReq(r.httpClient, r.mac, url, nil)
+	return info.Err
+}
+
+// RoomQuery 房间查询响应结果
+// IsEnd: bool 类型,分页查询是否已经查完所有房间。
+// Offset: int 类型,下次分页查询使用的位移标记。
+// Rooms: 当前活跃的房间名列表。
+type RoomQuery struct {
+	IsEnd  bool       `json:"end"`
+	Offset int        `json:"offset"`
+	Rooms  []RoomName `json:"rooms"`
+}
+
+// RoomName 房间名
+type RoomName string
+
+// ListActiveRooms 根据 appID, roomNamePrefix, offset, limit 查询当前活跃的房间
+// appID: 连麦房间所属的 app 。
+// roomNamePrefix: 所查询房间名的前缀索引,可以为空。
+// offset: int 类型,分页查询的位移标记。
+// limit: int 类型,此次查询的最大长度。
+func (r *Manager) ListActiveRooms(appID, roomNamePrefix string, offset, limit int) (RoomQuery, error) {
+	ret, _, err := r.doListActiveRoom(appID, roomNamePrefix, offset, limit)
+	return ret, err
+}
+
+// ListAllActiveRooms 根据 appID, roomNamePrefix 查询当前活跃的房间
+// appID: 连麦房间所属的 app 。
+// roomNamePrefix: 所查询房间名的前缀索引,可以为空。
+func (r *Manager) ListAllActiveRooms(appID, roomNamePrefix string) ([]RoomName, error) {
+	ns := []RoomName{}
+	var outErr error
+	for offset := 0; ; {
+		q, info, err := r.doListActiveRoom(appID, roomNamePrefix, offset, 100)
+		if err != nil && info.Code != 401 {
+			time.Sleep(500 * time.Millisecond)
+			q, info, err = r.doListActiveRoom(appID, roomNamePrefix, offset, 100)
+		}
+
+		if err != nil || len(q.Rooms) == 0 {
+			outErr = err
+			break
+		}
+		offset = q.Offset
+		ns = append(ns, q.Rooms...)
+		if q.IsEnd {
+			break
+		}
+	}
+	return ns, outErr
+}
+
+func (r *Manager) doListActiveRoom(appID, roomNamePrefix string, offset, limit int) (RoomQuery, resInfo, error) {
+	query := ""
+	roomNamePrefix = strings.TrimSpace(roomNamePrefix)
+	if len(roomNamePrefix) != 0 {
+		query = "prefix=" + roomNamePrefix + "&"
+	}
+	query += fmt.Sprintf("offset=%v&limit=%v", offset, limit)
+	url := buildURL("/v3/apps/" + appID + "/rooms?" + query)
+	ret := RoomQuery{}
+	info := getReq(r.httpClient, r.mac, url, &ret)
+	return ret, *info, info.Err
+}
+
+// RoomAccess 房间管理凭证
+// AppID: 房间所属帐号的 app 。
+// RoomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$
+// UserID: 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$
+// ExpireAt: int64 类型,鉴权的有效时间,传入以秒为单位的64位Unix绝对时间,token 将在该时间后失效。
+// Permission: 该用户的房间管理权限,"admin" 或 "user",默认为 "user" 。当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权.
+type RoomAccess struct {
+	AppID      string `json:"appId"`
+	RoomName   string `json:"roomName"`
+	UserID     string `json:"userId"`
+	ExpireAt   int64  `json:"expireAt"`
+	Permission string `json:"permission"`
+}
+
+// GetRoomToken 生成房间管理鉴权,连麦用户终端通过房间管理鉴权获取七牛连麦服务。
+func (r *Manager) GetRoomToken(roomAccess RoomAccess) (token string, err error) {
+	roomAccessByte, err := json.Marshal(roomAccess)
+	if err != nil {
+		return
+	}
+	buf := make([]byte, base64.URLEncoding.EncodedLen(len(roomAccessByte)))
+	base64.URLEncoding.Encode(buf, roomAccessByte)
+
+	hmacsha1 := hmac.New(sha1.New, r.mac.SecretKey)
+	hmacsha1.Write(buf)
+	sign := hmacsha1.Sum(nil)
+
+	encodedSign := base64.URLEncoding.EncodeToString(sign)
+	token = r.mac.AccessKey + ":" + encodedSign + ":" + string(buf)
+	return
+}
diff --git a/controllers/user/user_controller.go b/controllers/user/user_controller.go
index ae4be57..0dc3bdb 100644
--- a/controllers/user/user_controller.go
+++ b/controllers/user/user_controller.go
@@ -65,6 +65,7 @@ func SendMessage(c *gin.Context) {
 	appIdUint64, _ := strconv.ParseInt(appIdStr, 10, 32)
 	appId := uint32(appIdUint64)
 
+
 	fmt.Println("http_request 给用户发送消息", appIdStr, userId, msgId, message)
 
 	// TODO::进行用户权限认证,一般是客户端传入TOKEN,然后检验TOKEN是否合法,通过TOKEN解析出来用户ID
diff --git a/go.mod b/go.mod
index 53e0f08..bbb1d21 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
 	github.com/myesui/uuid v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/ossrs/go-oryx-lib v0.0.9
+	github.com/qiniu/api.v7/v7 v7.8.2
 	github.com/spf13/cast v1.3.1-0.20190531093228-c01685bb8421 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/viper v1.4.1-0.20190728125013-1b33e8258e07
diff --git a/go.sum b/go.sum
index 034ee1a..fa3c2ca 100644
--- a/go.sum
+++ b/go.sum
@@ -362,6 +362,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
+github.com/gookit/color v1.3.6 h1:Rgbazd4JO5AgSTVGS3o0nvaSdwdrS8bzvIXwtK6OiMk=
+github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -569,6 +571,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/qiniu/api.v7/v7 v7.8.2 h1:f08kI0MmsJNzK4sUS8bG3HDH67ktwd/ji23Gkiy2ra4=
+github.com/qiniu/api.v7/v7 v7.8.2/go.mod h1:FPsIqxh1Ym3X01sANE5ZwXfLZSWoCUp5+jNI8cLo3l0=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/routers/web_routers.go b/routers/web_routers.go
index c70f2b6..0491a6b 100644
--- a/routers/web_routers.go
+++ b/routers/web_routers.go
@@ -19,6 +19,7 @@ import (
 )
 
 func Init(router *gin.Engine) {
+	
 	router.LoadHTMLGlob("views/**/*")
 	// 静态文件
 	router.StaticFS("/static", http.Dir("static/"))
@@ -47,6 +48,9 @@ func Init(router *gin.Engine) {
 	rtcRouter := router.Group("/rtc")
 	{
 		rtcRouter.GET("/get_token", rtc.GetToken)
+		//TODO: options请求转发
+		rtcRouter.OPTIONS("/get_qiniu_token", rtc.GetRoomToken)
+		rtcRouter.GET("/get_qiniu_token", rtc.GetRoomToken)
 	}
 
 	// docker