✨ add qq oauth2 login
This commit is contained in:
@@ -50,6 +50,12 @@ type GiteeUser struct {
|
||||
}
|
||||
|
||||
// GetGiteeRedirectUrl 获取Gitee登录地址
|
||||
// @Summary 获取Gitee登录地址
|
||||
// @Description 获取Gitee登录地址
|
||||
// @Tags OAuth
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "登录地址"
|
||||
// @Router /api/oauth/gitee/get_url [get]
|
||||
func (OAuthAPI) GetGiteeRedirectUrl(c *gin.Context) {
|
||||
clientID := global.CONFIG.OAuth.Gitee.ClientID
|
||||
redirectURI := global.CONFIG.OAuth.Gitee.RedirectURI
|
||||
@@ -123,6 +129,11 @@ func GetGiteeUserInfo(token *Token) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// GiteeCallback 处理Gitee回调
|
||||
// @Summary 处理Gitee回调
|
||||
// @Description 处理Gitee回调
|
||||
// @Tags OAuth
|
||||
// @Produce json
|
||||
// @Router /api/oauth/gitee/callback [get]
|
||||
func (OAuthAPI) GiteeCallback(c *gin.Context) {
|
||||
var err error
|
||||
// 获取 code
|
||||
|
@@ -16,9 +16,6 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
type GitHubUser struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
Bio interface{} `json:"bio"`
|
||||
@@ -56,10 +53,17 @@ type GitHubUser struct {
|
||||
}
|
||||
|
||||
// GetRedirectUrl 获取github登录url
|
||||
// @Summary 获取github登录url
|
||||
// @Description 获取github登录url
|
||||
// @Tags OAuth
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "登录url"
|
||||
// @Router /api/oauth/github/get_url [get]
|
||||
func (OAuthAPI) GetRedirectUrl(c *gin.Context) {
|
||||
state := c.Query("state")
|
||||
clientId := global.CONFIG.OAuth.Github.ClientID
|
||||
redirectUrl := global.CONFIG.OAuth.Github.RedirectURI
|
||||
url := "https://github.com/login/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + redirectUrl
|
||||
url := "https://github.com/login/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + redirectUrl + "&state=" + state
|
||||
result.OkWithData(url, c)
|
||||
return
|
||||
}
|
||||
@@ -129,6 +133,13 @@ func GetUserInfo(token *Token) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// Callback 登录回调函数
|
||||
// @Summary 登录回调函数
|
||||
// @Description 登录回调函数
|
||||
// @Tags OAuth
|
||||
// @Produce json
|
||||
// @Param code query string true "code"
|
||||
// @Success 200 {string} string "登录成功"
|
||||
// @Router /api/oauth/github/callback [get]
|
||||
func (OAuthAPI) Callback(c *gin.Context) {
|
||||
var err error
|
||||
// 获取 code
|
||||
|
@@ -20,6 +20,10 @@ var rolePermissionService = service.Service.RolePermissionService
|
||||
var permissionServiceService = service.Service.PermissionService
|
||||
var roleService = service.Service.RoleService
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
var script = `
|
||||
<script>
|
||||
window.opener.postMessage('%s', '%s');
|
||||
@@ -58,8 +62,11 @@ func HandelUserLogin(user model.ScaAuthUser) (bool, map[string]interface{}) {
|
||||
if er != nil {
|
||||
return false, nil
|
||||
}
|
||||
accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: user.UID, RoleID: ids})
|
||||
|
||||
accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID, RoleID: ids})
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: user.UID, RoleID: ids}, time.Hour*24*7)
|
||||
data := dto.ResponseData{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
|
@@ -1 +1,160 @@
|
||||
package oauth_api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"schisandra-cloud-album/common/result"
|
||||
"schisandra-cloud-album/global"
|
||||
)
|
||||
|
||||
type AuthQQme struct {
|
||||
ClientID string `json:"client_id"`
|
||||
OpenID string `json:"openid"`
|
||||
}
|
||||
|
||||
// GetQQRedirectUrl 获取登录地址
|
||||
// @Summary 获取QQ登录地址
|
||||
// @Description 获取QQ登录地址
|
||||
// @Tags 登录
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "登录地址"
|
||||
// @Router /api/oauth/qq/get_url [get]
|
||||
func (OAuthAPI) GetQQRedirectUrl(c *gin.Context) {
|
||||
state := c.Query("state")
|
||||
clientId := global.CONFIG.OAuth.QQ.ClientID
|
||||
redirectURI := global.CONFIG.OAuth.QQ.RedirectURI
|
||||
url := "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirectURI + "&state=" + state
|
||||
result.OkWithData(url, c)
|
||||
return
|
||||
}
|
||||
|
||||
// GetQQTokenAuthUrl 通过code获取token认证url
|
||||
func GetQQTokenAuthUrl(code string) string {
|
||||
clientId := global.CONFIG.OAuth.QQ.ClientID
|
||||
clientSecret := global.CONFIG.OAuth.QQ.ClientSecret
|
||||
redirectURI := global.CONFIG.OAuth.QQ.RedirectURI
|
||||
return fmt.Sprintf(
|
||||
"https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s",
|
||||
clientId, clientSecret, code, redirectURI,
|
||||
)
|
||||
}
|
||||
|
||||
// GetQQToken 获取 token
|
||||
func GetQQToken(url string) (*Token, error) {
|
||||
|
||||
// 形成请求
|
||||
var req *http.Request
|
||||
var err error
|
||||
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("accept", "application/json")
|
||||
|
||||
// 发送请求并获得响应
|
||||
var httpClient = http.Client{}
|
||||
var res *http.Response
|
||||
if res, err = httpClient.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将响应体解析为 token,并返回
|
||||
var token Token
|
||||
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// GetQQUserOpenID 获取用户 openid
|
||||
func GetQQUserOpenID(token *Token) (*AuthQQme, error) {
|
||||
|
||||
// 形成请求
|
||||
var userInfoUrl = "https://graph.qq.com/oauth2.0/me" // github用户信息获取接口
|
||||
var req *http.Request
|
||||
var err error
|
||||
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("accept", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
|
||||
// 发送请求并获取响应
|
||||
var client = http.Client{}
|
||||
var res *http.Response
|
||||
if res, err = client.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将响应体解析为 AuthQQme,并返回
|
||||
var authQQme AuthQQme
|
||||
if err = json.NewDecoder(res.Body).Decode(&authQQme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authQQme, nil
|
||||
}
|
||||
|
||||
// GetQQUserUserInfo 获取用户信息
|
||||
func GetQQUserUserInfo(token *Token, openId string) (map[string]interface{}, error) {
|
||||
|
||||
clientId := global.CONFIG.OAuth.QQ.ClientID
|
||||
// 形成请求
|
||||
var userInfoUrl = "https://graph.qq.com/user/get_user_info??access_token=" + token.AccessToken + "&oauth_consumer_key=" + clientId + "&openid=" + openId
|
||||
var req *http.Request
|
||||
var err error
|
||||
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("accept", "application/json")
|
||||
//req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
|
||||
// 发送请求并获取响应
|
||||
var client = http.Client{}
|
||||
var res *http.Response
|
||||
if res, err = client.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将响应的数据写入 userInfo 中,并返回
|
||||
var userInfo = make(map[string]interface{})
|
||||
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
// QQCallback QQ登录回调
|
||||
// @Summary QQ登录回调
|
||||
// @Description QQ登录回调
|
||||
// @Tags 登录
|
||||
// @Produce json
|
||||
// @Router /api/oauth/qq/callback [get]
|
||||
func (OAuthAPI) QQCallback(c *gin.Context) {
|
||||
var err error
|
||||
// 获取 code
|
||||
var code = c.Query("code")
|
||||
if code == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
|
||||
return
|
||||
}
|
||||
// 通过 code, 获取 token
|
||||
var tokenAuthUrl = GetQQTokenAuthUrl(code)
|
||||
var token *Token
|
||||
if token, err = GetQQToken(tokenAuthUrl); err != nil {
|
||||
global.LOG.Error(err)
|
||||
return
|
||||
}
|
||||
authQQme, err := GetQQUserOpenID(token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 通过token,获取用户信息
|
||||
var userInfo map[string]interface{}
|
||||
if userInfo, err = GetQQUserUserInfo(token, authQQme.OpenID); err != nil {
|
||||
global.LOG.Error(err)
|
||||
return
|
||||
}
|
||||
result.OkWithData(userInfo, c)
|
||||
return
|
||||
}
|
||||
|
@@ -33,10 +33,19 @@ import (
|
||||
// @Summary 生成客户端ID
|
||||
// @Description 生成客户端ID
|
||||
// @Produce json
|
||||
// @Success 200 {object} result.Result{data=string} "客户端ID"
|
||||
// @Router /api/oauth/generate_client_id [get]
|
||||
func (OAuthAPI) GenerateClientId(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
// 尝试从 X-Real-IP 头部获取真实 IP
|
||||
ip := c.GetHeader("X-Real-IP")
|
||||
|
||||
// 如果 X-Real-IP 头部不存在,则尝试从 X-Forwarded-For 头部获取
|
||||
if ip == "" {
|
||||
ip = c.GetHeader("X-Forwarded-For")
|
||||
}
|
||||
// 如果两者都不存在,则使用默认的 ClientIP 方法获取 IP
|
||||
if ip == "" {
|
||||
ip = c.ClientIP()
|
||||
}
|
||||
clientId := redis.Get(constant.UserLoginClientRedisKey + ip).Val()
|
||||
if clientId != "" {
|
||||
result.OkWithData(clientId, c)
|
||||
@@ -52,7 +61,6 @@ func (OAuthAPI) GenerateClientId(c *gin.Context) {
|
||||
// @Summary 微信回调验证
|
||||
// @Description 微信回调验证
|
||||
// @Produce json
|
||||
// @Success 200 {object} result.Result{data=string} "验证结果"
|
||||
// @Router /api/oauth/callback_notify [POST]
|
||||
func (OAuthAPI) CallbackNotify(c *gin.Context) {
|
||||
rs, err := global.Wechat.Server.Notify(c.Request, func(event contract.EventInterface) interface{} {
|
||||
@@ -126,7 +134,6 @@ func (OAuthAPI) CallbackNotify(c *gin.Context) {
|
||||
// @Summary 微信回调验证
|
||||
// @Description 微信回调验证
|
||||
// @Produce json
|
||||
// @Success 200 {object} result.Result{data=string} "验证结果"
|
||||
// @Router /api/oauth/callback_verify [get]
|
||||
func (OAuthAPI) CallbackVerify(c *gin.Context) {
|
||||
rs, err := global.Wechat.Server.VerifyURL(c.Request)
|
||||
@@ -141,11 +148,20 @@ func (OAuthAPI) CallbackVerify(c *gin.Context) {
|
||||
// @Description 获取临时二维码
|
||||
// @Produce json
|
||||
// @Param client_id query string true "客户端ID"
|
||||
// @Success 200 {object} result.Result{data=string} "临时二维码"
|
||||
// @Router /api/oauth/get_temp_qrcode [get]
|
||||
func (OAuthAPI) GetTempQrCode(c *gin.Context) {
|
||||
clientId := c.Query("client_id")
|
||||
ip := c.ClientIP()
|
||||
// 尝试从 X-Real-IP 头部获取真实 IP
|
||||
ip := c.GetHeader("X-Real-IP")
|
||||
|
||||
// 如果 X-Real-IP 头部不存在,则尝试从 X-Forwarded-For 头部获取
|
||||
if ip == "" {
|
||||
ip = c.GetHeader("X-Forwarded-For")
|
||||
}
|
||||
// 如果两者都不存在,则使用默认的 ClientIP 方法获取 IP
|
||||
if ip == "" {
|
||||
ip = c.ClientIP()
|
||||
}
|
||||
if clientId == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
|
||||
return
|
||||
@@ -264,8 +280,11 @@ func handelUserLogin(user model.ScaAuthUser, clientId string) bool {
|
||||
if er != nil {
|
||||
return false
|
||||
}
|
||||
accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: user.UID, RoleID: ids})
|
||||
|
||||
accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID, RoleID: ids})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: user.UID, RoleID: ids}, time.Hour*24*7)
|
||||
data := dto.ResponseData{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
@@ -282,9 +301,19 @@ func handelUserLogin(user model.ScaAuthUser, clientId string) bool {
|
||||
"data": data,
|
||||
"success": true,
|
||||
}
|
||||
res := websocket_api.SendMessageData(clientId, responseData)
|
||||
if !res {
|
||||
tokenData, err := json.Marshal(responseData)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// gws方式发送消息
|
||||
err = websocket_api.Handler.SendMessageToClient(clientId, tokenData)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// gorilla websocket方式发送消息
|
||||
//res := websocket_api.SendMessageData(clientId, responseData)
|
||||
//if !res {
|
||||
// return false
|
||||
//}
|
||||
return true
|
||||
}
|
||||
|
@@ -9,14 +9,16 @@ type RefreshTokenRequest struct {
|
||||
|
||||
// PhoneLoginRequest 手机号登录请求
|
||||
type PhoneLoginRequest struct {
|
||||
Phone string `json:"phone"`
|
||||
Captcha string `json:"captcha"`
|
||||
Phone string `json:"phone"`
|
||||
Captcha string `json:"captcha"`
|
||||
AutoLogin bool `json:"auto_login"`
|
||||
}
|
||||
|
||||
// AccountLoginRequest 账号登录请求
|
||||
type AccountLoginRequest struct {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
AutoLogin bool `json:"auto_login"`
|
||||
}
|
||||
|
||||
// AddUserRequest 新增用户请求
|
||||
|
@@ -187,7 +187,7 @@ func (UserAPI) AccountLogin(c *gin.Context) {
|
||||
} else {
|
||||
verify := utils.Verify(*user.Password, password)
|
||||
if verify {
|
||||
handelUserLogin(user, c)
|
||||
handelUserLogin(user, accountLoginRequest.AutoLogin, c)
|
||||
return
|
||||
} else {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
|
||||
@@ -204,7 +204,7 @@ func (UserAPI) AccountLogin(c *gin.Context) {
|
||||
} else {
|
||||
verify := utils.Verify(*user.Password, password)
|
||||
if verify {
|
||||
handelUserLogin(user, c)
|
||||
handelUserLogin(user, accountLoginRequest.AutoLogin, c)
|
||||
return
|
||||
} else {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
|
||||
@@ -221,7 +221,7 @@ func (UserAPI) AccountLogin(c *gin.Context) {
|
||||
} else {
|
||||
verify := utils.Verify(*user.Password, password)
|
||||
if verify {
|
||||
handelUserLogin(user, c)
|
||||
handelUserLogin(user, accountLoginRequest.AutoLogin, c)
|
||||
return
|
||||
} else {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
|
||||
@@ -287,7 +287,7 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
|
||||
return
|
||||
}
|
||||
handelUserLogin(addUser, c)
|
||||
handelUserLogin(addUser, request.AutoLogin, c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -300,7 +300,7 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c)
|
||||
return
|
||||
} else {
|
||||
handelUserLogin(user, c)
|
||||
handelUserLogin(user, request.AutoLogin, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -327,20 +327,20 @@ func (UserAPI) RefreshHandler(c *gin.Context) {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
|
||||
return
|
||||
}
|
||||
parseRefreshToken, isUpd, err := utils.ParseToken(refreshToken)
|
||||
parseRefreshToken, isUpd, err := utils.ParseRefreshToken(refreshToken)
|
||||
if err != nil {
|
||||
global.LOG.Errorln(err)
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
|
||||
return
|
||||
}
|
||||
if isUpd {
|
||||
accessTokenString, err := utils.GenerateAccessToken(utils.JWTPayload{UserID: parseRefreshToken.UserID, RoleID: parseRefreshToken.RoleID})
|
||||
accessTokenString, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: parseRefreshToken.UserID, RoleID: parseRefreshToken.RoleID})
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
|
||||
return
|
||||
}
|
||||
wrong := redis.Get(constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID).Err()
|
||||
if wrong != nil {
|
||||
token := redis.Get(constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID).Val()
|
||||
if token == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
|
||||
return
|
||||
}
|
||||
@@ -360,7 +360,7 @@ func (UserAPI) RefreshHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
// handelUserLogin 处理用户登录
|
||||
func handelUserLogin(user model.ScaAuthUser, c *gin.Context) {
|
||||
func handelUserLogin(user model.ScaAuthUser, autoLogin bool, c *gin.Context) {
|
||||
ids, err := userRoleService.GetUserRoleIdsByUserId(user.ID)
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
|
||||
@@ -396,15 +396,25 @@ func handelUserLogin(user model.ScaAuthUser, c *gin.Context) {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
|
||||
return
|
||||
}
|
||||
accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: user.UID, RoleID: ids})
|
||||
|
||||
accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID, RoleID: ids})
|
||||
if err != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
|
||||
return
|
||||
}
|
||||
var days time.Duration
|
||||
if autoLogin {
|
||||
days = time.Hour * 24 * 7
|
||||
} else {
|
||||
days = time.Hour * 24 * 1
|
||||
}
|
||||
refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: user.UID, RoleID: ids}, days)
|
||||
data := dto.ResponseData{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt,
|
||||
UID: user.UID,
|
||||
}
|
||||
fail := redis.Set(constant.UserLoginTokenRedisKey+*user.UID, data, time.Hour*24*7).Err()
|
||||
fail := redis.Set(constant.UserLoginTokenRedisKey+*user.UID, data, time.Hour*24*1).Err()
|
||||
if fail != nil {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
|
||||
return
|
||||
|
@@ -14,9 +14,11 @@ const (
|
||||
HeartbeatWaitTimeout = 10 * time.Second // 心跳等待超时时间
|
||||
)
|
||||
|
||||
var Handler = NewWebSocket()
|
||||
|
||||
func (WebsocketAPI) NewGWSServer(c *gin.Context) {
|
||||
var handler = NewWebSocket()
|
||||
upgrader := gws.NewUpgrader(handler, &gws.ServerOption{
|
||||
|
||||
upgrader := gws.NewUpgrader(Handler, &gws.ServerOption{
|
||||
HandshakeTimeout: 5 * time.Second, // 握手超时时间
|
||||
ReadBufferSize: 1024, // 读缓冲区大小
|
||||
ParallelEnabled: true, // 开启并行消息处理
|
||||
|
Reference in New Issue
Block a user