chore: 并入 iotlcm-openapi 历史到 openapi/(原提交 3b05d5a)
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/bcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/utils"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/service"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const ydOperatorTokenURL = "https://api.iot.10086.cn/v5/ec/get/token"
|
||||
|
||||
type ydOperatorTokenResp struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result []struct {
|
||||
Token string `json:"token"`
|
||||
Ttl int64 `json:"ttl"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
var ydOperatorTokenSeq atomic.Int64
|
||||
|
||||
type sQueryToken struct {
|
||||
}
|
||||
|
||||
func NewQueryToken() *sQueryToken {
|
||||
return &sQueryToken{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterQueryToken(NewQueryToken())
|
||||
}
|
||||
|
||||
// Token 获取Token
|
||||
func (l *sQueryToken) Token(ctx context.Context, input model.TokenInputApi) (res model.TokenOutputApi, err error) {
|
||||
// 查询API集成信息
|
||||
var tApi entity.TApi
|
||||
err = dao.TApi.Ctx(ctx).Where(dao.TApi.Columns().AppKey, input.AppKey).Scan(&tApi)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "QueryToken.Token, Select t api by app key failed, err: %v, input: %v", err, input)
|
||||
return res, gerror.New("APPKey或APPSecret错误")
|
||||
}
|
||||
// 校验APPKey是否存在和APPSecret是否正确
|
||||
if tApi.Id == 0 {
|
||||
g.Log().Debugf(ctx, "QueryToken.Token, The t api does not exist, input: %v", input)
|
||||
return res, gerror.New("APPKey或APPSecret错误")
|
||||
}
|
||||
if tApi.AppSecret != input.AppSecret {
|
||||
g.Log().Debugf(ctx, "QueryToken.Token, APPSecret verification failed, input: %v", input)
|
||||
return res, gerror.New("APPKey或APPSecret错误")
|
||||
}
|
||||
if tApi.Status == 1 {
|
||||
g.Log().Debugf(ctx, "QueryToken.Token, The t api is disabled, input: %v", input)
|
||||
return res, gerror.New("API集成已停用")
|
||||
}
|
||||
// 验证客户
|
||||
// dept, err := service.SysDept().GetDeptById(ctx, tApi.DeptId)
|
||||
var dept entity.SysDept
|
||||
err = dao.SysDept.Ctx(ctx).Where(dao.SysDept.Columns().DeptId, tApi.DeptId).Scan(&dept)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "QueryToken.Token, Select dept by id failed, err: %v, input: %v", err, input)
|
||||
return res, gerror.New("APPKey或APPSecret错误")
|
||||
}
|
||||
if dept.DeptId == 0 {
|
||||
g.Log().Debugf(ctx, "QueryToken.Token, The organization does not exist, input: %v", input)
|
||||
return res, gerror.New("APPKey或APPSecret错误")
|
||||
}
|
||||
g.Log().Debugf(ctx, "dept.DeptId: %v", dept.DeptId)
|
||||
if dept.DeptKey == "" {
|
||||
g.Log().Debugf(ctx, "QueryToken.Token, The organization does not have API service enabled, input: %v", input)
|
||||
return res, gerror.New("客户未开启API服务")
|
||||
}
|
||||
|
||||
identity := &model.CustomClaimsApi{
|
||||
BaseClaimsApi: model.BaseClaimsApi{
|
||||
UUID: strings.ReplaceAll(uuid.New().String(), "-", ""),
|
||||
ApiId: tApi.Id,
|
||||
AppKey: tApi.AppKey,
|
||||
GroupId: tApi.GroupId,
|
||||
DeptKey: dept.DeptKey,
|
||||
DeptName: dept.DeptName,
|
||||
DeptType: dept.DeptType,
|
||||
TenantId: dept.TenantId,
|
||||
DeptId: dept.DeptId,
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
contextModel := new(model.Context)
|
||||
ctx = contexts.Set(ctx, contextModel)
|
||||
|
||||
if input.Refresh == "" {
|
||||
input.Refresh = "1"
|
||||
} else if input.Refresh != "0" && input.Refresh != "1" {
|
||||
return res, gerror.New("刷新参数错误")
|
||||
}
|
||||
|
||||
res.AppId = dept.DeptKey
|
||||
if input.Refresh == "0" {
|
||||
tokenUuid, cacheErr := bcache.GetApiAuthActiveToken(ctx, dept.DeptId)
|
||||
if cacheErr == nil && tokenUuid != "" {
|
||||
token, tokenErr := l.GenerateShortToken(ctx, tokenUuid)
|
||||
if tokenErr != nil {
|
||||
return res, tokenErr
|
||||
}
|
||||
res.AccessToken = token
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
if input.Refresh == "1" {
|
||||
tokenUuid, cacheErr := bcache.GetApiAuthActiveToken(ctx, dept.DeptId)
|
||||
if cacheErr == nil && tokenUuid != "" {
|
||||
_ = bcache.DelApiAuthToken(ctx, tokenUuid)
|
||||
}
|
||||
_ = bcache.DelApiAuthActiveToken(ctx, dept.DeptId)
|
||||
}
|
||||
|
||||
_, token, err := l.GenerateToken(ctx, identity)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.AccessToken = token
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// OperatorToken 获取移动运营商Token(仅需chanKey,不校验白名单和AppId)
|
||||
func (l *sQueryToken) OperatorToken(ctx context.Context, input model.OperatorTokenInput) (res model.OperatorTokenOutput, err error) {
|
||||
return l.fetchYdOperatorToken(ctx, input, false)
|
||||
}
|
||||
|
||||
// OperatorTokenWithAuth 获取移动运营商Token(校验白名单+AppId,需传appId+chanKey)
|
||||
func (l *sQueryToken) OperatorTokenWithAuth(ctx context.Context, input model.OperatorTokenInput) (res model.OperatorTokenOutput, err error) {
|
||||
if input.AppId == "" {
|
||||
return res, gerror.New("AppId不能为空")
|
||||
}
|
||||
return l.fetchYdOperatorToken(ctx, input, true)
|
||||
}
|
||||
|
||||
// fetchYdOperatorToken 内部实现,checkShare=true时校验白名单和AppId
|
||||
func (l *sQueryToken) fetchYdOperatorToken(ctx context.Context, input model.OperatorTokenInput, checkShare bool) (res model.OperatorTokenOutput, err error) {
|
||||
if input.ChanKey == "" {
|
||||
return res, gerror.New("通道Key不能为空")
|
||||
}
|
||||
|
||||
fields := []any{
|
||||
dao.TSimChannels.Columns().TempKey,
|
||||
dao.TSimChannels.Columns().ChanAlias,
|
||||
dao.TSimChannels.Columns().ChanKey,
|
||||
dao.TSimChannels.Columns().AppKey,
|
||||
dao.TSimChannels.Columns().AppSecret,
|
||||
}
|
||||
if checkShare {
|
||||
fields = append(fields, dao.TSimChannels.Columns().ShareEnabled)
|
||||
}
|
||||
|
||||
var simChannels entity.TSimChannels
|
||||
dbErr := dao.TSimChannels.DB().Model(dao.TSimChannels.Table()).Safe().Ctx(ctx).
|
||||
Fields(fields...).
|
||||
Where(dao.TSimChannels.Columns().ChanKey, input.ChanKey).
|
||||
Scan(&simChannels)
|
||||
if dbErr != nil {
|
||||
g.Log().Errorf(ctx, "QueryToken.fetchYdOperatorToken, Select sim channel by chan key failed, err: %v, input: %v", dbErr, input)
|
||||
return res, gerror.New("通道不存在")
|
||||
}
|
||||
if simChannels.ChanKey == "" {
|
||||
return res, gerror.New("通道不存在")
|
||||
}
|
||||
if checkShare {
|
||||
if simChannels.ShareEnabled != 1 {
|
||||
g.Log().Warningf(ctx, "QueryToken.fetchYdOperatorToken, sim channel share not enabled, chanKey: %s", input.ChanKey)
|
||||
return res, gerror.New("该通道未开放共享Token")
|
||||
}
|
||||
if input.AppId != simChannels.AppKey {
|
||||
g.Log().Warningf(ctx, "QueryToken.fetchYdOperatorToken, appId mismatch, chanKey: %s, inputAppId: %s, channelAppId: %s", input.ChanKey, input.AppId, simChannels.AppKey)
|
||||
return res, gerror.New("AppId与通道不匹配")
|
||||
}
|
||||
}
|
||||
|
||||
var tempModel string
|
||||
if simChannels.TempKey != "" {
|
||||
var template entity.TSimChannelTemplate
|
||||
if tmplErr := dao.TSimChannelTemplate.Ctx(ctx).
|
||||
Fields(dao.TSimChannelTemplate.Columns().TempModel).
|
||||
Where(dao.TSimChannelTemplate.Columns().TempKey, simChannels.TempKey).
|
||||
Limit(1).Scan(&template); tmplErr == nil {
|
||||
tempModel = strings.TrimSpace(template.TempModel)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.EqualFold(tempModel, "shared_token") {
|
||||
result, sharedErr := service.UpstreamChan().GetSharedToken(ctx, input.ChanKey)
|
||||
if sharedErr != nil {
|
||||
return res, gerror.Wrap(sharedErr, "获取共享Token失败")
|
||||
}
|
||||
return model.OperatorTokenOutput{
|
||||
ChanAlias: result.ChanAlias,
|
||||
ChanKey: result.ChanKey,
|
||||
AccessToken: result.AccessToken,
|
||||
AccessTokenExpire: result.AccessTokenExpire,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if simChannels.AppKey == "" || simChannels.AppSecret == "" {
|
||||
return res, gerror.New("通道不存在")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
transID := buildYdOperatorTokenTransID(simChannels.AppKey, now)
|
||||
params := g.Map{
|
||||
"appid": simChannels.AppKey,
|
||||
"password": simChannels.AppSecret,
|
||||
"transid": transID,
|
||||
"refresh": "0",
|
||||
}
|
||||
g.Log().Infof(ctx, "QueryToken.OperatorToken, request yd token url: %s, chanKey: %s", buildYdOperatorTokenRequestURL(simChannels.AppKey, simChannels.AppSecret, transID), input.ChanKey)
|
||||
client := g.Client()
|
||||
client.SetTimeout(10 * time.Second)
|
||||
resp, err := client.Get(ctx, ydOperatorTokenURL, params)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "QueryToken.OperatorToken, request yd token failed, err: %v, chanKey: %s", err, input.ChanKey)
|
||||
return res, gerror.New("获取移动运营商Token失败")
|
||||
}
|
||||
|
||||
body := resp.ReadAllString()
|
||||
var ydResp ydOperatorTokenResp
|
||||
if err = json.Unmarshal([]byte(body), &ydResp); err != nil {
|
||||
g.Log().Errorf(ctx, "QueryToken.OperatorToken, parse yd token response failed, err: %v, chanKey: %s, body: %s", err, input.ChanKey, body)
|
||||
return res, gerror.New("解析移动运营商Token响应失败")
|
||||
}
|
||||
if ydResp.Status != "0" {
|
||||
g.Log().Warningf(ctx, "QueryToken.OperatorToken, yd token response error, status: %s, message: %s, chanKey: %s", ydResp.Status, ydResp.Message, input.ChanKey)
|
||||
if ydResp.Message != "" {
|
||||
return res, gerror.New(ydResp.Message)
|
||||
}
|
||||
return res, gerror.New("获取移动运营商Token失败")
|
||||
}
|
||||
if len(ydResp.Result) == 0 || ydResp.Result[0].Token == "" {
|
||||
return res, gerror.New("移动运营商Token为空")
|
||||
}
|
||||
if ydResp.Result[0].Ttl <= 0 {
|
||||
return res, gerror.New("移动运营商Token有效期无效")
|
||||
}
|
||||
|
||||
return model.OperatorTokenOutput{
|
||||
ChanAlias: simChannels.ChanAlias,
|
||||
ChanKey: simChannels.ChanKey,
|
||||
AccessToken: ydResp.Result[0].Token,
|
||||
AccessTokenExpire: gtime.NewFromTime(now.Add(time.Duration(ydResp.Result[0].Ttl) * time.Second)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildYdOperatorTokenTransID(appKey string, now time.Time) string {
|
||||
return appKey + now.Format("20060102150405") + fmt.Sprintf("%08d", ydOperatorTokenSeq.Add(1)%100000000)
|
||||
}
|
||||
|
||||
func buildYdOperatorTokenRequestURL(appKey, password, transID string) string {
|
||||
values := url.Values{}
|
||||
values.Set("appid", appKey)
|
||||
values.Set("password", password)
|
||||
values.Set("transid", transID)
|
||||
values.Set("refresh", "0")
|
||||
return ydOperatorTokenURL + "?" + values.Encode()
|
||||
}
|
||||
|
||||
// GenerateToken 生成Token
|
||||
func (s *sQueryToken) GenerateToken(ctx context.Context, customer *model.CustomClaimsApi) (claims *model.CustomClaimsApi, shortToken string, err error) {
|
||||
ets := g.Cfg().MustGet(ctx, "jwt.expiresTime", "3650d").String()
|
||||
bts := g.Cfg().MustGet(ctx, "jwt.bufferTime", "1d").String()
|
||||
iss := g.Cfg().MustGet(ctx, "jwt.issuer", "JieIOTAPI").String()
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "49c54195e73304e74a8429b178965865").String()
|
||||
et, _ := utils.ParseDuration(ets)
|
||||
bt, _ := utils.ParseDuration(bts)
|
||||
g.Log().Infof(ctx, "GenerateToken: customer:%+v, et:%s, bt:%s, iss:%s", customer, et, bt, iss)
|
||||
|
||||
tokenUuid := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
expiresAt := time.Now().Add(et).Unix()
|
||||
claims = &model.CustomClaimsApi{
|
||||
BaseClaimsApi: model.BaseClaimsApi{
|
||||
UUID: tokenUuid,
|
||||
ApiId: customer.ApiId,
|
||||
AppKey: customer.AppKey,
|
||||
GroupId: customer.GroupId,
|
||||
DeptKey: customer.DeptKey,
|
||||
DeptName: customer.DeptName,
|
||||
DeptType: customer.DeptType,
|
||||
DeptId: customer.DeptId,
|
||||
TenantId: customer.TenantId,
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
BufferTime: int64(bt / time.Second),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{"JIEIOT"},
|
||||
ExpiresAt: jwt.NewNumericDate(time.Unix(expiresAt, 0)),
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)),
|
||||
Issuer: iss,
|
||||
},
|
||||
}
|
||||
|
||||
fullToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
fullTokenString, err := fullToken.SignedString([]byte(sk))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err = bcache.SetApiAuthToken(ctx, tokenUuid, fullTokenString, et); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err = bcache.SetApiAuthActiveToken(ctx, customer.DeptId, tokenUuid, et); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
shortToken, err = s.GenerateShortToken(ctx, tokenUuid)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return claims, shortToken, nil
|
||||
}
|
||||
|
||||
// GenerateShortToken 生成短令牌
|
||||
func (s *sQueryToken) GenerateShortToken(ctx context.Context, tokenUuid string) (token string, err error) {
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "49c54195e73304e74a8429b178965865").String()
|
||||
shortToken := jwt.NewWithClaims(jwt.SigningMethodHS256, &model.ApiTokenClaims{
|
||||
UUID: tokenUuid,
|
||||
})
|
||||
delete(shortToken.Header, "typ")
|
||||
return shortToken.SignedString([]byte(sk))
|
||||
}
|
||||
|
||||
// 解析Token
|
||||
func (s *sQueryToken) ParseToken(ctx context.Context, token string) (claims *model.CustomClaimsApi, err error) {
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "49c54195e73304e74a8429b178965865").String()
|
||||
t, err := jwt.ParseWithClaims(token, &model.ApiTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(sk), nil
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "sApiAuth.ParseToken short token err: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
shortClaims, ok := t.Claims.(*model.ApiTokenClaims)
|
||||
if !ok || !t.Valid {
|
||||
return nil, errors.New("token invalid")
|
||||
}
|
||||
|
||||
fullTokenString, err := bcache.GetApiAuthToken(ctx, shortClaims.UUID)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "sApiAuth.ParseToken get full token err: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
fullToken, err := jwt.ParseWithClaims(fullTokenString, &model.CustomClaimsApi{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(sk), nil
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "sApiAuth.ParseToken full token err: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
claims, ok = fullToken.Claims.(*model.CustomClaimsApi)
|
||||
if !ok || !fullToken.Valid {
|
||||
return nil, errors.New("token invalid")
|
||||
}
|
||||
if claims.ExpiresAt == nil || claims.ExpiresAt.Unix() < time.Now().Unix() {
|
||||
return nil, errors.New("token expired")
|
||||
}
|
||||
if claims.UUID != shortClaims.UUID {
|
||||
return nil, errors.New("token invalid")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// 删除Token
|
||||
func (s *sQueryToken) DeleteToken(ctx context.Context, token string) (err error) {
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "49c54195e73304e74a8429b178965865").String()
|
||||
t, err := jwt.ParseWithClaims(token, &model.ApiTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(sk), nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shortClaims, ok := t.Claims.(*model.ApiTokenClaims)
|
||||
if !ok {
|
||||
return errors.New("token invalid")
|
||||
}
|
||||
return bcache.DelApiAuthToken(ctx, shortClaims.UUID)
|
||||
}
|
||||
|
||||
// 获取token
|
||||
func (s *sQueryToken) GetAccessToken(ctx context.Context) (token string, err error) {
|
||||
headerToken := g.RequestFromCtx(ctx).Header.Get("X-Access-Token")
|
||||
if headerToken == "" {
|
||||
return "", gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
g.Log().Error(ctx, headerToken)
|
||||
token = strings.TrimPrefix(headerToken, "Bearer ")
|
||||
if token == "" {
|
||||
token = headerToken
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// 根据Token获取当前登录客户信息
|
||||
func (s *sQueryToken) GetCurrentCustomer(ctx context.Context) (claims *model.CustomClaimsApi, err error) {
|
||||
token, err := s.GetAccessToken(ctx)
|
||||
g.Log().Debugf(ctx, "sQueryToken.GetCurrentCustomer token: %+v, err: %+v", token, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claims, err = s.ParseToken(ctx, token)
|
||||
g.Log().Debugf(ctx, "sApiAuth.GetCurrentCustomer claims: %+v, err: %+v", claims, err)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "sApiAuth.GetCurrentCustomer err: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
Reference in New Issue
Block a user