Files
iotlcm/internal/logic/middleware/middleware.go
T
MiFaZhan 88dfe5c640 chore: 初始开源发布
iotlcm-openapi:对外 OpenAPI 网关(V3 接口 + 旧版兼容接口)。

- Go module:iotlcm-openapi → git.5iot.com/iotlcm-public/iotlcm-openapi(178 文件)
- 凭据与地址脱敏:JWT signingKey、MySQL DSN、Redis 口令、
  RabbitMQ DSN 全部改为 ${*} 占位;私网 IP → 文档地址段
- 7 处真实手机号 → 合法 11 位编造号
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- 移除内部资产:CI 触发器
- Dockerfile 改用公共基础镜像,移除 GOPRIVATE 内网设置
- README 重写(原为 GoFrame 模板占位文案),补 Apache-2.0 LICENSE,
  LF 行尾 + .gitattributes
2026-10-23 14:15:47 +08:00

352 lines
9.9 KiB
Go

package middleware
import (
"context"
"net/http"
"slices"
"strconv"
"strings"
"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/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gmeta"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
"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/packed/response"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/service"
)
type sMiddleware struct{}
const apiDataScopeRoleId int64 = 17
func New() *sMiddleware {
return &sMiddleware{}
}
func init() {
service.RegisterMiddleware(New())
}
func getContentType(r *ghttp.Request) (contentType string) {
contentType = r.Response.Header().Get("Content-Type")
if contentType != "" {
return
}
mime := gmeta.Get(r.GetHandlerResponse(), "mime").String()
if mime == "" {
contentType = consts.HTTPContentTypeJson
} else {
contentType = mime
}
return
}
func (m *sMiddleware) ResponseHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.BufferLength() > 0 || contexts.IsWebSocket(r.GetCtx()) {
return
}
if getContentType(r) == consts.HTTPContentTypeExcel {
r.Response.Status = http.StatusOK
return
}
err := r.GetError()
res := r.GetHandlerResponse()
var code gcode.Code = gcode.CodeOK
if err != nil {
code = gerror.Code(err)
if code == gcode.CodeNil {
code = gcode.CodeInternalError
}
response.JsonExit(r, code.Code(), err.Error())
return
}
response.Json(r, code.Code(), "", res)
}
func (s *sMiddleware) ApiCtx(r *ghttp.Request) {
ctx := r.GetCtx()
r.SetCtx(r.GetNeverDoneCtx())
if r.URL != nil && s.IsExceptLogin(ctx, r.URL.Path) {
r.Middleware.Next()
return
}
data, err := service.QueryToken().GetCurrentCustomer(ctx)
if err != nil {
g.Log().Errorf(ctx, "sMiddleware.ApiCtx current customer error: %v", err)
r.Middleware.Next()
return
}
if data != nil {
contextModel := new(model.Context)
contextModel.User = &model.Identity{
BaseClaims: model.BaseClaims{
ID: 0,
TenantId: data.BaseClaimsApi.TenantId,
DeptId: data.BaseClaimsApi.DeptId,
Roles: []model.Role{
{RoleId: apiDataScopeRoleId, DataScope: consts.SysRoleDataScopeDeptAndBelow},
},
LoginAt: data.BaseClaimsApi.LoginAt,
},
}
contextModel.AgentUser = &data.BaseClaimsApi
contexts.Init(r, contextModel)
}
r.Middleware.Next()
}
func (s *sMiddleware) ApiAuth(r *ghttp.Request) {
ctx := r.Context()
path := r.URL.Path
if s.IsExceptLogin(ctx, path) {
r.Middleware.Next()
return
}
deptKey := contexts.GetDeptKey(ctx)
if deptKey == "" {
g.Log().Error(ctx, "sMiddleware.ApiAuth deptKey is empty", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
return
}
appId := r.Get("appId").String()
if appId == "" {
g.Log().Error(ctx, "sMiddleware.ApiAuth appId is empty", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
return
}
if deptKey != appId {
g.Log().Error(ctx, "sMiddleware.ApiAuth deptKey not match appId", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
return
}
if contexts.GetAgentDeptId(ctx) == 0 {
g.Log().Error(ctx, "sMiddleware.ApiAuth deptId is 0", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
return
}
if !s.hasOpenApiPermission(ctx, r.Method, path) {
response.JsonExit(r, consts.CodeNoPermission.Code(), consts.CodeNoPermission.Message())
return
}
r.Middleware.Next()
}
func openApiMethodValues(method string) []string {
method = strings.ToUpper(strings.TrimSpace(method))
values := []string{method}
switch method {
case "GET":
values = append(values, "4")
case "POST":
values = append(values, "1")
case "PUT":
values = append(values, "2")
case "DELETE":
values = append(values, "3")
}
return values
}
func (s *sMiddleware) hasOpenApiPermission(ctx context.Context, method, path string) bool {
agentUser := contexts.GetAgentUser(ctx)
if agentUser == nil {
return false
}
if path == "/openapi/v3/query/token" {
return true
}
path = strings.TrimSpace(path)
if path != "/" {
path = strings.TrimRight(path, "/")
}
var apiResource entity.TApiResource
if err := dao.TApiResource.Ctx(ctx).
WhereIn(dao.TApiResource.Columns().Method, openApiMethodValues(method)).
Where(dao.TApiResource.Columns().Path, path).
Scan(&apiResource); err != nil {
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission resource not configured, method: %s, path: %s, err: %v", method, path, err)
return true
}
if apiResource.Id == 0 {
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission resource not configured, method: %s, path: %s", method, path)
return true
}
if apiResource.Status == 1 {
return false
}
var tApi entity.TApi
apiMod := dao.TApi.Ctx(ctx)
if agentUser.ApiId > 0 {
apiMod = apiMod.Where(dao.TApi.Columns().Id, agentUser.ApiId)
} else if agentUser.AppKey != "" {
apiMod = apiMod.Where(dao.TApi.Columns().AppKey, agentUser.AppKey)
} else {
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission api identity is empty, resourceApiId: %d, path: %s", apiResource.Id, path)
return false
}
if err := apiMod.Scan(&tApi); err != nil {
g.Log().Errorf(ctx, "sMiddleware.hasOpenApiPermission scan t_api error: %v", err)
return false
}
if tApi.Id == 0 || tApi.Status == 1 {
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission t_api unavailable, tApiId: %d, status: %d, resourceApiId: %d, path: %s", tApi.Id, tApi.Status, apiResource.Id, path)
return false
}
if tApi.GroupId <= 0 {
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission groupId is empty, tApiId: %d, appKey: %s, resourceApiId: %d, path: %s", tApi.Id, tApi.AppKey, apiResource.Id, path)
return false
}
count, err := dao.TApiGroupResource.Ctx(ctx).
Where(dao.TApiGroupResource.Columns().GroupId, tApi.GroupId).
Where(dao.TApiGroupResource.Columns().ApiId, apiResource.Id).
Count()
if err != nil {
g.Log().Errorf(ctx, "sMiddleware.hasOpenApiPermission scan relation error: %v", err)
return false
}
return count > 0
}
func (m *sMiddleware) OldResponseHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.BufferLength() > 0 || contexts.IsWebSocket(r.GetCtx()) {
return
}
if getContentType(r) == consts.HTTPContentTypeExcel {
r.Response.Status = http.StatusOK
return
}
err := r.GetError()
res := r.GetHandlerResponse()
var code gcode.Code = gcode.CodeOK
if err != nil {
code = gerror.Code(err)
if code == gcode.CodeNil {
code = gcode.CodeInternalError
}
response.OldJson(r, code.Code(), err.Error())
return
}
response.OldJson(r, code.Code(), "", res)
}
func (s *sMiddleware) OldCtx(r *ghttp.Request) {
ctx := r.GetCtx()
r.SetCtx(r.GetNeverDoneCtx())
verifyVar := r.Get("verify")
var verify model.VerifyItem
if err := verifyVar.Scan(&verify); err != nil {
g.Log().Errorf(ctx, "sMiddleware.OldCtx verify scan error: %v", err)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
return
}
var tApi entity.TApi
err := dao.TApi.Ctx(ctx).Where(g.Map{
dao.TApi.Columns().AppKey: verify.AppId,
}).Scan(&tApi)
if err != nil {
r.Middleware.Next()
return
}
if tApi.Id != 0 {
contextModel := new(model.Context)
contextModel.User = &model.Identity{
BaseClaims: model.BaseClaims{
ID: 0,
TenantId: tApi.TenantId,
DeptId: tApi.DeptId,
Roles: []model.Role{
{RoleId: apiDataScopeRoleId, DataScope: consts.SysRoleDataScopeDeptAndBelow},
},
LoginAt: gtime.Now().TimestampMilli(),
},
}
contextModel.AgentUser = &model.BaseClaimsApi{
ApiId: tApi.Id,
AppKey: tApi.AppKey,
GroupId: tApi.GroupId,
DeptId: tApi.DeptId,
TenantId: tApi.TenantId,
LoginAt: gtime.Now().TimestampMilli(),
}
contexts.Init(r, contextModel)
}
r.Middleware.Next()
}
func (s *sMiddleware) OldAuth(r *ghttp.Request) {
ctx := r.Context()
path := r.URL.Path
if s.IsExceptLogin(ctx, path) {
r.Middleware.Next()
return
}
verifyVar := r.Get("verify")
var verify model.VerifyItem
if err := verifyVar.Scan(&verify); err != nil {
g.Log().Errorf(ctx, "sMiddleware.OldAuth verify scan error: %v", err)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
return
}
if verify.TimeStamp == "" {
g.Log().Error(ctx, "sMiddleware.OldAuth timestamp is empty", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
return
}
givenTime, err := strconv.ParseInt(verify.TimeStamp, 10, 64)
if err != nil {
g.Log().Errorf(ctx, "sMiddleware.OldAuth timestamp parse error: %v", err)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
return
}
if givenTime > gtime.Now().TimestampMilli()+3*60*1000 {
g.Log().Error(ctx, "sMiddleware.OldAuth timestamp expired", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request timeout")
return
}
if verify.AppId == "" {
g.Log().Error(ctx, "sMiddleware.OldAuth appId is empty", "path", path)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
return
}
var tApi entity.TApi
err = dao.TApi.Ctx(ctx).Where(g.Map{
dao.TApi.Columns().AppKey: verify.AppId,
}).Scan(&tApi)
if err != nil || tApi.Id == 0 || verify.Password != tApi.AppSecret {
g.Log().Errorf(ctx, "sMiddleware.OldAuth auth failed: %v", err)
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeUserNotFound.Message())
return
}
r.Middleware.Next()
}
func (s *sMiddleware) IsExceptLogin(ctx context.Context, path string) bool {
pathList := g.Cfg().MustGet(ctx, "router.exceptLogin").Strings()
return slices.Contains(pathList, path)
}