mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 18:41:01 +00:00
Compare commits
573 Commits
Author | SHA1 | Date | |
---|---|---|---|
a09bafbf2a | |||
03de7456ca | |||
78f2fd8d14 | |||
a048ed51a7 | |||
164a9231e8 | |||
aaa7e33778 | |||
f7008e2877 | |||
cf6e825547 | |||
38f9a3c49e | |||
f18283a517 | |||
61aec396e0 | |||
5ba9e63086 | |||
bc773039ca | |||
868fc2a29f | |||
81f69f12ab | |||
0711c3289f | |||
c9b6e4f825 | |||
abd02c7de4 | |||
eb4e0e4030 | |||
96dffcd27f | |||
0d0d87f600 | |||
2c779a4287 | |||
f25893832f | |||
af73a2d515 | |||
67a6b8723c | |||
fdccd85330 | |||
ca394fc8ab | |||
9c9ba0129f | |||
b61c91d93d | |||
f15732798f | |||
efbe655d6a | |||
27ced1d9c3 | |||
8f6bce3b87 | |||
be75f93d43 | |||
541fb181fe | |||
293b54192f | |||
0e0718d110 | |||
6fec0a7831 | |||
654ebebe3c | |||
08c3ca642e | |||
b3c46a87ac | |||
464d080518 | |||
227c04c9e6 | |||
e5d68aceed | |||
67d9241abc | |||
110dbcb31f | |||
740bf72f0c | |||
6b3b8607a0 | |||
b21c59ee70 | |||
fc2c96a177 | |||
87ba3f3cd3 | |||
b80b7b06fc | |||
ad6c97ec1b | |||
d3d97de312 | |||
bf915c3280 | |||
19c5cd130d | |||
1df2662924 | |||
f979050a45 | |||
45b68d444d | |||
732f79e758 | |||
4e954e32b8 | |||
92e81ccf50 | |||
91f2005067 | |||
7c80bf6f9d | |||
cc2c98c112 | |||
c3c0adbf55 | |||
04c305f273 | |||
8c8cf46b55 | |||
e96ae0c24a | |||
98a3cda260 | |||
1fd7fa5df7 | |||
3d3f2ed4c5 | |||
0f73050567 | |||
a40899e6be | |||
a9a15e2c54 | |||
896c258e44 | |||
6df42d63e2 | |||
33bf80b052 | |||
d5c1c0e9a4 | |||
8e61a6a6de | |||
ccaa2dd9e0 | |||
507ea757d7 | |||
9d526dfd50 | |||
ba89253e4a | |||
0d6f190e72 | |||
91b9a65db0 | |||
e96a5fb3ca | |||
f5f70f386d | |||
242efcf7fa | |||
51cc6fc257 | |||
5fb29cb772 | |||
2da894d4a7 | |||
2623b15ce0 | |||
6db9ad7002 | |||
889408136b | |||
886fefe738 | |||
768406f134 | |||
075e63b2bd | |||
0057c08a90 | |||
09b073356d | |||
3c9ed48630 | |||
65d8b4f544 | |||
6d18d4dcdd | |||
21fe2d519e | |||
9a7554fa01 | |||
37d1c13603 | |||
5ed112e946 | |||
453f112094 | |||
faa3341603 | |||
ee9cf05796 | |||
6de538b136 | |||
47c1072b78 | |||
e81f1e53bf | |||
cf92d2c6ef | |||
0507076c3f | |||
59fd3952b7 | |||
7fd80e6aa1 | |||
24fa6189b5 | |||
0bde9cbd91 | |||
122414d789 | |||
aac69674ad | |||
94fba0b2aa | |||
80aa47f605 | |||
f16688817a | |||
2670a86005 | |||
0e369e6df8 | |||
84443b9c05 | |||
33be6803a3 | |||
aef2f1c66e | |||
619cd2d908 | |||
4613acd88e | |||
bf5c5626ab | |||
0fbbc67c3d | |||
3e1916ec3c | |||
b068a676dd | |||
ed73bdcfab | |||
ae94b705ea | |||
08fb921053 | |||
e67e57f8fb | |||
b30969704a | |||
646acc423e | |||
c3a81a23f9 | |||
103dd22151 | |||
ec6cb43711 | |||
84cb9a5986 | |||
9b8bc2aef7 | |||
9710d9e961 | |||
58bb49a78c | |||
df37739c7d | |||
a5dd5d161d | |||
6827107177 | |||
a30a89e57e | |||
d352e4abcb | |||
a948d4c1e1 | |||
b7eb3963f5 | |||
f7afb3cb75 | |||
348bf51a42 | |||
dfea2cc5f3 | |||
532eab8e1d | |||
3872382a4b | |||
4018693fbd | |||
3b829504f6 | |||
80fa51468c | |||
3504d2a4da | |||
229d8b9530 | |||
9536d460d0 | |||
e211e4839c | |||
3332dbe595 | |||
b9c8c08c03 | |||
663f22d849 | |||
fbaa4d1233 | |||
7dc8991140 | |||
0ce70b8c99 | |||
b169ea4b63 | |||
72ec4df679 | |||
b91263a254 | |||
e91afb1938 | |||
74eb613919 | |||
32d4310861 | |||
c56704f3fd | |||
c5118e9535 | |||
9b57566963 | |||
8d59e7afd1 | |||
fd733f76f0 | |||
37d4bb3df5 | |||
5697c6d7cc | |||
51a6162363 | |||
5a12b3d020 | |||
bebd2c469d | |||
d15e66a4ff | |||
c04d43695c | |||
d813334a24 | |||
9fef2f2eb4 | |||
0c746f4547 | |||
f5c8b1c6ac | |||
4921014c64 | |||
520753415f | |||
3162da131d | |||
a7354d2d08 | |||
07a9a2d0f3 | |||
c8c25549e7 | |||
6641a436a2 | |||
1dd50fb65f | |||
4bc4f77c29 | |||
ef36ecd376 | |||
c6cef853c7 | |||
33ad8d5db4 | |||
33e6d57754 | |||
afa57ca1f2 | |||
510dd02a06 | |||
166e88c103 | |||
51b6adeb24 | |||
51c19c374a | |||
b23452dc3f | |||
f61038e6bd | |||
b9117e2ff1 | |||
5a7a3da909 | |||
3f4502990a | |||
e14113aa0e | |||
d96289a81b | |||
4fc95b0d69 | |||
aa3d6c5363 | |||
5ac0cb929c | |||
b27ab53017 | |||
1aba294405 | |||
657e55ed59 | |||
621c25396e | |||
715ba918f0 | |||
8bb0a70847 | |||
94e79eddcf | |||
749a4028b4 | |||
fc55c2b57c | |||
d58ad2ee36 | |||
cb38ab4f85 | |||
fc86f6422d | |||
d453242e48 | |||
7c2ec075a4 | |||
c903de41e4 | |||
e8c8366308 | |||
4901567bba | |||
29bcd31b27 | |||
83a563c0ab | |||
e888fee4e0 | |||
c1ba11f531 | |||
ed558a0e70 | |||
6b9c3f4824 | |||
7ec819deed | |||
4cfb3678f8 | |||
3c17e2a7e6 | |||
e72b02b7cc | |||
82586c70e9 | |||
cb86bcc9e8 | |||
234708062a | |||
6e34f43721 | |||
3249ec8ebf | |||
31b2b21dbc | |||
d0c1936922 | |||
338a23a12b | |||
932def1ed2 | |||
16b5a11484 | |||
547fbce86c | |||
5a2eea07cb | |||
2231841d74 | |||
805a674825 | |||
f2925978f1 | |||
fe3a224a23 | |||
2754edc849 | |||
79f60274a0 | |||
a87c1c5e8e | |||
2b00b7d66d | |||
3d9286f089 | |||
55e6c15073 | |||
8b504e7d51 | |||
47ef2b343e | |||
80dcdb8645 | |||
d1c3bd8416 | |||
0240e182c6 | |||
7f2e3feb3c | |||
d15dd2795c | |||
0ea34fff27 | |||
4e8f212069 | |||
eb71d0ea7f | |||
b24ddb953c | |||
12f8fbe37f | |||
5e8312bc23 | |||
88d07058a5 | |||
cab8458c1c | |||
f0b95c552b | |||
ce677202e5 | |||
720c323e20 | |||
47e351e11d | |||
248beab557 | |||
388a5610fa | |||
41498758fe | |||
655484b4df | |||
11b4bf8aaa | |||
2513bcf584 | |||
3e51823c0f | |||
e32a18203b | |||
ee1d8bc30e | |||
828cbbdf5d | |||
d54cd4fa5f | |||
7747e9ec8b | |||
9765519f38 | |||
69f0b94745 | |||
3c9b6c99b7 | |||
b5c6eb54d2 | |||
e1c90bfc09 | |||
91400f10b0 | |||
c814893d65 | |||
2325090101 | |||
40bc52b844 | |||
589f3755f0 | |||
1004678005 | |||
0ac2e47162 | |||
b6a35a8944 | |||
74dc3c7500 | |||
cb4f252a06 | |||
bceefc9075 | |||
10cd1070f4 | |||
9b01b1c63d | |||
b2e7720fcd | |||
83814a76cc | |||
d3a16dca85 | |||
7452151bee | |||
1b8f05cef1 | |||
cfb2f68dd6 | |||
e76423e6dc | |||
947980b5eb | |||
44bdf1df63 | |||
79b66ef053 | |||
a91e2e9950 | |||
ea3d0690cf | |||
1c32c011a1 | |||
64b475d7d6 | |||
aa8f7bc146 | |||
3e29078f68 | |||
a1bc94e648 | |||
4cba78afd9 | |||
f311ae9ebe | |||
cbd831042a | |||
522b3a4a70 | |||
cd67f13bf9 | |||
0f554d9b1a | |||
9b79437778 | |||
3742d1178c | |||
b9e3cbbf44 | |||
3c0c87f473 | |||
3b29a9c12a | |||
d03285a0ee | |||
e810f2e930 | |||
41aac79ac0 | |||
52f916a28a | |||
864693d2f8 | |||
08ea9b3339 | |||
19f4a6ac0b | |||
bf6bd6b292 | |||
89e01d125c | |||
3bb4ca5adc | |||
712df81c99 | |||
3d7ef599cc | |||
9aedb4d05a | |||
453691728a | |||
e7e3ca77ad | |||
d3f3956def | |||
7206214105 | |||
b08ace7532 | |||
d1a2583972 | |||
0cb8de4218 | |||
0cd31a247f | |||
405c170d45 | |||
932019770d | |||
d5c03f5b8f | |||
3d20c0b8f4 | |||
a4fb4c6a03 | |||
a9941b6edc | |||
6a32b048bd | |||
fb04d3cff1 | |||
5c7673e73d | |||
54b05377d9 | |||
f49f3f92ec | |||
b18b94f03b | |||
bf469f0b55 | |||
c12709dbc9 | |||
2808a13f07 | |||
a8d48aa5ea | |||
fdb2660a2a | |||
7c3a997735 | |||
1760f0ceca | |||
c387aeeb36 | |||
83d4385f1f | |||
ae0a75c464 | |||
a05e5a7c09 | |||
75ca7b77b6 | |||
b0e2012a17 | |||
12dff072fa | |||
d956444965 | |||
8dbf9eb0bf | |||
faa981fb6a | |||
1ea3c13ff5 | |||
46d8fef0ad | |||
37c1ffc57a | |||
856fde28dc | |||
3204d7631b | |||
f5fc2edfd3 | |||
21d1267c14 | |||
3e37b97549 | |||
206f736819 | |||
24d4a27842 | |||
46f3ea4f43 | |||
c9cc642d37 | |||
b34853f8cc | |||
d41f4c0a3a | |||
8e46decc8e | |||
323a1c4214 | |||
c5f838e785 | |||
f53e98d11b | |||
c2f7f3efa7 | |||
1e5051e112 | |||
fa44ff54bb | |||
4c6de379e0 | |||
6d997366ed | |||
e0250e2871 | |||
3d629e7320 | |||
74045090cc | |||
49fffe3ebe | |||
50e294be32 | |||
b235b48de4 | |||
28011a5835 | |||
b03b0779dc | |||
393e4c4969 | |||
1d49a4bcbd | |||
fc2c0f4fba | |||
2117aecf65 | |||
8a2b697625 | |||
be586572e0 | |||
4e047bd483 | |||
db67ffbb94 | |||
90b03d34cc | |||
872a964145 | |||
dade92d98b | |||
dfa74faf43 | |||
95562cff41 | |||
9b714a7518 | |||
2a660820c9 | |||
b55e20ac60 | |||
b0dcb5b91d | |||
5c76f62103 | |||
126dbdae2f | |||
24d8290a3f | |||
957c0630c0 | |||
e32d173b0d | |||
fbc9f8e640 | |||
82d2ace3bd | |||
3fa7fc6e41 | |||
d1b58a00ce | |||
6a2ee371a5 | |||
a8a2dffc59 | |||
9266ece7a4 | |||
09c405990c | |||
61e694f388 | |||
195c9b24eb | |||
2f6da122fd | |||
f0d1d7149b | |||
96387e9a9b | |||
86f6470fb2 | |||
d77160dafe | |||
caca5e37ba | |||
189c73986b | |||
fe21305bb3 | |||
75ec8d33a2 | |||
d736d0ca87 | |||
99093693c8 | |||
c9c284be27 | |||
fa12dcf792 | |||
b93f5c6f9c | |||
f9791c1221 | |||
8fac2d8d58 | |||
e90f4bee1a | |||
eb50221a15 | |||
fc4801494d | |||
eba6afd6fb | |||
81328b72fa | |||
c0c113036b | |||
b788d74fd1 | |||
90999717dd | |||
a20ccde90d | |||
d548ddeb5b | |||
de99ac5da5 | |||
e2d9d34c75 | |||
bdb525831d | |||
22dba90ec4 | |||
7af6dad58e | |||
5af26446ec | |||
39d40ba8fa | |||
5bc3e30653 | |||
34e2b26b99 | |||
24015e9ace | |||
9ff88f0b35 | |||
27c59a8017 | |||
a74ebaa1eb | |||
b5c29d6143 | |||
0a822209c8 | |||
3baac14095 | |||
ef3655877a | |||
5a8c40710c | |||
b635af5a8c | |||
683e6856ef | |||
8beefc8bfd | |||
e430de3307 | |||
2b442e842e | |||
41633900da | |||
aaf6e775d6 | |||
2f6fc3f62b | |||
84310b1652 | |||
5ceac1dd04 | |||
51b31c6cb0 | |||
5488a5bbd7 | |||
33f7f46670 | |||
3c05eafbc4 | |||
dfa9e03980 | |||
53e996d4c3 | |||
b151a9616e | |||
1effb6ce30 | |||
0be05eb47c | |||
1090ca0154 | |||
a328584238 | |||
f19ad3fdd3 | |||
836be7ab9a | |||
bba04dd864 | |||
9499b3eb90 | |||
9f6bbe3c51 | |||
2d87d4feaf | |||
15a45ccc7b | |||
e0c59fcf0b | |||
083f697c59 | |||
a5a6546b91 | |||
9cafbf6a21 | |||
faba0d7273 | |||
c3116d3601 | |||
868e14b8ba | |||
421bf97b84 | |||
c16507607c | |||
2b7dd85b92 | |||
a58115fed2 | |||
f9b5b0f551 | |||
e53c147129 | |||
da0e6e790d | |||
58ffc6f5f8 | |||
d5fb74aa94 | |||
11247d41a7 | |||
5b21c7cd71 | |||
dd0f05b1f1 | |||
a32241e7d3 | |||
0ef357ebd7 | |||
32dd976620 | |||
fcd8a2024e | |||
30661472c8 | |||
6ced26660e | |||
7d6c45d4c9 | |||
98740fddac | |||
6d3042f5e5 | |||
8b9d6eee1a | |||
11ef5929aa | |||
c697b98006 | |||
7c2e563879 | |||
56aa224a6e | |||
8c37a07adb | |||
161c061376 | |||
aa091cea42 | |||
7df74c0a30 | |||
1e1e900278 | |||
fb2343567b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
4
.gosimpleignore
Normal file
4
.gosimpleignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/astaxie/beego/*/*:S1012
|
||||||
|
github.com/astaxie/beego/*:S1012
|
||||||
|
github.com/astaxie/beego/*/*:S1007
|
||||||
|
github.com/astaxie/beego/*:S1007
|
23
.travis.yml
23
.travis.yml
@ -1,9 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.6
|
- "1.9.2"
|
||||||
- 1.5.3
|
- "1.10.3"
|
||||||
- 1.4.3
|
|
||||||
services:
|
services:
|
||||||
- redis-server
|
- redis-server
|
||||||
- mysql
|
- mysql
|
||||||
@ -11,7 +10,6 @@ services:
|
|||||||
- memcached
|
- memcached
|
||||||
env:
|
env:
|
||||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||||
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
|
|
||||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||||
before_install:
|
before_install:
|
||||||
- git clone git://github.com/ideawu/ssdb.git
|
- git clone git://github.com/ideawu/ssdb.git
|
||||||
@ -23,14 +21,24 @@ install:
|
|||||||
- go get github.com/go-sql-driver/mysql
|
- go get github.com/go-sql-driver/mysql
|
||||||
- go get github.com/mattn/go-sqlite3
|
- go get github.com/mattn/go-sqlite3
|
||||||
- go get github.com/bradfitz/gomemcache/memcache
|
- go get github.com/bradfitz/gomemcache/memcache
|
||||||
- go get github.com/garyburd/redigo/redis
|
- go get github.com/gomodule/redigo/redis
|
||||||
- go get github.com/beego/x2j
|
- go get github.com/beego/x2j
|
||||||
- go get github.com/couchbase/go-couchbase
|
- go get github.com/couchbase/go-couchbase
|
||||||
- go get github.com/beego/goyaml2
|
- go get github.com/beego/goyaml2
|
||||||
|
- go get gopkg.in/yaml.v2
|
||||||
- go get github.com/belogik/goes
|
- go get github.com/belogik/goes
|
||||||
- go get github.com/siddontang/ledisdb/config
|
- go get github.com/siddontang/ledisdb/config
|
||||||
- go get github.com/siddontang/ledisdb/ledis
|
- go get github.com/siddontang/ledisdb/ledis
|
||||||
- go get github.com/ssdb/gossdb/ssdb
|
- go get github.com/ssdb/gossdb/ssdb
|
||||||
|
- go get github.com/cloudflare/golz4
|
||||||
|
- go get github.com/gogo/protobuf/proto
|
||||||
|
- go get github.com/Knetic/govaluate
|
||||||
|
- go get github.com/casbin/casbin
|
||||||
|
- go get -u honnef.co/go/tools/cmd/gosimple
|
||||||
|
- go get -u github.com/mdempsky/unconvert
|
||||||
|
- go get -u github.com/gordonklaus/ineffassign
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
- go get -u github.com/go-redis/redis
|
||||||
before_script:
|
before_script:
|
||||||
- psql --version
|
- psql --version
|
||||||
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
||||||
@ -45,5 +53,10 @@ after_script:
|
|||||||
- rm -rf ./res/var/*
|
- rm -rf ./res/var/*
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
- gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/)
|
||||||
|
- unconvert $(go list ./... | grep -v /vendor/)
|
||||||
|
- ineffassign .
|
||||||
|
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||||
|
- golint ./...
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.4"
|
postgresql: "9.4"
|
||||||
|
30
README.md
30
README.md
@ -1,19 +1,18 @@
|
|||||||
## Beego
|
# Beego [](https://travis-ci.org/astaxie/beego) [](http://godoc.org/github.com/astaxie/beego) [](http://golangfoundation.org) [](https://goreportcard.com/report/github.com/astaxie/beego)
|
||||||
|
|
||||||
[](https://travis-ci.org/astaxie/beego)
|
|
||||||
[](http://godoc.org/github.com/astaxie/beego)
|
|
||||||
|
|
||||||
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
||||||
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
||||||
|
|
||||||
More info [beego.me](http://beego.me)
|
###### More info at [beego.me](http://beego.me).
|
||||||
|
|
||||||
##Quick Start
|
## Quick Start
|
||||||
######Download and install
|
|
||||||
|
#### Download and install
|
||||||
|
|
||||||
go get github.com/astaxie/beego
|
go get github.com/astaxie/beego
|
||||||
|
|
||||||
######Create file `hello.go`
|
#### Create file `hello.go`
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -23,15 +22,16 @@ func main(){
|
|||||||
beego.Run()
|
beego.Run()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
######Build and run
|
#### Build and run
|
||||||
```bash
|
|
||||||
go build hello.go
|
go build hello.go
|
||||||
./hello
|
./hello
|
||||||
```
|
|
||||||
######Congratulations!
|
#### Go to [http://localhost:8080](http://localhost:8080)
|
||||||
You just built your first beego app.
|
|
||||||
Open your browser and visit `http://localhost:8080`.
|
Congratulations! You've just built your first **beego** app.
|
||||||
Please see [Documentation](http://beego.me/docs) for more.
|
|
||||||
|
###### Please see [Documentation](http://beego.me/docs) for more.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ Please see [Documentation](http://beego.me/docs) for more.
|
|||||||
* [http://beego.me/community](http://beego.me/community)
|
* [http://beego.me/community](http://beego.me/community)
|
||||||
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
||||||
|
|
||||||
## LICENSE
|
## License
|
||||||
|
|
||||||
beego source code is licensed under the Apache Licence, Version 2.0
|
beego source code is licensed under the Apache Licence, Version 2.0
|
||||||
(http://www.apache.org/licenses/LICENSE-2.0.html).
|
(http://www.apache.org/licenses/LICENSE-2.0.html).
|
||||||
|
99
admin.go
99
admin.go
@ -37,7 +37,7 @@ var beeAdminApp *adminApp
|
|||||||
// FilterMonitorFunc is default monitor filter when admin module is enable.
|
// FilterMonitorFunc is default monitor filter when admin module is enable.
|
||||||
// if this func returns, admin module records qbs for this request by condition of this function logic.
|
// if this func returns, admin module records qbs for this request by condition of this function logic.
|
||||||
// usage:
|
// usage:
|
||||||
// func MyFilterMonitor(method, requestPath string, t time.Duration) bool {
|
// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool {
|
||||||
// if method == "POST" {
|
// if method == "POST" {
|
||||||
// return false
|
// return false
|
||||||
// }
|
// }
|
||||||
@ -50,7 +50,7 @@ var beeAdminApp *adminApp
|
|||||||
// return true
|
// return true
|
||||||
// }
|
// }
|
||||||
// beego.FilterMonitorFunc = MyFilterMonitor.
|
// beego.FilterMonitorFunc = MyFilterMonitor.
|
||||||
var FilterMonitorFunc func(string, string, time.Duration) bool
|
var FilterMonitorFunc func(string, string, time.Duration, string, int) bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
beeAdminApp = &adminApp{
|
beeAdminApp = &adminApp{
|
||||||
@ -62,7 +62,7 @@ func init() {
|
|||||||
beeAdminApp.Route("/healthcheck", healthcheck)
|
beeAdminApp.Route("/healthcheck", healthcheck)
|
||||||
beeAdminApp.Route("/task", taskStatus)
|
beeAdminApp.Route("/task", taskStatus)
|
||||||
beeAdminApp.Route("/listconf", listConf)
|
beeAdminApp.Route("/listconf", listConf)
|
||||||
FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
|
FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true }
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminIndex is the default http.Handler for admin module.
|
// AdminIndex is the default http.Handler for admin module.
|
||||||
@ -76,6 +76,18 @@ func adminIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
data := make(map[interface{}]interface{})
|
data := make(map[interface{}]interface{})
|
||||||
data["Content"] = toolbox.StatisticsMap.GetMap()
|
data["Content"] = toolbox.StatisticsMap.GetMap()
|
||||||
|
|
||||||
|
// do html escape before display path, avoid xss
|
||||||
|
if content, ok := (data["Content"]).(map[string]interface{}); ok {
|
||||||
|
if resultLists, ok := (content["Data"]).([][]string); ok {
|
||||||
|
for i := range resultLists {
|
||||||
|
if len(resultLists[i]) > 0 {
|
||||||
|
resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,29 +117,12 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
|||||||
tmpl.Execute(rw, data)
|
tmpl.Execute(rw, data)
|
||||||
|
|
||||||
case "router":
|
case "router":
|
||||||
var (
|
content := PrintTree()
|
||||||
content = map[string]interface{}{
|
content["Fields"] = []string{
|
||||||
"Fields": []string{
|
"Router Pattern",
|
||||||
"Router Pattern",
|
"Methods",
|
||||||
"Methods",
|
"Controller",
|
||||||
"Controller",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
methods = []string{}
|
|
||||||
methodsData = make(map[string]interface{})
|
|
||||||
)
|
|
||||||
for method, t := range BeeApp.Handlers.routers {
|
|
||||||
|
|
||||||
resultList := new([][]string)
|
|
||||||
|
|
||||||
printTree(resultList, t)
|
|
||||||
|
|
||||||
methods = append(methods, method)
|
|
||||||
methodsData[method] = resultList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content["Data"] = methodsData
|
|
||||||
content["Methods"] = methods
|
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Routers"
|
data["Title"] = "Routers"
|
||||||
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
@ -157,8 +152,8 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
|||||||
resultList := new([][]string)
|
resultList := new([][]string)
|
||||||
for _, f := range bf {
|
for _, f := range bf {
|
||||||
var result = []string{
|
var result = []string{
|
||||||
fmt.Sprintf("%s", f.pattern),
|
f.pattern,
|
||||||
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
|
utils.GetFuncName(f.filterFunc),
|
||||||
}
|
}
|
||||||
*resultList = append(*resultList, result)
|
*resultList = append(*resultList, result)
|
||||||
}
|
}
|
||||||
@ -200,6 +195,28 @@ func list(root string, p interface{}, m map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrintTree prints all registered routers.
|
||||||
|
func PrintTree() map[string]interface{} {
|
||||||
|
var (
|
||||||
|
content = map[string]interface{}{}
|
||||||
|
methods = []string{}
|
||||||
|
methodsData = make(map[string]interface{})
|
||||||
|
)
|
||||||
|
for method, t := range BeeApp.Handlers.routers {
|
||||||
|
|
||||||
|
resultList := new([][]string)
|
||||||
|
|
||||||
|
printTree(resultList, t)
|
||||||
|
|
||||||
|
methods = append(methods, method)
|
||||||
|
methodsData[method] = resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = methodsData
|
||||||
|
content["Methods"] = methods
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
func printTree(resultList *[][]string, t *Tree) {
|
func printTree(resultList *[][]string, t *Tree) {
|
||||||
for _, tr := range t.fixrouters {
|
for _, tr := range t.fixrouters {
|
||||||
printTree(resultList, tr)
|
printTree(resultList, tr)
|
||||||
@ -208,12 +225,12 @@ func printTree(resultList *[][]string, t *Tree) {
|
|||||||
printTree(resultList, t.wildcard)
|
printTree(resultList, t.wildcard)
|
||||||
}
|
}
|
||||||
for _, l := range t.leaves {
|
for _, l := range t.leaves {
|
||||||
if v, ok := l.runObject.(*controllerInfo); ok {
|
if v, ok := l.runObject.(*ControllerInfo); ok {
|
||||||
if v.routerType == routerTypeBeego {
|
if v.routerType == routerTypeBeego {
|
||||||
var result = []string{
|
var result = []string{
|
||||||
v.pattern,
|
v.pattern,
|
||||||
fmt.Sprintf("%s", v.methods),
|
fmt.Sprintf("%s", v.methods),
|
||||||
fmt.Sprintf("%s", v.controllerType),
|
v.controllerType.String(),
|
||||||
}
|
}
|
||||||
*resultList = append(*resultList, result)
|
*resultList = append(*resultList, result)
|
||||||
} else if v.routerType == routerTypeRESTFul {
|
} else if v.routerType == routerTypeRESTFul {
|
||||||
@ -276,8 +293,8 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// it's in "/healthcheck" pattern in admin module.
|
// it's in "/healthcheck" pattern in admin module.
|
||||||
func healthcheck(rw http.ResponseWriter, req *http.Request) {
|
func healthcheck(rw http.ResponseWriter, req *http.Request) {
|
||||||
var (
|
var (
|
||||||
|
result []string
|
||||||
data = make(map[interface{}]interface{})
|
data = make(map[interface{}]interface{})
|
||||||
result = []string{}
|
|
||||||
resultList = new([][]string)
|
resultList = new([][]string)
|
||||||
content = map[string]interface{}{
|
content = map[string]interface{}{
|
||||||
"Fields": []string{"Name", "Message", "Status"},
|
"Fields": []string{"Name", "Message", "Status"},
|
||||||
@ -287,21 +304,20 @@ func healthcheck(rw http.ResponseWriter, req *http.Request) {
|
|||||||
for name, h := range toolbox.AdminCheckList {
|
for name, h := range toolbox.AdminCheckList {
|
||||||
if err := h.Check(); err != nil {
|
if err := h.Check(); err != nil {
|
||||||
result = []string{
|
result = []string{
|
||||||
fmt.Sprintf("error"),
|
"error",
|
||||||
fmt.Sprintf("%s", name),
|
name,
|
||||||
fmt.Sprintf("%s", err.Error()),
|
err.Error(),
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
result = []string{
|
result = []string{
|
||||||
fmt.Sprintf("success"),
|
"success",
|
||||||
fmt.Sprintf("%s", name),
|
name,
|
||||||
fmt.Sprintf("OK"),
|
"OK",
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
*resultList = append(*resultList, result)
|
*resultList = append(*resultList, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
content["Data"] = resultList
|
content["Data"] = resultList
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Health Check"
|
data["Title"] = "Health Check"
|
||||||
@ -330,7 +346,6 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// List Tasks
|
// List Tasks
|
||||||
content := make(map[string]interface{})
|
content := make(map[string]interface{})
|
||||||
resultList := new([][]string)
|
resultList := new([][]string)
|
||||||
var result = []string{}
|
|
||||||
var fields = []string{
|
var fields = []string{
|
||||||
"Task Name",
|
"Task Name",
|
||||||
"Task Spec",
|
"Task Spec",
|
||||||
@ -339,10 +354,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
|||||||
"",
|
"",
|
||||||
}
|
}
|
||||||
for tname, tk := range toolbox.AdminTaskList {
|
for tname, tk := range toolbox.AdminTaskList {
|
||||||
result = []string{
|
result := []string{
|
||||||
tname,
|
tname,
|
||||||
fmt.Sprintf("%s", tk.GetSpec()),
|
tk.GetSpec(),
|
||||||
fmt.Sprintf("%s", tk.GetStatus()),
|
tk.GetStatus(),
|
||||||
tk.GetPrev().String(),
|
tk.GetPrev().String(),
|
||||||
}
|
}
|
||||||
*resultList = append(*resultList, result)
|
*resultList = append(*resultList, result)
|
||||||
|
@ -65,7 +65,10 @@ func oldMap() map[string]interface{} {
|
|||||||
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||||
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||||
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||||
|
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||||
|
m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs
|
||||||
|
m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat
|
||||||
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||||
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||||
return m
|
return m
|
||||||
|
13
adminui.go
13
adminui.go
@ -78,13 +78,14 @@ var qpsTpl = `{{define "content"}}
|
|||||||
{{range $i, $elem := .Content.Data}}
|
{{range $i, $elem := .Content.Data}}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{{range $elem}}
|
<td>{{index $elem 0}}</td>
|
||||||
<td>
|
<td>{{index $elem 1}}</td>
|
||||||
{{.}}
|
<td>{{index $elem 2}}</td>
|
||||||
</td>
|
<td data-order="{{index $elem 3}}">{{index $elem 4}}</td>
|
||||||
{{end}}
|
<td data-order="{{index $elem 5}}">{{index $elem 6}}</td>
|
||||||
|
<td data-order="{{index $elem 7}}">{{index $elem 8}}</td>
|
||||||
|
<td data-order="{{index $elem 9}}">{{index $elem 10}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
|
157
app.go
157
app.go
@ -15,17 +15,22 @@
|
|||||||
package beego
|
package beego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/fcgi"
|
"net/http/fcgi"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/grace"
|
"github.com/astaxie/beego/grace"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -51,8 +56,11 @@ func NewApp() *App {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MiddleWare function for http.Handler
|
||||||
|
type MiddleWare func(http.Handler) http.Handler
|
||||||
|
|
||||||
// Run beego application.
|
// Run beego application.
|
||||||
func (app *App) Run() {
|
func (app *App) Run(mws ...MiddleWare) {
|
||||||
addr := BConfig.Listen.HTTPAddr
|
addr := BConfig.Listen.HTTPAddr
|
||||||
|
|
||||||
if BConfig.Listen.HTTPPort != 0 {
|
if BConfig.Listen.HTTPPort != 0 {
|
||||||
@ -94,6 +102,12 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Server.Handler = app.Handlers
|
app.Server.Handler = app.Handlers
|
||||||
|
for i := len(mws) - 1; i >= 0; i-- {
|
||||||
|
if mws[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app.Server.Handler = mws[i](app.Server.Handler)
|
||||||
|
}
|
||||||
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
||||||
@ -102,9 +116,9 @@ func (app *App) Run() {
|
|||||||
if BConfig.Listen.Graceful {
|
if BConfig.Listen.Graceful {
|
||||||
httpsAddr := BConfig.Listen.HTTPSAddr
|
httpsAddr := BConfig.Listen.HTTPSAddr
|
||||||
app.Server.Addr = httpsAddr
|
app.Server.Addr = httpsAddr
|
||||||
if BConfig.Listen.EnableHTTPS {
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(20 * time.Microsecond)
|
time.Sleep(1000 * time.Microsecond)
|
||||||
if BConfig.Listen.HTTPSPort != 0 {
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
app.Server.Addr = httpsAddr
|
app.Server.Addr = httpsAddr
|
||||||
@ -112,10 +126,27 @@ func (app *App) Run() {
|
|||||||
server := grace.NewServer(httpsAddr, app.Handlers)
|
server := grace.NewServer(httpsAddr, app.Handlers)
|
||||||
server.Server.ReadTimeout = app.Server.ReadTimeout
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
server.Server.WriteTimeout = app.Server.WriteTimeout
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
if BConfig.Listen.EnableMutualHTTPS {
|
||||||
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil {
|
||||||
time.Sleep(100 * time.Microsecond)
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
endRunning <- true
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
}
|
||||||
|
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -139,22 +170,44 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run normal mode
|
// run normal mode
|
||||||
if BConfig.Listen.EnableHTTPS {
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(20 * time.Microsecond)
|
time.Sleep(1000 * time.Microsecond)
|
||||||
if BConfig.Listen.HTTPSPort != 0 {
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
} else if BConfig.Listen.EnableHTTP {
|
} else if BConfig.Listen.EnableHTTP {
|
||||||
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
|
BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logs.Info("https server Running on https://%s", app.Server.Addr)
|
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
} else if BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
||||||
|
if err != nil {
|
||||||
|
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
app.Server.TLSConfig = &tls.Config{
|
||||||
|
ClientCAs: pool,
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
logs.Critical("ListenAndServeTLS: ", err)
|
logs.Critical("ListenAndServeTLS: ", err)
|
||||||
time.Sleep(100 * time.Microsecond)
|
time.Sleep(100 * time.Microsecond)
|
||||||
endRunning <- true
|
endRunning <- true
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
if BConfig.Listen.EnableHTTP {
|
if BConfig.Listen.EnableHTTP {
|
||||||
go func() {
|
go func() {
|
||||||
@ -207,6 +260,84 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A
|
|||||||
return BeeApp
|
return BeeApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful
|
||||||
|
// in web applications that inherit most routes from a base webapp via the underscore
|
||||||
|
// import, and aim to overwrite only certain paths.
|
||||||
|
// The method parameter can be empty or "*" for all HTTP methods, or a particular
|
||||||
|
// method type (e.g. "GET" or "POST") for selective removal.
|
||||||
|
//
|
||||||
|
// Usage (replace "GET" with "*" for all methods):
|
||||||
|
// beego.UnregisterFixedRoute("/yourpreviouspath", "GET")
|
||||||
|
// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage")
|
||||||
|
func UnregisterFixedRoute(fixedRoute string, method string) *App {
|
||||||
|
subPaths := splitPath(fixedRoute)
|
||||||
|
if method == "" || method == "*" {
|
||||||
|
for m := range HTTPMETHOD {
|
||||||
|
if _, ok := BeeApp.Handlers.routers[m]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[m])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
// Single HTTP method
|
||||||
|
um := strings.ToUpper(method)
|
||||||
|
if _, ok := BeeApp.Handlers.routers[um]; ok {
|
||||||
|
if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[um])
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) {
|
||||||
|
for i := range entryPointTree.fixrouters {
|
||||||
|
if entryPointTree.fixrouters[i].prefix == paths[0] {
|
||||||
|
if len(paths) == 1 {
|
||||||
|
if len(entryPointTree.fixrouters[i].fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.fixrouters[i].leaves) > 0 {
|
||||||
|
entryPointTree.fixrouters[i].leaves[0] = nil
|
||||||
|
entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove the *Tree from the fixrouters slice
|
||||||
|
entryPointTree.fixrouters[i] = nil
|
||||||
|
|
||||||
|
if i == len(entryPointTree.fixrouters)-1 {
|
||||||
|
entryPointTree.fixrouters = entryPointTree.fixrouters[:i]
|
||||||
|
} else {
|
||||||
|
entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveSingleTree(entryPointTree *Tree) {
|
||||||
|
if entryPointTree == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(entryPointTree.fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.leaves) > 0 {
|
||||||
|
entryPointTree.leaves[0] = nil
|
||||||
|
entryPointTree.leaves = entryPointTree.leaves[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Include will generate router file in the router/xxx.go from the controller's comments
|
// Include will generate router file in the router/xxx.go from the controller's comments
|
||||||
// usage:
|
// usage:
|
||||||
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
||||||
@ -348,9 +479,9 @@ func Any(rootpath string, f FilterFunc) *App {
|
|||||||
|
|
||||||
// Handler used to register a Handler router
|
// Handler used to register a Handler router
|
||||||
// usage:
|
// usage:
|
||||||
// beego.Handler("/api", func(ctx *context.Context){
|
// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
|
||||||
// ctx.Output.Body("hello world")
|
// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||||
// })
|
// }))
|
||||||
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
|
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
|
||||||
BeeApp.Handlers.Handler(rootpath, h, options...)
|
BeeApp.Handlers.Handler(rootpath, h, options...)
|
||||||
return BeeApp
|
return BeeApp
|
||||||
|
47
beego.go
47
beego.go
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION represent beego web framework version.
|
// VERSION represent beego web framework version.
|
||||||
VERSION = "1.7.0"
|
VERSION = "1.10.0"
|
||||||
|
|
||||||
// DEV is for develop
|
// DEV is for develop
|
||||||
DEV = "dev"
|
DEV = "dev"
|
||||||
@ -40,9 +40,9 @@ var (
|
|||||||
|
|
||||||
// AddAPPStartHook is used to register the hookfunc
|
// AddAPPStartHook is used to register the hookfunc
|
||||||
// The hookfuncs will run in beego.Run()
|
// The hookfuncs will run in beego.Run()
|
||||||
// such as sessionInit, middlerware start, buildtemplate, admin start
|
// such as initiating session , starting middleware , building template, starting admin control and so on.
|
||||||
func AddAPPStartHook(hf hookfunc) {
|
func AddAPPStartHook(hf ...hookfunc) {
|
||||||
hooks = append(hooks, hf)
|
hooks = append(hooks, hf...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run beego application.
|
// Run beego application.
|
||||||
@ -62,19 +62,39 @@ func Run(params ...string) {
|
|||||||
if len(strs) > 1 && strs[1] != "" {
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BConfig.Listen.Domains = params
|
||||||
}
|
}
|
||||||
|
|
||||||
BeeApp.Run()
|
BeeApp.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunWithMiddleWares Run beego application with middlewares.
|
||||||
|
func RunWithMiddleWares(addr string, mws ...MiddleWare) {
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 0 && strs[0] != "" {
|
||||||
|
BConfig.Listen.HTTPAddr = strs[0]
|
||||||
|
BConfig.Listen.Domains = []string{strs[0]}
|
||||||
|
}
|
||||||
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
BeeApp.Run(mws...)
|
||||||
|
}
|
||||||
|
|
||||||
func initBeforeHTTPRun() {
|
func initBeforeHTTPRun() {
|
||||||
//init hooks
|
//init hooks
|
||||||
AddAPPStartHook(registerMime)
|
AddAPPStartHook(
|
||||||
AddAPPStartHook(registerDefaultErrorHandler)
|
registerMime,
|
||||||
AddAPPStartHook(registerSession)
|
registerDefaultErrorHandler,
|
||||||
AddAPPStartHook(registerTemplate)
|
registerSession,
|
||||||
AddAPPStartHook(registerAdmin)
|
registerTemplate,
|
||||||
AddAPPStartHook(registerGzip)
|
registerAdmin,
|
||||||
|
registerGzip,
|
||||||
|
)
|
||||||
|
|
||||||
for _, hk := range hooks {
|
for _, hk := range hooks {
|
||||||
if err := hk(); err != nil {
|
if err := hk(); err != nil {
|
||||||
@ -85,8 +105,13 @@ func initBeforeHTTPRun() {
|
|||||||
|
|
||||||
// TestBeegoInit is for test package init
|
// TestBeegoInit is for test package init
|
||||||
func TestBeegoInit(ap string) {
|
func TestBeegoInit(ap string) {
|
||||||
appConfigPath = filepath.Join(ap, "conf", "app.conf")
|
path := filepath.Join(ap, "conf", "app.conf")
|
||||||
os.Chdir(ap)
|
os.Chdir(ap)
|
||||||
|
InitBeegoBeforeTest(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitBeegoBeforeTest is for test package init
|
||||||
|
func InitBeegoBeforeTest(appConfigPath string) {
|
||||||
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
2
cache/README.md
vendored
2
cache/README.md
vendored
@ -52,7 +52,7 @@ Configure like this:
|
|||||||
|
|
||||||
## Redis adapter
|
## Redis adapter
|
||||||
|
|
||||||
Redis adapter use the [redigo](http://github.com/garyburd/redigo) client.
|
Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
|
2
cache/cache.go
vendored
2
cache/cache.go
vendored
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package cache provide a Cache interface and some implemetn engine
|
// Package cache provide a Cache interface and some implement engine
|
||||||
// Usage:
|
// Usage:
|
||||||
//
|
//
|
||||||
// import(
|
// import(
|
||||||
|
2
cache/conv.go
vendored
2
cache/conv.go
vendored
@ -28,7 +28,7 @@ func GetString(v interface{}) string {
|
|||||||
return string(result)
|
return string(result)
|
||||||
default:
|
default:
|
||||||
if v != nil {
|
if v != nil {
|
||||||
return fmt.Sprintf("%v", result)
|
return fmt.Sprint(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
6
cache/conv_test.go
vendored
6
cache/conv_test.go
vendored
@ -118,14 +118,14 @@ func TestGetFloat64(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetBool(t *testing.T) {
|
func TestGetBool(t *testing.T) {
|
||||||
var t1 = true
|
var t1 = true
|
||||||
if true != GetBool(t1) {
|
if !GetBool(t1) {
|
||||||
t.Error("get bool from bool error")
|
t.Error("get bool from bool error")
|
||||||
}
|
}
|
||||||
var t2 = "true"
|
var t2 = "true"
|
||||||
if true != GetBool(t2) {
|
if !GetBool(t2) {
|
||||||
t.Error("get bool from string error")
|
t.Error("get bool from string error")
|
||||||
}
|
}
|
||||||
if false != GetBool(nil) {
|
if GetBool(nil) {
|
||||||
t.Error("get bool from nil error")
|
t.Error("get bool from nil error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
cache/file.go
vendored
25
cache/file.go
vendored
@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -222,33 +223,13 @@ func exists(path string) (bool, error) {
|
|||||||
// FileGetContents Get bytes to file.
|
// FileGetContents Get bytes to file.
|
||||||
// if non-exist, create this file.
|
// if non-exist, create this file.
|
||||||
func FileGetContents(filename string) (data []byte, e error) {
|
func FileGetContents(filename string) (data []byte, e error) {
|
||||||
f, e := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
return ioutil.ReadFile(filename)
|
||||||
if e != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
stat, e := f.Stat()
|
|
||||||
if e != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = make([]byte, stat.Size())
|
|
||||||
result, e := f.Read(data)
|
|
||||||
if e != nil || int64(result) != stat.Size() {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilePutContents Put bytes to file.
|
// FilePutContents Put bytes to file.
|
||||||
// if non-exist, create this file.
|
// if non-exist, create this file.
|
||||||
func FilePutContents(filename string, content []byte) error {
|
func FilePutContents(filename string, content []byte) error {
|
||||||
fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
return ioutil.WriteFile(filename, content, os.ModePerm)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
_, err = fp.Write(content)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GobEncode Gob encodes file cache item.
|
// GobEncode Gob encodes file cache item.
|
||||||
|
26
cache/memcache/memcache.go
vendored
26
cache/memcache/memcache.go
vendored
@ -33,12 +33,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bradfitz/gomemcache/memcache"
|
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache Memcache adapter.
|
// Cache Memcache adapter.
|
||||||
@ -60,7 +58,7 @@ func (rc *Cache) Get(key string) interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if item, err := rc.conn.Get(key); err == nil {
|
if item, err := rc.conn.Get(key); err == nil {
|
||||||
return string(item.Value)
|
return item.Value
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -80,7 +78,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
|
|||||||
mv, err := rc.conn.GetMulti(keys)
|
mv, err := rc.conn.GetMulti(keys)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, v := range mv {
|
for _, v := range mv {
|
||||||
rv = append(rv, string(v.Value))
|
rv = append(rv, v.Value)
|
||||||
}
|
}
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
@ -90,18 +88,21 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
|
|||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put put value to memcache. only support string.
|
// Put put value to memcache.
|
||||||
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
if rc.conn == nil {
|
if rc.conn == nil {
|
||||||
if err := rc.connectInit(); err != nil {
|
if err := rc.connectInit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v, ok := val.(string)
|
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
|
||||||
if !ok {
|
if v, ok := val.([]byte); ok {
|
||||||
return errors.New("val must string")
|
item.Value = v
|
||||||
|
} else if str, ok := val.(string); ok {
|
||||||
|
item.Value = []byte(str)
|
||||||
|
} else {
|
||||||
|
return errors.New("val only support string and []byte")
|
||||||
}
|
}
|
||||||
item := memcache.Item{Key: key, Value: []byte(v), Expiration: int32(timeout / time.Second)}
|
|
||||||
return rc.conn.Set(&item)
|
return rc.conn.Set(&item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,10 +146,7 @@ func (rc *Cache) IsExist(key string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := rc.conn.Get(key)
|
_, err := rc.conn.Get(key)
|
||||||
if err != nil {
|
return !(err != nil)
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAll clear all cached in memcache.
|
// ClearAll clear all cached in memcache.
|
||||||
|
12
cache/memcache/memcache_test.go
vendored
12
cache/memcache/memcache_test.go
vendored
@ -46,7 +46,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("set Error", err)
|
t.Error("set Error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 {
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("Incr Error", err)
|
t.Error("Incr Error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 2 {
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("Decr Error", err)
|
t.Error("Decr Error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 {
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
bm.Delete("astaxie")
|
bm.Delete("astaxie")
|
||||||
@ -78,7 +78,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("check err")
|
t.Error("check err")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := bm.Get("astaxie").(string); v != "author" {
|
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,10 +94,10 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
if len(vv) != 2 {
|
if len(vv) != 2 {
|
||||||
t.Error("GetMulti ERROR")
|
t.Error("GetMulti ERROR")
|
||||||
}
|
}
|
||||||
if vv[0].(string) != "author" && vv[0].(string) != "author1" {
|
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
|
||||||
t.Error("GetMulti ERROR")
|
t.Error("GetMulti ERROR")
|
||||||
}
|
}
|
||||||
if vv[1].(string) != "author1" && vv[1].(string) != "author" {
|
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
|
||||||
t.Error("GetMulti ERROR")
|
t.Error("GetMulti ERROR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
cache/memory.go
vendored
31
cache/memory.go
vendored
@ -217,26 +217,31 @@ func (bc *MemoryCache) vaccuum() {
|
|||||||
if bc.items == nil {
|
if bc.items == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for name := range bc.items {
|
if keys := bc.expiredKeys(); len(keys) != 0 {
|
||||||
bc.itemExpired(name)
|
bc.clearItems(keys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// itemExpired returns true if an item is expired.
|
// expiredKeys returns key list which are expired.
|
||||||
func (bc *MemoryCache) itemExpired(name string) bool {
|
func (bc *MemoryCache) expiredKeys() (keys []string) {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
for key, itm := range bc.items {
|
||||||
|
if itm.isExpire() {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearItems removes all the items which key in keys.
|
||||||
|
func (bc *MemoryCache) clearItems(keys []string) {
|
||||||
bc.Lock()
|
bc.Lock()
|
||||||
defer bc.Unlock()
|
defer bc.Unlock()
|
||||||
|
for _, key := range keys {
|
||||||
itm, ok := bc.items[name]
|
delete(bc.items, key)
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if itm.isExpire() {
|
|
||||||
delete(bc.items, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
82
cache/redis/redis.go
vendored
82
cache/redis/redis.go
vendored
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
// Package redis for cache provider
|
// Package redis for cache provider
|
||||||
//
|
//
|
||||||
// depend on github.com/garyburd/redigo/redis
|
// depend on github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// go install github.com/garyburd/redigo/redis
|
// go install github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// import(
|
// import(
|
||||||
@ -32,10 +32,11 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
)
|
)
|
||||||
@ -52,6 +53,7 @@ type Cache struct {
|
|||||||
dbNum int
|
dbNum int
|
||||||
key string
|
key string
|
||||||
password string
|
password string
|
||||||
|
maxIdle int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisCache create new redis cache with default collection name.
|
// NewRedisCache create new redis cache with default collection name.
|
||||||
@ -59,14 +61,23 @@ func NewRedisCache() cache.Cache {
|
|||||||
return &Cache{key: DefaultKey}
|
return &Cache{key: DefaultKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
// actually do the redis cmds
|
// actually do the redis cmds, args[0] must be the key name.
|
||||||
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errors.New("missing required arguments")
|
||||||
|
}
|
||||||
|
args[0] = rc.associate(args[0])
|
||||||
c := rc.p.Get()
|
c := rc.p.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
return c.Do(commandName, args...)
|
return c.Do(commandName, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// associate with config key.
|
||||||
|
func (rc *Cache) associate(originKey interface{}) string {
|
||||||
|
return fmt.Sprintf("%s:%s", rc.key, originKey)
|
||||||
|
}
|
||||||
|
|
||||||
// Get cache from redis.
|
// Get cache from redis.
|
||||||
func (rc *Cache) Get(key string) interface{} {
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
if v, err := rc.do("GET", key); err == nil {
|
if v, err := rc.do("GET", key); err == nil {
|
||||||
@ -77,57 +88,28 @@ func (rc *Cache) Get(key string) interface{} {
|
|||||||
|
|
||||||
// GetMulti get cache from redis.
|
// GetMulti get cache from redis.
|
||||||
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
size := len(keys)
|
|
||||||
var rv []interface{}
|
|
||||||
c := rc.p.Get()
|
c := rc.p.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
var err error
|
var args []interface{}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
err = c.Send("GET", key)
|
args = append(args, rc.associate(key))
|
||||||
if err != nil {
|
|
||||||
goto ERROR
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = c.Flush(); err != nil {
|
values, err := redis.Values(c.Do("MGET", args...))
|
||||||
goto ERROR
|
if err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
for i := 0; i < size; i++ {
|
return values
|
||||||
if v, err := c.Receive(); err == nil {
|
|
||||||
rv = append(rv, v.([]byte))
|
|
||||||
} else {
|
|
||||||
rv = append(rv, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
ERROR:
|
|
||||||
rv = rv[0:0]
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
rv = append(rv, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put put cache to redis.
|
// Put put cache to redis.
|
||||||
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
var err error
|
_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
|
||||||
if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = rc.do("HSET", rc.key, key, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete cache in redis.
|
// Delete delete cache in redis.
|
||||||
func (rc *Cache) Delete(key string) error {
|
func (rc *Cache) Delete(key string) error {
|
||||||
var err error
|
_, err := rc.do("DEL", key)
|
||||||
if _, err = rc.do("DEL", key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = rc.do("HDEL", rc.key, key)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +119,6 @@ func (rc *Cache) IsExist(key string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if v == false {
|
|
||||||
if _, err = rc.do("HDEL", rc.key, key); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,16 +136,17 @@ func (rc *Cache) Decr(key string) error {
|
|||||||
|
|
||||||
// ClearAll clean all cache in redis. delete this redis collection.
|
// ClearAll clean all cache in redis. delete this redis collection.
|
||||||
func (rc *Cache) ClearAll() error {
|
func (rc *Cache) ClearAll() error {
|
||||||
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, str := range cachedKeys {
|
for _, str := range cachedKeys {
|
||||||
if _, err = rc.do("DEL", str); err != nil {
|
if _, err = c.Do("DEL", str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = rc.do("DEL", rc.key)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,10 +170,14 @@ func (rc *Cache) StartAndGC(config string) error {
|
|||||||
if _, ok := cf["password"]; !ok {
|
if _, ok := cf["password"]; !ok {
|
||||||
cf["password"] = ""
|
cf["password"] = ""
|
||||||
}
|
}
|
||||||
|
if _, ok := cf["maxIdle"]; !ok {
|
||||||
|
cf["maxIdle"] = "3"
|
||||||
|
}
|
||||||
rc.key = cf["key"]
|
rc.key = cf["key"]
|
||||||
rc.conninfo = cf["conn"]
|
rc.conninfo = cf["conn"]
|
||||||
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
||||||
rc.password = cf["password"]
|
rc.password = cf["password"]
|
||||||
|
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
|
||||||
|
|
||||||
rc.connectInit()
|
rc.connectInit()
|
||||||
|
|
||||||
@ -229,7 +211,7 @@ func (rc *Cache) connectInit() {
|
|||||||
}
|
}
|
||||||
// initialize a new pool
|
// initialize a new pool
|
||||||
rc.p = &redis.Pool{
|
rc.p = &redis.Pool{
|
||||||
MaxIdle: 3,
|
MaxIdle: rc.maxIdle,
|
||||||
IdleTimeout: 180 * time.Second,
|
IdleTimeout: 180 * time.Second,
|
||||||
Dial: dialFunc,
|
Dial: dialFunc,
|
||||||
}
|
}
|
||||||
|
2
cache/redis/redis_test.go
vendored
2
cache/redis/redis_test.go
vendored
@ -19,7 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisCache(t *testing.T) {
|
func TestRedisCache(t *testing.T) {
|
||||||
|
21
cache/ssdb/ssdb.go
vendored
21
cache/ssdb/ssdb.go
vendored
@ -53,7 +53,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
|
|||||||
resSize := len(res)
|
resSize := len(res)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for i := 1; i < resSize; i += 2 {
|
for i := 1; i < resSize; i += 2 {
|
||||||
values = append(values, string(res[i+1]))
|
values = append(values, res[i+1])
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
@ -71,10 +71,7 @@ func (rc *Cache) DelMulti(keys []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := rc.conn.Do("multi_del", keys)
|
_, err := rc.conn.Do("multi_del", keys)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put put value to memcache. only support string.
|
// Put put value to memcache. only support string.
|
||||||
@ -113,10 +110,7 @@ func (rc *Cache) Delete(key string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := rc.conn.Del(key)
|
_, err := rc.conn.Del(key)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incr increase counter.
|
// Incr increase counter.
|
||||||
@ -152,7 +146,7 @@ func (rc *Cache) IsExist(key string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if resp[1] == "1" {
|
if len(resp) == 2 && resp[1] == "1" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -175,7 +169,7 @@ func (rc *Cache) ClearAll() error {
|
|||||||
}
|
}
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
for i := 1; i < size; i += 2 {
|
for i := 1; i < size; i += 2 {
|
||||||
keys = append(keys, string(resp[i]))
|
keys = append(keys, resp[i])
|
||||||
}
|
}
|
||||||
_, e := rc.conn.Do("multi_del", keys)
|
_, e := rc.conn.Do("multi_del", keys)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@ -229,10 +223,7 @@ func (rc *Cache) connectInit() error {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
rc.conn, err = ssdb.Connect(host, port)
|
rc.conn, err = ssdb.Connect(host, port)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
167
config.go
167
config.go
@ -19,9 +19,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/config"
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
@ -34,10 +36,12 @@ type Config struct {
|
|||||||
RouterCaseSensitive bool
|
RouterCaseSensitive bool
|
||||||
ServerName string
|
ServerName string
|
||||||
RecoverPanic bool
|
RecoverPanic bool
|
||||||
|
RecoverFunc func(*context.Context)
|
||||||
CopyRequestBody bool
|
CopyRequestBody bool
|
||||||
EnableGzip bool
|
EnableGzip bool
|
||||||
MaxMemory int64
|
MaxMemory int64
|
||||||
EnableErrorsShow bool
|
EnableErrorsShow bool
|
||||||
|
EnableErrorsRender bool
|
||||||
Listen Listen
|
Listen Listen
|
||||||
WebConfig WebConfig
|
WebConfig WebConfig
|
||||||
Log LogConfig
|
Log LogConfig
|
||||||
@ -45,22 +49,27 @@ type Config struct {
|
|||||||
|
|
||||||
// Listen holds for http and https related config
|
// Listen holds for http and https related config
|
||||||
type Listen struct {
|
type Listen struct {
|
||||||
Graceful bool // Graceful means use graceful module to start the server
|
Graceful bool // Graceful means use graceful module to start the server
|
||||||
ServerTimeOut int64
|
ServerTimeOut int64
|
||||||
ListenTCP4 bool
|
ListenTCP4 bool
|
||||||
EnableHTTP bool
|
EnableHTTP bool
|
||||||
HTTPAddr string
|
HTTPAddr string
|
||||||
HTTPPort int
|
HTTPPort int
|
||||||
EnableHTTPS bool
|
AutoTLS bool
|
||||||
HTTPSAddr string
|
Domains []string
|
||||||
HTTPSPort int
|
TLSCacheDir string
|
||||||
HTTPSCertFile string
|
EnableHTTPS bool
|
||||||
HTTPSKeyFile string
|
EnableMutualHTTPS bool
|
||||||
EnableAdmin bool
|
HTTPSAddr string
|
||||||
AdminAddr string
|
HTTPSPort int
|
||||||
AdminPort int
|
HTTPSCertFile string
|
||||||
EnableFcgi bool
|
HTTPSKeyFile string
|
||||||
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
TrustCaFile string
|
||||||
|
EnableAdmin bool
|
||||||
|
AdminAddr string
|
||||||
|
AdminPort int
|
||||||
|
EnableFcgi bool
|
||||||
|
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebConfig holds web related config
|
// WebConfig holds web related config
|
||||||
@ -83,24 +92,27 @@ type WebConfig struct {
|
|||||||
|
|
||||||
// SessionConfig holds session related config
|
// SessionConfig holds session related config
|
||||||
type SessionConfig struct {
|
type SessionConfig struct {
|
||||||
SessionOn bool
|
SessionOn bool
|
||||||
SessionProvider string
|
SessionProvider string
|
||||||
SessionName string
|
SessionName string
|
||||||
SessionGCMaxLifetime int64
|
SessionGCMaxLifetime int64
|
||||||
SessionProviderConfig string
|
SessionProviderConfig string
|
||||||
SessionCookieLifeTime int
|
SessionCookieLifeTime int
|
||||||
SessionAutoSetCookie bool
|
SessionAutoSetCookie bool
|
||||||
SessionDomain string
|
SessionDomain string
|
||||||
EnableSidInHttpHeader bool // enable store/get the sessionId into/from http headers
|
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
|
||||||
SessionNameInHttpHeader string
|
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
|
||||||
EnableSidInUrlQuery bool // enable get the sessionId from Url Query params
|
SessionNameInHTTPHeader string
|
||||||
|
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogConfig holds Log related config
|
// LogConfig holds Log related config
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
AccessLogs bool
|
AccessLogs bool
|
||||||
FileLineNum bool
|
EnableStaticLogs bool //log static files requests default: false
|
||||||
Outputs map[string]string // Store Adaptor : config
|
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
|
||||||
|
FileLineNum bool
|
||||||
|
Outputs map[string]string // Store Adaptor : config
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -129,9 +141,13 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
appConfigPath = filepath.Join(workPath, "conf", "app.conf")
|
var filename = "app.conf"
|
||||||
|
if os.Getenv("BEEGO_RUNMODE") != "" {
|
||||||
|
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
|
||||||
|
}
|
||||||
|
appConfigPath = filepath.Join(workPath, "conf", filename)
|
||||||
if !utils.FileExists(appConfigPath) {
|
if !utils.FileExists(appConfigPath) {
|
||||||
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
appConfigPath = filepath.Join(AppPath, "conf", filename)
|
||||||
if !utils.FileExists(appConfigPath) {
|
if !utils.FileExists(appConfigPath) {
|
||||||
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
||||||
return
|
return
|
||||||
@ -142,22 +158,63 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recoverPanic(ctx *context.Context) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if err == ErrAbort {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !BConfig.RecoverPanic {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if BConfig.EnableErrorsShow {
|
||||||
|
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||||
|
exception(fmt.Sprint(err), ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stack string
|
||||||
|
logs.Critical("the request url is ", ctx.Input.URL())
|
||||||
|
logs.Critical("Handler crashed with error", err)
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
}
|
||||||
|
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
|
||||||
|
showErr(err, ctx, stack)
|
||||||
|
}
|
||||||
|
if ctx.Output.Status != 0 {
|
||||||
|
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
|
||||||
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newBConfig() *Config {
|
func newBConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AppName: "beego",
|
AppName: "beego",
|
||||||
RunMode: DEV,
|
RunMode: PROD,
|
||||||
RouterCaseSensitive: true,
|
RouterCaseSensitive: true,
|
||||||
ServerName: "beegoServer:" + VERSION,
|
ServerName: "beegoServer:" + VERSION,
|
||||||
RecoverPanic: true,
|
RecoverPanic: true,
|
||||||
|
RecoverFunc: recoverPanic,
|
||||||
CopyRequestBody: false,
|
CopyRequestBody: false,
|
||||||
EnableGzip: false,
|
EnableGzip: false,
|
||||||
MaxMemory: 1 << 26, //64MB
|
MaxMemory: 1 << 26, //64MB
|
||||||
EnableErrorsShow: true,
|
EnableErrorsShow: true,
|
||||||
|
EnableErrorsRender: true,
|
||||||
Listen: Listen{
|
Listen: Listen{
|
||||||
Graceful: false,
|
Graceful: false,
|
||||||
ServerTimeOut: 0,
|
ServerTimeOut: 0,
|
||||||
ListenTCP4: false,
|
ListenTCP4: false,
|
||||||
EnableHTTP: true,
|
EnableHTTP: true,
|
||||||
|
AutoTLS: false,
|
||||||
|
Domains: []string{},
|
||||||
|
TLSCacheDir: ".",
|
||||||
HTTPAddr: "",
|
HTTPAddr: "",
|
||||||
HTTPPort: 8080,
|
HTTPPort: 8080,
|
||||||
EnableHTTPS: false,
|
EnableHTTPS: false,
|
||||||
@ -186,23 +243,26 @@ func newBConfig() *Config {
|
|||||||
XSRFKey: "beegoxsrf",
|
XSRFKey: "beegoxsrf",
|
||||||
XSRFExpire: 0,
|
XSRFExpire: 0,
|
||||||
Session: SessionConfig{
|
Session: SessionConfig{
|
||||||
SessionOn: false,
|
SessionOn: false,
|
||||||
SessionProvider: "memory",
|
SessionProvider: "memory",
|
||||||
SessionName: "beegosessionID",
|
SessionName: "beegosessionID",
|
||||||
SessionGCMaxLifetime: 3600,
|
SessionGCMaxLifetime: 3600,
|
||||||
SessionProviderConfig: "",
|
SessionProviderConfig: "",
|
||||||
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
SessionDisableHTTPOnly: false,
|
||||||
SessionAutoSetCookie: true,
|
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||||
SessionDomain: "",
|
SessionAutoSetCookie: true,
|
||||||
EnableSidInHttpHeader: false, // enable store/get the sessionId into/from http headers
|
SessionDomain: "",
|
||||||
SessionNameInHttpHeader: "Beegosessionid",
|
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
|
||||||
EnableSidInUrlQuery: false, // enable get the sessionId from Url Query params
|
SessionNameInHTTPHeader: "Beegosessionid",
|
||||||
|
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Log: LogConfig{
|
Log: LogConfig{
|
||||||
AccessLogs: false,
|
AccessLogs: false,
|
||||||
FileLineNum: true,
|
EnableStaticLogs: false,
|
||||||
Outputs: map[string]string{"console": ""},
|
AccessLogsFormat: "APACHE_FORMAT",
|
||||||
|
FileLineNum: true,
|
||||||
|
Outputs: map[string]string{"console": ""},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,6 +277,9 @@ func parseConfig(appConfigPath string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func assignConfig(ac config.Configer) error {
|
func assignConfig(ac config.Configer) error {
|
||||||
|
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
|
||||||
|
assignSingleConfig(i, ac)
|
||||||
|
}
|
||||||
// set the run mode first
|
// set the run mode first
|
||||||
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
||||||
BConfig.RunMode = envRunMode
|
BConfig.RunMode = envRunMode
|
||||||
@ -224,10 +287,6 @@ func assignConfig(ac config.Configer) error {
|
|||||||
BConfig.RunMode = runMode
|
BConfig.RunMode = runMode
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
|
|
||||||
assignSingleConfig(i, ac)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sd := ac.String("StaticDir"); sd != "" {
|
if sd := ac.String("StaticDir"); sd != "" {
|
||||||
BConfig.WebConfig.StaticDir = map[string]string{}
|
BConfig.WebConfig.StaticDir = map[string]string{}
|
||||||
sds := strings.Fields(sd)
|
sds := strings.Fields(sd)
|
||||||
@ -259,6 +318,10 @@ func assignConfig(ac config.Configer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if lo := ac.String("LogOutputs"); lo != "" {
|
if lo := ac.String("LogOutputs"); lo != "" {
|
||||||
|
// if lo is not nil or empty
|
||||||
|
// means user has set his own LogOutputs
|
||||||
|
// clear the default setting to BConfig.Log.Outputs
|
||||||
|
BConfig.Log.Outputs = make(map[string]string)
|
||||||
los := strings.Split(lo, ";")
|
los := strings.Split(lo, ";")
|
||||||
for _, v := range los {
|
for _, v := range los {
|
||||||
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
||||||
@ -303,7 +366,7 @@ func assignSingleConfig(p interface{}, ac config.Configer) {
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
pf.SetString(ac.DefaultString(name, pf.String()))
|
pf.SetString(ac.DefaultString(name, pf.String()))
|
||||||
case reflect.Int, reflect.Int64:
|
case reflect.Int, reflect.Int64:
|
||||||
pf.SetInt(int64(ac.DefaultInt64(name, pf.Int())))
|
pf.SetInt(ac.DefaultInt64(name, pf.Int()))
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
|
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
@ -43,6 +43,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configer defines how to get and set value from configuration raw data.
|
// Configer defines how to get and set value from configuration raw data.
|
||||||
@ -148,12 +150,12 @@ func ExpandValueEnv(value string) (realValue string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
key := ""
|
key := ""
|
||||||
defalutV := ""
|
defaultV := ""
|
||||||
// value start with "${"
|
// value start with "${"
|
||||||
for i := 2; i < vLen; i++ {
|
for i := 2; i < vLen; i++ {
|
||||||
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
||||||
key = value[2:i]
|
key = value[2:i]
|
||||||
defalutV = value[i+2 : vLen-1] // other string is default value.
|
defaultV = value[i+2 : vLen-1] // other string is default value.
|
||||||
break
|
break
|
||||||
} else if value[i] == '}' {
|
} else if value[i] == '}' {
|
||||||
key = value[2:i]
|
key = value[2:i]
|
||||||
@ -163,7 +165,7 @@ func ExpandValueEnv(value string) (realValue string) {
|
|||||||
|
|
||||||
realValue = os.Getenv(key)
|
realValue = os.Getenv(key)
|
||||||
if realValue == "" {
|
if realValue == "" {
|
||||||
realValue = defalutV
|
realValue = defaultV
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -187,16 +189,16 @@ func ParseBool(val interface{}) (value bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
case int8, int32, int64:
|
case int8, int32, int64:
|
||||||
strV := fmt.Sprintf("%s", v)
|
strV := fmt.Sprintf("%d", v)
|
||||||
if strV == "1" {
|
if strV == "1" {
|
||||||
return true, nil
|
return true, nil
|
||||||
} else if strV == "0" {
|
} else if strV == "0" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
case float64:
|
case float64:
|
||||||
if v == 1 {
|
if v == 1.0 {
|
||||||
return true, nil
|
return true, nil
|
||||||
} else if v == 0 {
|
} else if v == 0.0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,3 +206,37 @@ func ParseBool(val interface{}) (value bool, err error) {
|
|||||||
}
|
}
|
||||||
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToString converts values of any type to string.
|
||||||
|
func ToString(x interface{}) string {
|
||||||
|
switch y := x.(type) {
|
||||||
|
|
||||||
|
// Handle dates with special logic
|
||||||
|
// This needs to come above the fmt.Stringer
|
||||||
|
// test since time.Time's have a .String()
|
||||||
|
// method
|
||||||
|
case time.Time:
|
||||||
|
return y.Format("A Monday")
|
||||||
|
|
||||||
|
// Handle type string
|
||||||
|
case string:
|
||||||
|
return y
|
||||||
|
|
||||||
|
// Handle type with .String() method
|
||||||
|
case fmt.Stringer:
|
||||||
|
return y.String()
|
||||||
|
|
||||||
|
// Handle type with .Error() method
|
||||||
|
case error:
|
||||||
|
return y.Error()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle named string type
|
||||||
|
if v := reflect.ValueOf(x); v.Kind() == reflect.String {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fmt package for anything else like numeric types
|
||||||
|
return fmt.Sprint(x)
|
||||||
|
}
|
||||||
|
87
config/env/env.go
vendored
Normal file
87
config/env/env.go
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package env is used to parse environment.
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var env *utils.BeeMap
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env = utils.NewBeeMap()
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
splits := strings.Split(e, "=")
|
||||||
|
env.Set(splits[0], os.Getenv(splits[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value by key.
|
||||||
|
// If the key does not exist, the default value will be returned.
|
||||||
|
func Get(key string, defVal string) string {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns a value by key.
|
||||||
|
// If the key does not exist, it will return an error.
|
||||||
|
func MustGet(key string) (string, error) {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no env variable with %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a value in the ENV copy.
|
||||||
|
// This does not affect the child process environment.
|
||||||
|
func Set(key string, value string) {
|
||||||
|
env.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustSet sets a value in the ENV copy and the child process environment.
|
||||||
|
// It returns an error in case the set operation failed.
|
||||||
|
func MustSet(key string, value string) error {
|
||||||
|
err := os.Setenv(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env.Set(key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all keys/values in the current child process environment.
|
||||||
|
func GetAll() map[string]string {
|
||||||
|
items := env.Items()
|
||||||
|
envs := make(map[string]string, env.Count())
|
||||||
|
|
||||||
|
for key, val := range items {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case string:
|
||||||
|
switch val := val.(type) {
|
||||||
|
case string:
|
||||||
|
envs[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envs
|
||||||
|
}
|
75
config/env/env_test.go
vendored
Normal file
75
config/env/env_test.go
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvGet(t *testing.T) {
|
||||||
|
gopath := Get("GOPATH", "")
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Error("expected GOPATH not empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
noExistVar := Get("NOEXISTVAR", "foo")
|
||||||
|
if noExistVar != "foo" {
|
||||||
|
t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustGet(t *testing.T) {
|
||||||
|
gopath, err := MustGet("GOPATH")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Errorf("expected GOPATH to be the same, got %s.", gopath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = MustGet("NOEXISTVAR")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to be non-nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvSet(t *testing.T) {
|
||||||
|
Set("MYVAR", "foo")
|
||||||
|
myVar := Get("MYVAR", "bar")
|
||||||
|
if myVar != "foo" {
|
||||||
|
t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustSet(t *testing.T) {
|
||||||
|
err := MustSet("FOO", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooVar := os.Getenv("FOO")
|
||||||
|
if fooVar != "bar" {
|
||||||
|
t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvGetAll(t *testing.T) {
|
||||||
|
envMap := GetAll()
|
||||||
|
if len(envMap) == 0 {
|
||||||
|
t.Error("expected environment not empty.")
|
||||||
|
}
|
||||||
|
}
|
@ -126,7 +126,7 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
|
|||||||
|
|
||||||
var _ Configer = new(fakeConfigContainer)
|
var _ Configer = new(fakeConfigContainer)
|
||||||
|
|
||||||
// NewFakeConfig return a fake Congiger
|
// NewFakeConfig return a fake Configer
|
||||||
func NewFakeConfig() Configer {
|
func NewFakeConfig() Configer {
|
||||||
return &fakeConfigContainer{
|
return &fakeConfigContainer{
|
||||||
data: make(map[string]string),
|
data: make(map[string]string),
|
||||||
|
@ -18,15 +18,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -51,24 +50,26 @@ func (ini *IniConfig) Parse(name string) (Configer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||||
file, err := os.Open(name)
|
data, err := ioutil.ReadFile(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ini.parseData(filepath.Dir(name), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
|
||||||
cfg := &IniConfigContainer{
|
cfg := &IniConfigContainer{
|
||||||
file.Name(),
|
data: make(map[string]map[string]string),
|
||||||
make(map[string]map[string]string),
|
sectionComment: make(map[string]string),
|
||||||
make(map[string]string),
|
keyComment: make(map[string]string),
|
||||||
make(map[string]string),
|
RWMutex: sync.RWMutex{},
|
||||||
sync.RWMutex{},
|
|
||||||
}
|
}
|
||||||
cfg.Lock()
|
cfg.Lock()
|
||||||
defer cfg.Unlock()
|
defer cfg.Unlock()
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var comment bytes.Buffer
|
var comment bytes.Buffer
|
||||||
buf := bufio.NewReader(file)
|
buf := bufio.NewReader(bytes.NewBuffer(data))
|
||||||
// check the BOM
|
// check the BOM
|
||||||
head, err := buf.Peek(3)
|
head, err := buf.Peek(3)
|
||||||
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
|
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
|
||||||
@ -129,16 +130,20 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
|||||||
|
|
||||||
// handle include "other.conf"
|
// handle include "other.conf"
|
||||||
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
|
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
|
||||||
|
|
||||||
includefiles := strings.Fields(key)
|
includefiles := strings.Fields(key)
|
||||||
if includefiles[0] == "include" && len(includefiles) == 2 {
|
if includefiles[0] == "include" && len(includefiles) == 2 {
|
||||||
|
|
||||||
otherfile := strings.Trim(includefiles[1], "\"")
|
otherfile := strings.Trim(includefiles[1], "\"")
|
||||||
if !path.IsAbs(otherfile) {
|
if !filepath.IsAbs(otherfile) {
|
||||||
otherfile = path.Join(path.Dir(name), otherfile)
|
otherfile = filepath.Join(dir, otherfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
i, err := ini.parseFile(otherfile)
|
i, err := ini.parseFile(otherfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for sec, dt := range i.data {
|
for sec, dt := range i.data {
|
||||||
if _, ok := cfg.data[sec]; !ok {
|
if _, ok := cfg.data[sec]; !ok {
|
||||||
cfg.data[sec] = make(map[string]string)
|
cfg.data[sec] = make(map[string]string)
|
||||||
@ -147,12 +152,15 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
|||||||
cfg.data[sec][k] = v
|
cfg.data[sec][k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for sec, comm := range i.sectionComment {
|
for sec, comm := range i.sectionComment {
|
||||||
cfg.sectionComment[sec] = comm
|
cfg.sectionComment[sec] = comm
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, comm := range i.keyComment {
|
for k, comm := range i.keyComment {
|
||||||
cfg.keyComment[k] = comm
|
cfg.keyComment[k] = comm
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,20 +184,25 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseData parse ini the data
|
// ParseData parse ini the data
|
||||||
|
// When include other.conf,other.conf is either absolute directory
|
||||||
|
// or under beego in default temporary directory(/tmp/beego[-username]).
|
||||||
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
|
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
|
||||||
// Save memory data to temporary file
|
dir := "beego"
|
||||||
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
|
currentUser, err := user.Current()
|
||||||
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
|
if err == nil {
|
||||||
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
|
dir = "beego-" + currentUser.Username
|
||||||
|
}
|
||||||
|
dir = filepath.Join(os.TempDir(), dir)
|
||||||
|
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ini.Parse(tmpName)
|
|
||||||
|
return ini.parseData(dir, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IniConfigContainer A Config represents the ini configuration.
|
// IniConfigContainer A Config represents the ini configuration.
|
||||||
// When set and get value, support key as section:name type.
|
// When set and get value, support key as section:name type.
|
||||||
type IniConfigContainer struct {
|
type IniConfigContainer struct {
|
||||||
filename string
|
|
||||||
data map[string]map[string]string // section=> key:val
|
data map[string]map[string]string // section=> key:val
|
||||||
sectionComment map[string]string // section : comment
|
sectionComment map[string]string // section : comment
|
||||||
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
||||||
@ -202,7 +215,7 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultBool returns the boolean value for a given key.
|
// DefaultBool returns the boolean value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
v, err := c.Bool(key)
|
v, err := c.Bool(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -217,7 +230,7 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -232,7 +245,7 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -247,7 +260,7 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -262,7 +275,7 @@ func (c *IniConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -282,7 +295,7 @@ func (c *IniConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -296,12 +309,12 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
|
|||||||
if v, ok := c.data[section]; ok {
|
if v, ok := c.data[section]; ok {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("not exist setction")
|
return nil, errors.New("not exist section")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfigFile save the config into file.
|
// SaveConfigFile save the config into file.
|
||||||
//
|
//
|
||||||
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
|
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
|
||||||
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
// Write configuration file by filename.
|
// Write configuration file by filename.
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
@ -312,7 +325,10 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|||||||
|
|
||||||
// Get section or key comments. Fixed #1607
|
// Get section or key comments. Fixed #1607
|
||||||
getCommentStr := func(section, key string) string {
|
getCommentStr := func(section, key string) string {
|
||||||
comment, ok := "", false
|
var (
|
||||||
|
comment string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
comment, ok = c.sectionComment[section]
|
comment, ok = c.sectionComment[section]
|
||||||
} else {
|
} else {
|
||||||
@ -392,11 +408,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_, err = buf.WriteTo(f)
|
||||||
if _, err = buf.WriteTo(f); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set writes a new value for key.
|
// Set writes a new value for key.
|
||||||
@ -411,7 +424,7 @@ func (c *IniConfigContainer) Set(key, value string) error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
section, k string
|
section, k string
|
||||||
sectionKey = strings.Split(key, "::")
|
sectionKey = strings.Split(strings.ToLower(key), "::")
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(sectionKey) >= 2 {
|
if len(sectionKey) >= 2 {
|
||||||
|
@ -181,7 +181,7 @@ name=mysql
|
|||||||
cfgData := string(data)
|
cfgData := string(data)
|
||||||
datas := strings.Split(saveResult, "\n")
|
datas := strings.Split(saveResult, "\n")
|
||||||
for _, line := range datas {
|
for _, line := range datas {
|
||||||
if strings.Contains(cfgData, line+"\n") == false {
|
if !strings.Contains(cfgData, line+"\n") {
|
||||||
t.Fatalf("different after save ini config file. need contains %q", line)
|
t.Fatalf("different after save ini config file. need contains %q", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
if v, err := c.Int(key); err == nil {
|
if v, err := c.Int(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -122,7 +122,7 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
if v, err := c.Int64(key); err == nil {
|
if v, err := c.Int64(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -143,7 +143,7 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
if v, err := c.Float(key); err == nil {
|
if v, err := c.Float(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -163,7 +163,7 @@ func (c *JSONConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
// TODO FIXME should not use "" to replace non existence
|
// TODO FIXME should not use "" to replace non existence
|
||||||
if v := c.String(key); v != "" {
|
if v := c.String(key); v != "" {
|
||||||
@ -182,7 +182,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
if v := c.Strings(key); v != nil {
|
if v := c.Strings(key); v != nil {
|
||||||
return v
|
return v
|
||||||
|
@ -216,7 +216,7 @@ func TestJson(t *testing.T) {
|
|||||||
t.Error("unknown keys should return an error when expecting a Bool")
|
t.Error("unknown keys should return an error when expecting a Bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !jsonconf.DefaultBool("unknow", true) {
|
if !jsonconf.DefaultBool("unknown", true) {
|
||||||
t.Error("unknown keys with default value wrong")
|
t.Error("unknown keys with default value wrong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/config"
|
"github.com/astaxie/beego/config"
|
||||||
"github.com/beego/x2j"
|
"github.com/beego/x2j"
|
||||||
@ -52,36 +50,26 @@ type Config struct{}
|
|||||||
|
|
||||||
// Parse returns a ConfigContainer with parsed xml config map.
|
// Parse returns a ConfigContainer with parsed xml config map.
|
||||||
func (xc *Config) Parse(filename string) (config.Configer, error) {
|
func (xc *Config) Parse(filename string) (config.Configer, error) {
|
||||||
file, err := os.Open(filename)
|
context, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
|
return xc.ParseData(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData xml data
|
||||||
|
func (xc *Config) ParseData(data []byte) (config.Configer, error) {
|
||||||
x := &ConfigContainer{data: make(map[string]interface{})}
|
x := &ConfigContainer{data: make(map[string]interface{})}
|
||||||
content, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := x2j.DocToMap(string(content))
|
d, err := x2j.DocToMap(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
|
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseData xml data
|
return x, nil
|
||||||
func (xc *Config) ParseData(data []byte) (config.Configer, error) {
|
|
||||||
// Save memory data to temporary file
|
|
||||||
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
|
|
||||||
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
|
|
||||||
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return xc.Parse(tmpName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigContainer A Config represents the xml configuration.
|
// ConfigContainer A Config represents the xml configuration.
|
||||||
@ -114,7 +102,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,7 +117,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -145,7 +133,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -163,7 +151,7 @@ func (c *ConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -182,7 +170,7 @@ func (c *ConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -193,10 +181,14 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
|
|||||||
|
|
||||||
// GetSection returns map for the given section
|
// GetSection returns map for the given section
|
||||||
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
|
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
if v, ok := c.data[section]; ok {
|
if v, ok := c.data[section].(map[string]interface{}); ok {
|
||||||
return v.(map[string]string), nil
|
mapstr := make(map[string]string)
|
||||||
|
for k, val := range v {
|
||||||
|
mapstr[k] = config.ToString(val)
|
||||||
|
}
|
||||||
|
return mapstr, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("not exist setction")
|
return nil, fmt.Errorf("section '%s' not found", section)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfigFile save the config into file
|
// SaveConfigFile save the config into file
|
||||||
|
@ -37,6 +37,10 @@ func TestXML(t *testing.T) {
|
|||||||
<copyrequestbody>true</copyrequestbody>
|
<copyrequestbody>true</copyrequestbody>
|
||||||
<path1>${GOPATH}</path1>
|
<path1>${GOPATH}</path1>
|
||||||
<path2>${GOPATH||/home/go}</path2>
|
<path2>${GOPATH||/home/go}</path2>
|
||||||
|
<mysection>
|
||||||
|
<id>1</id>
|
||||||
|
<name>MySection</name>
|
||||||
|
</mysection>
|
||||||
</config>
|
</config>
|
||||||
`
|
`
|
||||||
keyValue = map[string]interface{}{
|
keyValue = map[string]interface{}{
|
||||||
@ -65,11 +69,22 @@ func TestXML(t *testing.T) {
|
|||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
defer os.Remove("testxml.conf")
|
defer os.Remove("testxml.conf")
|
||||||
|
|
||||||
xmlconf, err := config.NewConfig("xml", "testxml.conf")
|
xmlconf, err := config.NewConfig("xml", "testxml.conf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xmlsection map[string]string
|
||||||
|
xmlsection, err = xmlconf.GetSection("mysection")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xmlsection) == 0 {
|
||||||
|
t.Error("section should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range keyValue {
|
for k, v := range keyValue {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -37,10 +37,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/config"
|
"github.com/astaxie/beego/config"
|
||||||
"github.com/beego/goyaml2"
|
"github.com/beego/goyaml2"
|
||||||
@ -63,26 +61,30 @@ func (yaml *Config) Parse(filename string) (y config.Configer, err error) {
|
|||||||
|
|
||||||
// ParseData parse yaml data
|
// ParseData parse yaml data
|
||||||
func (yaml *Config) ParseData(data []byte) (config.Configer, error) {
|
func (yaml *Config) ParseData(data []byte) (config.Configer, error) {
|
||||||
// Save memory data to temporary file
|
cnf, err := parseYML(data)
|
||||||
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
|
if err != nil {
|
||||||
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
|
|
||||||
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return yaml.Parse(tmpName)
|
|
||||||
|
return &ConfigContainer{
|
||||||
|
data: cnf,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadYmlReader Read yaml file to map.
|
// ReadYmlReader Read yaml file to map.
|
||||||
// if json like, use json package, unless goyaml2 package.
|
// if json like, use json package, unless goyaml2 package.
|
||||||
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
||||||
f, err := os.Open(path)
|
buf, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(f)
|
return parseYML(buf)
|
||||||
if err != nil || len(buf) < 3 {
|
}
|
||||||
|
|
||||||
|
// parseYML parse yaml formatted []byte to map.
|
||||||
|
func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
|
||||||
|
if len(buf) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
|||||||
// ConfigContainer A Config represents the yaml configuration.
|
// ConfigContainer A Config represents the yaml configuration.
|
||||||
type ConfigContainer struct {
|
type ConfigContainer struct {
|
||||||
data map[string]interface{}
|
data map[string]interface{}
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool returns the boolean value for a given key.
|
// Bool returns the boolean value for a given key.
|
||||||
@ -152,7 +154,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,7 +174,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,7 +198,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -216,7 +218,7 @@ func (c *ConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -235,7 +237,7 @@ func (c *ConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -250,7 +252,7 @@ func (c *ConfigContainer) GetSection(section string) (map[string]string, error)
|
|||||||
if v, ok := c.data[section]; ok {
|
if v, ok := c.data[section]; ok {
|
||||||
return v.(map[string]string), nil
|
return v.(map[string]string), nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("not exist setction")
|
return nil, errors.New("not exist section")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfigFile save the config into file
|
// SaveConfigFile save the config into file
|
||||||
@ -283,9 +285,25 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
|
|||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return nil, errors.New("key is empty")
|
return nil, errors.New("key is empty")
|
||||||
}
|
}
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
if v, ok := c.data[key]; ok {
|
keys := strings.Split(key, ".")
|
||||||
return v, nil
|
tmpData := c.data
|
||||||
|
for _, k := range keys {
|
||||||
|
if v, ok := tmpData[k]; ok {
|
||||||
|
switch v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
{
|
||||||
|
tmpData = v.(map[string]interface{})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("not exist key %q", key)
|
return nil, fmt.Errorf("not exist key %q", key)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func TestAssignConfig_02(t *testing.T) {
|
|||||||
|
|
||||||
jcf := &config.JSONConfig{}
|
jcf := &config.JSONConfig{}
|
||||||
bs, _ = json.Marshal(configMap)
|
bs, _ = json.Marshal(configMap)
|
||||||
ac, _ := jcf.ParseData([]byte(bs))
|
ac, _ := jcf.ParseData(bs)
|
||||||
|
|
||||||
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
||||||
assignSingleConfig(i, ac)
|
assignSingleConfig(i, ac)
|
||||||
|
@ -39,6 +39,7 @@ var (
|
|||||||
getMethodOnly bool
|
getMethodOnly bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InitGzip init the gzipcompress
|
||||||
func InitGzip(minLength, compressLevel int, methods []string) {
|
func InitGzip(minLength, compressLevel int, methods []string) {
|
||||||
if minLength >= 0 {
|
if minLength >= 0 {
|
||||||
gzipMinLength = minLength
|
gzipMinLength = minLength
|
||||||
|
@ -171,6 +171,22 @@ func (ctx *Context) CheckXSRFCookie() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderMethodResult renders the return value of a controller method to the output
|
||||||
|
func (ctx *Context) RenderMethodResult(result interface{}) {
|
||||||
|
if result != nil {
|
||||||
|
renderer, ok := result.(Renderer)
|
||||||
|
if !ok {
|
||||||
|
err, ok := result.(error)
|
||||||
|
if ok {
|
||||||
|
renderer = errorRenderer(err)
|
||||||
|
} else {
|
||||||
|
renderer = jsonRenderer(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderer.Render(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Response is a wrapper for the http.ResponseWriter
|
//Response is a wrapper for the http.ResponseWriter
|
||||||
//started set to true if response was written to then don't execute other handler
|
//started set to true if response was written to then don't execute other handler
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -16,9 +16,12 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -34,18 +37,21 @@ var (
|
|||||||
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
|
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
|
||||||
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
|
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
|
||||||
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
|
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
|
||||||
|
acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`)
|
||||||
maxParam = 50
|
maxParam = 50
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoInput operates the http request header, data, cookie and body.
|
// BeegoInput operates the http request header, data, cookie and body.
|
||||||
// it also contains router params and current session.
|
// it also contains router params and current session.
|
||||||
type BeegoInput struct {
|
type BeegoInput struct {
|
||||||
Context *Context
|
Context *Context
|
||||||
CruSession session.Store
|
CruSession session.Store
|
||||||
pnames []string
|
pnames []string
|
||||||
pvalues []string
|
pvalues []string
|
||||||
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
||||||
RequestBody []byte
|
RequestBody []byte
|
||||||
|
RunMethod string
|
||||||
|
RunController reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInput return BeegoInput generated by Context.
|
// NewInput return BeegoInput generated by Context.
|
||||||
@ -111,9 +117,8 @@ func (input *BeegoInput) Domain() string {
|
|||||||
// if no host info in request, return localhost.
|
// if no host info in request, return localhost.
|
||||||
func (input *BeegoInput) Host() string {
|
func (input *BeegoInput) Host() string {
|
||||||
if input.Context.Request.Host != "" {
|
if input.Context.Request.Host != "" {
|
||||||
hostParts := strings.Split(input.Context.Request.Host, ":")
|
if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
if len(hostParts) > 0 {
|
return hostPart
|
||||||
return hostParts[0]
|
|
||||||
}
|
}
|
||||||
return input.Context.Request.Host
|
return input.Context.Request.Host
|
||||||
}
|
}
|
||||||
@ -199,23 +204,27 @@ func (input *BeegoInput) AcceptsXML() bool {
|
|||||||
func (input *BeegoInput) AcceptsJSON() bool {
|
func (input *BeegoInput) AcceptsJSON() bool {
|
||||||
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
||||||
}
|
}
|
||||||
|
// AcceptsYAML Checks if request accepts json response
|
||||||
|
func (input *BeegoInput) AcceptsYAML() bool {
|
||||||
|
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
// IP returns request client ip.
|
// IP returns request client ip.
|
||||||
// if in proxy, return first proxy id.
|
// if in proxy, return first proxy id.
|
||||||
// if error, return 127.0.0.1.
|
// if error, return RemoteAddr.
|
||||||
func (input *BeegoInput) IP() string {
|
func (input *BeegoInput) IP() string {
|
||||||
ips := input.Proxy()
|
ips := input.Proxy()
|
||||||
if len(ips) > 0 && ips[0] != "" {
|
if len(ips) > 0 && ips[0] != "" {
|
||||||
rip := strings.Split(ips[0], ":")
|
rip, _, err := net.SplitHostPort(ips[0])
|
||||||
return rip[0]
|
if err != nil {
|
||||||
}
|
rip = ips[0]
|
||||||
ip := strings.Split(input.Context.Request.RemoteAddr, ":")
|
|
||||||
if len(ip) > 0 {
|
|
||||||
if ip[0] != "[" {
|
|
||||||
return ip[0]
|
|
||||||
}
|
}
|
||||||
|
return rip
|
||||||
}
|
}
|
||||||
return "127.0.0.1"
|
if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return input.Context.Request.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy returns proxy client ips slice.
|
// Proxy returns proxy client ips slice.
|
||||||
@ -249,9 +258,8 @@ func (input *BeegoInput) SubDomains() string {
|
|||||||
// Port returns request client port.
|
// Port returns request client port.
|
||||||
// when error or empty, return 80.
|
// when error or empty, return 80.
|
||||||
func (input *BeegoInput) Port() int {
|
func (input *BeegoInput) Port() int {
|
||||||
parts := strings.Split(input.Context.Request.Host, ":")
|
if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
if len(parts) == 2 {
|
port, _ := strconv.Atoi(portPart)
|
||||||
port, _ := strconv.Atoi(parts[1])
|
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
return 80
|
return 80
|
||||||
@ -347,11 +355,22 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
|
|||||||
if input.Context.Request.Body == nil {
|
if input.Context.Request.Body == nil {
|
||||||
return []byte{}
|
return []byte{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requestbody []byte
|
||||||
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
|
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
|
||||||
requestbody, _ := ioutil.ReadAll(safe)
|
if input.Header("Content-Encoding") == "gzip" {
|
||||||
|
reader, err := gzip.NewReader(safe)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
requestbody, _ = ioutil.ReadAll(reader)
|
||||||
|
} else {
|
||||||
|
requestbody, _ = ioutil.ReadAll(safe)
|
||||||
|
}
|
||||||
|
|
||||||
input.Context.Request.Body.Close()
|
input.Context.Request.Body.Close()
|
||||||
bf := bytes.NewBuffer(requestbody)
|
bf := bytes.NewBuffer(requestbody)
|
||||||
input.Context.Request.Body = ioutil.NopCloser(bf)
|
input.Context.Request.Body = http.MaxBytesReader(input.Context.ResponseWriter, ioutil.NopCloser(bf), MaxMemory)
|
||||||
input.RequestBody = requestbody
|
input.RequestBody = requestbody
|
||||||
return requestbody
|
return requestbody
|
||||||
}
|
}
|
||||||
@ -411,7 +430,13 @@ func (input *BeegoInput) Bind(dest interface{}, key string) error {
|
|||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errors.New("beego: non-settable variable passed to Bind: " + key)
|
return errors.New("beego: non-settable variable passed to Bind: " + key)
|
||||||
}
|
}
|
||||||
rv := input.bind(key, value.Type())
|
typ := value.Type()
|
||||||
|
// Get real type if dest define with interface{}.
|
||||||
|
// e.g var dest interface{} dest=1.0
|
||||||
|
if value.Kind() == reflect.Interface {
|
||||||
|
typ = value.Elem().Type()
|
||||||
|
}
|
||||||
|
rv := input.bind(key, typ)
|
||||||
if !rv.IsValid() {
|
if !rv.IsValid() {
|
||||||
return errors.New("beego: reflect value is empty")
|
return errors.New("beego: reflect value is empty")
|
||||||
}
|
}
|
||||||
@ -420,6 +445,9 @@ func (input *BeegoInput) Bind(dest interface{}, key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
|
func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
|
||||||
|
if input.Context.Request.Form == nil {
|
||||||
|
input.Context.Request.ParseForm()
|
||||||
|
}
|
||||||
rv := reflect.Zero(typ)
|
rv := reflect.Zero(typ)
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
@ -15,81 +15,97 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
r, _ := http.NewRequest("GET", "/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
|
type testItem struct {
|
||||||
beegoInput := NewInput()
|
field string
|
||||||
beegoInput.Context = NewContext()
|
empty interface{}
|
||||||
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
want interface{}
|
||||||
beegoInput.ParseFormOrMulitForm(1 << 20)
|
}
|
||||||
|
type Human struct {
|
||||||
|
ID int
|
||||||
|
Nick string
|
||||||
|
Pwd string
|
||||||
|
Ms bool
|
||||||
|
}
|
||||||
|
|
||||||
var id int
|
cases := []struct {
|
||||||
err := beegoInput.Bind(&id, "id")
|
request string
|
||||||
if id != 123 || err != nil {
|
valueGp []testItem
|
||||||
t.Fatal("id should has int value")
|
}{
|
||||||
}
|
{"/?p=str", []testItem{{"p", interface{}(""), interface{}("str")}}},
|
||||||
fmt.Println(id)
|
|
||||||
|
|
||||||
var isok bool
|
{"/?p=", []testItem{{"p", "", ""}}},
|
||||||
err = beegoInput.Bind(&isok, "isok")
|
{"/?p=str", []testItem{{"p", "", "str"}}},
|
||||||
if !isok || err != nil {
|
|
||||||
t.Fatal("isok should be true")
|
|
||||||
}
|
|
||||||
fmt.Println(isok)
|
|
||||||
|
|
||||||
var float float64
|
{"/?p=123", []testItem{{"p", 0, 123}}},
|
||||||
err = beegoInput.Bind(&float, "ft")
|
{"/?p=123", []testItem{{"p", uint(0), uint(123)}}},
|
||||||
if float != 1.2 || err != nil {
|
|
||||||
t.Fatal("float should be equal to 1.2")
|
|
||||||
}
|
|
||||||
fmt.Println(float)
|
|
||||||
|
|
||||||
ol := make([]int, 0, 2)
|
{"/?p=1.0", []testItem{{"p", 0.0, 1.0}}},
|
||||||
err = beegoInput.Bind(&ol, "ol")
|
{"/?p=1", []testItem{{"p", false, true}}},
|
||||||
if len(ol) != 2 || err != nil || ol[0] != 1 || ol[1] != 2 {
|
|
||||||
t.Fatal("ol should has two elements")
|
|
||||||
}
|
|
||||||
fmt.Println(ol)
|
|
||||||
|
|
||||||
ul := make([]string, 0, 2)
|
{"/?p=true", []testItem{{"p", false, true}}},
|
||||||
err = beegoInput.Bind(&ul, "ul")
|
{"/?p=ON", []testItem{{"p", false, true}}},
|
||||||
if len(ul) != 2 || err != nil || ul[0] != "str" || ul[1] != "array" {
|
{"/?p=on", []testItem{{"p", false, true}}},
|
||||||
t.Fatal("ul should has two elements")
|
{"/?p=1", []testItem{{"p", false, true}}},
|
||||||
}
|
{"/?p=2", []testItem{{"p", false, false}}},
|
||||||
fmt.Println(ul)
|
{"/?p=false", []testItem{{"p", false, false}}},
|
||||||
|
|
||||||
type User struct {
|
{"/?p[a]=1&p[b]=2&p[c]=3", []testItem{{"p", map[string]int{}, map[string]int{"a": 1, "b": 2, "c": 3}}}},
|
||||||
Name string
|
{"/?p[a]=v1&p[b]=v2&p[c]=v3", []testItem{{"p", map[string]string{}, map[string]string{"a": "v1", "b": "v2", "c": "v3"}}}},
|
||||||
}
|
|
||||||
user := User{}
|
|
||||||
err = beegoInput.Bind(&user, "user")
|
|
||||||
if err != nil || user.Name != "astaxie" {
|
|
||||||
t.Fatal("user should has name")
|
|
||||||
}
|
|
||||||
fmt.Println(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse2(t *testing.T) {
|
{"/?p[]=8&p[]=9&p[]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
|
||||||
r, _ := http.NewRequest("GET", "/?user[0][Username]=Raph&user[1].Username=Leo&user[0].Password=123456&user[1][Password]=654321", nil)
|
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
|
||||||
beegoInput := NewInput()
|
{"/?p[0]=8&p[1]=9&p[2]=10&p[5]=14", []testItem{{"p", []int{}, []int{8, 9, 10, 0, 0, 14}}}},
|
||||||
beegoInput.Context = NewContext()
|
{"/?p[0]=8.0&p[1]=9.0&p[2]=10.0", []testItem{{"p", []float64{}, []float64{8.0, 9.0, 10.0}}}},
|
||||||
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
|
||||||
beegoInput.ParseFormOrMulitForm(1 << 20)
|
{"/?p[]=10&p[]=9&p[]=8", []testItem{{"p", []string{}, []string{"10", "9", "8"}}}},
|
||||||
type User struct {
|
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []string{}, []string{"8", "9", "10"}}}},
|
||||||
Username string
|
|
||||||
Password string
|
{"/?p[0]=true&p[1]=false&p[2]=true&p[5]=1&p[6]=ON&p[7]=other", []testItem{{"p", []bool{}, []bool{true, false, true, false, false, true, true, false}}}},
|
||||||
|
|
||||||
|
{"/?human.Nick=astaxie", []testItem{{"human", Human{}, Human{Nick: "astaxie"}}}},
|
||||||
|
{"/?human.ID=888&human.Nick=astaxie&human.Ms=true&human[Pwd]=pass", []testItem{{"human", Human{}, Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass"}}}},
|
||||||
|
{"/?human[0].ID=888&human[0].Nick=astaxie&human[0].Ms=true&human[0][Pwd]=pass01&human[1].ID=999&human[1].Nick=ysqi&human[1].Ms=On&human[1].Pwd=pass02",
|
||||||
|
[]testItem{{"human", []Human{}, []Human{
|
||||||
|
{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass01"},
|
||||||
|
{ID: 999, Nick: "ysqi", Ms: true, Pwd: "pass02"},
|
||||||
|
}}}},
|
||||||
|
|
||||||
|
{
|
||||||
|
"/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&human.Nick=astaxie",
|
||||||
|
[]testItem{
|
||||||
|
{"id", 0, 123},
|
||||||
|
{"isok", false, true},
|
||||||
|
{"ft", 0.0, 1.2},
|
||||||
|
{"ol", []int{}, []int{1, 2}},
|
||||||
|
{"ul", []string{}, []string{"str", "array"}},
|
||||||
|
{"human", Human{}, Human{Nick: "astaxie"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
var users []User
|
for _, c := range cases {
|
||||||
err := beegoInput.Bind(&users, "user")
|
r, _ := http.NewRequest("GET", c.request, nil)
|
||||||
fmt.Println(users)
|
beegoInput := NewInput()
|
||||||
if err != nil || users[0].Username != "Raph" || users[0].Password != "123456" || users[1].Username != "Leo" || users[1].Password != "654321" {
|
beegoInput.Context = NewContext()
|
||||||
t.Fatal("users info wrong")
|
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
||||||
|
|
||||||
|
for _, item := range c.valueGp {
|
||||||
|
got := item.empty
|
||||||
|
err := beegoInput.Bind(&got, item.field)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, item.want) {
|
||||||
|
t.Fatalf("Bind %q error,should be:\n%#v \ngot:\n%#v", item.field, item.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoOutput does work for sending response header.
|
// BeegoOutput does work for sending response header.
|
||||||
@ -67,6 +68,7 @@ func (output *BeegoOutput) Body(content []byte) error {
|
|||||||
}
|
}
|
||||||
if b, n, _ := WriteBody(encoding, buf, content); b {
|
if b, n, _ := WriteBody(encoding, buf, content); b {
|
||||||
output.Header("Content-Encoding", n)
|
output.Header("Content-Encoding", n)
|
||||||
|
output.Header("Content-Length", strconv.Itoa(buf.Len()))
|
||||||
} else {
|
} else {
|
||||||
output.Header("Content-Length", strconv.Itoa(len(content)))
|
output.Header("Content-Length", strconv.Itoa(len(content)))
|
||||||
}
|
}
|
||||||
@ -167,9 +169,22 @@ func sanitizeValue(v string) string {
|
|||||||
return cookieValueSanitizer.Replace(v)
|
return cookieValueSanitizer.Replace(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jsonRenderer(value interface{}) Renderer {
|
||||||
|
return rendererFunc(func(ctx *Context) {
|
||||||
|
ctx.Output.JSON(value, false, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorRenderer(err error) Renderer {
|
||||||
|
return rendererFunc(func(ctx *Context) {
|
||||||
|
ctx.Output.SetStatus(500)
|
||||||
|
ctx.Output.Body([]byte(err.Error()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// JSON writes json to response body.
|
// JSON writes json to response body.
|
||||||
// if coding is true, it converts utf-8 to \u0000 type.
|
// if encoding is true, it converts utf-8 to \u0000 type.
|
||||||
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
|
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
|
||||||
output.Header("Content-Type", "application/json; charset=utf-8")
|
output.Header("Content-Type", "application/json; charset=utf-8")
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
@ -182,12 +197,26 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
|
|||||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if coding {
|
if encoding {
|
||||||
content = []byte(stringsToJSON(string(content)))
|
content = []byte(stringsToJSON(string(content)))
|
||||||
}
|
}
|
||||||
return output.Body(content)
|
return output.Body(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// YAML writes yaml to response body.
|
||||||
|
func (output *BeegoOutput) YAML(data interface{}) error {
|
||||||
|
output.Header("Content-Type", "application/application/x-yaml; charset=utf-8")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
content, err = yaml.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return output.Body(content)
|
||||||
|
}
|
||||||
|
|
||||||
// JSONP writes jsonp to response body.
|
// JSONP writes jsonp to response body.
|
||||||
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||||
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||||
@ -246,7 +275,7 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
|
|||||||
} else {
|
} else {
|
||||||
fName = filepath.Base(file)
|
fName = filepath.Base(file)
|
||||||
}
|
}
|
||||||
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName))
|
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
|
||||||
output.Header("Content-Description", "File Transfer")
|
output.Header("Content-Description", "File Transfer")
|
||||||
output.Header("Content-Type", "application/octet-stream")
|
output.Header("Content-Type", "application/octet-stream")
|
||||||
output.Header("Content-Transfer-Encoding", "binary")
|
output.Header("Content-Transfer-Encoding", "binary")
|
||||||
@ -311,13 +340,13 @@ func (output *BeegoOutput) IsForbidden() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsNotFound returns boolean of this request is not found.
|
// IsNotFound returns boolean of this request is not found.
|
||||||
// HTTP 404 means forbidden.
|
// HTTP 404 means not found.
|
||||||
func (output *BeegoOutput) IsNotFound() bool {
|
func (output *BeegoOutput) IsNotFound() bool {
|
||||||
return output.Status == 404
|
return output.Status == 404
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsClientError returns boolean of this request client sends error data.
|
// IsClientError returns boolean of this request client sends error data.
|
||||||
// HTTP 4xx means forbidden.
|
// HTTP 4xx means client error.
|
||||||
func (output *BeegoOutput) IsClientError() bool {
|
func (output *BeegoOutput) IsClientError() bool {
|
||||||
return output.Status >= 400 && output.Status < 500
|
return output.Status >= 400 && output.Status < 500
|
||||||
}
|
}
|
||||||
@ -329,17 +358,22 @@ func (output *BeegoOutput) IsServerError() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stringsToJSON(str string) string {
|
func stringsToJSON(str string) string {
|
||||||
rs := []rune(str)
|
var jsons bytes.Buffer
|
||||||
jsons := ""
|
for _, r := range str {
|
||||||
for _, r := range rs {
|
|
||||||
rint := int(r)
|
rint := int(r)
|
||||||
if rint < 128 {
|
if rint < 128 {
|
||||||
jsons += string(r)
|
jsons.WriteRune(r)
|
||||||
} else {
|
} else {
|
||||||
jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
|
jsons.WriteString("\\u")
|
||||||
|
if rint < 0x100 {
|
||||||
|
jsons.WriteString("00")
|
||||||
|
} else if rint < 0x1000 {
|
||||||
|
jsons.WriteString("0")
|
||||||
|
}
|
||||||
|
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsons
|
return jsons.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session sets session item value with given key.
|
// Session sets session item value with given key.
|
||||||
|
78
context/param/conv.go
Normal file
78
context/param/conv.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
beecontext "github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertParams converts http method params to values that will be passed to the method controller as arguments
|
||||||
|
func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) {
|
||||||
|
result = make([]reflect.Value, 0, len(methodParams))
|
||||||
|
for i := 0; i < len(methodParams); i++ {
|
||||||
|
reflectValue := convertParam(methodParams[i], methodType.In(i), ctx)
|
||||||
|
result = append(result, reflectValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) {
|
||||||
|
paramValue := getParamValue(param, ctx)
|
||||||
|
if paramValue == "" {
|
||||||
|
if param.required {
|
||||||
|
ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name))
|
||||||
|
} else {
|
||||||
|
paramValue = param.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectValue, err := parseValue(param, paramValue, paramType)
|
||||||
|
if err != nil {
|
||||||
|
logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err))
|
||||||
|
ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParamValue(param *MethodParam, ctx *beecontext.Context) string {
|
||||||
|
switch param.in {
|
||||||
|
case body:
|
||||||
|
return string(ctx.Input.RequestBody)
|
||||||
|
case header:
|
||||||
|
return ctx.Input.Header(param.name)
|
||||||
|
case path:
|
||||||
|
return ctx.Input.Query(":" + param.name)
|
||||||
|
default:
|
||||||
|
return ctx.Input.Query(param.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) {
|
||||||
|
if paramValue == "" {
|
||||||
|
return reflect.Zero(paramType), nil
|
||||||
|
}
|
||||||
|
parser := getParser(param, paramType)
|
||||||
|
value, err := parser.parse(paramValue, paramType)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeConvert(reflect.ValueOf(value), paramType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
err, ok = r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result = value.Convert(t)
|
||||||
|
return
|
||||||
|
}
|
69
context/param/methodparams.go
Normal file
69
context/param/methodparams.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//MethodParam keeps param information to be auto passed to controller methods
|
||||||
|
type MethodParam struct {
|
||||||
|
name string
|
||||||
|
in paramType
|
||||||
|
required bool
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
param paramType = iota
|
||||||
|
path
|
||||||
|
body
|
||||||
|
header
|
||||||
|
)
|
||||||
|
|
||||||
|
//New creates a new MethodParam with name and specific options
|
||||||
|
func New(name string, opts ...MethodParamOption) *MethodParam {
|
||||||
|
return newParam(name, nil, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) {
|
||||||
|
param = &MethodParam{name: name}
|
||||||
|
for _, option := range opts {
|
||||||
|
option(param)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make creates an array of MethodParmas or an empty array
|
||||||
|
func Make(list ...*MethodParam) []*MethodParam {
|
||||||
|
if len(list) > 0 {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *MethodParam) String() string {
|
||||||
|
options := []string{}
|
||||||
|
result := "param.New(\"" + mp.name + "\""
|
||||||
|
if mp.required {
|
||||||
|
options = append(options, "param.IsRequired")
|
||||||
|
}
|
||||||
|
switch mp.in {
|
||||||
|
case path:
|
||||||
|
options = append(options, "param.InPath")
|
||||||
|
case body:
|
||||||
|
options = append(options, "param.InBody")
|
||||||
|
case header:
|
||||||
|
options = append(options, "param.InHeader")
|
||||||
|
}
|
||||||
|
if mp.defaultValue != "" {
|
||||||
|
options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue))
|
||||||
|
}
|
||||||
|
if len(options) > 0 {
|
||||||
|
result += ", "
|
||||||
|
}
|
||||||
|
result += strings.Join(options, ", ")
|
||||||
|
result += ")"
|
||||||
|
return result
|
||||||
|
}
|
37
context/param/options.go
Normal file
37
context/param/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MethodParamOption defines a func which apply options on a MethodParam
|
||||||
|
type MethodParamOption func(*MethodParam)
|
||||||
|
|
||||||
|
// IsRequired indicates that this param is required and can not be omitted from the http request
|
||||||
|
var IsRequired MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.required = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// InHeader indicates that this param is passed via an http header
|
||||||
|
var InHeader MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = header
|
||||||
|
}
|
||||||
|
|
||||||
|
// InPath indicates that this param is part of the URL path
|
||||||
|
var InPath MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// InBody indicates that this param is passed as an http request body
|
||||||
|
var InBody MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default provides a default value for the http param
|
||||||
|
func Default(defaultValue interface{}) MethodParamOption {
|
||||||
|
return func(p *MethodParam) {
|
||||||
|
if defaultValue != nil {
|
||||||
|
p.defaultValue = fmt.Sprint(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
context/param/parsers.go
Normal file
149
context/param/parsers.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type paramParser interface {
|
||||||
|
parse(value string, toType reflect.Type) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParser(param *MethodParam, t reflect.Type) paramParser {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return intParser{}
|
||||||
|
case reflect.Slice:
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string
|
||||||
|
return stringParser{}
|
||||||
|
}
|
||||||
|
if param.in == body {
|
||||||
|
return jsonParser{}
|
||||||
|
}
|
||||||
|
elemParser := getParser(param, t.Elem())
|
||||||
|
if elemParser == (jsonParser{}) {
|
||||||
|
return elemParser
|
||||||
|
}
|
||||||
|
return sliceParser(elemParser)
|
||||||
|
case reflect.Bool:
|
||||||
|
return boolParser{}
|
||||||
|
case reflect.String:
|
||||||
|
return stringParser{}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return floatParser{}
|
||||||
|
case reflect.Ptr:
|
||||||
|
elemParser := getParser(param, t.Elem())
|
||||||
|
if elemParser == (jsonParser{}) {
|
||||||
|
return elemParser
|
||||||
|
}
|
||||||
|
return ptrParser(elemParser)
|
||||||
|
default:
|
||||||
|
if t.PkgPath() == "time" && t.Name() == "Time" {
|
||||||
|
return timeParser{}
|
||||||
|
}
|
||||||
|
return jsonParser{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type parserFunc func(value string, toType reflect.Type) (interface{}, error)
|
||||||
|
|
||||||
|
func (f parserFunc) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return f(value, toType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p boolParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p stringParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type intParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p intParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.Atoi(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type floatParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
if toType.Kind() == reflect.Float32 {
|
||||||
|
res, err := strconv.ParseFloat(value, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return float32(res), nil
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(value, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p timeParser) parse(value string, toType reflect.Type) (result interface{}, err error) {
|
||||||
|
result, err = time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
result, err = time.Parse("2006-01-02", value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p jsonParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
pResult := reflect.New(toType)
|
||||||
|
v := pResult.Interface()
|
||||||
|
err := json.Unmarshal([]byte(value), v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pResult.Elem().Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceParser(elemParser paramParser) paramParser {
|
||||||
|
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
values := strings.Split(value, ",")
|
||||||
|
result := reflect.MakeSlice(toType, 0, len(values))
|
||||||
|
elemType := toType.Elem()
|
||||||
|
for _, v := range values {
|
||||||
|
parsedValue, err := elemParser.parse(v, elemType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = reflect.Append(result, reflect.ValueOf(parsedValue))
|
||||||
|
}
|
||||||
|
return result.Interface(), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrParser(elemParser paramParser) paramParser {
|
||||||
|
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
parsedValue, err := elemParser.parse(value, toType.Elem())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newValPtr := reflect.New(toType.Elem())
|
||||||
|
newVal := reflect.Indirect(newValPtr)
|
||||||
|
convertedVal, err := safeConvert(reflect.ValueOf(parsedValue), toType.Elem())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal.Set(convertedVal)
|
||||||
|
return newValPtr.Interface(), nil
|
||||||
|
})
|
||||||
|
}
|
84
context/param/parsers_test.go
Normal file
84
context/param/parsers_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
import "reflect"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type testDefinition struct {
|
||||||
|
strValue string
|
||||||
|
expectedValue interface{}
|
||||||
|
expectedParser paramParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Parsers(t *testing.T) {
|
||||||
|
|
||||||
|
//ints
|
||||||
|
checkParser(testDefinition{"1", 1, intParser{}}, t)
|
||||||
|
checkParser(testDefinition{"-1", int64(-1), intParser{}}, t)
|
||||||
|
checkParser(testDefinition{"1", uint64(1), intParser{}}, t)
|
||||||
|
|
||||||
|
//floats
|
||||||
|
checkParser(testDefinition{"1.0", float32(1.0), floatParser{}}, t)
|
||||||
|
checkParser(testDefinition{"-1.0", float64(-1.0), floatParser{}}, t)
|
||||||
|
|
||||||
|
//strings
|
||||||
|
checkParser(testDefinition{"AB", "AB", stringParser{}}, t)
|
||||||
|
checkParser(testDefinition{"AB", []byte{65, 66}, stringParser{}}, t)
|
||||||
|
|
||||||
|
//bools
|
||||||
|
checkParser(testDefinition{"true", true, boolParser{}}, t)
|
||||||
|
checkParser(testDefinition{"0", false, boolParser{}}, t)
|
||||||
|
|
||||||
|
//timeParser
|
||||||
|
checkParser(testDefinition{"2017-05-30T13:54:53Z", time.Date(2017, 5, 30, 13, 54, 53, 0, time.UTC), timeParser{}}, t)
|
||||||
|
checkParser(testDefinition{"2017-05-30", time.Date(2017, 5, 30, 0, 0, 0, 0, time.UTC), timeParser{}}, t)
|
||||||
|
|
||||||
|
//json
|
||||||
|
checkParser(testDefinition{`{"X": 5, "Y":"Z"}`, struct {
|
||||||
|
X int
|
||||||
|
Y string
|
||||||
|
}{5, "Z"}, jsonParser{}}, t)
|
||||||
|
|
||||||
|
//slice in query is parsed as comma delimited
|
||||||
|
checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t)
|
||||||
|
|
||||||
|
//slice in body is parsed as json
|
||||||
|
checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{in: body})
|
||||||
|
|
||||||
|
//pointers
|
||||||
|
var someInt = 1
|
||||||
|
checkParser(testDefinition{`1`, &someInt, ptrParser(intParser{})}, t)
|
||||||
|
|
||||||
|
var someStruct = struct{ X int }{5}
|
||||||
|
checkParser(testDefinition{`{"X": 5}`, &someStruct, jsonParser{}}, t)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) {
|
||||||
|
toType := reflect.TypeOf(def.expectedValue)
|
||||||
|
var mp MethodParam
|
||||||
|
if len(methodParam) == 0 {
|
||||||
|
mp = MethodParam{}
|
||||||
|
} else {
|
||||||
|
mp = methodParam[0]
|
||||||
|
}
|
||||||
|
parser := getParser(&mp, toType)
|
||||||
|
|
||||||
|
if reflect.TypeOf(parser) != reflect.TypeOf(def.expectedParser) {
|
||||||
|
t.Errorf("Invalid parser for value %v. Expected: %v, actual: %v", def.strValue, reflect.TypeOf(def.expectedParser).Name(), reflect.TypeOf(parser).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := parser.parse(def.strValue, toType)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Parsing error for value %v. Expected result: %v, error: %v", def.strValue, def.expectedValue, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
convResult, err := safeConvert(reflect.ValueOf(result), toType)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Conversion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) {
|
||||||
|
t.Errorf("Parsing error for value %v. Expected result: %v, actual: %v", def.strValue, def.expectedValue, result)
|
||||||
|
}
|
||||||
|
}
|
12
context/renderer.go
Normal file
12
context/renderer.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
// Renderer defines an http response renderer
|
||||||
|
type Renderer interface {
|
||||||
|
Render(ctx *Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rendererFunc func(ctx *Context)
|
||||||
|
|
||||||
|
func (f rendererFunc) Render(ctx *Context) {
|
||||||
|
f(ctx)
|
||||||
|
}
|
27
context/response.go
Normal file
27
context/response.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//BadRequest indicates http error 400
|
||||||
|
BadRequest StatusCode = http.StatusBadRequest
|
||||||
|
|
||||||
|
//NotFound indicates http error 404
|
||||||
|
NotFound StatusCode = http.StatusNotFound
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusCode sets the http response status code
|
||||||
|
type StatusCode int
|
||||||
|
|
||||||
|
func (s StatusCode) Error() string {
|
||||||
|
return strconv.Itoa(int(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sets the http status code
|
||||||
|
func (s StatusCode) Render(ctx *Context) {
|
||||||
|
ctx.Output.SetStatus(int(s))
|
||||||
|
}
|
@ -28,6 +28,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
applicationJSON = "application/json"
|
applicationJSON = "application/json"
|
||||||
applicationXML = "application/xml"
|
applicationXML = "application/xml"
|
||||||
|
applicationYAML = "application/x-yaml"
|
||||||
textXML = "text/xml"
|
textXML = "text/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,8 +53,16 @@ type ControllerComments struct {
|
|||||||
Router string
|
Router string
|
||||||
AllowHTTPMethods []string
|
AllowHTTPMethods []string
|
||||||
Params []map[string]string
|
Params []map[string]string
|
||||||
|
MethodParams []*param.MethodParam
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ControllerCommentsSlice implements the sort interface
|
||||||
|
type ControllerCommentsSlice []ControllerComments
|
||||||
|
|
||||||
|
func (p ControllerCommentsSlice) Len() int { return len(p) }
|
||||||
|
func (p ControllerCommentsSlice) Less(i, j int) bool { return p[i].Router < p[j].Router }
|
||||||
|
func (p ControllerCommentsSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
// Controller defines some basic http request handler operations, such as
|
// Controller defines some basic http request handler operations, such as
|
||||||
// http context, template and view, session and xsrf.
|
// http context, template and view, session and xsrf.
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@ -69,6 +79,7 @@ type Controller struct {
|
|||||||
|
|
||||||
// template data
|
// template data
|
||||||
TplName string
|
TplName string
|
||||||
|
ViewPath string
|
||||||
Layout string
|
Layout string
|
||||||
LayoutSections map[string]string // the key is the section name and the value is the template name
|
LayoutSections map[string]string // the key is the section name and the value is the template name
|
||||||
TplPrefix string
|
TplPrefix string
|
||||||
@ -185,7 +196,11 @@ func (c *Controller) Render() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
|
if c.Ctx.ResponseWriter.Header().Get("Content-Type") == "" {
|
||||||
|
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
return c.Ctx.Output.Body(rb)
|
return c.Ctx.Output.Body(rb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +224,7 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
err = ExecuteTemplate(&buf, sectionTpl, c.Data)
|
err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -218,7 +233,7 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
ExecuteTemplate(&buf, c.Layout, c.Data)
|
ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath(), c.Data)
|
||||||
}
|
}
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
@ -244,14 +259,36 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
|
BuildTemplate(c.viewPath(), buildFiles...)
|
||||||
}
|
}
|
||||||
return buf, ExecuteTemplate(&buf, c.TplName, c.Data)
|
return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) viewPath() string {
|
||||||
|
if c.ViewPath == "" {
|
||||||
|
return BConfig.WebConfig.ViewsPath
|
||||||
|
}
|
||||||
|
return c.ViewPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect sends the redirection response to url with status code.
|
// Redirect sends the redirection response to url with status code.
|
||||||
func (c *Controller) Redirect(url string, code int) {
|
func (c *Controller) Redirect(url string, code int) {
|
||||||
|
logAccess(c.Ctx, nil, code)
|
||||||
c.Ctx.Redirect(code, url)
|
c.Ctx.Redirect(code, url)
|
||||||
|
panic(ErrAbort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the data depending on the accepted
|
||||||
|
func (c *Controller) SetData(data interface{}) {
|
||||||
|
accept := c.Ctx.Input.Header("Accept")
|
||||||
|
switch accept {
|
||||||
|
case applicationJSON:
|
||||||
|
c.Data["json"] = data
|
||||||
|
case applicationXML, textXML:
|
||||||
|
c.Data["xml"] = data
|
||||||
|
default:
|
||||||
|
c.Data["json"] = data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
||||||
@ -302,7 +339,7 @@ func (c *Controller) ServeJSON(encoding ...bool) {
|
|||||||
if BConfig.RunMode == PROD {
|
if BConfig.RunMode == PROD {
|
||||||
hasIndent = false
|
hasIndent = false
|
||||||
}
|
}
|
||||||
if len(encoding) > 0 && encoding[0] == true {
|
if len(encoding) > 0 && encoding[0] {
|
||||||
hasEncoding = true
|
hasEncoding = true
|
||||||
}
|
}
|
||||||
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
|
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
|
||||||
@ -326,6 +363,11 @@ func (c *Controller) ServeXML() {
|
|||||||
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeXML sends xml response.
|
||||||
|
func (c *Controller) ServeYAML() {
|
||||||
|
c.Ctx.Output.YAML(c.Data["yaml"])
|
||||||
|
}
|
||||||
|
|
||||||
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
|
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
|
||||||
func (c *Controller) ServeFormatted() {
|
func (c *Controller) ServeFormatted() {
|
||||||
accept := c.Ctx.Input.Header("Accept")
|
accept := c.Ctx.Input.Header("Accept")
|
||||||
@ -334,6 +376,8 @@ func (c *Controller) ServeFormatted() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
case applicationXML, textXML:
|
case applicationXML, textXML:
|
||||||
c.ServeXML()
|
c.ServeXML()
|
||||||
|
case applicationYAML:
|
||||||
|
c.ServeYAML()
|
||||||
default:
|
default:
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@ -399,6 +443,16 @@ func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
|
|||||||
return int8(i64), err
|
return int8(i64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint8 return input as an uint8 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 8)
|
||||||
|
return uint8(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
|
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
|
||||||
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
|
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
|
||||||
strv := c.Ctx.Input.Query(key)
|
strv := c.Ctx.Input.Query(key)
|
||||||
@ -409,6 +463,16 @@ func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
|
|||||||
return int16(i64), err
|
return int16(i64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint16 returns input as an uint16 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 16)
|
||||||
|
return uint16(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
|
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
|
||||||
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
|
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
|
||||||
strv := c.Ctx.Input.Query(key)
|
strv := c.Ctx.Input.Query(key)
|
||||||
@ -419,6 +483,16 @@ func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
|
|||||||
return int32(i64), err
|
return int32(i64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint32 returns input as an uint32 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 32)
|
||||||
|
return uint32(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
|
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
|
||||||
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
|
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
|
||||||
strv := c.Ctx.Input.Query(key)
|
strv := c.Ctx.Input.Query(key)
|
||||||
@ -428,6 +502,15 @@ func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
|
|||||||
return strconv.ParseInt(strv, 10, 64)
|
return strconv.ParseInt(strv, 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint64 returns input value as uint64 or the default value while it's present and input is blank.
|
||||||
|
func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.ParseUint(strv, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBool returns input value as bool or the default value while it's present and input is blank.
|
// GetBool returns input value as bool or the default value while it's present and input is blank.
|
||||||
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
|
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
|
||||||
strv := c.Ctx.Input.Query(key)
|
strv := c.Ctx.Input.Query(key)
|
||||||
@ -453,7 +536,7 @@ func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFiles return multi-upload files
|
// GetFiles return multi-upload files
|
||||||
// files, err:=c.Getfiles("myfiles")
|
// files, err:=c.GetFiles("myfiles")
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// http.Error(w, err.Error(), http.StatusNoContent)
|
// http.Error(w, err.Error(), http.StatusNoContent)
|
||||||
// return
|
// return
|
||||||
|
@ -15,9 +15,13 @@
|
|||||||
package beego
|
package beego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetInt(t *testing.T) {
|
func TestGetInt(t *testing.T) {
|
||||||
@ -75,3 +79,103 @@ func TestGetInt64(t *testing.T) {
|
|||||||
t.Errorf("TestGeetInt64 expect 40,get %T,%v", val, val)
|
t.Errorf("TestGeetInt64 expect 40,get %T,%v", val, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUint8(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint8, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint8("age")
|
||||||
|
if val != math.MaxUint8 {
|
||||||
|
t.Errorf("TestGetUint8 expect %v,get %T,%v", math.MaxUint8, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint16(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint16, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint16("age")
|
||||||
|
if val != math.MaxUint16 {
|
||||||
|
t.Errorf("TestGetUint16 expect %v,get %T,%v", math.MaxUint16, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint32(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint32, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint32("age")
|
||||||
|
if val != math.MaxUint32 {
|
||||||
|
t.Errorf("TestGetUint32 expect %v,get %T,%v", math.MaxUint32, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint64(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint64, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint64("age")
|
||||||
|
if val != math.MaxUint64 {
|
||||||
|
t.Errorf("TestGetUint64 expect %v,get %T,%v", uint64(math.MaxUint64), val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdditionalViewPaths(t *testing.T) {
|
||||||
|
dir1 := "_beeTmp"
|
||||||
|
dir2 := "_beeTmp2"
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer os.RemoveAll(dir2)
|
||||||
|
|
||||||
|
dir1file := "file1.tpl"
|
||||||
|
dir2file := "file2.tpl"
|
||||||
|
|
||||||
|
genFile := func(dir string, name string, content string) {
|
||||||
|
os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777)
|
||||||
|
if f, err := os.Create(filepath.Join(dir, name)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString(content)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
genFile(dir1, dir1file, `<div>{{.Content}}</div>`)
|
||||||
|
genFile(dir2, dir2file, `<html>{{.Content}}</html>`)
|
||||||
|
|
||||||
|
AddViewPath(dir1)
|
||||||
|
AddViewPath(dir2)
|
||||||
|
|
||||||
|
ctrl := Controller{
|
||||||
|
TplName: "file1.tpl",
|
||||||
|
ViewPath: dir1,
|
||||||
|
}
|
||||||
|
ctrl.Data = map[interface{}]interface{}{
|
||||||
|
"Content": "value2",
|
||||||
|
}
|
||||||
|
if result, err := ctrl.RenderString(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
if result != "<div>value2</div>" {
|
||||||
|
t.Fatalf("TestAdditionalViewPaths expect %s got %s", "<div>value2</div>", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
ctrl.TplName = "file2.tpl"
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Fatal("TestAdditionalViewPaths expected error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ctrl.RenderString()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctrl.TplName = "file2.tpl"
|
||||||
|
ctrl.ViewPath = dir2
|
||||||
|
ctrl.RenderString()
|
||||||
|
}
|
||||||
|
30
error.go
30
error.go
@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errorTypeHandler = iota
|
errorTypeHandler = iota
|
||||||
errorTypeController
|
errorTypeController
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,7 +93,6 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
|
|||||||
"BeegoVersion": VERSION,
|
"BeegoVersion": VERSION,
|
||||||
"GoVersion": runtime.Version(),
|
"GoVersion": runtime.Version(),
|
||||||
}
|
}
|
||||||
ctx.ResponseWriter.WriteHeader(500)
|
|
||||||
t.Execute(ctx.ResponseWriter, data)
|
t.Execute(ctx.ResponseWriter, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +247,30 @@ func forbidden(rw http.ResponseWriter, r *http.Request) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show 422 missing xsrf token
|
||||||
|
func missingxsrf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
422,
|
||||||
|
"<br>The page you have requested is forbidden."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>'_xsrf' argument missing from POST"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 417 invalid xsrf token
|
||||||
|
func invalidxsrf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
417,
|
||||||
|
"<br>The page you have requested is forbidden."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>expected XSRF not found"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// show 404 not found error.
|
// show 404 not found error.
|
||||||
func notFound(rw http.ResponseWriter, r *http.Request) {
|
func notFound(rw http.ResponseWriter, r *http.Request) {
|
||||||
responseError(rw, r,
|
responseError(rw, r,
|
||||||
@ -411,6 +434,9 @@ func exception(errCode string, ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||||
|
//make sure to log the error in the access log
|
||||||
|
logAccess(ctx, nil, code)
|
||||||
|
|
||||||
if err.errorType == errorTypeHandler {
|
if err.errorType == errorTypeHandler {
|
||||||
ctx.ResponseWriter.WriteHeader(code)
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
err.handler(ctx.ResponseWriter, ctx.Request)
|
err.handler(ctx.ResponseWriter, ctx.Request)
|
||||||
|
@ -52,7 +52,7 @@ func TestErrorCode_01(t *testing.T) {
|
|||||||
if w.Code != code {
|
if w.Code != code {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(w.Body.Bytes()), http.StatusText(code)) {
|
if !strings.Contains(w.Body.String(), http.StatusText(code)) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ func TestErrorCode_03(t *testing.T) {
|
|||||||
if w.Code != 200 {
|
if w.Code != 200 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if string(w.Body.Bytes()) != parseCodeError {
|
if w.Body.String() != parseCodeError {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func TestFlashHeader(t *testing.T) {
|
|||||||
// match for the expected header
|
// match for the expected header
|
||||||
res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00")
|
res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00")
|
||||||
// validate the assertion
|
// validate the assertion
|
||||||
if res != true {
|
if !res {
|
||||||
t.Errorf("TestFlashHeader() unable to validate flash message")
|
t.Errorf("TestFlashHeader() unable to validate flash message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,17 @@ package grace
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type graceConn struct {
|
type graceConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
server *Server
|
server *Server
|
||||||
|
m sync.Mutex
|
||||||
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c graceConn) Close() (err error) {
|
func (c *graceConn) Close() (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
switch x := r.(type) {
|
switch x := r.(type) {
|
||||||
@ -23,6 +26,14 @@ func (c graceConn) Close() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
c.m.Lock()
|
||||||
|
if c.closed {
|
||||||
|
c.m.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
c.server.wg.Done()
|
c.server.wg.Done()
|
||||||
|
c.closed = true
|
||||||
|
c.m.Unlock()
|
||||||
return c.Conn.Close()
|
return c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -85,23 +85,31 @@ var (
|
|||||||
|
|
||||||
isChild bool
|
isChild bool
|
||||||
socketOrder string
|
socketOrder string
|
||||||
once sync.Once
|
|
||||||
|
hookableSignals []os.Signal
|
||||||
)
|
)
|
||||||
|
|
||||||
func onceInit() {
|
func init() {
|
||||||
regLock = &sync.Mutex{}
|
|
||||||
flag.BoolVar(&isChild, "graceful", false, "listen on open fd (after forking)")
|
flag.BoolVar(&isChild, "graceful", false, "listen on open fd (after forking)")
|
||||||
flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
|
flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
|
||||||
|
|
||||||
|
regLock = &sync.Mutex{}
|
||||||
runningServers = make(map[string]*Server)
|
runningServers = make(map[string]*Server)
|
||||||
runningServersOrder = []string{}
|
runningServersOrder = []string{}
|
||||||
socketPtrOffsetMap = make(map[string]uint)
|
socketPtrOffsetMap = make(map[string]uint)
|
||||||
|
|
||||||
|
hookableSignals = []os.Signal{
|
||||||
|
syscall.SIGHUP,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns a new graceServer.
|
// NewServer returns a new graceServer.
|
||||||
func NewServer(addr string, handler http.Handler) (srv *Server) {
|
func NewServer(addr string, handler http.Handler) (srv *Server) {
|
||||||
once.Do(onceInit)
|
|
||||||
regLock.Lock()
|
regLock.Lock()
|
||||||
defer regLock.Unlock()
|
defer regLock.Unlock()
|
||||||
|
|
||||||
if !flag.Parsed() {
|
if !flag.Parsed() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ func newGraceListener(l net.Listener, srv *Server) (el *graceListener) {
|
|||||||
server: srv,
|
server: srv,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
_ = <-el.stop
|
<-el.stop
|
||||||
el.stopped = true
|
el.stopped = true
|
||||||
el.stop <- el.Listener.Close()
|
el.stop <- el.Listener.Close()
|
||||||
}()
|
}()
|
||||||
@ -37,7 +37,7 @@ func (gl *graceListener) Accept() (c net.Conn, err error) {
|
|||||||
tc.SetKeepAlive(true)
|
tc.SetKeepAlive(true)
|
||||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
|
||||||
c = graceConn{
|
c = &graceConn{
|
||||||
Conn: tc,
|
Conn: tc,
|
||||||
server: gl.server,
|
server: gl.server,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ package grace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -65,7 +67,7 @@ func (srv *Server) ListenAndServe() (err error) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = process.Kill()
|
err = process.Signal(syscall.SIGTERM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,6 +116,62 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
|||||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
|
return srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls
|
||||||
|
// Serve to handle requests on incoming mutual TLS connections.
|
||||||
|
func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.TLSConfig == nil {
|
||||||
|
srv.TLSConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
if srv.TLSConfig.NextProtos == nil {
|
||||||
|
srv.TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||||
|
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(trustFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
srv.TLSConfig.ClientCAs = pool
|
||||||
|
log.Println("Mutual HTTPS")
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
l, err := srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||||
|
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||||
|
|
||||||
if srv.isChild {
|
if srv.isChild {
|
||||||
process, err := os.FindProcess(os.Getppid())
|
process, err := os.FindProcess(os.Getppid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -162,9 +220,7 @@ func (srv *Server) handleSignals() {
|
|||||||
|
|
||||||
signal.Notify(
|
signal.Notify(
|
||||||
srv.sigChan,
|
srv.sigChan,
|
||||||
syscall.SIGHUP,
|
hookableSignals...,
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pid := syscall.Getpid()
|
pid := syscall.Getpid()
|
||||||
@ -198,7 +254,6 @@ func (srv *Server) signalHooks(ppFlag int, sig os.Signal) {
|
|||||||
for _, f := range srv.SignalHooks[ppFlag][sig] {
|
for _, f := range srv.SignalHooks[ppFlag][sig] {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdown closes the listener so that no new connections are accepted. it also
|
// shutdown closes the listener so that no new connections are accepted. it also
|
||||||
@ -290,3 +345,19 @@ func (srv *Server) fork() (err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal.
|
||||||
|
func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err error) {
|
||||||
|
if ppFlag != PreSignal && ppFlag != PostSignal {
|
||||||
|
err = fmt.Errorf("Invalid ppFlag argument. Must be either grace.PreSignal or grace.PostSignal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, s := range hookableSignals {
|
||||||
|
if s == sig {
|
||||||
|
srv.SignalHooks[ppFlag][sig] = append(srv.SignalHooks[ppFlag][sig], f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("Signal '%v' is not supported", sig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
12
hooks.go
12
hooks.go
@ -32,6 +32,8 @@ func registerDefaultErrorHandler() error {
|
|||||||
"502": badGateway,
|
"502": badGateway,
|
||||||
"503": serviceUnavailable,
|
"503": serviceUnavailable,
|
||||||
"504": gatewayTimeout,
|
"504": gatewayTimeout,
|
||||||
|
"417": invalidxsrf,
|
||||||
|
"422": missingxsrf,
|
||||||
}
|
}
|
||||||
for e, h := range m {
|
for e, h := range m {
|
||||||
if _, ok := ErrorMaps[e]; !ok {
|
if _, ok := ErrorMaps[e]; !ok {
|
||||||
@ -53,10 +55,11 @@ func registerSession() error {
|
|||||||
conf.Secure = BConfig.Listen.EnableHTTPS
|
conf.Secure = BConfig.Listen.EnableHTTPS
|
||||||
conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
|
conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||||
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
|
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
|
||||||
|
conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
conf.Domain = BConfig.WebConfig.Session.SessionDomain
|
conf.Domain = BConfig.WebConfig.Session.SessionDomain
|
||||||
conf.EnableSidInHttpHeader = BConfig.WebConfig.Session.EnableSidInHttpHeader
|
conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
|
||||||
conf.SessionNameInHttpHeader = BConfig.WebConfig.Session.SessionNameInHttpHeader
|
conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
|
||||||
conf.EnableSidInUrlQuery = BConfig.WebConfig.Session.EnableSidInUrlQuery
|
conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
|
||||||
} else {
|
} else {
|
||||||
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
|
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -71,7 +74,8 @@ func registerSession() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerTemplate() error {
|
func registerTemplate() error {
|
||||||
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
|
defer lockViewPaths()
|
||||||
|
if err := AddViewPath(BConfig.WebConfig.ViewsPath); err != nil {
|
||||||
if BConfig.RunMode == DEV {
|
if BConfig.RunMode == DEV {
|
||||||
logs.Warn(err)
|
logs.Warn(err)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ The default timeout is `60` seconds, function prototype:
|
|||||||
|
|
||||||
SetTimeout(connectTimeout, readWriteTimeout time.Duration)
|
SetTimeout(connectTimeout, readWriteTimeout time.Duration)
|
||||||
|
|
||||||
Exmaple:
|
Example:
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||||
|
@ -50,6 +50,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultSetting = BeegoHTTPSettings{
|
var defaultSetting = BeegoHTTPSettings{
|
||||||
@ -136,9 +137,11 @@ type BeegoHTTPSettings struct {
|
|||||||
TLSClientConfig *tls.Config
|
TLSClientConfig *tls.Config
|
||||||
Proxy func(*http.Request) (*url.URL, error)
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
|
CheckRedirect func(req *http.Request, via []*http.Request) error
|
||||||
EnableCookie bool
|
EnableCookie bool
|
||||||
Gzip bool
|
Gzip bool
|
||||||
DumpBody bool
|
DumpBody bool
|
||||||
|
Retries int // if set to -1 means will retry forever
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
|
// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
|
||||||
@ -188,6 +191,15 @@ func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retries sets Retries times.
|
||||||
|
// default is 0 means no retried.
|
||||||
|
// -1 means retried forever.
|
||||||
|
// others means retried times.
|
||||||
|
func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
|
||||||
|
b.setting.Retries = times
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// DumpBody setting whether need to Dump the Body.
|
// DumpBody setting whether need to Dump the Body.
|
||||||
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
|
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
|
||||||
b.setting.DumpBody = isdump
|
b.setting.DumpBody = isdump
|
||||||
@ -265,6 +277,15 @@ func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error))
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCheckRedirect specifies the policy for handling redirects.
|
||||||
|
//
|
||||||
|
// If CheckRedirect is nil, the Client uses its default policy,
|
||||||
|
// which is to stop after 10 consecutive requests.
|
||||||
|
func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest {
|
||||||
|
b.setting.CheckRedirect = redirect
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// Param adds query param in to request.
|
// Param adds query param in to request.
|
||||||
// params build query string as ?key1=value1&key2=value2...
|
// params build query string as ?key1=value1&key2=value2...
|
||||||
func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
|
func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
|
||||||
@ -298,6 +319,34 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XMLBody adds request raw body encoding by XML.
|
||||||
|
func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := xml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/xml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLBody adds request raw body encoding by YAML.
|
||||||
|
func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := yaml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/x+yaml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
// JSONBody adds request raw body encoding by JSON.
|
// JSONBody adds request raw body encoding by JSON.
|
||||||
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
if b.req.Body == nil && obj != nil {
|
if b.req.Body == nil && obj != nil {
|
||||||
@ -315,7 +364,7 @@ func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error)
|
|||||||
func (b *BeegoHTTPRequest) buildURL(paramBody string) {
|
func (b *BeegoHTTPRequest) buildURL(paramBody string) {
|
||||||
// build GET url with query string
|
// build GET url with query string
|
||||||
if b.req.Method == "GET" && len(paramBody) > 0 {
|
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||||
if strings.Index(b.url, "?") != -1 {
|
if strings.Contains(b.url, "?") {
|
||||||
b.url += "&" + paramBody
|
b.url += "&" + paramBody
|
||||||
} else {
|
} else {
|
||||||
b.url = b.url + "?" + paramBody
|
b.url = b.url + "?" + paramBody
|
||||||
@ -324,7 +373,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build POST/PUT/PATCH url and body
|
// build POST/PUT/PATCH url and body
|
||||||
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil {
|
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
|
||||||
// with files
|
// with files
|
||||||
if len(b.files) > 0 {
|
if len(b.files) > 0 {
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
@ -380,7 +429,7 @@ func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DoRequest will do the client.Do
|
// DoRequest will do the client.Do
|
||||||
func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
|
||||||
var paramBody string
|
var paramBody string
|
||||||
if len(b.params) > 0 {
|
if len(b.params) > 0 {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -397,12 +446,12 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.buildURL(paramBody)
|
b.buildURL(paramBody)
|
||||||
url, err := url.Parse(b.url)
|
urlParsed, err := url.Parse(b.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.req.URL = url
|
b.req.URL = urlParsed
|
||||||
|
|
||||||
trans := b.setting.Transport
|
trans := b.setting.Transport
|
||||||
|
|
||||||
@ -412,7 +461,7 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
|||||||
TLSClientConfig: b.setting.TLSClientConfig,
|
TLSClientConfig: b.setting.TLSClientConfig,
|
||||||
Proxy: b.setting.Proxy,
|
Proxy: b.setting.Proxy,
|
||||||
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
||||||
MaxIdleConnsPerHost: -1,
|
MaxIdleConnsPerHost: 100,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if b.transport is *http.Transport then set the settings.
|
// if b.transport is *http.Transport then set the settings.
|
||||||
@ -446,6 +495,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
|||||||
b.req.Header.Set("User-Agent", b.setting.UserAgent)
|
b.req.Header.Set("User-Agent", b.setting.UserAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.setting.CheckRedirect != nil {
|
||||||
|
client.CheckRedirect = b.setting.CheckRedirect
|
||||||
|
}
|
||||||
|
|
||||||
if b.setting.ShowDebug {
|
if b.setting.ShowDebug {
|
||||||
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
|
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -453,7 +506,16 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
b.dump = dump
|
b.dump = dump
|
||||||
}
|
}
|
||||||
return client.Do(b.req)
|
// retries default value is 0, it will run once.
|
||||||
|
// retries equal to -1, it will run forever until success
|
||||||
|
// retries is setted, it will retries fixed times.
|
||||||
|
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
|
||||||
|
resp, err = client.Do(b.req)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the body string in response.
|
// String returns the body string in response.
|
||||||
@ -487,9 +549,9 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.body, err = ioutil.ReadAll(reader)
|
b.body, err = ioutil.ReadAll(reader)
|
||||||
} else {
|
return b.body, err
|
||||||
b.body, err = ioutil.ReadAll(resp.Body)
|
|
||||||
}
|
}
|
||||||
|
b.body, err = ioutil.ReadAll(resp.Body)
|
||||||
return b.body, err
|
return b.body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,6 +596,16 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
|
|||||||
return xml.Unmarshal(data, v)
|
return xml.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToYAML returns the map that marshals from the body bytes as yaml in response .
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
// Response executes request client gets response mannually.
|
// Response executes request client gets response mannually.
|
||||||
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
|
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
|
||||||
return b.getResponse()
|
return b.getResponse()
|
||||||
|
@ -102,6 +102,14 @@ func TestSimpleDelete(t *testing.T) {
|
|||||||
t.Log(str)
|
t.Log(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimpleDeleteParam(t *testing.T) {
|
||||||
|
str, err := Delete("http://httpbin.org/delete").Param("key", "val").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithCookie(t *testing.T) {
|
func TestWithCookie(t *testing.T) {
|
||||||
v := "smallfish"
|
v := "smallfish"
|
||||||
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String()
|
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String()
|
||||||
|
@ -16,48 +16,57 @@ As of now this logs support console, file,smtp and conn.
|
|||||||
|
|
||||||
First you must import it
|
First you must import it
|
||||||
|
|
||||||
import (
|
```golang
|
||||||
"github.com/astaxie/beego/logs"
|
import (
|
||||||
)
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
Then init a Log (example with console adapter)
|
Then init a Log (example with console adapter)
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("console", "")
|
log := logs.NewLogger(10000)
|
||||||
|
log.SetLogger("console", "")
|
||||||
|
```
|
||||||
|
|
||||||
> the first params stand for how many channel
|
> the first params stand for how many channel
|
||||||
|
|
||||||
Use it like this:
|
Use it like this:
|
||||||
|
|
||||||
log.Trace("trace")
|
|
||||||
log.Info("info")
|
|
||||||
log.Warn("warning")
|
|
||||||
log.Debug("debug")
|
|
||||||
log.Critical("critical")
|
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log.Trace("trace")
|
||||||
|
log.Info("info")
|
||||||
|
log.Warn("warning")
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Critical("critical")
|
||||||
|
```
|
||||||
|
|
||||||
## File adapter
|
## File adapter
|
||||||
|
|
||||||
Configure file adapter like this:
|
Configure file adapter like this:
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
```
|
||||||
|
|
||||||
## Conn adapter
|
## Conn adapter
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
log := NewLogger(1000)
|
```golang
|
||||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
log := NewLogger(1000)
|
||||||
log.Info("info")
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Info("info")
|
||||||
|
```
|
||||||
|
|
||||||
## Smtp adapter
|
## Smtp adapter
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
log := NewLogger(10000)
|
||||||
log.Critical("sendmail critical")
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
time.Sleep(time.Second * 30)
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
```
|
||||||
|
83
logs/accesslog.go
Normal file
83
logs/accesslog.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||||
|
apacheFormat = "APACHE_FORMAT"
|
||||||
|
jsonFormat = "JSON_FORMAT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLogRecord struct for holding access log data.
|
||||||
|
type AccessLogRecord struct {
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
RequestTime time.Time `json:"request_time"`
|
||||||
|
RequestMethod string `json:"request_method"`
|
||||||
|
Request string `json:"request"`
|
||||||
|
ServerProtocol string `json:"server_protocol"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||||
|
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||||
|
HTTPReferrer string `json:"http_referrer"`
|
||||||
|
HTTPUserAgent string `json:"http_user_agent"`
|
||||||
|
RemoteUser string `json:"remote_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
disableEscapeHTML(encoder)
|
||||||
|
|
||||||
|
err := encoder.Encode(r)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableEscapeHTML(i interface{}) {
|
||||||
|
if e, ok := i.(interface {
|
||||||
|
SetEscapeHTML(bool)
|
||||||
|
}); ok {
|
||||||
|
e.SetEscapeHTML(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessLog - Format and print access log.
|
||||||
|
func AccessLog(r *AccessLogRecord, format string) {
|
||||||
|
var msg string
|
||||||
|
switch format {
|
||||||
|
case apacheFormat:
|
||||||
|
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||||
|
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||||
|
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||||
|
case jsonFormat:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
jsonData, err := r.json()
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||||
|
} else {
|
||||||
|
msg = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
|
||||||
|
}
|
186
logs/alils/alils.go
Normal file
186
logs/alils/alils.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheSize set the flush size
|
||||||
|
CacheSize int = 64
|
||||||
|
// Delimiter define the topic delimiter
|
||||||
|
Delimiter string = "##"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the Config for Ali Log
|
||||||
|
type Config struct {
|
||||||
|
Project string `json:"project"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
KeyID string `json:"key_id"`
|
||||||
|
KeySecret string `json:"key_secret"`
|
||||||
|
LogStore string `json:"log_store"`
|
||||||
|
Topics []string `json:"topics"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
FlushWhen int `json:"flush_when"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliLSWriter implements LoggerInterface.
|
||||||
|
// it writes messages in keep-live tcp connection.
|
||||||
|
type aliLSWriter struct {
|
||||||
|
store *LogStore
|
||||||
|
group []*LogGroup
|
||||||
|
withMap bool
|
||||||
|
groupMap map[string]*LogGroup
|
||||||
|
lock *sync.Mutex
|
||||||
|
Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAliLS create a new Logger
|
||||||
|
func NewAliLS() logs.Logger {
|
||||||
|
alils := new(aliLSWriter)
|
||||||
|
alils.Level = logs.LevelTrace
|
||||||
|
return alils
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init parse config and init struct
|
||||||
|
func (c *aliLSWriter) Init(jsonConfig string) (err error) {
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
|
||||||
|
if c.FlushWhen > CacheSize {
|
||||||
|
c.FlushWhen = CacheSize
|
||||||
|
}
|
||||||
|
|
||||||
|
prj := &LogProject{
|
||||||
|
Name: c.Project,
|
||||||
|
Endpoint: c.Endpoint,
|
||||||
|
AccessKeyID: c.KeyID,
|
||||||
|
AccessKeySecret: c.KeySecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.store, err = prj.GetLogStore(c.LogStore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create default Log Group
|
||||||
|
c.group = append(c.group, &LogGroup{
|
||||||
|
Topic: proto.String(""),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create other Log Group
|
||||||
|
c.groupMap = make(map[string]*LogGroup)
|
||||||
|
for _, topic := range c.Topics {
|
||||||
|
|
||||||
|
lg := &LogGroup{
|
||||||
|
Topic: proto.String(topic),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.group = append(c.group, lg)
|
||||||
|
c.groupMap[topic] = lg
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.group) == 1 {
|
||||||
|
c.withMap = false
|
||||||
|
} else {
|
||||||
|
c.withMap = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock = &sync.Mutex{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in connection.
|
||||||
|
// if connection is down, try to re-connect.
|
||||||
|
func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) {
|
||||||
|
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic string
|
||||||
|
var content string
|
||||||
|
var lg *LogGroup
|
||||||
|
if c.withMap {
|
||||||
|
|
||||||
|
// Topic,LogGroup
|
||||||
|
strs := strings.SplitN(msg, Delimiter, 2)
|
||||||
|
if len(strs) == 2 {
|
||||||
|
pos := strings.LastIndex(strs[0], " ")
|
||||||
|
topic = strs[0][pos+1 : len(strs[0])]
|
||||||
|
content = strs[0][0:pos] + strs[1]
|
||||||
|
lg = c.groupMap[topic]
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to empty Topic
|
||||||
|
if lg == nil {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 := &LogContent{
|
||||||
|
Key: proto.String("msg"),
|
||||||
|
Value: proto.String(content),
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &Log{
|
||||||
|
Time: proto.Uint32(uint32(when.Unix())),
|
||||||
|
Contents: []*LogContent{
|
||||||
|
c1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
lg.Logs = append(lg.Logs, l)
|
||||||
|
c.lock.Unlock()
|
||||||
|
|
||||||
|
if len(lg.Logs) >= c.FlushWhen {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *aliLSWriter) Flush() {
|
||||||
|
|
||||||
|
// flush all group
|
||||||
|
for _, lg := range c.group {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *aliLSWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliLSWriter) flush(lg *LogGroup) {
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
err := c.store.PutLogs(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lg.Logs = make([]*Log, 0, c.FlushWhen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register(logs.AdapterAliLS, NewAliLS)
|
||||||
|
}
|
13
logs/alils/config.go
Executable file
13
logs/alils/config.go
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = "0.5.0" // SDK version
|
||||||
|
signatureMethod = "hmac-sha1" // Signature method
|
||||||
|
|
||||||
|
// OffsetNewest stands for the log head offset, i.e. the offset that will be
|
||||||
|
// assigned to the next message that will be produced to the shard.
|
||||||
|
OffsetNewest = "end"
|
||||||
|
// OffsetOldest stands for the oldest offset available on the logstore for a
|
||||||
|
// shard.
|
||||||
|
OffsetOldest = "begin"
|
||||||
|
)
|
1038
logs/alils/log.pb.go
Executable file
1038
logs/alils/log.pb.go
Executable file
File diff suppressed because it is too large
Load Diff
42
logs/alils/log_config.go
Executable file
42
logs/alils/log_config.go
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
// InputDetail define log detail
|
||||||
|
type InputDetail struct {
|
||||||
|
LogType string `json:"logType"`
|
||||||
|
LogPath string `json:"logPath"`
|
||||||
|
FilePattern string `json:"filePattern"`
|
||||||
|
LocalStorage bool `json:"localStorage"`
|
||||||
|
TimeFormat string `json:"timeFormat"`
|
||||||
|
LogBeginRegex string `json:"logBeginRegex"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Keys []string `json:"key"`
|
||||||
|
FilterKeys []string `json:"filterKey"`
|
||||||
|
FilterRegex []string `json:"filterRegex"`
|
||||||
|
TopicFormat string `json:"topicFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputDetail define the output detail
|
||||||
|
type OutputDetail struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
LogStoreName string `json:"logstoreName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogConfig define Log Config
|
||||||
|
type LogConfig struct {
|
||||||
|
Name string `json:"configName"`
|
||||||
|
InputType string `json:"inputType"`
|
||||||
|
InputDetail InputDetail `json:"inputDetail"`
|
||||||
|
OutputType string `json:"outputType"`
|
||||||
|
OutputDetail OutputDetail `json:"outputDetail"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroup returns applied machine group of this config.
|
||||||
|
func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) {
|
||||||
|
groupNames, err = c.project.GetAppliedMachineGroups(c.Name)
|
||||||
|
return
|
||||||
|
}
|
819
logs/alils/log_project.go
Executable file
819
logs/alils/log_project.go
Executable file
@ -0,0 +1,819 @@
|
|||||||
|
/*
|
||||||
|
Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
|
||||||
|
|
||||||
|
For more description about SLS, please read this article:
|
||||||
|
http://gitlab.alibaba-inc.com/sls/doc.
|
||||||
|
*/
|
||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error message in SLS HTTP response.
|
||||||
|
type errorMessage struct {
|
||||||
|
Code string `json:"errorCode"`
|
||||||
|
Message string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogProject Define the Ali Project detail
|
||||||
|
type LogProject struct {
|
||||||
|
Name string // Project name
|
||||||
|
Endpoint string // IP or hostname of SLS endpoint
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogProject creates a new SLS project.
|
||||||
|
func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
|
||||||
|
p = &LogProject{
|
||||||
|
Name: name,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
AccessKeyID: AccessKeyID,
|
||||||
|
AccessKeySecret: accessKeySecret,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogStore returns all logstore names of project p.
|
||||||
|
func (p *LogProject) ListLogStore() (storeNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores")
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
LogStores []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storeNames = body.LogStores
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogStore returns logstore according by logstore name.
|
||||||
|
func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = &LogStore{}
|
||||||
|
err = json.Unmarshal(buf, s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogStore creates a new logstore in SLS,
|
||||||
|
// where name is logstore name,
|
||||||
|
// and ttl is time-to-live(in day) of logs,
|
||||||
|
// and shardCnt is the number of shards.
|
||||||
|
func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogStore deletes a logstore according by logstore name.
|
||||||
|
func (p *LogProject) DeleteLogStore(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogStore updates a logstore according by logstore name,
|
||||||
|
// obviously we can't modify the logstore name itself.
|
||||||
|
func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachineGroup returns machine group name list and the total number of machine groups.
|
||||||
|
// The offset starts from 0 and the size is the max number of machine groups could be returned.
|
||||||
|
func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
MachineGroups []string
|
||||||
|
Count int
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = body.MachineGroups
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMachineGroup retruns machine group according by machine group name.
|
||||||
|
func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get machine group:%v", name)
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = &MachineGroup{}
|
||||||
|
err = json.Unmarshal(buf, m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMachineGroup creates a new machine group in SLS.
|
||||||
|
func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/machinegroups", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMachineGroup updates a machine group.
|
||||||
|
func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMachineGroup deletes machine group according machine group name.
|
||||||
|
func (p *LogProject) DeleteMachineGroup(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConfig returns config names list and the total number of configs.
|
||||||
|
// The offset starts from 0 and the size is the max number of configs could be returned.
|
||||||
|
func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Total int
|
||||||
|
Configs []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgNames = body.Configs
|
||||||
|
total = body.Total
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns config according by config name.
|
||||||
|
func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &LogConfig{}
|
||||||
|
err = json.Unmarshal(buf, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig updates a config.
|
||||||
|
func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConfig creates a new config in SLS.
|
||||||
|
func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/configs", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfig deletes a config according by config name.
|
||||||
|
func (p *LogProject) DeleteConfig(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroups returns applied machine group names list according config name.
|
||||||
|
func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get applied machine groups")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
Machinegroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Body{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNames = body.Machinegroups
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied config names list according machine group name groupName.
|
||||||
|
func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to applied configs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cfg struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Configs []string `json:"configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Cfg{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confNames = body.Configs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyConfigToMachineGroup applies config to machine group.
|
||||||
|
func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "PUT", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to apply config to machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConfigFromMachineGroup removes config from machine group.
|
||||||
|
func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "DELETE", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
271
logs/alils/log_store.go
Executable file
271
logs/alils/log_store.go
Executable file
@ -0,0 +1,271 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
lz4 "github.com/cloudflare/golz4"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogStore Store the logs
|
||||||
|
type LogStore struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int
|
||||||
|
ShardCount int
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shard define the Log Shard
|
||||||
|
type Shard struct {
|
||||||
|
ShardID int `json:"shardID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListShards returns shard id list of this logstore.
|
||||||
|
func (s *LogStore) ListShards() (shardIDs []int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards", s.Name)
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shards []*Shard
|
||||||
|
err = json.Unmarshal(buf, &shards)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range shards {
|
||||||
|
shardIDs = append(shardIDs, v.ShardID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutLogs put logs into logstore.
|
||||||
|
// The callers should transform user logs into LogGroup.
|
||||||
|
func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
|
||||||
|
body, err := proto.Marshal(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compresse body with lz4
|
||||||
|
out := make([]byte, lz4.CompressBound(body))
|
||||||
|
n, err := lz4.Compress(body, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-compresstype": "lz4",
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/x-protobuf",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v", s.Name)
|
||||||
|
r, err := request(s.project, "POST", uri, h, out[:n])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to put logs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCursor gets log cursor of one shard specified by shardID.
|
||||||
|
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
|
||||||
|
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
|
||||||
|
func (s *LogStore) GetCursor(shardID int, from string) (cursor string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
|
||||||
|
s.Name, shardID, from)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Cursor string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cursor = body.Cursor
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogsBytes gets logs binary data from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogsBytes(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (out []byte, nextCursor string, err error) {
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
"Accept": "application/x-protobuf",
|
||||||
|
"Accept-Encoding": "lz4",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
|
||||||
|
s.Name, shardID, cursor, logGroupMaxCount)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := r.Header["X-Sls-Compresstype"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-compresstype' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != "lz4" {
|
||||||
|
err = fmt.Errorf("unexpected compress type:%v", v[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Cursor"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-cursor' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextCursor = v[0]
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Bodyrawsize"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bodyRawSize, err := strconv.Atoi(v[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = make([]byte, bodyRawSize)
|
||||||
|
err = lz4.Uncompress(buf, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogsBytesDecode decodes logs binary data retruned by GetLogsBytes API
|
||||||
|
func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
|
||||||
|
|
||||||
|
gl = &LogGroupList{}
|
||||||
|
err = proto.Unmarshal(data, gl)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs gets logs from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogs(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
|
||||||
|
|
||||||
|
out, nextCursor, err := s.GetLogsBytes(shardID, cursor, logGroupMaxCount)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gl, err = LogsBytesDecode(out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
91
logs/alils/machine_group.go
Executable file
91
logs/alils/machine_group.go
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MachineGroupAttribute define the Attribute
|
||||||
|
type MachineGroupAttribute struct {
|
||||||
|
ExternalName string `json:"externalName"`
|
||||||
|
TopicName string `json:"groupTopic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineGroup define the machine Group
|
||||||
|
type MachineGroup struct {
|
||||||
|
Name string `json:"groupName"`
|
||||||
|
Type string `json:"groupType"`
|
||||||
|
MachineIDType string `json:"machineIdentifyType"`
|
||||||
|
MachineIDList []string `json:"machineList"`
|
||||||
|
|
||||||
|
Attribute MachineGroupAttribute `json:"groupAttribute"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine define the Machine
|
||||||
|
type Machine struct {
|
||||||
|
IP string
|
||||||
|
UniqueID string `json:"machine-uniqueid"`
|
||||||
|
UserdefinedID string `json:"userdefined-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineList define the Machine List
|
||||||
|
type MachineList struct {
|
||||||
|
Total int
|
||||||
|
Machines []*Machine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachines returns machine list of this machine group.
|
||||||
|
func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name)
|
||||||
|
r, err := request(m.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &MachineList{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ms = body.Machines
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied configs of this machine group.
|
||||||
|
func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) {
|
||||||
|
confNames, err = m.project.GetAppliedConfigs(m.Name)
|
||||||
|
return
|
||||||
|
}
|
62
logs/alils/request.go
Executable file
62
logs/alils/request.go
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// request sends a request to SLS.
|
||||||
|
func request(project *LogProject, method, uri string, headers map[string]string,
|
||||||
|
body []byte) (resp *http.Response, err error) {
|
||||||
|
|
||||||
|
// The caller should provide 'x-sls-bodyrawsize' header
|
||||||
|
if _, ok := headers["x-sls-bodyrawsize"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SLS public request headers
|
||||||
|
headers["Host"] = project.Name + "." + project.Endpoint
|
||||||
|
headers["Date"] = nowRFC1123()
|
||||||
|
headers["x-sls-apiversion"] = version
|
||||||
|
headers["x-sls-signaturemethod"] = signatureMethod
|
||||||
|
if body != nil {
|
||||||
|
bodyMD5 := fmt.Sprintf("%X", md5.Sum(body))
|
||||||
|
headers["Content-MD5"] = bodyMD5
|
||||||
|
|
||||||
|
if _, ok := headers["Content-Type"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Content-Type' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc Authorization
|
||||||
|
// Authorization = "SLS <AccessKeyID>:<Signature>"
|
||||||
|
digest, err := signature(project, method, uri, headers)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest)
|
||||||
|
headers["Authorization"] = auth
|
||||||
|
|
||||||
|
// Initialize http request
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri)
|
||||||
|
req, err := http.NewRequest(method, urlStr, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ready to do request
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
111
logs/alils/signature.go
Executable file
111
logs/alils/signature.go
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GMT location
|
||||||
|
var gmtLoc = time.FixedZone("GMT", 0)
|
||||||
|
|
||||||
|
// NowRFC1123 returns now time in RFC1123 format with GMT timezone,
|
||||||
|
// eg. "Mon, 02 Jan 2006 15:04:05 GMT".
|
||||||
|
func nowRFC1123() string {
|
||||||
|
return time.Now().In(gmtLoc).Format(time.RFC1123)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature calculates a request's signature digest.
|
||||||
|
func signature(project *LogProject, method, uri string,
|
||||||
|
headers map[string]string) (digest string, err error) {
|
||||||
|
var contentMD5, contentType, date, canoHeaders, canoResource string
|
||||||
|
var slsHeaderKeys sort.StringSlice
|
||||||
|
|
||||||
|
// SignString = VERB + "\n"
|
||||||
|
// + CONTENT-MD5 + "\n"
|
||||||
|
// + CONTENT-TYPE + "\n"
|
||||||
|
// + DATE + "\n"
|
||||||
|
// + CanonicalizedSLSHeaders + "\n"
|
||||||
|
// + CanonicalizedResource
|
||||||
|
|
||||||
|
if val, ok := headers["Content-MD5"]; ok {
|
||||||
|
contentMD5 = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := headers["Content-Type"]; ok {
|
||||||
|
contentType = val
|
||||||
|
}
|
||||||
|
|
||||||
|
date, ok := headers["Date"]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Date' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedSLSHeaders
|
||||||
|
slsHeaders := make(map[string]string, len(headers))
|
||||||
|
for k, v := range headers {
|
||||||
|
l := strings.TrimSpace(strings.ToLower(k))
|
||||||
|
if strings.HasPrefix(l, "x-sls-") {
|
||||||
|
slsHeaders[l] = strings.TrimSpace(v)
|
||||||
|
slsHeaderKeys = append(slsHeaderKeys, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(slsHeaderKeys)
|
||||||
|
for i, k := range slsHeaderKeys {
|
||||||
|
canoHeaders += k + ":" + slsHeaders[k]
|
||||||
|
if i+1 < len(slsHeaderKeys) {
|
||||||
|
canoHeaders += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedResource
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canoResource += url.QueryEscape(u.Path)
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
var keys sort.StringSlice
|
||||||
|
|
||||||
|
vals := u.Query()
|
||||||
|
for k := range vals {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(keys)
|
||||||
|
canoResource += "?"
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
canoResource += "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vals[k] {
|
||||||
|
canoResource += k + "=" + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signStr := method + "\n" +
|
||||||
|
contentMD5 + "\n" +
|
||||||
|
contentType + "\n" +
|
||||||
|
date + "\n" +
|
||||||
|
canoHeaders + "\n" +
|
||||||
|
canoResource
|
||||||
|
|
||||||
|
// Signature = base64(hmac-sha1(UTF8-Encoding-Of(SignString),AccessKeySecret))
|
||||||
|
mac := hmac.New(sha1.New, []byte(project.AccessKeySecret))
|
||||||
|
_, err = mac.Write([]byte(signStr))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
return
|
||||||
|
}
|
@ -361,7 +361,7 @@ func isParameterChar(b byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||||
r, nw, first, last := 0, 0, 0, 0
|
var r, nw, first, last int
|
||||||
if cw.mode != DiscardNonColorEscSeq {
|
if cw.mode != DiscardNonColorEscSeq {
|
||||||
cw.state = outsideCsiCode
|
cw.state = outsideCsiCode
|
||||||
cw.resetBuffer()
|
cw.resetBuffer()
|
||||||
|
@ -41,7 +41,7 @@ var colors = []brush{
|
|||||||
newBrush("1;33"), // Warning yellow
|
newBrush("1;33"), // Warning yellow
|
||||||
newBrush("1;32"), // Notice green
|
newBrush("1;32"), // Notice green
|
||||||
newBrush("1;34"), // Informational blue
|
newBrush("1;34"), // Informational blue
|
||||||
newBrush("1;34"), // Debug blue
|
newBrush("1;44"), // Debug Background blue
|
||||||
}
|
}
|
||||||
|
|
||||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||||
|
76
logs/file.go
76
logs/file.go
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -40,6 +41,9 @@ type fileLogWriter struct {
|
|||||||
MaxLines int `json:"maxlines"`
|
MaxLines int `json:"maxlines"`
|
||||||
maxLinesCurLines int
|
maxLinesCurLines int
|
||||||
|
|
||||||
|
MaxFiles int `json:"maxfiles"`
|
||||||
|
MaxFilesCurFiles int
|
||||||
|
|
||||||
// Rotate at size
|
// Rotate at size
|
||||||
MaxSize int `json:"maxsize"`
|
MaxSize int `json:"maxsize"`
|
||||||
maxSizeCurSize int
|
maxSizeCurSize int
|
||||||
@ -56,17 +60,23 @@ type fileLogWriter struct {
|
|||||||
|
|
||||||
Perm string `json:"perm"`
|
Perm string `json:"perm"`
|
||||||
|
|
||||||
|
RotatePerm string `json:"rotateperm"`
|
||||||
|
|
||||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
func newFileWriter() Logger {
|
func newFileWriter() Logger {
|
||||||
w := &fileLogWriter{
|
w := &fileLogWriter{
|
||||||
Daily: true,
|
Daily: true,
|
||||||
MaxDays: 7,
|
MaxDays: 7,
|
||||||
Rotate: true,
|
Rotate: true,
|
||||||
Level: LevelTrace,
|
RotatePerm: "0440",
|
||||||
Perm: "0660",
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
MaxLines: 10000000,
|
||||||
|
MaxFiles: 999,
|
||||||
|
MaxSize: 1 << 28,
|
||||||
}
|
}
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
@ -158,6 +168,10 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(perm))
|
||||||
|
|
||||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||||
@ -170,7 +184,7 @@ func (w *fileLogWriter) initFd() error {
|
|||||||
fd := w.fileWriter
|
fd := w.fileWriter
|
||||||
fInfo, err := fd.Stat()
|
fInfo, err := fd.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get stat err: %s\n", err)
|
return fmt.Errorf("get stat err: %s", err)
|
||||||
}
|
}
|
||||||
w.maxSizeCurSize = int(fInfo.Size())
|
w.maxSizeCurSize = int(fInfo.Size())
|
||||||
w.dailyOpenTime = time.Now()
|
w.dailyOpenTime = time.Now()
|
||||||
@ -179,7 +193,7 @@ func (w *fileLogWriter) initFd() error {
|
|||||||
if w.Daily {
|
if w.Daily {
|
||||||
go w.dailyRotate(w.dailyOpenTime)
|
go w.dailyRotate(w.dailyOpenTime)
|
||||||
}
|
}
|
||||||
if fInfo.Size() > 0 {
|
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||||
count, err := w.lines()
|
count, err := w.lines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -193,16 +207,14 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
|||||||
y, m, d := openTime.Add(24 * time.Hour).Date()
|
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||||
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||||
select {
|
<-tm.C
|
||||||
case <-tm.C:
|
w.Lock()
|
||||||
w.Lock()
|
if w.needRotate(0, time.Now().Day()) {
|
||||||
if w.needRotate(0, time.Now().Day()) {
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
if err := w.doRotate(time.Now()); err != nil {
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
w.Unlock()
|
|
||||||
}
|
}
|
||||||
|
w.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *fileLogWriter) lines() (int, error) {
|
func (w *fileLogWriter) lines() (int, error) {
|
||||||
@ -237,31 +249,33 @@ func (w *fileLogWriter) lines() (int, error) {
|
|||||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||||
// file exists
|
// file exists
|
||||||
// Find the next available number
|
// Find the next available number
|
||||||
num := 1
|
num := w.MaxFilesCurFiles + 1
|
||||||
fName := ""
|
fName := ""
|
||||||
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err := os.Lstat(w.Filename)
|
_, err = os.Lstat(w.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//even if the file is not exist or other ,we should RESTART the logger
|
//even if the file is not exist or other ,we should RESTART the logger
|
||||||
goto RESTART_LOGGER
|
goto RESTART_LOGGER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only when one of them be setted, then the file would be splited
|
||||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||||
for ; err == nil && num <= 999; num++ {
|
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
|
||||||
_, err = os.Lstat(fName)
|
_, err = os.Lstat(fName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
|
||||||
_, err = os.Lstat(fName)
|
_, err = os.Lstat(fName)
|
||||||
for ; err == nil && num <= 999; num++ {
|
w.MaxFilesCurFiles = num
|
||||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
|
|
||||||
_, err = os.Lstat(fName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// return error if the last file checked still existed
|
// return error if the last file checked still existed
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
|
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// close fileWriter before rename
|
// close fileWriter before rename
|
||||||
@ -270,20 +284,24 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
|||||||
// Rename the file to its new found name
|
// Rename the file to its new found name
|
||||||
// even if occurs error,we MUST guarantee to restart new logger
|
// even if occurs error,we MUST guarantee to restart new logger
|
||||||
err = os.Rename(w.Filename, fName)
|
err = os.Rename(w.Filename, fName)
|
||||||
// re-start logger
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||||
|
|
||||||
RESTART_LOGGER:
|
RESTART_LOGGER:
|
||||||
|
|
||||||
startLoggerErr := w.startLogger()
|
startLoggerErr := w.startLogger()
|
||||||
go w.deleteOldLog()
|
go w.deleteOldLog()
|
||||||
|
|
||||||
if startLoggerErr != nil {
|
if startLoggerErr != nil {
|
||||||
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
|
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Rotate: %s\n", err)
|
return fmt.Errorf("Rotate: %s", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *fileLogWriter) deleteOldLog() {
|
func (w *fileLogWriter) deleteOldLog() {
|
||||||
@ -299,7 +317,7 @@ func (w *fileLogWriter) deleteOldLog() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
|
@ -135,7 +135,7 @@ func TestFileRotate_01(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileRotate_02(t *testing.T) {
|
func TestFileRotate_02(t *testing.T) {
|
||||||
fn1 := "rotate_day.log"
|
fn1 := "rotate_day.log"
|
||||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
testFileRotate(t, fn1, fn2)
|
testFileRotate(t, fn1, fn2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ func TestFileRotate_03(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileRotate_04(t *testing.T) {
|
func TestFileRotate_04(t *testing.T) {
|
||||||
fn1 := "rotate_day.log"
|
fn1 := "rotate_day.log"
|
||||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
testFileDailyRotate(t, fn1, fn2)
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,14 +162,35 @@ func TestFileRotate_05(t *testing.T) {
|
|||||||
testFileDailyRotate(t, fn1, fn2)
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
os.Remove(fn)
|
os.Remove(fn)
|
||||||
}
|
}
|
||||||
|
func TestFileRotate_06(t *testing.T) { //test file mode
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||||
|
s, _ := os.Lstat(rotateName)
|
||||||
|
if s.Mode() != 0440 {
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate file mode error")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
func testFileRotate(t *testing.T, fn1, fn2 string) {
|
func testFileRotate(t *testing.T, fn1, fn2 string) {
|
||||||
fw := &fileLogWriter{
|
fw := &fileLogWriter{
|
||||||
Daily: true,
|
Daily: true,
|
||||||
MaxDays: 7,
|
MaxDays: 7,
|
||||||
Rotate: true,
|
Rotate: true,
|
||||||
Level: LevelTrace,
|
Level: LevelTrace,
|
||||||
Perm: "0660",
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
}
|
}
|
||||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
@ -179,6 +200,7 @@ func testFileRotate(t *testing.T, fn1, fn2 string) {
|
|||||||
for _, file := range []string{fn1, fn2} {
|
for _, file := range []string{fn1, fn2} {
|
||||||
_, err := os.Stat(file)
|
_, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
os.Remove(file)
|
os.Remove(file)
|
||||||
@ -188,11 +210,12 @@ func testFileRotate(t *testing.T, fn1, fn2 string) {
|
|||||||
|
|
||||||
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||||
fw := &fileLogWriter{
|
fw := &fileLogWriter{
|
||||||
Daily: true,
|
Daily: true,
|
||||||
MaxDays: 7,
|
MaxDays: 7,
|
||||||
Rotate: true,
|
Rotate: true,
|
||||||
Level: LevelTrace,
|
Level: LevelTrace,
|
||||||
Perm: "0660",
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
}
|
}
|
||||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
|
72
logs/jianliao.go
Normal file
72
logs/jianliao.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type JLWriter struct {
|
||||||
|
AuthorName string `json:"authorname"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
RedirectURL string `json:"redirecturl,omitempty"`
|
||||||
|
ImageURL string `json:"imageurl,omitempty"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJLWriter create jiaoliao writer.
|
||||||
|
func newJLWriter() Logger {
|
||||||
|
return &JLWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init JLWriter with json config string
|
||||||
|
func (s *JLWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("authorName", s.AuthorName)
|
||||||
|
form.Add("title", s.Title)
|
||||||
|
form.Add("text", text)
|
||||||
|
if s.RedirectURL != "" {
|
||||||
|
form.Add("redirectUrl", s.RedirectURL)
|
||||||
|
}
|
||||||
|
if s.ImageURL != "" {
|
||||||
|
form.Add("imageUrl", s.ImageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *JLWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *JLWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterJianLiao, newJLWriter)
|
||||||
|
}
|
56
logs/log.go
56
logs/log.go
@ -47,7 +47,7 @@ import (
|
|||||||
|
|
||||||
// RFC5424 log message levels.
|
// RFC5424 log message levels.
|
||||||
const (
|
const (
|
||||||
LevelEmergency = iota
|
LevelEmergency = iota
|
||||||
LevelAlert
|
LevelAlert
|
||||||
LevelCritical
|
LevelCritical
|
||||||
LevelError
|
LevelError
|
||||||
@ -66,9 +66,12 @@ const (
|
|||||||
AdapterConsole = "console"
|
AdapterConsole = "console"
|
||||||
AdapterFile = "file"
|
AdapterFile = "file"
|
||||||
AdapterMultiFile = "multifile"
|
AdapterMultiFile = "multifile"
|
||||||
AdapterMail = "stmp"
|
AdapterMail = "smtp"
|
||||||
AdapterConn = "conn"
|
AdapterConn = "conn"
|
||||||
AdapterEs = "es"
|
AdapterEs = "es"
|
||||||
|
AdapterJianLiao = "jianliao"
|
||||||
|
AdapterSlack = "slack"
|
||||||
|
AdapterAliLS = "alils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legacy log level constants to ensure backwards compatibility.
|
// Legacy log level constants to ensure backwards compatibility.
|
||||||
@ -113,6 +116,7 @@ type BeeLogger struct {
|
|||||||
enableFuncCallDepth bool
|
enableFuncCallDepth bool
|
||||||
loggerFuncCallDepth int
|
loggerFuncCallDepth int
|
||||||
asynchronous bool
|
asynchronous bool
|
||||||
|
prefix string
|
||||||
msgChanLen int64
|
msgChanLen int64
|
||||||
msgChan chan *logMsg
|
msgChan chan *logMsg
|
||||||
signalChan chan string
|
signalChan chan string
|
||||||
@ -244,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
// writeMsg will always add a '\n' character
|
// writeMsg will always add a '\n' character
|
||||||
if p[len(p)-1] == '\n' {
|
if p[len(p)-1] == '\n' {
|
||||||
p = p[0 : len(p)-1]
|
p = p[0: len(p)-1]
|
||||||
}
|
}
|
||||||
// set levelLoggerImpl to ensure all log message will be write out
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||||
@ -264,6 +268,9 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
|
|||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
msg = fmt.Sprintf(msg, v...)
|
msg = fmt.Sprintf(msg, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg = bl.prefix + " " + msg
|
||||||
|
|
||||||
when := time.Now()
|
when := time.Now()
|
||||||
if bl.enableFuncCallDepth {
|
if bl.enableFuncCallDepth {
|
||||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
@ -272,7 +279,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
|
|||||||
line = 0
|
line = 0
|
||||||
}
|
}
|
||||||
_, filename := path.Split(file)
|
_, filename := path.Split(file)
|
||||||
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
|
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
//set level info in front of filename info
|
//set level info in front of filename info
|
||||||
@ -302,6 +309,11 @@ func (bl *BeeLogger) SetLevel(l int) {
|
|||||||
bl.level = l
|
bl.level = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLevel Get Current log message level.
|
||||||
|
func (bl *BeeLogger) GetLevel() int {
|
||||||
|
return bl.level
|
||||||
|
}
|
||||||
|
|
||||||
// SetLogFuncCallDepth set log funcCallDepth
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||||
bl.loggerFuncCallDepth = d
|
bl.loggerFuncCallDepth = d
|
||||||
@ -317,6 +329,11 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
|||||||
bl.enableFuncCallDepth = b
|
bl.enableFuncCallDepth = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
func (bl *BeeLogger) SetPrefix(s string) {
|
||||||
|
bl.prefix = s
|
||||||
|
}
|
||||||
|
|
||||||
// start logger chan reading.
|
// start logger chan reading.
|
||||||
// when chan is not empty, write logs.
|
// when chan is not empty, write logs.
|
||||||
func (bl *BeeLogger) startLogger() {
|
func (bl *BeeLogger) startLogger() {
|
||||||
@ -378,7 +395,10 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
|||||||
|
|
||||||
// Warning Log WARNING level message.
|
// Warning Log WARNING level message.
|
||||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||||
bl.Warn(format, v...)
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelWarn, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice Log NOTICE level message.
|
// Notice Log NOTICE level message.
|
||||||
@ -391,7 +411,10 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
|||||||
|
|
||||||
// Informational Log INFORMATIONAL level message.
|
// Informational Log INFORMATIONAL level message.
|
||||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||||
bl.Info(format, v...)
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelInfo, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug Log DEBUG level message.
|
// Debug Log DEBUG level message.
|
||||||
@ -423,7 +446,10 @@ func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
|||||||
// Trace Log TRACE level message.
|
// Trace Log TRACE level message.
|
||||||
// compatibility alias for Debug()
|
// compatibility alias for Debug()
|
||||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||||
bl.Debug(format, v...)
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelDebug, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush flush all chan data.
|
// Flush flush all chan data.
|
||||||
@ -480,9 +506,9 @@ func (bl *BeeLogger) flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// beeLogger references the used application logger.
|
// beeLogger references the used application logger.
|
||||||
var beeLogger *BeeLogger = NewLogger()
|
var beeLogger = NewLogger()
|
||||||
|
|
||||||
// GetLogger returns the default BeeLogger
|
// GetBeeLogger returns the default BeeLogger
|
||||||
func GetBeeLogger() *BeeLogger {
|
func GetBeeLogger() *BeeLogger {
|
||||||
return beeLogger
|
return beeLogger
|
||||||
}
|
}
|
||||||
@ -522,6 +548,7 @@ func Reset() {
|
|||||||
beeLogger.Reset()
|
beeLogger.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Async set the beelogger with Async mode and hold msglen messages
|
||||||
func Async(msgLen ...int64) *BeeLogger {
|
func Async(msgLen ...int64) *BeeLogger {
|
||||||
return beeLogger.Async(msgLen...)
|
return beeLogger.Async(msgLen...)
|
||||||
}
|
}
|
||||||
@ -531,6 +558,11 @@ func SetLevel(l int) {
|
|||||||
beeLogger.SetLevel(l)
|
beeLogger.SetLevel(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPrefix sets the prefix
|
||||||
|
func SetPrefix(s string) {
|
||||||
|
beeLogger.SetPrefix(s)
|
||||||
|
}
|
||||||
|
|
||||||
// EnableFuncCallDepth enable log funcCallDepth
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
func EnableFuncCallDepth(b bool) {
|
func EnableFuncCallDepth(b bool) {
|
||||||
beeLogger.enableFuncCallDepth = b
|
beeLogger.enableFuncCallDepth = b
|
||||||
@ -549,11 +581,7 @@ func SetLogFuncCallDepth(d int) {
|
|||||||
|
|
||||||
// SetLogger sets a new logger.
|
// SetLogger sets a new logger.
|
||||||
func SetLogger(adapter string, config ...string) error {
|
func SetLogger(adapter string, config ...string) error {
|
||||||
err := beeLogger.SetLogger(adapter, config...)
|
return beeLogger.SetLogger(adapter, config...)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emergency logs a message at emergency level.
|
// Emergency logs a message at emergency level.
|
||||||
|
@ -87,13 +87,15 @@ const (
|
|||||||
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
ns1 = `0123456789`
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatTimeHeader(when time.Time) ([]byte, int) {
|
func formatTimeHeader(when time.Time) ([]byte, int) {
|
||||||
y, mo, d := when.Date()
|
y, mo, d := when.Date()
|
||||||
h, mi, s := when.Clock()
|
h, mi, s := when.Clock()
|
||||||
//len("2006/01/02 15:04:05 ")==20
|
ns := when.Nanosecond() / 1000000
|
||||||
var buf [20]byte
|
//len("2006/01/02 15:04:05.123 ")==24
|
||||||
|
var buf [24]byte
|
||||||
|
|
||||||
buf[0] = y1[y/1000%10]
|
buf[0] = y1[y/1000%10]
|
||||||
buf[1] = y2[y/100]
|
buf[1] = y2[y/100]
|
||||||
@ -114,7 +116,12 @@ func formatTimeHeader(when time.Time) ([]byte, int) {
|
|||||||
buf[16] = ':'
|
buf[16] = ':'
|
||||||
buf[17] = s1[s]
|
buf[17] = s1[s]
|
||||||
buf[18] = s2[s]
|
buf[18] = s2[s]
|
||||||
buf[19] = ' '
|
buf[19] = '.'
|
||||||
|
buf[20] = ns1[ns/100]
|
||||||
|
buf[21] = ns1[ns%100/10]
|
||||||
|
buf[22] = ns1[ns%10]
|
||||||
|
|
||||||
|
buf[23] = ' '
|
||||||
|
|
||||||
return buf[0:], d
|
return buf[0:], d
|
||||||
}
|
}
|
||||||
@ -139,6 +146,11 @@ var (
|
|||||||
reset = string([]byte{27, 91, 48, 109})
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ColorByStatus return color by http code
|
||||||
|
// 2xx return Green
|
||||||
|
// 3xx return White
|
||||||
|
// 4xx return Yellow
|
||||||
|
// 5xx return Red
|
||||||
func ColorByStatus(cond bool, code int) string {
|
func ColorByStatus(cond bool, code int) string {
|
||||||
switch {
|
switch {
|
||||||
case code >= 200 && code < 300:
|
case code >= 200 && code < 300:
|
||||||
@ -152,6 +164,14 @@ func ColorByStatus(cond bool, code int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ColorByMethod return color by http code
|
||||||
|
// GET return Blue
|
||||||
|
// POST return Cyan
|
||||||
|
// PUT return Yellow
|
||||||
|
// DELETE return Red
|
||||||
|
// PATCH return Green
|
||||||
|
// HEAD return Magenta
|
||||||
|
// OPTIONS return WHITE
|
||||||
func ColorByMethod(cond bool, method string) string {
|
func ColorByMethod(cond bool, method string) string {
|
||||||
switch method {
|
switch method {
|
||||||
case "GET":
|
case "GET":
|
||||||
@ -173,10 +193,10 @@ func ColorByMethod(cond bool, method string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard Mutex to guarantee atomicity of W32Debug(string) function
|
// Guard Mutex to guarantee atomic of W32Debug(string) function
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
// Helper method to output colored logs in Windows terminals
|
// W32Debug Helper method to output colored logs in Windows terminals
|
||||||
func W32Debug(msg string) {
|
func W32Debug(msg string) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
@ -31,7 +31,7 @@ func TestFormatHeader_0(t *testing.T) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
h, _ := formatTimeHeader(tm)
|
h, _ := formatTimeHeader(tm)
|
||||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
t.Log(tm)
|
t.Log(tm)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ func TestFormatHeader_1(t *testing.T) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
h, _ := formatTimeHeader(tm)
|
h, _ := formatTimeHeader(tm)
|
||||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
t.Log(tm)
|
t.Log(tm)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,10 @@ func (f *multiFileLogWriter) Init(config string) error {
|
|||||||
jsonMap["level"] = i
|
jsonMap["level"] = i
|
||||||
bs, _ := json.Marshal(jsonMap)
|
bs, _ := json.Marshal(jsonMap)
|
||||||
writer = newFileWriter().(*fileLogWriter)
|
writer = newFileWriter().(*fileLogWriter)
|
||||||
writer.Init(string(bs))
|
err := writer.Init(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f.writers[i] = writer
|
f.writers[i] = writer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
60
logs/slack.go
Normal file
60
logs/slack.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type SLACKWriter struct {
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSLACKWriter create jiaoliao writer.
|
||||||
|
func newSLACKWriter() Logger {
|
||||||
|
return &SLACKWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SLACKWriter with json config string
|
||||||
|
func (s *SLACKWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("payload", text)
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterSlack, newSLACKWriter)
|
||||||
|
}
|
17
logs/smtp.go
17
logs/smtp.go
@ -52,11 +52,7 @@ func newSMTPWriter() Logger {
|
|||||||
// "level":LevelError
|
// "level":LevelError
|
||||||
// }
|
// }
|
||||||
func (s *SMTPWriter) Init(jsonconfig string) error {
|
func (s *SMTPWriter) Init(jsonconfig string) error {
|
||||||
err := json.Unmarshal([]byte(jsonconfig), s)
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||||
@ -106,7 +102,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = w.Write([]byte(msgContent))
|
_, err = w.Write(msgContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -116,12 +112,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Quit()
|
return client.Quit()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg write message in smtp writer.
|
// WriteMsg write message in smtp writer.
|
||||||
@ -147,12 +138,10 @@ func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
|||||||
|
|
||||||
// Flush implementing method. empty.
|
// Flush implementing method. empty.
|
||||||
func (s *SMTPWriter) Flush() {
|
func (s *SMTPWriter) Flush() {
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy implementing method. empty.
|
// Destroy implementing method. empty.
|
||||||
func (s *SMTPWriter) Destroy() {
|
func (s *SMTPWriter) Destroy() {
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
392
migration/ddl.go
392
migration/ddl.go
@ -14,40 +14,382 @@
|
|||||||
|
|
||||||
package migration
|
package migration
|
||||||
|
|
||||||
// Table store the tablename and Column
|
import (
|
||||||
type Table struct {
|
"fmt"
|
||||||
TableName string
|
|
||||||
Columns []*Column
|
"github.com/astaxie/beego"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Index struct defines the structure of Index Columns
|
||||||
|
type Index struct {
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create return the create sql
|
// Unique struct defines a single unique key combination
|
||||||
func (t *Table) Create() string {
|
type Unique struct {
|
||||||
return ""
|
Definition string
|
||||||
|
Columns []*Column
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop return the drop sql
|
//Column struct defines a single column of a table
|
||||||
func (t *Table) Drop() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column define the columns name type and Default
|
|
||||||
type Column struct {
|
type Column struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Inc string
|
||||||
Default interface{}
|
Null string
|
||||||
|
Default string
|
||||||
|
Unsign string
|
||||||
|
DataType string
|
||||||
|
remove bool
|
||||||
|
Modify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create return create sql with the provided tbname and columns
|
// Foreign struct defines a single foreign relationship
|
||||||
func Create(tbname string, columns ...Column) string {
|
type Foreign struct {
|
||||||
return ""
|
ForeignTable string
|
||||||
|
ForeignColumn string
|
||||||
|
OnDelete string
|
||||||
|
OnUpdate string
|
||||||
|
Column
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop return the drop sql with the provided tbname and columns
|
// RenameColumn struct allows renaming of columns
|
||||||
func Drop(tbname string, columns ...Column) string {
|
type RenameColumn struct {
|
||||||
return ""
|
OldName string
|
||||||
|
OldNull string
|
||||||
|
OldDefault string
|
||||||
|
OldUnsign string
|
||||||
|
OldDataType string
|
||||||
|
NewName string
|
||||||
|
Column
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableDDL is still in think
|
// CreateTable creates the table on system
|
||||||
func TableDDL(tbname string, columns ...Column) string {
|
func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) {
|
||||||
return ""
|
m.TableName = tablename
|
||||||
|
m.Engine = engine
|
||||||
|
m.Charset = charset
|
||||||
|
m.ModifyType = "create"
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlterTable set the ModifyType to alter
|
||||||
|
func (m *Migration) AlterTable(tablename string) {
|
||||||
|
m.TableName = tablename
|
||||||
|
m.ModifyType = "alter"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCol creates a new standard column and attaches it to m struct
|
||||||
|
func (m *Migration) NewCol(name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//PriCol creates a new primary column and attaches it to m struct
|
||||||
|
func (m *Migration) PriCol(name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
m.AddPrimary(col)
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//UniCol creates / appends columns to specified unique key and attaches it to m struct
|
||||||
|
func (m *Migration) UniCol(uni, name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
|
||||||
|
uniqueOriginal := &Unique{}
|
||||||
|
|
||||||
|
for _, unique := range m.Uniques {
|
||||||
|
if unique.Definition == uni {
|
||||||
|
unique.AddColumnsToUnique(col)
|
||||||
|
uniqueOriginal = unique
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uniqueOriginal.Definition == "" {
|
||||||
|
unique := &Unique{Definition: uni}
|
||||||
|
unique.AddColumnsToUnique(col)
|
||||||
|
m.AddUnique(unique)
|
||||||
|
}
|
||||||
|
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//ForeignCol creates a new foreign column and returns the instance of column
|
||||||
|
func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) {
|
||||||
|
|
||||||
|
foreign = &Foreign{ForeignColumn: foreigncol, ForeignTable: foreigntable}
|
||||||
|
foreign.Name = colname
|
||||||
|
m.AddForeign(foreign)
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOnDelete sets the on delete of foreign
|
||||||
|
func (foreign *Foreign) SetOnDelete(del string) *Foreign {
|
||||||
|
foreign.OnDelete = "ON DELETE" + del
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOnUpdate sets the on update of foreign
|
||||||
|
func (foreign *Foreign) SetOnUpdate(update string) *Foreign {
|
||||||
|
foreign.OnUpdate = "ON UPDATE" + update
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove marks the columns to be removed.
|
||||||
|
//it allows reverse m to create the column.
|
||||||
|
func (c *Column) Remove() {
|
||||||
|
c.remove = true
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetAuto enables auto_increment of column (can be used once)
|
||||||
|
func (c *Column) SetAuto(inc bool) *Column {
|
||||||
|
if inc {
|
||||||
|
c.Inc = "auto_increment"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetNullable sets the column to be null
|
||||||
|
func (c *Column) SetNullable(null bool) *Column {
|
||||||
|
if null {
|
||||||
|
c.Null = ""
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.Null = "NOT NULL"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetDefault sets the default value, prepend with "DEFAULT "
|
||||||
|
func (c *Column) SetDefault(def string) *Column {
|
||||||
|
c.Default = "DEFAULT " + def
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetUnsigned sets the column to be unsigned int
|
||||||
|
func (c *Column) SetUnsigned(unsign bool) *Column {
|
||||||
|
if unsign {
|
||||||
|
c.Unsign = "UNSIGNED"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetDataType sets the dataType of the column
|
||||||
|
func (c *Column) SetDataType(dataType string) *Column {
|
||||||
|
c.DataType = dataType
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldNullable allows reverting to previous nullable on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn {
|
||||||
|
if null {
|
||||||
|
c.OldNull = ""
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.OldNull = "NOT NULL"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldDefault allows reverting to previous default on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldDefault(def string) *RenameColumn {
|
||||||
|
c.OldDefault = def
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldUnsigned allows reverting to previous unsgined on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn {
|
||||||
|
if unsign {
|
||||||
|
c.OldUnsign = "UNSIGNED"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldDataType allows reverting to previous datatype on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn {
|
||||||
|
c.OldDataType = dataType
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetPrimary adds the columns to the primary key (can only be used any number of times in only one m)
|
||||||
|
func (c *Column) SetPrimary(m *Migration) *Column {
|
||||||
|
m.Primary = append(m.Primary, c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddColumnsToUnique adds the columns to Unique Struct
|
||||||
|
func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique {
|
||||||
|
|
||||||
|
unique.Columns = append(unique.Columns, columns...)
|
||||||
|
|
||||||
|
return unique
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddColumns adds columns to m struct
|
||||||
|
func (m *Migration) AddColumns(columns ...*Column) *Migration {
|
||||||
|
|
||||||
|
m.Columns = append(m.Columns, columns...)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddPrimary adds the column to primary in m struct
|
||||||
|
func (m *Migration) AddPrimary(primary *Column) *Migration {
|
||||||
|
m.Primary = append(m.Primary, primary)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddUnique adds the column to unique in m struct
|
||||||
|
func (m *Migration) AddUnique(unique *Unique) *Migration {
|
||||||
|
m.Uniques = append(m.Uniques, unique)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddForeign adds the column to foreign in m struct
|
||||||
|
func (m *Migration) AddForeign(foreign *Foreign) *Migration {
|
||||||
|
m.Foreigns = append(m.Foreigns, foreign)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddIndex adds the column to index in m struct
|
||||||
|
func (m *Migration) AddIndex(index *Index) *Migration {
|
||||||
|
m.Indexes = append(m.Indexes, index)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//RenameColumn allows renaming of columns
|
||||||
|
func (m *Migration) RenameColumn(from, to string) *RenameColumn {
|
||||||
|
rename := &RenameColumn{OldName: from, NewName: to}
|
||||||
|
m.Renames = append(m.Renames, rename)
|
||||||
|
return rename
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetSQL returns the generated sql depending on ModifyType
|
||||||
|
func (m *Migration) GetSQL() (sql string) {
|
||||||
|
sql = ""
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "create":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("CREATE TABLE `%s` (", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
sql += fmt.Sprintf("\n `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf(",\n PRIMARY KEY( ")
|
||||||
|
}
|
||||||
|
for index, column := range m.Primary {
|
||||||
|
sql += fmt.Sprintf(" `%s`", column.Name)
|
||||||
|
if len(m.Primary) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, unique := range m.Uniques {
|
||||||
|
sql += fmt.Sprintf(",\n UNIQUE KEY `%s`( ", unique.Definition)
|
||||||
|
for index, column := range unique.Columns {
|
||||||
|
sql += fmt.Sprintf(" `%s`", column.Name)
|
||||||
|
if len(unique.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql += fmt.Sprintf(")")
|
||||||
|
}
|
||||||
|
for _, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf(",\n `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
|
||||||
|
sql += fmt.Sprintf(",\n KEY `%s_%s_foreign`(`%s`),", m.TableName, foreign.Column.Name, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf("\n CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
|
||||||
|
|
||||||
|
}
|
||||||
|
sql += fmt.Sprintf(")ENGINE=%s DEFAULT CHARSET=%s;", m.Engine, m.Charset)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "alter":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
if !column.remove {
|
||||||
|
beego.BeeLogger.Info("col")
|
||||||
|
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
} else {
|
||||||
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index, column := range m.Renames {
|
||||||
|
sql += fmt.Sprintf("CHANGE COLUMN `%s` `%s` %s %s %s %s %s", column.OldName, column.NewName, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
if len(m.Renames) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf("ADD `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
|
||||||
|
sql += fmt.Sprintf(",\n ADD KEY `%s_%s_foreign`(`%s`)", m.TableName, foreign.Column.Name, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n ADD CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
|
||||||
|
if len(m.Foreigns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql += ";"
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "reverse":
|
||||||
|
{
|
||||||
|
|
||||||
|
sql += fmt.Sprintf("ALTER TABLE `%s`", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
if column.remove {
|
||||||
|
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
} else {
|
||||||
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
|
}
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf("\n DROP PRIMARY KEY,")
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, unique := range m.Uniques {
|
||||||
|
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
|
||||||
|
if len(m.Uniques) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
for index, column := range m.Renames {
|
||||||
|
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
|
||||||
|
if len(m.Renames) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf("\n DROP KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n DROP FOREIGN KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n DROP COLUMN `%s`", foreign.Name)
|
||||||
|
}
|
||||||
|
sql += ";"
|
||||||
|
}
|
||||||
|
case "delete":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", m.TableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
32
migration/doc.go
Normal file
32
migration/doc.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Package migration enables you to generate migrations back and forth. It generates both migrations.
|
||||||
|
//
|
||||||
|
// //Creates a table
|
||||||
|
// m.CreateTable("tablename","InnoDB","utf8");
|
||||||
|
//
|
||||||
|
// //Alter a table
|
||||||
|
// m.AlterTable("tablename")
|
||||||
|
//
|
||||||
|
// Standard Column Methods
|
||||||
|
// * SetDataType
|
||||||
|
// * SetNullable
|
||||||
|
// * SetDefault
|
||||||
|
// * SetUnsigned (use only on integer types unless produces error)
|
||||||
|
//
|
||||||
|
// //Sets a primary column, multiple calls allowed, standard column methods available
|
||||||
|
// m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true)
|
||||||
|
//
|
||||||
|
// //UniCol Can be used multiple times, allows standard Column methods. Use same "index" string to add to same index
|
||||||
|
// m.UniCol("index","column")
|
||||||
|
//
|
||||||
|
// //Standard Column Initialisation, can call .Remove() after NewCol("") on alter to remove
|
||||||
|
// m.NewCol("name").SetDataType("VARCHAR(255) COLLATE utf8_unicode_ci").SetNullable(false)
|
||||||
|
// m.NewCol("value").SetDataType("DOUBLE(8,2)").SetNullable(false)
|
||||||
|
//
|
||||||
|
// //Rename Columns , only use with Alter table, doesn't works with Create, prefix standard column methods with "Old" to
|
||||||
|
// //create a true reversible migration eg: SetOldDataType("DOUBLE(12,3)")
|
||||||
|
// m.RenameColumn("from","to")...
|
||||||
|
//
|
||||||
|
// //Foreign Columns, single columns are only supported, SetOnDelete & SetOnUpdate are available, call appropriately.
|
||||||
|
// //Supports standard column methods, automatic reverse.
|
||||||
|
// m.ForeignCol("local_col","foreign_col","foreign_table")
|
||||||
|
package migration
|
@ -52,6 +52,26 @@ type Migrationer interface {
|
|||||||
GetCreated() int64
|
GetCreated() int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Migration defines the migrations by either SQL or DDL
|
||||||
|
type Migration struct {
|
||||||
|
sqls []string
|
||||||
|
Created string
|
||||||
|
TableName string
|
||||||
|
Engine string
|
||||||
|
Charset string
|
||||||
|
ModifyType string
|
||||||
|
Columns []*Column
|
||||||
|
Indexes []*Index
|
||||||
|
Primary []*Column
|
||||||
|
Uniques []*Unique
|
||||||
|
Foreigns []*Foreign
|
||||||
|
Renames []*RenameColumn
|
||||||
|
RemoveColumns []*Column
|
||||||
|
RemoveIndexes []*Index
|
||||||
|
RemoveUniques []*Unique
|
||||||
|
RemoveForeigns []*Foreign
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
migrationMap map[string]Migrationer
|
migrationMap map[string]Migrationer
|
||||||
)
|
)
|
||||||
@ -60,20 +80,34 @@ func init() {
|
|||||||
migrationMap = make(map[string]Migrationer)
|
migrationMap = make(map[string]Migrationer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration the basic type which will implement the basic type
|
|
||||||
type Migration struct {
|
|
||||||
sqls []string
|
|
||||||
Created string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up implement in the Inheritance struct for upgrade
|
// Up implement in the Inheritance struct for upgrade
|
||||||
func (m *Migration) Up() {
|
func (m *Migration) Up() {
|
||||||
|
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "reverse":
|
||||||
|
m.ModifyType = "alter"
|
||||||
|
case "delete":
|
||||||
|
m.ModifyType = "create"
|
||||||
|
}
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down implement in the Inheritance struct for down
|
// Down implement in the Inheritance struct for down
|
||||||
func (m *Migration) Down() {
|
func (m *Migration) Down() {
|
||||||
|
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "alter":
|
||||||
|
m.ModifyType = "reverse"
|
||||||
|
case "create":
|
||||||
|
m.ModifyType = "delete"
|
||||||
|
}
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Migrate adds the SQL to the execution list
|
||||||
|
func (m *Migration) Migrate(migrationType string) {
|
||||||
|
m.ModifyType = migrationType
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQL add sql want to execute
|
// SQL add sql want to execute
|
||||||
@ -138,7 +172,7 @@ func Register(name string, m Migrationer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade upgrate the migration from lasttime
|
// Upgrade upgrade the migration from lasttime
|
||||||
func Upgrade(lasttime int64) error {
|
func Upgrade(lasttime int64) error {
|
||||||
sm := sortMap(migrationMap)
|
sm := sortMap(migrationMap)
|
||||||
i := 0
|
i := 0
|
||||||
|
11
namespace.go
11
namespace.go
@ -267,13 +267,12 @@ func addPrefix(t *Tree, prefix string) {
|
|||||||
addPrefix(t.wildcard, prefix)
|
addPrefix(t.wildcard, prefix)
|
||||||
}
|
}
|
||||||
for _, l := range t.leaves {
|
for _, l := range t.leaves {
|
||||||
if c, ok := l.runObject.(*controllerInfo); ok {
|
if c, ok := l.runObject.(*ControllerInfo); ok {
|
||||||
if !strings.HasPrefix(c.pattern, prefix) {
|
if !strings.HasPrefix(c.pattern, prefix) {
|
||||||
c.pattern = prefix + c.pattern
|
c.pattern = prefix + c.pattern
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NSCond is Namespace Condition
|
// NSCond is Namespace Condition
|
||||||
@ -284,16 +283,16 @@ func NSCond(cond namespaceCond) LinkNamespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NSBefore Namespace BeforeRouter filter
|
// NSBefore Namespace BeforeRouter filter
|
||||||
func NSBefore(filiterList ...FilterFunc) LinkNamespace {
|
func NSBefore(filterList ...FilterFunc) LinkNamespace {
|
||||||
return func(ns *Namespace) {
|
return func(ns *Namespace) {
|
||||||
ns.Filter("before", filiterList...)
|
ns.Filter("before", filterList...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NSAfter add Namespace FinishRouter filter
|
// NSAfter add Namespace FinishRouter filter
|
||||||
func NSAfter(filiterList ...FilterFunc) LinkNamespace {
|
func NSAfter(filterList ...FilterFunc) LinkNamespace {
|
||||||
return func(ns *Namespace) {
|
return func(ns *Namespace) {
|
||||||
ns.Filter("after", filiterList...)
|
ns.Filter("after", filterList...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,10 +139,7 @@ func TestNamespaceCond(t *testing.T) {
|
|||||||
|
|
||||||
ns := NewNamespace("/v2")
|
ns := NewNamespace("/v2")
|
||||||
ns.Cond(func(ctx *context.Context) bool {
|
ns.Cond(func(ctx *context.Context) bool {
|
||||||
if ctx.Input.Domain() == "beego.me" {
|
return ctx.Input.Domain() == "beego.me"
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}).
|
}).
|
||||||
AutoRouter(&TestController{})
|
AutoRouter(&TestController{})
|
||||||
AddNamespace(ns)
|
AddNamespace(ns)
|
||||||
|
@ -61,6 +61,9 @@ func init() {
|
|||||||
|
|
||||||
// set default database
|
// set default database
|
||||||
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||||
|
|
||||||
|
// create table
|
||||||
|
orm.RunSyncdb("default", false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -150,7 +150,7 @@ func (d *commandSyncDb) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, fi := range mi.fields.fieldsDB {
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
if _, ok := columns[fi.column]; ok == false {
|
if _, ok := columns[fi.column]; !ok {
|
||||||
fields = append(fields, fi)
|
fields = append(fields, fi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ func (d *commandSyncDb) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, idx := range indexes[mi.table] {
|
for _, idx := range indexes[mi.table] {
|
||||||
if d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) == false {
|
if !d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) {
|
||||||
if !d.noInfo {
|
if !d.noInfo {
|
||||||
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
||||||
}
|
}
|
||||||
|
@ -51,12 +51,14 @@ checkColumn:
|
|||||||
switch fieldType {
|
switch fieldType {
|
||||||
case TypeBooleanField:
|
case TypeBooleanField:
|
||||||
col = T["bool"]
|
col = T["bool"]
|
||||||
case TypeCharField:
|
case TypeVarCharField:
|
||||||
if al.Driver == DRPostgres && fi.toText {
|
if al.Driver == DRPostgres && fi.toText {
|
||||||
col = T["string-text"]
|
col = T["string-text"]
|
||||||
} else {
|
} else {
|
||||||
col = fmt.Sprintf(T["string"], fieldSize)
|
col = fmt.Sprintf(T["string"], fieldSize)
|
||||||
}
|
}
|
||||||
|
case TypeCharField:
|
||||||
|
col = fmt.Sprintf(T["string-char"], fieldSize)
|
||||||
case TypeTextField:
|
case TypeTextField:
|
||||||
col = T["string-text"]
|
col = T["string-text"]
|
||||||
case TypeTimeField:
|
case TypeTimeField:
|
||||||
@ -89,20 +91,20 @@ checkColumn:
|
|||||||
col = T["float64"]
|
col = T["float64"]
|
||||||
case TypeDecimalField:
|
case TypeDecimalField:
|
||||||
s := T["float64-decimal"]
|
s := T["float64-decimal"]
|
||||||
if strings.Index(s, "%d") == -1 {
|
if !strings.Contains(s, "%d") {
|
||||||
col = s
|
col = s
|
||||||
} else {
|
} else {
|
||||||
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
||||||
}
|
}
|
||||||
case TypeJSONField:
|
case TypeJSONField:
|
||||||
if al.Driver != DRPostgres {
|
if al.Driver != DRPostgres {
|
||||||
fieldType = TypeCharField
|
fieldType = TypeVarCharField
|
||||||
goto checkColumn
|
goto checkColumn
|
||||||
}
|
}
|
||||||
col = T["json"]
|
col = T["json"]
|
||||||
case TypeJsonbField:
|
case TypeJsonbField:
|
||||||
if al.Driver != DRPostgres {
|
if al.Driver != DRPostgres {
|
||||||
fieldType = TypeCharField
|
fieldType = TypeVarCharField
|
||||||
goto checkColumn
|
goto checkColumn
|
||||||
}
|
}
|
||||||
col = T["jsonb"]
|
col = T["jsonb"]
|
||||||
@ -120,7 +122,7 @@ func getColumnAddQuery(al *alias, fi *fieldInfo) string {
|
|||||||
Q := al.DbBaser.TableQuote()
|
Q := al.DbBaser.TableQuote()
|
||||||
typ := getColumnTyp(al, fi)
|
typ := getColumnTyp(al, fi)
|
||||||
|
|
||||||
if fi.null == false {
|
if !fi.null {
|
||||||
typ += " " + "NOT NULL"
|
typ += " " + "NOT NULL"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +174,7 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
} else {
|
} else {
|
||||||
column += col
|
column += col
|
||||||
|
|
||||||
if fi.null == false {
|
if !fi.null {
|
||||||
column += " " + "NOT NULL"
|
column += " " + "NOT NULL"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,9 +194,13 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Index(column, "%COL%") != -1 {
|
if strings.Contains(column, "%COL%") {
|
||||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fi.description != "" {
|
||||||
|
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
|
||||||
|
}
|
||||||
|
|
||||||
columns = append(columns, column)
|
columns = append(columns, column)
|
||||||
}
|
}
|
||||||
|
93
orm/db.go
93
orm/db.go
@ -48,6 +48,7 @@ var (
|
|||||||
"lte": true,
|
"lte": true,
|
||||||
"eq": true,
|
"eq": true,
|
||||||
"nq": true,
|
"nq": true,
|
||||||
|
"ne": true,
|
||||||
"startswith": true,
|
"startswith": true,
|
||||||
"endswith": true,
|
"endswith": true,
|
||||||
"istartswith": true,
|
"istartswith": true,
|
||||||
@ -86,7 +87,7 @@ func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string,
|
|||||||
} else {
|
} else {
|
||||||
panic(fmt.Errorf("wrong db field/column name `%s` for model `%s`", column, mi.fullName))
|
panic(fmt.Errorf("wrong db field/column name `%s` for model `%s`", column, mi.fullName))
|
||||||
}
|
}
|
||||||
if fi.dbcol == false || fi.auto && skipAuto {
|
if !fi.dbcol || fi.auto && skipAuto {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
|
value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
|
||||||
@ -141,7 +142,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
|||||||
} else {
|
} else {
|
||||||
value = field.Bool()
|
value = field.Bool()
|
||||||
}
|
}
|
||||||
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
|
case TypeVarCharField, TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
|
||||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||||
value = nil
|
value = nil
|
||||||
if ns.Valid {
|
if ns.Valid {
|
||||||
@ -223,7 +224,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
|||||||
value = nil
|
value = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fi.null == false && value == nil {
|
if !fi.null && value == nil {
|
||||||
return nil, fmt.Errorf("field `%s` cannot be NULL", fi.fullName)
|
return nil, fmt.Errorf("field `%s` cannot be NULL", fi.fullName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +271,7 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
|
|||||||
dbcols := make([]string, 0, len(mi.fields.dbcols))
|
dbcols := make([]string, 0, len(mi.fields.dbcols))
|
||||||
marks := make([]string, 0, len(mi.fields.dbcols))
|
marks := make([]string, 0, len(mi.fields.dbcols))
|
||||||
for _, fi := range mi.fields.fieldsDB {
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
if fi.auto == false {
|
if !fi.auto {
|
||||||
dbcols = append(dbcols, fi.column)
|
dbcols = append(dbcols, fi.column)
|
||||||
marks = append(marks, "?")
|
marks = append(marks, "?")
|
||||||
}
|
}
|
||||||
@ -310,7 +311,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// query sql ,read records and persist in dbBaser.
|
// query sql ,read records and persist in dbBaser.
|
||||||
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) error {
|
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
|
||||||
var whereCols []string
|
var whereCols []string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
@ -325,7 +326,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
|
|||||||
} else {
|
} else {
|
||||||
// default use pk value as where condtion.
|
// default use pk value as where condtion.
|
||||||
pkColumn, pkValue, ok := getExistPk(mi, ind)
|
pkColumn, pkValue, ok := getExistPk(mi, ind)
|
||||||
if ok == false {
|
if !ok {
|
||||||
return ErrMissPK
|
return ErrMissPK
|
||||||
}
|
}
|
||||||
whereCols = []string{pkColumn}
|
whereCols = []string{pkColumn}
|
||||||
@ -341,7 +342,12 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
|
|||||||
sep = fmt.Sprintf("%s = ? AND %s", Q, Q)
|
sep = fmt.Sprintf("%s = ? AND %s", Q, Q)
|
||||||
wheres := strings.Join(whereCols, sep)
|
wheres := strings.Join(whereCols, sep)
|
||||||
|
|
||||||
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ?", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q)
|
forUpdate := ""
|
||||||
|
if isForUpdate {
|
||||||
|
forUpdate = "FOR UPDATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ? %s", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q, forUpdate)
|
||||||
|
|
||||||
refs := make([]interface{}, colsNum)
|
refs := make([]interface{}, colsNum)
|
||||||
for i := range refs {
|
for i := range refs {
|
||||||
@ -501,10 +507,9 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
|||||||
case DRPostgres:
|
case DRPostgres:
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName)
|
return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName)
|
||||||
} else {
|
|
||||||
args0 = strings.ToLower(args[0])
|
|
||||||
iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0)
|
|
||||||
}
|
}
|
||||||
|
args0 = strings.ToLower(args[0])
|
||||||
|
iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0)
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
|
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
|
||||||
}
|
}
|
||||||
@ -586,7 +591,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
|||||||
row := q.QueryRow(query, values...)
|
row := q.QueryRow(query, values...)
|
||||||
var id int64
|
var id int64
|
||||||
err = row.Scan(&id)
|
err = row.Scan(&id)
|
||||||
if err.Error() == `pq: syntax error at or near "ON"` {
|
if err != nil && err.Error() == `pq: syntax error at or near "ON"` {
|
||||||
err = fmt.Errorf("postgres version must 9.5 or higher")
|
err = fmt.Errorf("postgres version must 9.5 or higher")
|
||||||
}
|
}
|
||||||
return id, err
|
return id, err
|
||||||
@ -595,7 +600,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
|||||||
// execute update sql dbQuerier with given struct reflect.Value.
|
// execute update sql dbQuerier with given struct reflect.Value.
|
||||||
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
|
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
|
||||||
pkName, pkValue, ok := getExistPk(mi, ind)
|
pkName, pkValue, ok := getExistPk(mi, ind)
|
||||||
if ok == false {
|
if !ok {
|
||||||
return 0, ErrMissPK
|
return 0, ErrMissPK
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,18 +639,36 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
|||||||
|
|
||||||
// execute delete sql dbQuerier with given struct reflect.Value.
|
// execute delete sql dbQuerier with given struct reflect.Value.
|
||||||
// delete index is pk.
|
// delete index is pk.
|
||||||
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
|
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
|
||||||
pkName, pkValue, ok := getExistPk(mi, ind)
|
var whereCols []string
|
||||||
if ok == false {
|
var args []interface{}
|
||||||
return 0, ErrMissPK
|
// if specify cols length > 0, then use it for where condition.
|
||||||
|
if len(cols) > 0 {
|
||||||
|
var err error
|
||||||
|
whereCols = make([]string, 0, len(cols))
|
||||||
|
args, _, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// default use pk value as where condtion.
|
||||||
|
pkColumn, pkValue, ok := getExistPk(mi, ind)
|
||||||
|
if !ok {
|
||||||
|
return 0, ErrMissPK
|
||||||
|
}
|
||||||
|
whereCols = []string{pkColumn}
|
||||||
|
args = append(args, pkValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
Q := d.ins.TableQuote()
|
Q := d.ins.TableQuote()
|
||||||
|
|
||||||
query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, pkName, Q)
|
sep := fmt.Sprintf("%s = ? AND %s", Q, Q)
|
||||||
|
wheres := strings.Join(whereCols, sep)
|
||||||
|
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, wheres, Q)
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
res, err := q.Exec(query, pkValue)
|
res, err := q.Exec(query, args...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
num, err := res.RowsAffected()
|
num, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -659,7 +682,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
|||||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
|
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := d.deleteRels(q, mi, []interface{}{pkValue}, tz)
|
err := d.deleteRels(q, mi, args, tz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return num, err
|
return num, err
|
||||||
}
|
}
|
||||||
@ -675,7 +698,7 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
|||||||
columns := make([]string, 0, len(params))
|
columns := make([]string, 0, len(params))
|
||||||
values := make([]interface{}, 0, len(params))
|
values := make([]interface{}, 0, len(params))
|
||||||
for col, val := range params {
|
for col, val := range params {
|
||||||
if fi, ok := mi.fields.GetByAny(col); ok == false || fi.dbcol == false {
|
if fi, ok := mi.fields.GetByAny(col); !ok || !fi.dbcol {
|
||||||
panic(fmt.Errorf("wrong field/column name `%s`", col))
|
panic(fmt.Errorf("wrong field/column name `%s`", col))
|
||||||
} else {
|
} else {
|
||||||
columns = append(columns, fi.column)
|
columns = append(columns, fi.column)
|
||||||
@ -810,7 +833,11 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
|||||||
if err := rs.Scan(&ref); err != nil {
|
if err := rs.Scan(&ref); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
args = append(args, reflect.ValueOf(ref).Interface())
|
pkValue, err := d.convertValueFromDB(mi.fields.pk, reflect.ValueOf(ref).Interface(), tz)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
args = append(args, pkValue)
|
||||||
cnt++
|
cnt++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,7 +932,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
if hasRel {
|
if hasRel {
|
||||||
for _, fi := range mi.fields.fieldsDB {
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
if fi.fieldType&IsRelField > 0 {
|
if fi.fieldType&IsRelField > 0 {
|
||||||
if maps[fi.column] == false {
|
if !maps[fi.column] {
|
||||||
tCols = append(tCols, fi.column)
|
tCols = append(tCols, fi.column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -942,6 +969,10 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
}
|
}
|
||||||
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
||||||
|
|
||||||
|
if qs.forupdate {
|
||||||
|
query += " FOR UPDATE"
|
||||||
|
}
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
var rs *sql.Rows
|
var rs *sql.Rows
|
||||||
@ -963,7 +994,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
|
|
||||||
var cnt int64
|
var cnt int64
|
||||||
for rs.Next() {
|
for rs.Next() {
|
||||||
if one && cnt == 0 || one == false {
|
if one && cnt == 0 || !one {
|
||||||
if err := rs.Scan(refs...); err != nil {
|
if err := rs.Scan(refs...); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -1043,7 +1074,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
cnt++
|
cnt++
|
||||||
}
|
}
|
||||||
|
|
||||||
if one == false {
|
if !one {
|
||||||
if cnt > 0 {
|
if cnt > 0 {
|
||||||
ind.Set(slice)
|
ind.Set(slice)
|
||||||
} else {
|
} else {
|
||||||
@ -1086,7 +1117,7 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
|
|||||||
|
|
||||||
// generate sql with replacing operator string placeholders and replaced values.
|
// generate sql with replacing operator string placeholders and replaced values.
|
||||||
func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator string, args []interface{}, tz *time.Location) (string, []interface{}) {
|
func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator string, args []interface{}, tz *time.Location) (string, []interface{}) {
|
||||||
sql := ""
|
var sql string
|
||||||
params := getFlatParams(fi, args, tz)
|
params := getFlatParams(fi, args, tz)
|
||||||
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
@ -1213,7 +1244,7 @@ setValue:
|
|||||||
}
|
}
|
||||||
value = b
|
value = b
|
||||||
}
|
}
|
||||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||||
if str == nil {
|
if str == nil {
|
||||||
value = ToStr(val)
|
value = ToStr(val)
|
||||||
} else {
|
} else {
|
||||||
@ -1333,7 +1364,7 @@ end:
|
|||||||
func (d *dbBase) setFieldValue(fi *fieldInfo, value interface{}, field reflect.Value) (interface{}, error) {
|
func (d *dbBase) setFieldValue(fi *fieldInfo, value interface{}, field reflect.Value) (interface{}, error) {
|
||||||
|
|
||||||
fieldType := fi.fieldType
|
fieldType := fi.fieldType
|
||||||
isNative := fi.isFielder == false
|
isNative := !fi.isFielder
|
||||||
|
|
||||||
setValue:
|
setValue:
|
||||||
switch {
|
switch {
|
||||||
@ -1359,7 +1390,7 @@ setValue:
|
|||||||
field.SetBool(value.(bool))
|
field.SetBool(value.(bool))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||||
if isNative {
|
if isNative {
|
||||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
@ -1509,7 +1540,7 @@ setValue:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNative == false {
|
if !isNative {
|
||||||
fd := field.Addr().Interface().(Fielder)
|
fd := field.Addr().Interface().(Fielder)
|
||||||
err := fd.SetRaw(value)
|
err := fd.SetRaw(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1570,7 +1601,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
|
|||||||
infos = make([]*fieldInfo, 0, len(exprs))
|
infos = make([]*fieldInfo, 0, len(exprs))
|
||||||
for _, ex := range exprs {
|
for _, ex := range exprs {
|
||||||
index, name, fi, suc := tables.parseExprs(mi, strings.Split(ex, ExprSep))
|
index, name, fi, suc := tables.parseExprs(mi, strings.Split(ex, ExprSep))
|
||||||
if suc == false {
|
if !suc {
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", ex))
|
panic(fmt.Errorf("unknown field/column name `%s`", ex))
|
||||||
}
|
}
|
||||||
cols = append(cols, fmt.Sprintf("%s.%s%s%s %s%s%s", index, Q, fi.column, Q, Q, name, Q))
|
cols = append(cols, fmt.Sprintf("%s.%s%s%s %s%s%s", index, Q, fi.column, Q, Q, name, Q))
|
||||||
@ -1709,7 +1740,7 @@ func (d *dbBase) TableQuote() string {
|
|||||||
return "`"
|
return "`"
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace value placeholer in parametered sql string.
|
// replace value placeholder in parametered sql string.
|
||||||
func (d *dbBase) ReplaceMarks(query *string) {
|
func (d *dbBase) ReplaceMarks(query *string) {
|
||||||
// default use `?` as mark, do nothing
|
// default use `?` as mark, do nothing
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,8 @@ var (
|
|||||||
"sqlite3": DRSqlite,
|
"sqlite3": DRSqlite,
|
||||||
"tidb": DRTiDB,
|
"tidb": DRTiDB,
|
||||||
"oracle": DROracle,
|
"oracle": DROracle,
|
||||||
|
"oci8": DROracle, // github.com/mattn/go-oci8
|
||||||
|
"ora": DROracle, //https://github.com/rana/ora
|
||||||
}
|
}
|
||||||
dbBasers = map[DriverType]dbBaser{
|
dbBasers = map[DriverType]dbBaser{
|
||||||
DRMySQL: newdbBaseMysql(),
|
DRMySQL: newdbBaseMysql(),
|
||||||
@ -80,7 +82,7 @@ type _dbCache struct {
|
|||||||
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
||||||
ac.mux.Lock()
|
ac.mux.Lock()
|
||||||
defer ac.mux.Unlock()
|
defer ac.mux.Unlock()
|
||||||
if _, ok := ac.cache[name]; ok == false {
|
if _, ok := ac.cache[name]; !ok {
|
||||||
ac.cache[name] = al
|
ac.cache[name] = al
|
||||||
added = true
|
added = true
|
||||||
}
|
}
|
||||||
@ -117,7 +119,7 @@ type alias struct {
|
|||||||
func detectTZ(al *alias) {
|
func detectTZ(al *alias) {
|
||||||
// orm timezone system match database
|
// orm timezone system match database
|
||||||
// default use Local
|
// default use Local
|
||||||
al.TZ = time.Local
|
al.TZ = DefaultTimeLoc
|
||||||
|
|
||||||
if al.DriverName == "sphinx" {
|
if al.DriverName == "sphinx" {
|
||||||
return
|
return
|
||||||
@ -134,7 +136,9 @@ func detectTZ(al *alias) {
|
|||||||
}
|
}
|
||||||
t, err := time.Parse("-07:00:00", tz)
|
t, err := time.Parse("-07:00:00", tz)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
al.TZ = t.Location()
|
if t.Location().String() != "" {
|
||||||
|
al.TZ = t.Location()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||||
}
|
}
|
||||||
@ -186,7 +190,7 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
|
|||||||
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataBaseCache.add(aliasName, al) == false {
|
if !dataBaseCache.add(aliasName, al) {
|
||||||
return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,11 +248,11 @@ end:
|
|||||||
|
|
||||||
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
||||||
func RegisterDriver(driverName string, typ DriverType) error {
|
func RegisterDriver(driverName string, typ DriverType) error {
|
||||||
if t, ok := drivers[driverName]; ok == false {
|
if t, ok := drivers[driverName]; !ok {
|
||||||
drivers[driverName] = typ
|
drivers[driverName] = typ
|
||||||
} else {
|
} else {
|
||||||
if t != typ {
|
if t != typ {
|
||||||
return fmt.Errorf("driverName `%s` db driver already registered and is other type\n", driverName)
|
return fmt.Errorf("driverName `%s` db driver already registered and is other type", driverName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -259,7 +263,7 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
|||||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||||
al.TZ = tz
|
al.TZ = tz
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("DataBase alias name `%s` not registered\n", aliasName)
|
return fmt.Errorf("DataBase alias name `%s` not registered", aliasName)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -294,5 +298,5 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
|
|||||||
if ok {
|
if ok {
|
||||||
return al.DB, nil
|
return al.DB, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("DataBase of alias name `%s` not found\n", name)
|
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ package orm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mysql operators.
|
// mysql operators.
|
||||||
@ -44,6 +46,7 @@ var mysqlTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
"string-text": "longtext",
|
"string-text": "longtext",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "datetime",
|
"time.Time": "datetime",
|
||||||
@ -96,6 +99,82 @@ func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool
|
|||||||
return cnt > 0
|
return cnt > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InsertOrUpdate a row
|
||||||
|
// If your primary key or unique column conflict will update
|
||||||
|
// If no will insert
|
||||||
|
// Add "`" for mysql sql building
|
||||||
|
func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
|
||||||
|
var iouStr string
|
||||||
|
argsMap := map[string]string{}
|
||||||
|
|
||||||
|
iouStr = "ON DUPLICATE KEY UPDATE"
|
||||||
|
|
||||||
|
//Get on the key-value pairs
|
||||||
|
for _, v := range args {
|
||||||
|
kv := strings.Split(v, "=")
|
||||||
|
if len(kv) == 2 {
|
||||||
|
argsMap[strings.ToLower(kv[0])] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMulti := false
|
||||||
|
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
||||||
|
Q := d.ins.TableQuote()
|
||||||
|
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
marks := make([]string, len(names))
|
||||||
|
updateValues := make([]interface{}, 0)
|
||||||
|
updates := make([]string, len(names))
|
||||||
|
|
||||||
|
for i, v := range names {
|
||||||
|
marks[i] = "?"
|
||||||
|
valueStr := argsMap[strings.ToLower(v)]
|
||||||
|
if valueStr != "" {
|
||||||
|
updates[i] = "`" + v + "`" + "=" + valueStr
|
||||||
|
} else {
|
||||||
|
updates[i] = "`" + v + "`" + "=?"
|
||||||
|
updateValues = append(updateValues, values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, updateValues...)
|
||||||
|
|
||||||
|
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||||
|
qmarks := strings.Join(marks, ", ")
|
||||||
|
qupdates := strings.Join(updates, ", ")
|
||||||
|
columns := strings.Join(names, sep)
|
||||||
|
|
||||||
|
multi := len(values) / len(names)
|
||||||
|
|
||||||
|
if isMulti {
|
||||||
|
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||||
|
}
|
||||||
|
//conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||||
|
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||||
|
|
||||||
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||||
|
res, err := q.Exec(query, values...)
|
||||||
|
if err == nil {
|
||||||
|
if isMulti {
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
return res.LastInsertId()
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := q.QueryRow(query, values...)
|
||||||
|
var id int64
|
||||||
|
err = row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
// create new mysql dbBaser.
|
// create new mysql dbBaser.
|
||||||
func newdbBaseMysql() dbBaser {
|
func newdbBaseMysql() dbBaser {
|
||||||
b := new(dbBaseMysql)
|
b := new(dbBaseMysql)
|
||||||
|
@ -34,6 +34,7 @@ var oracleTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "VARCHAR2(%d)",
|
"string": "VARCHAR2(%d)",
|
||||||
|
"string-char": "CHAR(%d)",
|
||||||
"string-text": "VARCHAR2(%d)",
|
"string-text": "VARCHAR2(%d)",
|
||||||
"time.Time-date": "DATE",
|
"time.Time-date": "DATE",
|
||||||
"time.Time": "TIMESTAMP",
|
"time.Time": "TIMESTAMP",
|
||||||
@ -94,3 +95,43 @@ func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool
|
|||||||
row.Scan(&cnt)
|
row.Scan(&cnt)
|
||||||
return cnt > 0
|
return cnt > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute insert sql with given struct and given values.
|
||||||
|
// insert the given values, not the field values in struct.
|
||||||
|
func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
|
||||||
|
Q := d.ins.TableQuote()
|
||||||
|
|
||||||
|
marks := make([]string, len(names))
|
||||||
|
for i := range marks {
|
||||||
|
marks[i] = ":" + names[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||||
|
qmarks := strings.Join(marks, ", ")
|
||||||
|
columns := strings.Join(names, sep)
|
||||||
|
|
||||||
|
multi := len(values) / len(names)
|
||||||
|
|
||||||
|
if isMulti {
|
||||||
|
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
|
||||||
|
|
||||||
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||||
|
res, err := q.Exec(query, values...)
|
||||||
|
if err == nil {
|
||||||
|
if isMulti {
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
return res.LastInsertId()
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
row := q.QueryRow(query, values...)
|
||||||
|
var id int64
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@ var postgresTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
"string-text": "text",
|
"string-text": "text",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "timestamp with time zone",
|
"time.Time": "timestamp with time zone",
|
||||||
|
@ -43,6 +43,7 @@ var sqliteTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "character(%d)",
|
||||||
"string-text": "text",
|
"string-text": "text",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "datetime",
|
"time.Time": "datetime",
|
||||||
@ -134,7 +135,7 @@ func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var tmp, index sql.NullString
|
var tmp, index sql.NullString
|
||||||
rows.Scan(&tmp, &index, &tmp)
|
rows.Scan(&tmp, &index, &tmp, &tmp, &tmp)
|
||||||
if name == index.String {
|
if name == index.String {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool)
|
|||||||
// add table info to collection.
|
// add table info to collection.
|
||||||
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
||||||
name := strings.Join(names, ExprSep)
|
name := strings.Join(names, ExprSep)
|
||||||
if _, ok := t.tablesM[name]; ok == false {
|
if _, ok := t.tablesM[name]; !ok {
|
||||||
i := len(t.tables) + 1
|
i := len(t.tables) + 1
|
||||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||||
t.tablesM[name] = jt
|
t.tablesM[name] = jt
|
||||||
@ -261,7 +261,7 @@ loopFor:
|
|||||||
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
|
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if isRel && (fi.mi.isThrough == false || num != i) {
|
if isRel && (!fi.mi.isThrough || num != i) {
|
||||||
if fi.null || t.skipEnd {
|
if fi.null || t.skipEnd {
|
||||||
inner = false
|
inner = false
|
||||||
}
|
}
|
||||||
@ -364,7 +364,7 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
|
|||||||
}
|
}
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(mi, exprs)
|
index, _, fi, suc := t.parseExprs(mi, exprs)
|
||||||
if suc == false {
|
if !suc {
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +383,7 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sub == false && where != "" {
|
if !sub && where != "" {
|
||||||
where = "WHERE " + where
|
where = "WHERE " + where
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
|
|||||||
exprs := strings.Split(group, ExprSep)
|
exprs := strings.Split(group, ExprSep)
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
||||||
if suc == false {
|
if !suc {
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,7 +432,7 @@ func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
|
|||||||
exprs := strings.Split(order, ExprSep)
|
exprs := strings.Split(order, ExprSep)
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
||||||
if suc == false {
|
if !suc {
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interfac
|
|||||||
vu := v.Int()
|
vu := v.Int()
|
||||||
exist = true
|
exist = true
|
||||||
value = vu
|
value = vu
|
||||||
|
} else if fi.fieldType&IsRelField > 0 {
|
||||||
|
_, value, exist = getExistPk(fi.relModelInfo, reflect.Indirect(v))
|
||||||
} else {
|
} else {
|
||||||
vu := v.String()
|
vu := v.String()
|
||||||
exist = vu != ""
|
exist = vu != ""
|
||||||
@ -145,16 +147,18 @@ outFor:
|
|||||||
if v, ok := arg.(time.Time); ok {
|
if v, ok := arg.(time.Time); ok {
|
||||||
if fi != nil && fi.fieldType == TypeDateField {
|
if fi != nil && fi.fieldType == TypeDateField {
|
||||||
arg = v.In(tz).Format(formatDate)
|
arg = v.In(tz).Format(formatDate)
|
||||||
} else if fi.fieldType == TypeDateTimeField {
|
} else if fi != nil && fi.fieldType == TypeDateTimeField {
|
||||||
arg = v.In(tz).Format(formatDateTime)
|
arg = v.In(tz).Format(formatDateTime)
|
||||||
} else {
|
} else if fi != nil && fi.fieldType == TypeTimeField {
|
||||||
arg = v.In(tz).Format(formatTime)
|
arg = v.In(tz).Format(formatTime)
|
||||||
|
} else {
|
||||||
|
arg = v.In(tz).Format(formatDateTime)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
name := getFullName(typ)
|
name := getFullName(typ)
|
||||||
var value interface{}
|
var value interface{}
|
||||||
if mmi, ok := modelCache.getByFN(name); ok {
|
if mmi, ok := modelCache.getByFullName(name); ok {
|
||||||
if _, vu, exist := getExistPk(mmi, val); exist {
|
if _, vu, exist := getExistPk(mmi, val); exist {
|
||||||
value = vu
|
value = vu
|
||||||
}
|
}
|
||||||
|
@ -29,39 +29,18 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
modelCache = &_modelCache{
|
modelCache = &_modelCache{
|
||||||
cache: make(map[string]*modelInfo),
|
cache: make(map[string]*modelInfo),
|
||||||
cacheByFN: make(map[string]*modelInfo),
|
cacheByFullName: make(map[string]*modelInfo),
|
||||||
}
|
|
||||||
supportTag = map[string]int{
|
|
||||||
"-": 1,
|
|
||||||
"null": 1,
|
|
||||||
"index": 1,
|
|
||||||
"unique": 1,
|
|
||||||
"pk": 1,
|
|
||||||
"auto": 1,
|
|
||||||
"auto_now": 1,
|
|
||||||
"auto_now_add": 1,
|
|
||||||
"size": 2,
|
|
||||||
"column": 2,
|
|
||||||
"default": 2,
|
|
||||||
"rel": 2,
|
|
||||||
"reverse": 2,
|
|
||||||
"rel_table": 2,
|
|
||||||
"rel_through": 2,
|
|
||||||
"digits": 2,
|
|
||||||
"decimals": 2,
|
|
||||||
"on_delete": 2,
|
|
||||||
"type": 2,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// model info collection
|
// model info collection
|
||||||
type _modelCache struct {
|
type _modelCache struct {
|
||||||
sync.RWMutex
|
sync.RWMutex // only used outsite for bootStrap
|
||||||
orders []string
|
orders []string
|
||||||
cache map[string]*modelInfo
|
cache map[string]*modelInfo
|
||||||
cacheByFN map[string]*modelInfo
|
cacheByFullName map[string]*modelInfo
|
||||||
done bool
|
done bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all model info
|
// get all model info
|
||||||
@ -73,7 +52,7 @@ func (mc *_modelCache) all() map[string]*modelInfo {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// get orderd model info
|
// get ordered model info
|
||||||
func (mc *_modelCache) allOrdered() []*modelInfo {
|
func (mc *_modelCache) allOrdered() []*modelInfo {
|
||||||
m := make([]*modelInfo, 0, len(mc.orders))
|
m := make([]*modelInfo, 0, len(mc.orders))
|
||||||
for _, table := range mc.orders {
|
for _, table := range mc.orders {
|
||||||
@ -88,9 +67,9 @@ func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get model info by field name
|
// get model info by full name
|
||||||
func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
|
func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
|
||||||
mi, ok = mc.cacheByFN[name]
|
mi, ok = mc.cacheByFullName[name]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +77,7 @@ func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
|
|||||||
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
||||||
mii := mc.cache[table]
|
mii := mc.cache[table]
|
||||||
mc.cache[table] = mi
|
mc.cache[table] = mi
|
||||||
mc.cacheByFN[mi.fullName] = mi
|
mc.cacheByFullName[mi.fullName] = mi
|
||||||
if mii == nil {
|
if mii == nil {
|
||||||
mc.orders = append(mc.orders, table)
|
mc.orders = append(mc.orders, table)
|
||||||
}
|
}
|
||||||
@ -109,7 +88,7 @@ func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
|||||||
func (mc *_modelCache) clean() {
|
func (mc *_modelCache) clean() {
|
||||||
mc.orders = make([]string, 0)
|
mc.orders = make([]string, 0)
|
||||||
mc.cache = make(map[string]*modelInfo)
|
mc.cache = make(map[string]*modelInfo)
|
||||||
mc.cacheByFN = make(map[string]*modelInfo)
|
mc.cacheByFullName = make(map[string]*modelInfo)
|
||||||
mc.done = false
|
mc.done = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -23,24 +22,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// register models.
|
// register models.
|
||||||
// prefix means table name prefix.
|
// PrefixOrSuffix means table name prefix or suffix.
|
||||||
func registerModel(prefix string, model interface{}) {
|
// isPrefix whether the prefix is prefix or suffix
|
||||||
|
func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
|
||||||
val := reflect.ValueOf(model)
|
val := reflect.ValueOf(model)
|
||||||
ind := reflect.Indirect(val)
|
typ := reflect.Indirect(val).Type()
|
||||||
typ := ind.Type()
|
|
||||||
|
|
||||||
if val.Kind() != reflect.Ptr {
|
if val.Kind() != reflect.Ptr {
|
||||||
panic(fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
panic(fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
||||||
}
|
}
|
||||||
|
// For this case:
|
||||||
|
// u := &User{}
|
||||||
|
// registerModel(&u)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
panic(fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ))
|
||||||
|
}
|
||||||
|
|
||||||
table := getTableName(val)
|
table := getTableName(val)
|
||||||
|
|
||||||
if prefix != "" {
|
if PrefixOrSuffix != "" {
|
||||||
table = prefix + table
|
if isPrefix {
|
||||||
|
table = PrefixOrSuffix + table
|
||||||
|
} else {
|
||||||
|
table = table + PrefixOrSuffix
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// models's fullname is pkgpath + struct name
|
||||||
name := getFullName(typ)
|
name := getFullName(typ)
|
||||||
if _, ok := modelCache.getByFN(name); ok {
|
if _, ok := modelCache.getByFullName(name); ok {
|
||||||
fmt.Printf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
fmt.Printf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
@ -50,66 +59,65 @@ func registerModel(prefix string, model interface{}) {
|
|||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
info := newModelInfo(val)
|
mi := newModelInfo(val)
|
||||||
if info.fields.pk == nil {
|
if mi.fields.pk == nil {
|
||||||
outFor:
|
outFor:
|
||||||
for _, fi := range info.fields.fieldsDB {
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
if strings.ToLower(fi.name) == "id" {
|
if strings.ToLower(fi.name) == "id" {
|
||||||
switch fi.addrValue.Elem().Kind() {
|
switch fi.addrValue.Elem().Kind() {
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
fi.auto = true
|
fi.auto = true
|
||||||
fi.pk = true
|
fi.pk = true
|
||||||
info.fields.pk = fi
|
mi.fields.pk = fi
|
||||||
break outFor
|
break outFor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.fields.pk == nil {
|
if mi.fields.pk == nil {
|
||||||
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name)
|
fmt.Printf("<orm.RegisterModel> `%s` needs a primary key field, default is to use 'id' if not set\n", name)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.table = table
|
mi.table = table
|
||||||
info.pkg = typ.PkgPath()
|
mi.pkg = typ.PkgPath()
|
||||||
info.model = model
|
mi.model = model
|
||||||
info.manual = true
|
mi.manual = true
|
||||||
|
|
||||||
modelCache.set(table, info)
|
modelCache.set(table, mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// boostrap models
|
// bootstrap models
|
||||||
func bootStrap() {
|
func bootStrap() {
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
models map[string]*modelInfo
|
models map[string]*modelInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
if dataBaseCache.getDefault() == nil {
|
if dataBaseCache.getDefault() == nil {
|
||||||
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set rel and reverse model
|
||||||
|
// RelManyToMany set the relTable
|
||||||
models = modelCache.all()
|
models = modelCache.all()
|
||||||
for _, mi := range models {
|
for _, mi := range models {
|
||||||
for _, fi := range mi.fields.columns {
|
for _, fi := range mi.fields.columns {
|
||||||
if fi.rel || fi.reverse {
|
if fi.rel || fi.reverse {
|
||||||
elm := fi.addrValue.Type().Elem()
|
elm := fi.addrValue.Type().Elem()
|
||||||
switch fi.fieldType {
|
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
|
||||||
case RelReverseMany, RelManyToMany:
|
|
||||||
elm = elm.Elem()
|
elm = elm.Elem()
|
||||||
}
|
}
|
||||||
|
// check the rel or reverse model already register
|
||||||
name := getFullName(elm)
|
name := getFullName(elm)
|
||||||
mii, ok := modelCache.getByFN(name)
|
mii, ok := modelCache.getByFullName(name)
|
||||||
if ok == false || mii.pkg != elm.PkgPath() {
|
if !ok || mii.pkg != elm.PkgPath() {
|
||||||
err = fmt.Errorf("can not found rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
fi.relModelInfo = mii
|
fi.relModelInfo = mii
|
||||||
@ -117,20 +125,17 @@ func bootStrap() {
|
|||||||
switch fi.fieldType {
|
switch fi.fieldType {
|
||||||
case RelManyToMany:
|
case RelManyToMany:
|
||||||
if fi.relThrough != "" {
|
if fi.relThrough != "" {
|
||||||
msg := fmt.Sprintf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
|
||||||
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
||||||
pn := fi.relThrough[:i]
|
pn := fi.relThrough[:i]
|
||||||
rmi, ok := modelCache.getByFN(fi.relThrough)
|
rmi, ok := modelCache.getByFullName(fi.relThrough)
|
||||||
if ok == false || pn != rmi.pkg {
|
if !ok || pn != rmi.pkg {
|
||||||
err = errors.New(msg + " cannot find table")
|
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.relThroughModelInfo = rmi
|
fi.relThroughModelInfo = rmi
|
||||||
fi.relTable = rmi.table
|
fi.relTable = rmi.table
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New(msg)
|
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -138,7 +143,6 @@ func bootStrap() {
|
|||||||
if fi.relTable != "" {
|
if fi.relTable != "" {
|
||||||
i.table = fi.relTable
|
i.table = fi.relTable
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := modelCache.set(i.table, i); v != nil {
|
if v := modelCache.set(i.table, i); v != nil {
|
||||||
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
||||||
goto end
|
goto end
|
||||||
@ -153,6 +157,8 @@ func bootStrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the rel filed while the relModelInfo also has filed point to current model
|
||||||
|
// if not exist, add a new field to the relModelInfo
|
||||||
models = modelCache.all()
|
models = modelCache.all()
|
||||||
for _, mi := range models {
|
for _, mi := range models {
|
||||||
for _, fi := range mi.fields.fieldsRel {
|
for _, fi := range mi.fields.fieldsRel {
|
||||||
@ -165,8 +171,7 @@ func bootStrap() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !inModel {
|
||||||
if inModel == false {
|
|
||||||
rmi := fi.relModelInfo
|
rmi := fi.relModelInfo
|
||||||
ffi := new(fieldInfo)
|
ffi := new(fieldInfo)
|
||||||
ffi.name = mi.name
|
ffi.name = mi.name
|
||||||
@ -180,7 +185,7 @@ func bootStrap() {
|
|||||||
} else {
|
} else {
|
||||||
ffi.fieldType = RelReverseMany
|
ffi.fieldType = RelReverseMany
|
||||||
}
|
}
|
||||||
if rmi.fields.Add(ffi) == false {
|
if !rmi.fields.Add(ffi) {
|
||||||
added := false
|
added := false
|
||||||
for cnt := 0; cnt < 5; cnt++ {
|
for cnt := 0; cnt < 5; cnt++ {
|
||||||
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
|
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
|
||||||
@ -190,7 +195,7 @@ func bootStrap() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if added == false {
|
if !added {
|
||||||
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
|
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +221,6 @@ func bootStrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.reverseFieldInfoTwo == nil {
|
if fi.reverseFieldInfoTwo == nil {
|
||||||
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
||||||
fi.relThroughModelInfo.fullName)
|
fi.relThroughModelInfo.fullName)
|
||||||
@ -244,7 +248,7 @@ func bootStrap() {
|
|||||||
break mForA
|
break mForA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found == false {
|
if !found {
|
||||||
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
@ -263,7 +267,7 @@ func bootStrap() {
|
|||||||
break mForB
|
break mForB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found == false {
|
if !found {
|
||||||
mForC:
|
mForC:
|
||||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
|
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
|
||||||
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
|
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
|
||||||
@ -283,7 +287,7 @@ func bootStrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found == false {
|
if !found {
|
||||||
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
@ -300,27 +304,40 @@ end:
|
|||||||
|
|
||||||
// RegisterModel register models
|
// RegisterModel register models
|
||||||
func RegisterModel(models ...interface{}) {
|
func RegisterModel(models ...interface{}) {
|
||||||
|
if modelCache.done {
|
||||||
|
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
|
||||||
|
}
|
||||||
RegisterModelWithPrefix("", models...)
|
RegisterModelWithPrefix("", models...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterModelWithPrefix register models with a prefix
|
// RegisterModelWithPrefix register models with a prefix
|
||||||
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
|
panic(fmt.Errorf("RegisterModelWithPrefix must be run before BootStrap"))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, model := range models {
|
for _, model := range models {
|
||||||
registerModel(prefix, model)
|
registerModel(prefix, model, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BootStrap bootrap models.
|
// RegisterModelWithSuffix register models with a suffix
|
||||||
|
func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
||||||
|
if modelCache.done {
|
||||||
|
panic(fmt.Errorf("RegisterModelWithSuffix must be run before BootStrap"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, model := range models {
|
||||||
|
registerModel(suffix, model, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootStrap bootstrap models.
|
||||||
// make all model parsed and can not add more models
|
// make all model parsed and can not add more models
|
||||||
func BootStrap() {
|
func BootStrap() {
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
modelCache.Lock()
|
modelCache.Lock()
|
||||||
defer modelCache.Unlock()
|
defer modelCache.Unlock()
|
||||||
bootStrap()
|
bootStrap()
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
// Define the Type enum
|
// Define the Type enum
|
||||||
const (
|
const (
|
||||||
TypeBooleanField = 1 << iota
|
TypeBooleanField = 1 << iota
|
||||||
|
TypeVarCharField
|
||||||
TypeCharField
|
TypeCharField
|
||||||
TypeTextField
|
TypeTextField
|
||||||
TypeTimeField
|
TypeTimeField
|
||||||
@ -49,9 +50,9 @@ const (
|
|||||||
|
|
||||||
// Define some logic enum
|
// Define some logic enum
|
||||||
const (
|
const (
|
||||||
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6
|
IsIntegerField = ^-TypePositiveBigIntegerField >> 6 << 7
|
||||||
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10
|
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 10 << 11
|
||||||
IsRelField = ^-RelReverseMany >> 17 << 18
|
IsRelField = ^-RelReverseMany >> 18 << 19
|
||||||
IsFieldType = ^-RelReverseMany<<1 + 1
|
IsFieldType = ^-RelReverseMany<<1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ func (e *BooleanField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Bool()
|
v, err := StrTo(d).Bool()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -126,7 +127,7 @@ func (e *CharField) String() string {
|
|||||||
|
|
||||||
// FieldType return the enum type
|
// FieldType return the enum type
|
||||||
func (e *CharField) FieldType() int {
|
func (e *CharField) FieldType() int {
|
||||||
return TypeCharField
|
return TypeVarCharField
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRaw set the interface to string
|
// SetRaw set the interface to string
|
||||||
@ -190,7 +191,7 @@ func (e *TimeField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatTime)
|
v, err := timeParse(d, formatTime)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -232,7 +233,7 @@ func (e *DateField) Set(d time.Time) {
|
|||||||
*e = DateField(d)
|
*e = DateField(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String convert datatime to string
|
// String convert datetime to string
|
||||||
func (e *DateField) String() string {
|
func (e *DateField) String() string {
|
||||||
return e.Value().String()
|
return e.Value().String()
|
||||||
}
|
}
|
||||||
@ -249,7 +250,7 @@ func (e *DateField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatDate)
|
v, err := timeParse(d, formatDate)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -272,12 +273,12 @@ var _ Fielder = new(DateField)
|
|||||||
// Takes the same extra arguments as DateField.
|
// Takes the same extra arguments as DateField.
|
||||||
type DateTimeField time.Time
|
type DateTimeField time.Time
|
||||||
|
|
||||||
// Value return the datatime value
|
// Value return the datetime value
|
||||||
func (e DateTimeField) Value() time.Time {
|
func (e DateTimeField) Value() time.Time {
|
||||||
return time.Time(e)
|
return time.Time(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set set the time.Time to datatime
|
// Set set the time.Time to datetime
|
||||||
func (e *DateTimeField) Set(d time.Time) {
|
func (e *DateTimeField) Set(d time.Time) {
|
||||||
*e = DateTimeField(d)
|
*e = DateTimeField(d)
|
||||||
}
|
}
|
||||||
@ -299,7 +300,7 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatDateTime)
|
v, err := timeParse(d, formatDateTime)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -309,12 +310,12 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawValue return the datatime value
|
// RawValue return the datetime value
|
||||||
func (e *DateTimeField) RawValue() interface{} {
|
func (e *DateTimeField) RawValue() interface{} {
|
||||||
return e.Value()
|
return e.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify datatime implement fielder
|
// verify datetime implement fielder
|
||||||
var _ Fielder = new(DateTimeField)
|
var _ Fielder = new(DateTimeField)
|
||||||
|
|
||||||
// FloatField A floating-point number represented in go by a float32 value.
|
// FloatField A floating-point number represented in go by a float32 value.
|
||||||
@ -349,9 +350,10 @@ func (e *FloatField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Float64()
|
v, err := StrTo(d).Float64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -396,9 +398,10 @@ func (e *SmallIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int16()
|
v, err := StrTo(d).Int16()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -443,9 +446,10 @@ func (e *IntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int32()
|
v, err := StrTo(d).Int32()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -490,9 +494,10 @@ func (e *BigIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int64()
|
v, err := StrTo(d).Int64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -537,9 +542,10 @@ func (e *PositiveSmallIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint16()
|
v, err := StrTo(d).Uint16()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -584,9 +590,10 @@ func (e *PositiveIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint32()
|
v, err := StrTo(d).Uint32()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -631,9 +638,10 @@ func (e *PositiveBigIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint64()
|
v, err := StrTo(d).Uint64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func (f *fields) Add(fi *fieldInfo) (added bool) {
|
|||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := f.fieldsByType[fi.fieldType]; ok == false {
|
if _, ok := f.fieldsByType[fi.fieldType]; !ok {
|
||||||
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
|
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
|
||||||
}
|
}
|
||||||
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
|
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
|
||||||
@ -104,7 +104,7 @@ type fieldInfo struct {
|
|||||||
mi *modelInfo
|
mi *modelInfo
|
||||||
fieldIndex []int
|
fieldIndex []int
|
||||||
fieldType int
|
fieldType int
|
||||||
dbcol bool
|
dbcol bool // table column fk and onetoone
|
||||||
inModel bool
|
inModel bool
|
||||||
name string
|
name string
|
||||||
fullName string
|
fullName string
|
||||||
@ -116,13 +116,13 @@ type fieldInfo struct {
|
|||||||
null bool
|
null bool
|
||||||
index bool
|
index bool
|
||||||
unique bool
|
unique bool
|
||||||
colDefault bool
|
colDefault bool // whether has default tag
|
||||||
initial StrTo
|
initial StrTo // store the default value
|
||||||
size int
|
size int
|
||||||
toText bool
|
toText bool
|
||||||
autoNow bool
|
autoNow bool
|
||||||
autoNowAdd bool
|
autoNowAdd bool
|
||||||
rel bool
|
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
|
||||||
reverse bool
|
reverse bool
|
||||||
reverseField string
|
reverseField string
|
||||||
reverseFieldInfo *fieldInfo
|
reverseFieldInfo *fieldInfo
|
||||||
@ -134,8 +134,9 @@ type fieldInfo struct {
|
|||||||
relModelInfo *modelInfo
|
relModelInfo *modelInfo
|
||||||
digits int
|
digits int
|
||||||
decimals int
|
decimals int
|
||||||
isFielder bool
|
isFielder bool // implement Fielder interface
|
||||||
onDelete string
|
onDelete string
|
||||||
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
// new field info
|
// new field info
|
||||||
@ -143,7 +144,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN
|
|||||||
var (
|
var (
|
||||||
tag string
|
tag string
|
||||||
tagValue string
|
tagValue string
|
||||||
initial StrTo
|
initial StrTo // store the default value
|
||||||
fieldType int
|
fieldType int
|
||||||
attrs map[string]bool
|
attrs map[string]bool
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
@ -152,6 +153,10 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN
|
|||||||
|
|
||||||
fi = new(fieldInfo)
|
fi = new(fieldInfo)
|
||||||
|
|
||||||
|
// if field which CanAddr is the follow type
|
||||||
|
// A value is addressable if it is an element of a slice,
|
||||||
|
// an element of an addressable array, a field of an
|
||||||
|
// addressable struct, or the result of dereferencing a pointer.
|
||||||
addrField = field
|
addrField = field
|
||||||
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
||||||
addrField = field.Addr()
|
addrField = field.Addr()
|
||||||
@ -162,7 +167,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseStructTag(sf.Tag.Get(defaultStructTagName), &attrs, &tags)
|
attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName))
|
||||||
|
|
||||||
if _, ok := attrs["-"]; ok {
|
if _, ok := attrs["-"]; ok {
|
||||||
return nil, errSkipField
|
return nil, errSkipField
|
||||||
@ -188,7 +193,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
fieldType = f.FieldType()
|
fieldType = f.FieldType()
|
||||||
if fieldType&IsRelField > 0 {
|
if fieldType&IsRelField > 0 {
|
||||||
err = fmt.Errorf("unsupport rel type custom field")
|
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/astaxie/beego/blob/master/orm/models_fields.go#L24-L42")
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -211,7 +216,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
break checkType
|
break checkType
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("error")
|
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
|
||||||
goto wrongTag
|
goto wrongTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +236,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
break checkType
|
break checkType
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("error")
|
err = fmt.Errorf("reverse only allow these value: one, many")
|
||||||
goto wrongTag
|
goto wrongTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,8 +245,10 @@ checkType:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
if fieldType == TypeCharField {
|
if fieldType == TypeVarCharField {
|
||||||
switch tags["type"] {
|
switch tags["type"] {
|
||||||
|
case "char":
|
||||||
|
fieldType = TypeCharField
|
||||||
case "text":
|
case "text":
|
||||||
fieldType = TypeTextField
|
fieldType = TypeTextField
|
||||||
case "json":
|
case "json":
|
||||||
@ -261,6 +268,9 @@ checkType:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the rel and reverse type
|
||||||
|
// rel should Ptr
|
||||||
|
// reverse should slice []*struct
|
||||||
switch fieldType {
|
switch fieldType {
|
||||||
case RelForeignKey, RelOneToOne, RelReverseOne:
|
case RelForeignKey, RelOneToOne, RelReverseOne:
|
||||||
if field.Kind() != reflect.Ptr {
|
if field.Kind() != reflect.Ptr {
|
||||||
@ -291,6 +301,7 @@ checkType:
|
|||||||
fi.sf = sf
|
fi.sf = sf
|
||||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||||
|
|
||||||
|
fi.description = sf.Tag.Get("description")
|
||||||
fi.null = attrs["null"]
|
fi.null = attrs["null"]
|
||||||
fi.index = attrs["index"]
|
fi.index = attrs["index"]
|
||||||
fi.auto = attrs["auto"]
|
fi.auto = attrs["auto"]
|
||||||
@ -327,12 +338,12 @@ checkType:
|
|||||||
switch onDelete {
|
switch onDelete {
|
||||||
case odCascade, odDoNothing:
|
case odCascade, odDoNothing:
|
||||||
case odSetDefault:
|
case odSetDefault:
|
||||||
if initial.Exist() == false {
|
if !initial.Exist() {
|
||||||
err = errors.New("on_delete: set_default need set field a default value")
|
err = errors.New("on_delete: set_default need set field a default value")
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
case odSetNULL:
|
case odSetNULL:
|
||||||
if fi.null == false {
|
if !fi.null {
|
||||||
err = errors.New("on_delete: set_null need set field null")
|
err = errors.New("on_delete: set_null need set field null")
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
@ -350,7 +361,7 @@ checkType:
|
|||||||
|
|
||||||
switch fieldType {
|
switch fieldType {
|
||||||
case TypeBooleanField:
|
case TypeBooleanField:
|
||||||
case TypeCharField, TypeJSONField, TypeJsonbField:
|
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
|
||||||
if size != "" {
|
if size != "" {
|
||||||
v, e := StrTo(size).Int32()
|
v, e := StrTo(size).Int32()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@ -399,14 +410,12 @@ checkType:
|
|||||||
|
|
||||||
if fi.auto || fi.pk {
|
if fi.auto || fi.pk {
|
||||||
if fi.auto {
|
if fi.auto {
|
||||||
|
|
||||||
switch addrField.Elem().Kind() {
|
switch addrField.Elem().Kind() {
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.pk = true
|
fi.pk = true
|
||||||
}
|
}
|
||||||
fi.null = false
|
fi.null = false
|
||||||
@ -418,8 +427,8 @@ checkType:
|
|||||||
fi.index = false
|
fi.index = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// can not set default for these type
|
||||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||||
// can not set default
|
|
||||||
initial.Clear()
|
initial.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user