feat: implement httpx response utilities and refactor API controllers to use them
This commit is contained in:
@@ -7,56 +7,33 @@ import (
|
||||
"cs-agent/internal/controllers/api"
|
||||
"cs-agent/internal/controllers/dashboard"
|
||||
"cs-agent/internal/controllers/third"
|
||||
"cs-agent/internal/pkg/httpx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func writeJSON(ctx *gin.Context, result *web.JsonResult) {
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, result)
|
||||
httpx.WriteJSON(ctx, result)
|
||||
}
|
||||
|
||||
func pathInt64(ctx *gin.Context, name string) (int64, bool) {
|
||||
value, err := strconv.ParseInt(ctx.Param(name), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, web.JsonErrorMsg("路径参数错误"))
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg("路径参数错误"))
|
||||
return 0, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func registerApiAuthRoutes(group *gin.RouterGroup) {
|
||||
group.POST("/login", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
writeJSON(ctx, controller.PostLogin())
|
||||
})
|
||||
group.POST("/logout", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
writeJSON(ctx, controller.PostLogout())
|
||||
})
|
||||
group.GET("/profile", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
writeJSON(ctx, controller.GetProfile())
|
||||
})
|
||||
group.GET("/wxwork_callback", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
controller.GetWxwork_callback()
|
||||
})
|
||||
group.POST("/wxwork_exchange", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
writeJSON(ctx, controller.PostWxwork_exchange())
|
||||
})
|
||||
group.GET("/wxwork_login", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
controller.GetWxwork_login()
|
||||
})
|
||||
group.GET("/wxwork_qr_login", func(ctx *gin.Context) {
|
||||
controller := &api.AuthController{Ctx: ctx}
|
||||
controller.GetWxwork_qr_login()
|
||||
})
|
||||
group.POST("/login", api.Login)
|
||||
group.POST("/logout", api.Logout)
|
||||
group.GET("/profile", api.Profile)
|
||||
group.GET("/wxwork_callback", api.WxWorkCallback)
|
||||
group.POST("/wxwork_exchange", api.WxWorkExchange)
|
||||
group.GET("/wxwork_login", api.WxWorkLogin)
|
||||
group.GET("/wxwork_qr_login", api.WxWorkQRLogin)
|
||||
}
|
||||
|
||||
func registerApiChannelRoutes(group *gin.RouterGroup) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"cs-agent/internal/pkg/config"
|
||||
"cs-agent/internal/pkg/dto/request"
|
||||
"cs-agent/internal/pkg/httpx"
|
||||
"cs-agent/internal/pkg/httpx/params"
|
||||
"cs-agent/internal/services"
|
||||
"net/http"
|
||||
@@ -10,91 +11,92 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
Ctx *gin.Context
|
||||
}
|
||||
|
||||
func (c *AuthController) PostLogin() *web.JsonResult {
|
||||
func Login(ctx *gin.Context) {
|
||||
cfg := config.Current()
|
||||
req := request.LoginRequest{}
|
||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
|
||||
ret, err := services.AuthService.Login(req, cfg.Auth, c.Ctx.ClientIP(), c.Ctx.GetHeader("User-Agent"))
|
||||
if err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
return web.JsonData(ret)
|
||||
}
|
||||
|
||||
func (c *AuthController) GetWxwork_login() {
|
||||
loginURL, err := services.WxWorkLoginService.BuildWxWorkLoginURL(c.Ctx.Query("next"))
|
||||
if err != nil {
|
||||
c.redirectWxWorkError(err.Error())
|
||||
if err := params.ReadJSON(ctx, &req); err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, loginURL)
|
||||
}
|
||||
|
||||
func (c *AuthController) GetWxwork_qr_login() {
|
||||
loginURL, err := services.WxWorkLoginService.BuildWxWorkQRCodeLoginURL(c.Ctx.Query("next"))
|
||||
ret, err := services.AuthService.Login(req, cfg.Auth, ctx.ClientIP(), ctx.GetHeader("User-Agent"))
|
||||
if err != nil {
|
||||
c.redirectWxWorkError(err.Error())
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, loginURL)
|
||||
httpx.WriteJSON(ctx, ret)
|
||||
}
|
||||
|
||||
func (c *AuthController) GetWxwork_callback() {
|
||||
func WxWorkLogin(ctx *gin.Context) {
|
||||
loginURL, err := services.WxWorkLoginService.BuildWxWorkLoginURL(ctx.Query("next"))
|
||||
if err != nil {
|
||||
redirectWxWorkError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.Redirect(http.StatusFound, loginURL)
|
||||
}
|
||||
|
||||
func WxWorkQRLogin(ctx *gin.Context) {
|
||||
loginURL, err := services.WxWorkLoginService.BuildWxWorkQRCodeLoginURL(ctx.Query("next"))
|
||||
if err != nil {
|
||||
redirectWxWorkError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.Redirect(http.StatusFound, loginURL)
|
||||
}
|
||||
|
||||
func WxWorkCallback(ctx *gin.Context) {
|
||||
cfg := config.Current()
|
||||
ticket, next, err := services.WxWorkLoginService.LoginByWxWork(
|
||||
c.Ctx.Query("code"),
|
||||
c.Ctx.Query("state"),
|
||||
ctx.Query("code"),
|
||||
ctx.Query("state"),
|
||||
cfg.Auth,
|
||||
c.Ctx.ClientIP(),
|
||||
c.Ctx.GetHeader("User-Agent"),
|
||||
ctx.ClientIP(),
|
||||
ctx.GetHeader("User-Agent"),
|
||||
)
|
||||
if err != nil {
|
||||
c.redirectWxWorkError(err.Error())
|
||||
redirectWxWorkError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, "/dashboard/login/wxwork/callback?ticket="+url.QueryEscape(ticket)+"&next="+url.QueryEscape(next))
|
||||
ctx.Redirect(http.StatusFound, "/dashboard/login/wxwork/callback?ticket="+url.QueryEscape(ticket)+"&next="+url.QueryEscape(next))
|
||||
}
|
||||
|
||||
func (c *AuthController) PostWxwork_exchange() *web.JsonResult {
|
||||
func WxWorkExchange(ctx *gin.Context) {
|
||||
req := request.WxWorkExchangeRequest{}
|
||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
||||
return web.JsonError(err)
|
||||
if err := params.ReadJSON(ctx, &req); err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
ret, err := services.WxWorkLoginService.ExchangeWxWorkLoginTicket(req.Ticket)
|
||||
if err != nil {
|
||||
return web.JsonError(err)
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
return web.JsonData(ret)
|
||||
httpx.WriteJSON(ctx, ret)
|
||||
}
|
||||
|
||||
func (c *AuthController) PostLogout() *web.JsonResult {
|
||||
if err := services.AuthService.Logout(c.Ctx.GetHeader("Authorization")); err != nil {
|
||||
return web.JsonError(err)
|
||||
func Logout(ctx *gin.Context) {
|
||||
if err := services.AuthService.Logout(ctx.GetHeader("Authorization")); err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
return web.JsonSuccess()
|
||||
httpx.WriteJSON(ctx, nil)
|
||||
}
|
||||
|
||||
func (c *AuthController) GetProfile() *web.JsonResult {
|
||||
ret, err := services.AuthService.CurrentProfile(c.Ctx)
|
||||
func Profile(ctx *gin.Context) {
|
||||
ret, err := services.AuthService.CurrentProfile(ctx)
|
||||
if err != nil {
|
||||
return web.JsonError(err)
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
return web.JsonData(ret)
|
||||
httpx.WriteJSON(ctx, ret)
|
||||
}
|
||||
|
||||
func (c *AuthController) redirectWxWorkError(message string) {
|
||||
func redirectWxWorkError(ctx *gin.Context, message string) {
|
||||
if idx := strings.Index(message, ": "); idx >= 0 {
|
||||
message = message[idx+2:]
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, "/login?wxworkError="+url.QueryEscape(message))
|
||||
ctx.Redirect(http.StatusFound, "/login?wxworkError="+url.QueryEscape(message))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
type cursorData struct {
|
||||
results any
|
||||
cursor string
|
||||
hasMore bool
|
||||
}
|
||||
|
||||
type pageData struct {
|
||||
results any
|
||||
paging *sqls.Paging
|
||||
}
|
||||
|
||||
func CursorData(results any, cursor string, hasMore bool) any {
|
||||
return cursorData{results: results, cursor: cursor, hasMore: hasMore}
|
||||
}
|
||||
|
||||
func PageData(results any, paging *sqls.Paging) any {
|
||||
return pageData{results: results, paging: paging}
|
||||
}
|
||||
|
||||
func WriteJSON(ctx *gin.Context, result any) {
|
||||
ctx.JSON(http.StatusOK, buildJSONResult(result))
|
||||
}
|
||||
|
||||
func WriteHttpStatusJSON(ctx *gin.Context, statusCode int, result any) {
|
||||
ctx.JSON(statusCode, buildJSONResult(result))
|
||||
}
|
||||
|
||||
func buildJSONResult(result any) *web.JsonResult {
|
||||
switch value := result.(type) {
|
||||
case nil:
|
||||
return web.JsonSuccess()
|
||||
case *web.JsonResult:
|
||||
return value
|
||||
case web.JsonResult:
|
||||
return &value
|
||||
case *web.CodeError:
|
||||
return web.JsonError(value)
|
||||
case web.CodeError:
|
||||
return web.JsonError(&value)
|
||||
case error:
|
||||
return web.JsonError(value)
|
||||
case cursorData:
|
||||
return web.JsonCursorData(value.results, value.cursor, value.hasMore)
|
||||
case pageData:
|
||||
return web.JsonPageData(value.results, value.paging)
|
||||
case web.RspBuilder:
|
||||
return value.JsonResult()
|
||||
case *web.RspBuilder:
|
||||
return value.JsonResult()
|
||||
default:
|
||||
return web.JsonData(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func TestWriteJSONWrapsCommonResultTypes(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
wantStatus int
|
||||
want web.JsonResult
|
||||
}{
|
||||
{
|
||||
name: "nil becomes success",
|
||||
input: nil,
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonSuccess(),
|
||||
},
|
||||
{
|
||||
name: "plain value becomes data",
|
||||
input: map[string]any{"id": float64(1)},
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonData(map[string]any{"id": float64(1)}),
|
||||
},
|
||||
{
|
||||
name: "error becomes json error",
|
||||
input: errors.New("boom"),
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonError(errors.New("boom")),
|
||||
},
|
||||
{
|
||||
name: "page data becomes page result",
|
||||
input: PageData([]any{"a"}, &sqls.Paging{Page: 1, Limit: 20, Total: 1}),
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonPageData([]any{"a"}, &sqls.Paging{Page: 1, Limit: 20, Total: 1}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, recorder := testContext()
|
||||
|
||||
WriteJSON(ctx, tt.input)
|
||||
|
||||
if recorder.Code != tt.wantStatus {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, tt.wantStatus)
|
||||
}
|
||||
var got web.JsonResult
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if got.Success != tt.want.Success || got.ErrorCode != tt.want.ErrorCode || got.Message != tt.want.Message {
|
||||
t.Fatalf("result = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteHttpStatusJSONUsesProvidedStatus(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx, recorder := testContext()
|
||||
|
||||
WriteHttpStatusJSON(ctx, http.StatusUnauthorized, web.JsonErrorMsg("unauthorized"))
|
||||
|
||||
if recorder.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func testContext() (*gin.Context, *httptest.ResponseRecorder) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
return ctx, recorder
|
||||
}
|
||||
Reference in New Issue
Block a user