Merge branch 'develop' of https://github.com/beego/beego into feature/route-method-expressions
This commit is contained in:
commit
0c27df10c7
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -8,7 +8,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||||
branches:
|
branches:
|
||||||
- master
|
- develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changelog:
|
changelog:
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
# developing
|
# developing
|
||||||
|
- Add sonar check and ignore test. [4432](https://github.com/beego/beego/pull/4432) [4433](https://github.com/beego/beego/pull/4433)
|
||||||
|
- Update changlog.yml to check every PR to develop branch.[4427](https://github.com/beego/beego/pull/4427)
|
||||||
- Fix 4396: Add context.param module into adapter. [4398](https://github.com/beego/beego/pull/4398)
|
- Fix 4396: Add context.param module into adapter. [4398](https://github.com/beego/beego/pull/4398)
|
||||||
- Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391)
|
- Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391)
|
||||||
- Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385)
|
- Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385)
|
||||||
- Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385)
|
- Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385)
|
||||||
- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386)
|
- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386)
|
||||||
|
- Support 4144: Add new api for order by for supporting multiple way to query [4294](https://github.com/beego/beego/pull/4294)
|
||||||
|
- Support session Filter chain. [4404](https://github.com/beego/beego/pull/4404)
|
||||||
@ -301,6 +301,28 @@ func TestAddFilter(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
|
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterChainOrder(t *testing.T) {
|
||||||
|
req := Get("http://beego.me")
|
||||||
|
req.AddFilters(func(next Filter) Filter {
|
||||||
|
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
|
||||||
|
return NewHttpResponseWithJsonBody("first"), nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
req.AddFilters(func(next Filter) Filter {
|
||||||
|
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
|
||||||
|
return NewHttpResponseWithJsonBody("second"), nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := req.DoRequestWithCtx(context.Background())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
data := make([]byte, 5)
|
||||||
|
_, _ = resp.Body.Read(data)
|
||||||
|
assert.Equal(t, "first", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
func TestHead(t *testing.T) {
|
func TestHead(t *testing.T) {
|
||||||
req := Head("http://beego.me")
|
req := Head("http://beego.me")
|
||||||
assert.NotNil(t, req)
|
assert.NotNil(t, req)
|
||||||
|
|||||||
6
client/orm/clauses/const.go
Normal file
6
client/orm/clauses/const.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package clauses
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExprSep = "__"
|
||||||
|
ExprDot = "."
|
||||||
|
)
|
||||||
103
client/orm/clauses/order_clause/order.go
Normal file
103
client/orm/clauses/order_clause/order.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package order_clause
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sort int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
None Sort = 0
|
||||||
|
Ascending Sort = 1
|
||||||
|
Descending Sort = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(order *Order)
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
column string
|
||||||
|
sort Sort
|
||||||
|
isRaw bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clause(options ...Option) *Order {
|
||||||
|
o := &Order{}
|
||||||
|
for _, option := range options {
|
||||||
|
option(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) GetColumn() string {
|
||||||
|
return o.column
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) GetSort() Sort {
|
||||||
|
return o.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) SortString() string {
|
||||||
|
switch o.GetSort() {
|
||||||
|
case Ascending:
|
||||||
|
return "ASC"
|
||||||
|
case Descending:
|
||||||
|
return "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) IsRaw() bool {
|
||||||
|
return o.isRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseOrder(expressions ...string) []*Order {
|
||||||
|
var orders []*Order
|
||||||
|
for _, expression := range expressions {
|
||||||
|
sort := Ascending
|
||||||
|
column := strings.ReplaceAll(expression, clauses.ExprSep, clauses.ExprDot)
|
||||||
|
if column[0] == '-' {
|
||||||
|
sort = Descending
|
||||||
|
column = column[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
orders = append(orders, &Order{
|
||||||
|
column: column,
|
||||||
|
sort: sort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders
|
||||||
|
}
|
||||||
|
|
||||||
|
func Column(column string) Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.column = strings.ReplaceAll(column, clauses.ExprSep, clauses.ExprDot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sort(sort Sort) Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.sort = sort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortAscending() Option {
|
||||||
|
return sort(Ascending)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortDescending() Option {
|
||||||
|
return sort(Descending)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortNone() Option {
|
||||||
|
return sort(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Raw() Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.isRaw = true
|
||||||
|
}
|
||||||
|
}
|
||||||
144
client/orm/clauses/order_clause/order_test.go
Normal file
144
client/orm/clauses/order_clause/order_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package order_clause
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClause(t *testing.T) {
|
||||||
|
var (
|
||||||
|
column = `a`
|
||||||
|
)
|
||||||
|
|
||||||
|
o := Clause(
|
||||||
|
Column(column),
|
||||||
|
)
|
||||||
|
|
||||||
|
if o.GetColumn() != column {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortAscending(t *testing.T) {
|
||||||
|
o := Clause(
|
||||||
|
SortAscending(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if o.GetSort() != Ascending {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortDescending(t *testing.T) {
|
||||||
|
o := Clause(
|
||||||
|
SortDescending(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if o.GetSort() != Descending {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortNone(t *testing.T) {
|
||||||
|
o1 := Clause(
|
||||||
|
SortNone(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if o1.GetSort() != None {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
o2 := Clause()
|
||||||
|
|
||||||
|
if o2.GetSort() != None {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRaw(t *testing.T) {
|
||||||
|
o1 := Clause()
|
||||||
|
|
||||||
|
if o1.IsRaw() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
o2 := Clause(
|
||||||
|
Raw(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if !o2.IsRaw() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColumn(t *testing.T) {
|
||||||
|
o1 := Clause(
|
||||||
|
Column(`aaa`),
|
||||||
|
)
|
||||||
|
|
||||||
|
if o1.GetColumn() != `aaa` {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOrder(t *testing.T) {
|
||||||
|
orders := ParseOrder(
|
||||||
|
`-user__status`,
|
||||||
|
`status`,
|
||||||
|
`user__status`,
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Log(orders)
|
||||||
|
|
||||||
|
if orders[0].GetSort() != Descending {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orders[0].GetColumn() != `user.status` {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orders[1].GetColumn() != `status` {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orders[1].GetSort() != Ascending {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orders[2].GetColumn() != `user.status` {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder_GetColumn(t *testing.T) {
|
||||||
|
o := Clause(
|
||||||
|
Column(`user__id`),
|
||||||
|
)
|
||||||
|
if o.GetColumn() != `user.id` {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder_GetSort(t *testing.T) {
|
||||||
|
o := Clause(
|
||||||
|
SortDescending(),
|
||||||
|
)
|
||||||
|
if o.GetSort() != Descending {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder_IsRaw(t *testing.T) {
|
||||||
|
o1 := Clause()
|
||||||
|
if o1.IsRaw() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
o2 := Clause(
|
||||||
|
Raw(),
|
||||||
|
)
|
||||||
|
if !o2.IsRaw() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,8 @@ package orm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -421,7 +423,7 @@ func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate order sql.
|
// generate order sql.
|
||||||
func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
|
func (t *dbTables) getOrderSQL(orders []*order_clause.Order) (orderSQL string) {
|
||||||
if len(orders) == 0 {
|
if len(orders) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -430,19 +432,25 @@ func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
|
|||||||
|
|
||||||
orderSqls := make([]string, 0, len(orders))
|
orderSqls := make([]string, 0, len(orders))
|
||||||
for _, order := range orders {
|
for _, order := range orders {
|
||||||
asc := "ASC"
|
column := order.GetColumn()
|
||||||
if order[0] == '-' {
|
clause := strings.Split(column, clauses.ExprDot)
|
||||||
asc = "DESC"
|
|
||||||
order = order[1:]
|
|
||||||
}
|
|
||||||
exprs := strings.Split(order, ExprSep)
|
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
if order.IsRaw() {
|
||||||
|
if len(clause) == 2 {
|
||||||
|
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", clause[0], Q, clause[1], Q, order.SortString()))
|
||||||
|
} else if len(clause) == 1 {
|
||||||
|
orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", Q, clause[0], Q, order.SortString()))
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index, _, fi, suc := t.parseExprs(t.mi, clause)
|
||||||
if !suc {
|
if !suc {
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||||
}
|
}
|
||||||
|
|
||||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, asc))
|
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, order.SortString()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
||||||
|
|||||||
@ -58,6 +58,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
@ -351,7 +352,7 @@ func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name s
|
|||||||
qs.relDepth = relDepth
|
qs.relDepth = relDepth
|
||||||
|
|
||||||
if len(order) > 0 {
|
if len(order) > 0 {
|
||||||
qs.orders = []string{order}
|
qs.orders = order_clause.ParseOrder(order)
|
||||||
}
|
}
|
||||||
|
|
||||||
find := ind.FieldByIndex(fi.fieldIndex)
|
find := ind.FieldByIndex(fi.fieldIndex)
|
||||||
|
|||||||
@ -16,12 +16,13 @@ package orm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExprSep define the expression separation
|
// ExprSep define the expression separation
|
||||||
const (
|
const (
|
||||||
ExprSep = "__"
|
ExprSep = clauses.ExprSep
|
||||||
)
|
)
|
||||||
|
|
||||||
type condValue struct {
|
type condValue struct {
|
||||||
|
|||||||
@ -17,8 +17,8 @@ package orm
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/hints"
|
"github.com/beego/beego/v2/client/orm/hints"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type colValue struct {
|
type colValue struct {
|
||||||
@ -71,7 +71,7 @@ type querySet struct {
|
|||||||
limit int64
|
limit int64
|
||||||
offset int64
|
offset int64
|
||||||
groups []string
|
groups []string
|
||||||
orders []string
|
orders []*order_clause.Order
|
||||||
distinct bool
|
distinct bool
|
||||||
forUpdate bool
|
forUpdate bool
|
||||||
useIndex int
|
useIndex int
|
||||||
@ -140,8 +140,20 @@ func (o querySet) GroupBy(exprs ...string) QuerySeter {
|
|||||||
|
|
||||||
// add ORDER expression.
|
// add ORDER expression.
|
||||||
// "column" means ASC, "-column" means DESC.
|
// "column" means ASC, "-column" means DESC.
|
||||||
func (o querySet) OrderBy(exprs ...string) QuerySeter {
|
func (o querySet) OrderBy(expressions ...string) QuerySeter {
|
||||||
o.orders = exprs
|
if len(expressions) <= 0 {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
o.orders = order_clause.ParseOrder(expressions...)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ORDER expression.
|
||||||
|
func (o querySet) OrderClauses(orders ...*order_clause.Order) QuerySeter {
|
||||||
|
if len(orders) <= 0 {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
o.orders = orders
|
||||||
return &o
|
return &o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@ -1146,6 +1147,26 @@ func TestOrderBy(t *testing.T) {
|
|||||||
num, err = qs.OrderBy("-profile__age").Filter("user_name", "astaxie").Count()
|
num, err = qs.OrderBy("-profile__age").Filter("user_name", "astaxie").Count()
|
||||||
throwFail(t, err)
|
throwFail(t, err)
|
||||||
throwFail(t, AssertIs(num, 1))
|
throwFail(t, AssertIs(num, 1))
|
||||||
|
|
||||||
|
num, err = qs.OrderClauses(
|
||||||
|
order_clause.Clause(
|
||||||
|
order_clause.Column(`profile__age`),
|
||||||
|
order_clause.SortDescending(),
|
||||||
|
),
|
||||||
|
).Filter("user_name", "astaxie").Count()
|
||||||
|
throwFail(t, err)
|
||||||
|
throwFail(t, AssertIs(num, 1))
|
||||||
|
|
||||||
|
if IsMysql {
|
||||||
|
num, err = qs.OrderClauses(
|
||||||
|
order_clause.Clause(
|
||||||
|
order_clause.Column(`rand()`),
|
||||||
|
order_clause.Raw(),
|
||||||
|
),
|
||||||
|
).Filter("user_name", "astaxie").Count()
|
||||||
|
throwFail(t, err)
|
||||||
|
throwFail(t, AssertIs(num, 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAll(t *testing.T) {
|
func TestAll(t *testing.T) {
|
||||||
@ -1232,6 +1253,19 @@ func TestValues(t *testing.T) {
|
|||||||
throwFail(t, AssertIs(maps[2]["Profile"], nil))
|
throwFail(t, AssertIs(maps[2]["Profile"], nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
num, err = qs.OrderClauses(
|
||||||
|
order_clause.Clause(
|
||||||
|
order_clause.Column("Id"),
|
||||||
|
order_clause.SortAscending(),
|
||||||
|
),
|
||||||
|
).Values(&maps)
|
||||||
|
throwFail(t, err)
|
||||||
|
throwFail(t, AssertIs(num, 3))
|
||||||
|
if num == 3 {
|
||||||
|
throwFail(t, AssertIs(maps[0]["UserName"], "slene"))
|
||||||
|
throwFail(t, AssertIs(maps[2]["Profile"], nil))
|
||||||
|
}
|
||||||
|
|
||||||
num, err = qs.OrderBy("Id").Values(&maps, "UserName", "Profile__Age")
|
num, err = qs.OrderBy("Id").Values(&maps, "UserName", "Profile__Age")
|
||||||
throwFail(t, err)
|
throwFail(t, err)
|
||||||
throwFail(t, AssertIs(num, 3))
|
throwFail(t, AssertIs(num, 3))
|
||||||
|
|||||||
@ -17,6 +17,7 @@ package orm
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -289,6 +290,28 @@ type QuerySeter interface {
|
|||||||
// for example:
|
// for example:
|
||||||
// qs.OrderBy("-status")
|
// qs.OrderBy("-status")
|
||||||
OrderBy(exprs ...string) QuerySeter
|
OrderBy(exprs ...string) QuerySeter
|
||||||
|
// add ORDER expression by order clauses
|
||||||
|
// for example:
|
||||||
|
// OrderClauses(
|
||||||
|
// order_clause.Clause(
|
||||||
|
// order.Column("Id"),
|
||||||
|
// order.SortAscending(),
|
||||||
|
// ),
|
||||||
|
// order_clause.Clause(
|
||||||
|
// order.Column("status"),
|
||||||
|
// order.SortDescending(),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// OrderClauses(order_clause.Clause(
|
||||||
|
// order_clause.Column(`user__status`),
|
||||||
|
// order_clause.SortDescending(),//default None
|
||||||
|
// ))
|
||||||
|
// OrderClauses(order_clause.Clause(
|
||||||
|
// order_clause.Column(`random()`),
|
||||||
|
// order_clause.SortNone(),//default None
|
||||||
|
// order_clause.Raw(),//default false.if true, do not check field is valid or not
|
||||||
|
// ))
|
||||||
|
OrderClauses(orders ...*order_clause.Order) QuerySeter
|
||||||
// add FORCE INDEX expression.
|
// add FORCE INDEX expression.
|
||||||
// for example:
|
// for example:
|
||||||
// qs.ForceIndex(`idx_name1`,`idx_name2`)
|
// qs.ForceIndex(`idx_name1`,`idx_name2`)
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -25,7 +25,7 @@ require (
|
|||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible
|
github.com/gomodule/redigo v2.0.0+incompatible
|
||||||
github.com/google/go-cmp v0.5.0 // indirect
|
github.com/google/go-cmp v0.5.0 // indirect
|
||||||
github.com/google/uuid v1.1.1 // indirect
|
github.com/google/uuid v1.1.1
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6
|
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/beego/beego/v2/server/web/session"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -195,6 +196,22 @@ func (ctx *Context) RenderMethodResult(result interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Session return session store of this context of request
|
||||||
|
func (ctx *Context) Session() (store session.Store, err error) {
|
||||||
|
if ctx.Input != nil {
|
||||||
|
if ctx.Input.CruSession != nil {
|
||||||
|
store = ctx.Input.CruSession
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
err = errors.New(`no valid session store(please initialize session)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.New(`no valid input`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Response is a wrapper for the http.ResponseWriter
|
// Response is a wrapper for the http.ResponseWriter
|
||||||
// Started: if true, response was already written to so the other handler will not be executed
|
// Started: if true, response was already written to so the other handler will not be executed
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/beego/beego/v2/server/web/session"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
@ -45,3 +46,26 @@ func TestXsrfReset_01(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext_Session(t *testing.T) {
|
||||||
|
c := NewContext()
|
||||||
|
if store, err := c.Session(); store != nil || err == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Session1(t *testing.T) {
|
||||||
|
c := Context{}
|
||||||
|
if store, err := c.Session(); store != nil || err == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Session2(t *testing.T) {
|
||||||
|
c := NewContext()
|
||||||
|
c.Input.CruSession = &session.MemSessionStore{}
|
||||||
|
|
||||||
|
if store, err := c.Session(); store == nil || err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
@ -37,4 +38,5 @@ func TestFilterChain(t *testing.T) {
|
|||||||
ctx.Input.SetData("RouterPattern", "my-route")
|
ctx.Input.SetData("RouterPattern", "my-route")
|
||||||
filter(ctx)
|
filter(ctx)
|
||||||
assert.True(t, ctx.Input.GetData("invocation").(bool))
|
assert.True(t, ctx.Input.GetData("invocation").(bool))
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|||||||
35
server/web/filter/session/filter.go
Normal file
35
server/web/filter/session/filter.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/beego/beego/v2/core/logs"
|
||||||
|
"github.com/beego/beego/v2/server/web"
|
||||||
|
webContext "github.com/beego/beego/v2/server/web/context"
|
||||||
|
"github.com/beego/beego/v2/server/web/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Session maintain session for web service
|
||||||
|
//Session new a session storage and store it into webContext.Context
|
||||||
|
func Session(providerType session.ProviderType, options ...session.ManagerConfigOpt) web.FilterChain {
|
||||||
|
sessionConfig := session.NewManagerConfig(options...)
|
||||||
|
sessionManager, _ := session.NewManager(string(providerType), sessionConfig)
|
||||||
|
go sessionManager.GC()
|
||||||
|
|
||||||
|
return func(next web.FilterFunc) web.FilterFunc {
|
||||||
|
return func(ctx *webContext.Context) {
|
||||||
|
if ctx.Input.CruSession != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sess, err := sessionManager.SessionStart(ctx.ResponseWriter, ctx.Request); err != nil {
|
||||||
|
logs.Error(`init session error:%s`, err.Error())
|
||||||
|
} else {
|
||||||
|
//release session at the end of request
|
||||||
|
defer sess.SessionRelease(context.Background(), ctx.ResponseWriter)
|
||||||
|
ctx.Input.CruSession = sess
|
||||||
|
}
|
||||||
|
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
server/web/filter/session/filter_test.go
Normal file
86
server/web/filter/session/filter_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/beego/beego/v2/server/web"
|
||||||
|
webContext "github.com/beego/beego/v2/server/web/context"
|
||||||
|
"github.com/beego/beego/v2/server/web/session"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testRequest(t *testing.T, handler *web.ControllerRegister, path string, method string, code int) {
|
||||||
|
r, _ := http.NewRequest(method, path, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
if w.Code != code {
|
||||||
|
t.Errorf("%s, %s: %d, supposed to be %d", path, method, w.Code, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession(t *testing.T) {
|
||||||
|
storeKey := uuid.New().String()
|
||||||
|
handler := web.NewControllerRegister()
|
||||||
|
handler.InsertFilterChain(
|
||||||
|
"*",
|
||||||
|
Session(
|
||||||
|
session.ProviderMemory,
|
||||||
|
session.CfgCookieName(`go_session_id`),
|
||||||
|
session.CfgSetCookie(true),
|
||||||
|
session.CfgGcLifeTime(3600),
|
||||||
|
session.CfgMaxLifeTime(3600),
|
||||||
|
session.CfgSecure(false),
|
||||||
|
session.CfgCookieLifeTime(3600),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
handler.InsertFilterChain(
|
||||||
|
"*",
|
||||||
|
func(next web.FilterFunc) web.FilterFunc {
|
||||||
|
return func(ctx *webContext.Context) {
|
||||||
|
if store := ctx.Input.GetData(storeKey); store == nil {
|
||||||
|
t.Error(`store should not be nil`)
|
||||||
|
}
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
handler.Any("*", func(ctx *webContext.Context) {
|
||||||
|
ctx.Output.SetStatus(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
testRequest(t, handler, "/dataset1/resource1", "GET", 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession1(t *testing.T) {
|
||||||
|
handler := web.NewControllerRegister()
|
||||||
|
handler.InsertFilterChain(
|
||||||
|
"*",
|
||||||
|
Session(
|
||||||
|
session.ProviderMemory,
|
||||||
|
session.CfgCookieName(`go_session_id`),
|
||||||
|
session.CfgSetCookie(true),
|
||||||
|
session.CfgGcLifeTime(3600),
|
||||||
|
session.CfgMaxLifeTime(3600),
|
||||||
|
session.CfgSecure(false),
|
||||||
|
session.CfgCookieLifeTime(3600),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
handler.InsertFilterChain(
|
||||||
|
"*",
|
||||||
|
func(next web.FilterFunc) web.FilterFunc {
|
||||||
|
return func(ctx *webContext.Context) {
|
||||||
|
if store, err := ctx.Session(); store == nil || err != nil {
|
||||||
|
t.Error(`store should not be nil`)
|
||||||
|
}
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
handler.Any("*", func(ctx *webContext.Context) {
|
||||||
|
ctx.Output.SetStatus(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
testRequest(t, handler, "/dataset1/resource1", "GET", 200)
|
||||||
|
}
|
||||||
@ -15,9 +15,12 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
@ -36,13 +39,46 @@ func TestControllerRegister_InsertFilterChain(t *testing.T) {
|
|||||||
ns := NewNamespace("/chain")
|
ns := NewNamespace("/chain")
|
||||||
|
|
||||||
ns.Get("/*", func(ctx *context.Context) {
|
ns.Get("/*", func(ctx *context.Context) {
|
||||||
ctx.Output.Body([]byte("hello"))
|
_ = ctx.Output.Body([]byte("hello"))
|
||||||
})
|
})
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", "/chain/user", nil)
|
r, _ := http.NewRequest("GET", "/chain/user", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
BeeApp.Handlers.Init()
|
||||||
BeeApp.Handlers.ServeHTTP(w, r)
|
BeeApp.Handlers.ServeHTTP(w, r)
|
||||||
|
|
||||||
assert.Equal(t, "filter-chain", w.Header().Get("filter"))
|
assert.Equal(t, "filter-chain", w.Header().Get("filter"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestControllerRegister_InsertFilterChain_Order(t *testing.T) {
|
||||||
|
InsertFilterChain("/abc", func(next FilterFunc) FilterFunc {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
ctx.Output.Header("first", fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
InsertFilterChain("/abc", func(next FilterFunc) FilterFunc {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
ctx.Output.Header("second", fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
r, _ := http.NewRequest("GET", "/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
BeeApp.Handlers.Init()
|
||||||
|
BeeApp.Handlers.ServeHTTP(w, r)
|
||||||
|
first := w.Header().Get("first")
|
||||||
|
second := w.Header().Get("second")
|
||||||
|
|
||||||
|
ft, _ := strconv.ParseInt(first, 10, 64)
|
||||||
|
st, _ := strconv.ParseInt(second, 10, 64)
|
||||||
|
|
||||||
|
assert.True(t, st > ft)
|
||||||
|
}
|
||||||
|
|||||||
@ -140,6 +140,12 @@ func WithRouterSessionOn(sessionOn bool) ControllerOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filterChainConfig struct {
|
||||||
|
pattern string
|
||||||
|
chain FilterChain
|
||||||
|
opts []FilterOpt
|
||||||
|
}
|
||||||
|
|
||||||
// ControllerRegister containers registered router rules, controller handlers and filters.
|
// ControllerRegister containers registered router rules, controller handlers and filters.
|
||||||
type ControllerRegister struct {
|
type ControllerRegister struct {
|
||||||
routers map[string]*Tree
|
routers map[string]*Tree
|
||||||
@ -152,6 +158,9 @@ type ControllerRegister struct {
|
|||||||
// the filter created by FilterChain
|
// the filter created by FilterChain
|
||||||
chainRoot *FilterRouter
|
chainRoot *FilterRouter
|
||||||
|
|
||||||
|
// keep registered chain and build it when serve http
|
||||||
|
filterChains []filterChainConfig
|
||||||
|
|
||||||
cfg *Config
|
cfg *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,11 +181,23 @@ func NewControllerRegisterWithCfg(cfg *Config) *ControllerRegister {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
filterChains: make([]filterChainConfig, 0, 4),
|
||||||
}
|
}
|
||||||
res.chainRoot = newFilterRouter("/*", res.serveHttp, WithCaseSensitive(false))
|
res.chainRoot = newFilterRouter("/*", res.serveHttp, WithCaseSensitive(false))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init will be executed when HttpServer start running
|
||||||
|
func (p *ControllerRegister) Init() {
|
||||||
|
for i := len(p.filterChains) - 1; i >= 0 ; i -- {
|
||||||
|
fc := p.filterChains[i]
|
||||||
|
root := p.chainRoot
|
||||||
|
filterFunc := fc.chain(root.filterFunc)
|
||||||
|
p.chainRoot = newFilterRouter(fc.pattern, filterFunc, fc.opts...)
|
||||||
|
p.chainRoot.next = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add controller handler and pattern rules to ControllerRegister.
|
// Add controller handler and pattern rules to ControllerRegister.
|
||||||
// usage:
|
// usage:
|
||||||
// default methods is the same name as method
|
// default methods is the same name as method
|
||||||
@ -714,12 +735,13 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
func (p *ControllerRegister) InsertFilterChain(pattern string, chain FilterChain, opts ...FilterOpt) {
|
func (p *ControllerRegister) InsertFilterChain(pattern string, chain FilterChain, opts ...FilterOpt) {
|
||||||
root := p.chainRoot
|
|
||||||
filterFunc := chain(root.filterFunc)
|
|
||||||
opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive))
|
|
||||||
p.chainRoot = newFilterRouter(pattern, filterFunc, opts...)
|
|
||||||
p.chainRoot.next = root
|
|
||||||
|
|
||||||
|
opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive))
|
||||||
|
p.filterChains = append(p.filterChains, filterChainConfig{
|
||||||
|
pattern: pattern,
|
||||||
|
chain: chain,
|
||||||
|
opts: opts,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// add Filter into
|
// add Filter into
|
||||||
|
|||||||
@ -84,7 +84,9 @@ func (app *HttpServer) Run(addr string, mws ...MiddleWare) {
|
|||||||
|
|
||||||
initBeforeHTTPRun()
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
// init...
|
||||||
app.initAddr(addr)
|
app.initAddr(addr)
|
||||||
|
app.Handlers.Init()
|
||||||
|
|
||||||
addr = app.Cfg.Listen.HTTPAddr
|
addr = app.Cfg.Listen.HTTPAddr
|
||||||
|
|
||||||
|
|||||||
@ -15,21 +15,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRedis(t *testing.T) {
|
func TestRedis(t *testing.T) {
|
||||||
sessionConfig := &session.ManagerConfig{
|
|
||||||
CookieName: "gosessionid",
|
|
||||||
EnableSetCookie: true,
|
|
||||||
Gclifetime: 3600,
|
|
||||||
Maxlifetime: 3600,
|
|
||||||
Secure: false,
|
|
||||||
CookieLifeTime: 3600,
|
|
||||||
}
|
|
||||||
|
|
||||||
redisAddr := os.Getenv("REDIS_ADDR")
|
redisAddr := os.Getenv("REDIS_ADDR")
|
||||||
if redisAddr == "" {
|
if redisAddr == "" {
|
||||||
redisAddr = "127.0.0.1:6379"
|
redisAddr = "127.0.0.1:6379"
|
||||||
}
|
}
|
||||||
|
redisConfig := fmt.Sprintf("%s,100,,0,30", redisAddr)
|
||||||
|
|
||||||
|
sessionConfig := session.NewManagerConfig(
|
||||||
|
session.CfgCookieName(`gosessionid`),
|
||||||
|
session.CfgSetCookie(true),
|
||||||
|
session.CfgGcLifeTime(3600),
|
||||||
|
session.CfgMaxLifeTime(3600),
|
||||||
|
session.CfgSecure(false),
|
||||||
|
session.CfgCookieLifeTime(3600),
|
||||||
|
session.CfgProviderConfig(redisConfig),
|
||||||
|
)
|
||||||
|
|
||||||
sessionConfig.ProviderConfig = fmt.Sprintf("%s,100,,0,30", redisAddr)
|
|
||||||
globalSession, err := session.NewManager("redis", sessionConfig)
|
globalSession, err := session.NewManager("redis", sessionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not create manager:", err)
|
t.Fatal("could not create manager:", err)
|
||||||
|
|||||||
@ -13,15 +13,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisSentinel(t *testing.T) {
|
func TestRedisSentinel(t *testing.T) {
|
||||||
sessionConfig := &session.ManagerConfig{
|
sessionConfig := session.NewManagerConfig(
|
||||||
CookieName: "gosessionid",
|
session.CfgCookieName(`gosessionid`),
|
||||||
EnableSetCookie: true,
|
session.CfgSetCookie(true),
|
||||||
Gclifetime: 3600,
|
session.CfgGcLifeTime(3600),
|
||||||
Maxlifetime: 3600,
|
session.CfgMaxLifeTime(3600),
|
||||||
Secure: false,
|
session.CfgSecure(false),
|
||||||
CookieLifeTime: 3600,
|
session.CfgCookieLifeTime(3600),
|
||||||
ProviderConfig: "127.0.0.1:6379,100,,0,master",
|
session.CfgProviderConfig("127.0.0.1:6379,100,,0,master"),
|
||||||
}
|
)
|
||||||
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
|
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
t.Log(e)
|
t.Log(e)
|
||||||
|
|||||||
@ -91,25 +91,6 @@ func GetProvider(name string) (Provider, error) {
|
|||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManagerConfig define the session config
|
|
||||||
type ManagerConfig struct {
|
|
||||||
CookieName string `json:"cookieName"`
|
|
||||||
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
|
|
||||||
Gclifetime int64 `json:"gclifetime"`
|
|
||||||
Maxlifetime int64 `json:"maxLifetime"`
|
|
||||||
DisableHTTPOnly bool `json:"disableHTTPOnly"`
|
|
||||||
Secure bool `json:"secure"`
|
|
||||||
CookieLifeTime int `json:"cookieLifeTime"`
|
|
||||||
ProviderConfig string `json:"providerConfig"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
SessionIDLength int64 `json:"sessionIDLength"`
|
|
||||||
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
|
|
||||||
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
|
|
||||||
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
|
|
||||||
SessionIDPrefix string `json:"sessionIDPrefix"`
|
|
||||||
CookieSameSite http.SameSite `json:"cookieSameSite"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manager contains Provider and its configuration.
|
// Manager contains Provider and its configuration.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
provider Provider
|
provider Provider
|
||||||
|
|||||||
143
server/web/session/session_config.go
Normal file
143
server/web/session/session_config.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// ManagerConfig define the session config
|
||||||
|
type ManagerConfig struct {
|
||||||
|
CookieName string `json:"cookieName"`
|
||||||
|
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
|
||||||
|
Gclifetime int64 `json:"gclifetime"`
|
||||||
|
Maxlifetime int64 `json:"maxLifetime"`
|
||||||
|
DisableHTTPOnly bool `json:"disableHTTPOnly"`
|
||||||
|
Secure bool `json:"secure"`
|
||||||
|
CookieLifeTime int `json:"cookieLifeTime"`
|
||||||
|
ProviderConfig string `json:"providerConfig"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
SessionIDLength int64 `json:"sessionIDLength"`
|
||||||
|
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
|
||||||
|
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
|
||||||
|
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
|
||||||
|
SessionIDPrefix string `json:"sessionIDPrefix"`
|
||||||
|
CookieSameSite http.SameSite `json:"cookieSameSite"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ManagerConfig) Opts(opts ...ManagerConfigOpt) {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagerConfigOpt func(config *ManagerConfig)
|
||||||
|
|
||||||
|
func NewManagerConfig(opts ...ManagerConfigOpt) *ManagerConfig {
|
||||||
|
config := &ManagerConfig{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// CfgCookieName set key of session id
|
||||||
|
func CfgCookieName(cookieName string) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.CookieName = cookieName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CfgCookieName set len of session id
|
||||||
|
func CfgSessionIdLength(len int64) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.SessionIDLength = len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CfgSessionIdPrefix set prefix of session id
|
||||||
|
func CfgSessionIdPrefix(prefix string) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.SessionIDPrefix = prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgSetCookie whether set `Set-Cookie` header in HTTP response
|
||||||
|
func CfgSetCookie(enable bool) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.EnableSetCookie = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgGcLifeTime set session gc lift time
|
||||||
|
func CfgGcLifeTime(lifeTime int64) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.Gclifetime = lifeTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgMaxLifeTime set session lift time
|
||||||
|
func CfgMaxLifeTime(lifeTime int64) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.Maxlifetime = lifeTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgGcLifeTime set session lift time
|
||||||
|
func CfgCookieLifeTime(lifeTime int) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.CookieLifeTime = lifeTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgProviderConfig configure session provider
|
||||||
|
func CfgProviderConfig(providerConfig string) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.ProviderConfig = providerConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgDomain set cookie domain
|
||||||
|
func CfgDomain(domain string) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.Domain = domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgSessionIdInHTTPHeader enable session id in http header
|
||||||
|
func CfgSessionIdInHTTPHeader(enable bool) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.EnableSidInHTTPHeader = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgSetSessionNameInHTTPHeader set key of session id in http header
|
||||||
|
func CfgSetSessionNameInHTTPHeader(name string) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.SessionNameInHTTPHeader = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//EnableSidInURLQuery enable session id in query string
|
||||||
|
func CfgEnableSidInURLQuery(enable bool) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.EnableSidInURLQuery = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//DisableHTTPOnly set HTTPOnly for http.Cookie
|
||||||
|
func CfgHTTPOnly(HTTPOnly bool) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.DisableHTTPOnly = !HTTPOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgSecure set Secure for http.Cookie
|
||||||
|
func CfgSecure(Enable bool) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.Secure = Enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CfgSameSite set http.SameSite
|
||||||
|
func CfgSameSite(sameSite http.SameSite) ManagerConfigOpt {
|
||||||
|
return func(config *ManagerConfig) {
|
||||||
|
config.CookieSameSite = sameSite
|
||||||
|
}
|
||||||
|
}
|
||||||
222
server/web/session/session_config_test.go
Normal file
222
server/web/session/session_config_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCfgCookieLifeTime(t *testing.T) {
|
||||||
|
value := 8754
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgCookieLifeTime(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.CookieLifeTime != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgDomain(t *testing.T) {
|
||||||
|
value := `http://domain.com`
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgDomain(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Domain != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSameSite(t *testing.T) {
|
||||||
|
value := http.SameSiteLaxMode
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSameSite(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.CookieSameSite != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSecure(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSecure(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Secure != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSecure1(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSecure(false),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Secure != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSessionIdPrefix(t *testing.T) {
|
||||||
|
value := `sodiausodkljalsd`
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSessionIdPrefix(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.SessionIDPrefix != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSetSessionNameInHTTPHeader(t *testing.T) {
|
||||||
|
value := `sodiausodkljalsd`
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSetSessionNameInHTTPHeader(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.SessionNameInHTTPHeader != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgCookieName(t *testing.T) {
|
||||||
|
value := `sodiausodkljalsd`
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgCookieName(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.CookieName != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgEnableSidInURLQuery(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgEnableSidInURLQuery(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableSidInURLQuery != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgGcLifeTime(t *testing.T) {
|
||||||
|
value := int64(5454)
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgGcLifeTime(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Gclifetime != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgHTTPOnly(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgHTTPOnly(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.DisableHTTPOnly != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgHTTPOnly2(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgHTTPOnly(false),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.DisableHTTPOnly != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgMaxLifeTime(t *testing.T) {
|
||||||
|
value := int64(5454)
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgMaxLifeTime(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Maxlifetime != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgProviderConfig(t *testing.T) {
|
||||||
|
value := `asodiuasldkj12i39809as`
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgProviderConfig(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.ProviderConfig != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSessionIdInHTTPHeader(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSessionIdInHTTPHeader(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableSidInHTTPHeader != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSessionIdInHTTPHeader1(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSessionIdInHTTPHeader(false),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableSidInHTTPHeader != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSessionIdLength(t *testing.T) {
|
||||||
|
value := int64(100)
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSessionIdLength(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.SessionIDLength != value {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSetCookie(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSetCookie(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableSetCookie != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCfgSetCookie1(t *testing.T) {
|
||||||
|
c := NewManagerConfig(
|
||||||
|
CfgSetCookie(false),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableSetCookie != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewManagerConfig(t *testing.T) {
|
||||||
|
c := NewManagerConfig()
|
||||||
|
if c == nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerConfig_Opts(t *testing.T) {
|
||||||
|
c := NewManagerConfig()
|
||||||
|
c.Opts(CfgSetCookie(true))
|
||||||
|
|
||||||
|
if c.EnableSetCookie != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
18
server/web/session/session_provider_type.go
Normal file
18
server/web/session/session_provider_type.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
type ProviderType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderCookie ProviderType = `cookie`
|
||||||
|
ProviderFile ProviderType = `file`
|
||||||
|
ProviderMemory ProviderType = `memory`
|
||||||
|
ProviderCouchbase ProviderType = `couchbase`
|
||||||
|
ProviderLedis ProviderType = `ledis`
|
||||||
|
ProviderMemcache ProviderType = `memcache`
|
||||||
|
ProviderMysql ProviderType = `mysql`
|
||||||
|
ProviderPostgresql ProviderType = `postgresql`
|
||||||
|
ProviderRedis ProviderType = `redis`
|
||||||
|
ProviderRedisCluster ProviderType = `redis_cluster`
|
||||||
|
ProviderRedisSentinel ProviderType = `redis_sentinel`
|
||||||
|
ProviderSsdb ProviderType = `ssdb`
|
||||||
|
)
|
||||||
7
sonar-project.properties
Normal file
7
sonar-project.properties
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
sonar.organization=beego
|
||||||
|
sonar.projectKey=beego_beego
|
||||||
|
|
||||||
|
# relative paths to source directories. More details and properties are described
|
||||||
|
# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/
|
||||||
|
sonar.sources=.
|
||||||
|
sonar.exclusions=**/*_test.go
|
||||||
@ -109,6 +109,23 @@ func TestTask_Run(t *testing.T) {
|
|||||||
assert.Equal(t, "Hello, world! 101", l[1].errinfo)
|
assert.Equal(t, "Hello, world! 101", l[1].errinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCrudTask(t *testing.T) {
|
||||||
|
m := newTaskManager()
|
||||||
|
m.AddTask("my-task1", NewTask("my-task1", "0/30 * * * * *", func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.AddTask("my-task2", NewTask("my-task2", "0/30 * * * * *", func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.DeleteTask("my-task1")
|
||||||
|
assert.Equal(t, 1, len(m.adminTaskList))
|
||||||
|
|
||||||
|
m.ClearTask()
|
||||||
|
assert.Equal(t, 0, len(m.adminTaskList))
|
||||||
|
}
|
||||||
|
|
||||||
func wait(wg *sync.WaitGroup) chan bool {
|
func wait(wg *sync.WaitGroup) chan bool {
|
||||||
ch := make(chan bool)
|
ch := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user