Compare commits
524 Commits
Author | SHA1 | Date | |
---|---|---|---|
d0e2c5c67a | |||
cfcfeb7b99 | |||
62188a37c6 | |||
0b659961ba | |||
37aa2dc19f | |||
4a3902432a | |||
fc19f8f183 | |||
8c9320725b | |||
0eaa58e0ca | |||
2add87b829 | |||
5260a60ad2 | |||
eb5d11c01b | |||
e858f903a3 | |||
4037f952ec | |||
6cb3f588c7 | |||
8b0929b4bc | |||
fc339fc3e0 | |||
2c868a9557 | |||
8ba6dbb9a0 | |||
ff18ae2562 | |||
57781d1001 | |||
419c3fc772 | |||
2ad399db05 | |||
eb9b958309 | |||
ce332713c4 | |||
e18b9f0321 | |||
b459cf2347 | |||
933e98e4f2 | |||
3f3bf299a6 | |||
b08a4a86c1 | |||
3f0e55de56 | |||
235d2740c7 | |||
00020139c5 | |||
7aa307bd24 | |||
1c434dc6f4 | |||
8cc8b022ee | |||
05e197ffff | |||
d718d9c8ee | |||
f752c98d81 | |||
7c3d1d3402 | |||
764bbd9897 | |||
67b9c63528 | |||
b87e122ac4 | |||
dcaff38cb9 | |||
b68c814c50 | |||
9076ce7d17 | |||
c9ccb9cc71 | |||
0269a66940 | |||
e481671814 | |||
53b2a29b44 | |||
cb49be7815 | |||
68d4c2c0d8 | |||
de0113ae6a | |||
7242bc862e | |||
bc0d4ac7cb | |||
b346617dc1 | |||
b8ed790858 | |||
48cefc6767 | |||
079a41136e | |||
e01fbd497c | |||
9edf3143e1 | |||
00065f2b08 | |||
0869df5588 | |||
54c89c0d63 | |||
1c52f6834a | |||
c3bc2bedc0 | |||
7b27b7fed0 | |||
3a996c132d | |||
65b9011bc1 | |||
b9fdbdf7b5 | |||
436f9a7468 | |||
f8708d01bf | |||
7fd18ba639 | |||
55f638ac78 | |||
31f862c526 | |||
505fca93c3 | |||
849dbddc5a | |||
d24fbe8426 | |||
f841b5962b | |||
f26c19052d | |||
72af5ce582 | |||
aea3c68c98 | |||
8368360aec | |||
a5029cc4ca | |||
9995168f9a | |||
02eacb8e95 | |||
2a4459b98b | |||
d48b7e0101 | |||
495033b977 | |||
9c164408d3 | |||
9f2327ee57 | |||
68f2a5ddc7 | |||
b1374b6a08 | |||
19119e99f7 | |||
d603a6714d | |||
c8724d40d2 | |||
9ff937591b | |||
3c92cce9f4 | |||
fd78ea5c4a | |||
c3c49e9172 | |||
a43a1be0b4 | |||
98d1f79ca4 | |||
c3fcc72969 | |||
efd285a6c4 | |||
3a0b2e3b95 | |||
34ba7a8e6c | |||
b733b98408 | |||
b97d9896a4 | |||
52ebaece73 | |||
690ecb8168 | |||
830ccb6660 | |||
4fa625dea6 | |||
ce8a1be8d9 | |||
1ea18adce8 | |||
35d15b8977 | |||
48ad0202bf | |||
2220968a01 | |||
1611476288 | |||
0bdd400fe7 | |||
8cf34fce98 | |||
d79977977d | |||
de70732cd4 | |||
32d9d13dc7 | |||
7125bd8faa | |||
7196d6ede3 | |||
54ef08c039 | |||
c2079276eb | |||
b6bf712195 | |||
27a02082a3 | |||
39b2e07dc8 | |||
12a37f71be | |||
9f3058caad | |||
4181ea8620 | |||
cfc604b53d | |||
420e816bed | |||
fb6312a303 | |||
3c91360d72 | |||
983f20642c | |||
12ade02f2d | |||
ec745693dc | |||
9cac7504c9 | |||
75d09d138a | |||
07312e240c | |||
9c4414f995 | |||
8e7fe8bb66 | |||
0ba36763f5 | |||
3fce78c7cd | |||
a83a92cdab | |||
23ff7af0b7 | |||
9cc2e7237b | |||
63b82c438d | |||
ba5e393e99 | |||
690d77e985 | |||
bcc8f60677 | |||
6f93b2bcbe | |||
70dc9d7e14 | |||
01d87591e4 | |||
ff1b8588e0 | |||
54fb49ed95 | |||
fbd1c3f8b0 | |||
e823b6ea95 | |||
b16ef12ac0 | |||
f9e732b5ce | |||
ae2e25f4c2 | |||
7b405e9af7 | |||
e09e642a83 | |||
76c0636125 | |||
c7a0298546 | |||
b4fb657efd | |||
8c3b936c60 | |||
47fc32ba47 | |||
e5904443d9 | |||
4ee6cc3022 | |||
ceb4aa9e25 | |||
a0dff9148a | |||
c94189668f | |||
0122addd00 | |||
f52faf63f7 | |||
b9fb88f537 | |||
54185df46e | |||
16352579f3 | |||
c5eabcf469 | |||
b5b53b3849 | |||
ea513002c5 | |||
9776bb8a03 | |||
969464f8ad | |||
a9b6a0b81a | |||
2758c6da14 | |||
097bcb3b5b | |||
18335194bc | |||
6c13bdde25 | |||
a981dab536 | |||
6632c21d62 | |||
a5b5ae899c | |||
ac6108a87d | |||
2f75445520 | |||
43057a2fcb | |||
9446563e5b | |||
167ad203cb | |||
558738ade8 | |||
0fb7d4babb | |||
91c7635d0e | |||
2a81595c3e | |||
9e4d886a6c | |||
b644665952 | |||
9492e4131b | |||
2abda0954b | |||
dbf6ca2b4c | |||
bca6a33325 | |||
c8f86652a3 | |||
076bd0b440 | |||
a443a798e3 | |||
23d79b8b05 | |||
9ccdb31003 | |||
0adb864219 | |||
d39954c935 | |||
89c03870c8 | |||
1d44018128 | |||
d835b0c80f | |||
bc862e526d | |||
a4df6e403c | |||
290a9d184d | |||
00abdcd0a1 | |||
73a2081ae7 | |||
c6167ef184 | |||
4bcbc588fc | |||
5f4fe6d868 | |||
7131dec3af | |||
0ddde6fa9f | |||
5d54acba30 | |||
c1b2e1d0ca | |||
9fc4cd8958 | |||
742c432fd1 | |||
3ac5eec301 | |||
060631e952 | |||
57165f2fb5 | |||
5940fd4bbd | |||
13180c5a17 | |||
6ea8dd5999 | |||
2795ac6e6b | |||
83e6079ff7 | |||
d043ebcdd3 | |||
e11c40eee4 | |||
5d55ca19be | |||
3f91dfbca3 | |||
bf3830b6f0 | |||
278f8eb13e | |||
658a671b79 | |||
bf836a2126 | |||
35a136bcee | |||
aaf1490ff5 | |||
a62ed10ab3 | |||
e79d756d06 | |||
904b37032c | |||
8a37c30f35 | |||
c4edc13413 | |||
9ddbab59bb | |||
1eb87c5c59 | |||
fb1439dfb9 | |||
d393c329c3 | |||
bed0fe2218 | |||
02c2e16253 | |||
59a67720b4 | |||
93e1206d60 | |||
2249d745d9 | |||
f9ed74a9b2 | |||
65ec2db4e8 | |||
001e33f310 | |||
09aca2528a | |||
beecc5072e | |||
797bd98269 | |||
4a3d32dc1f | |||
048be29fcd | |||
4ce584c5a6 | |||
3c1d23bc62 | |||
95307a3d78 | |||
a85d91b4bd | |||
a616087cde | |||
198d6320dd | |||
1596aa7a34 | |||
8e8d39d3cb | |||
42958ddd41 | |||
0476da503e | |||
943fe971f1 | |||
9d84969bf6 | |||
3745bb7279 | |||
55fe3ba52f | |||
de2dd6e070 | |||
ffb347a2bb | |||
19862725f7 | |||
f502f84423 | |||
e788fb7239 | |||
72f5961dd8 | |||
9f6b803a10 | |||
0da2059f79 | |||
e904d26e08 | |||
1ff0a31d3f | |||
e7f08946d1 | |||
ea9c2cebfd | |||
02a03cec33 | |||
31fbd52462 | |||
a88750d2b3 | |||
da91565354 | |||
44f5c208b6 | |||
83e26fdc9b | |||
4a329db792 | |||
1f8022d4ad | |||
63da329468 | |||
acadea6afa | |||
bd61dd9ffc | |||
5b9ae54441 | |||
41dd6e580d | |||
22d2de9fc7 | |||
6064a7ab2a | |||
8f5ca303b5 | |||
dc8f932009 | |||
4922237847 | |||
2424618163 | |||
b96f7e2ef2 | |||
cde38fbe1a | |||
56a1603e1f | |||
f92973794e | |||
49bbca0ce3 | |||
6686d9235c | |||
7c72b2dca7 | |||
4c061feddf | |||
02d2990576 | |||
94f7ba8bdf | |||
7d7c9825e9 | |||
dee542df42 | |||
f2b4c29f83 | |||
28652c5d21 | |||
b104bfeb93 | |||
8af4ba8980 | |||
87f8fb0750 | |||
eaffabf388 | |||
ac8853dfd3 | |||
d2be74a4f2 | |||
17a99cfa81 | |||
fd677c45aa | |||
769735c207 | |||
b114f258d6 | |||
c38abf35da | |||
1fedaf21ec | |||
0b74db64eb | |||
f2222ba3c6 | |||
3cb8a96041 | |||
4f538e7fd2 | |||
e02f9a9931 | |||
aecf4af7f2 | |||
618cbf1e66 | |||
c81bbf9801 | |||
f02b286ad4 | |||
0372b817d1 | |||
a3363b0605 | |||
51e625debf | |||
1977d87d55 | |||
9cbefacf52 | |||
42f1d1aeef | |||
f4b3e7e4d2 | |||
c6a436ed5d | |||
27b84841a7 | |||
deb00809a5 | |||
eb06435f23 | |||
328f4566e4 | |||
a37b2bdfb0 | |||
50f3bd5835 | |||
1f3ae3d682 | |||
ca1354e77f | |||
459b97858c | |||
18c09bb2ed | |||
45345fa782 | |||
5c859466ef | |||
449fbe82f6 | |||
6c41e6dd78 | |||
9631c663d5 | |||
bbef213155 | |||
bc060c95f8 | |||
9e1d5036f7 | |||
e47b2b677d | |||
38f6f8eef7 | |||
115b1d03db | |||
0833d4baf8 | |||
f2b359d8e8 | |||
402932aa6e | |||
f1e2372a56 | |||
45aa071261 | |||
8563000235 | |||
9047d21ec5 | |||
74a95f6cbf | |||
a17dcf4991 | |||
fc528c51a3 | |||
ad2965bbf9 | |||
37f8c6a04a | |||
46668b811f | |||
10f4e822c3 | |||
b191e96f51 | |||
f9a31ea00a | |||
97d99fcef2 | |||
2fa534ff26 | |||
e47a147c3b | |||
4ecb9cc30b | |||
64ef8ad62b | |||
6f2cd326bf | |||
339346e307 | |||
a611480b94 | |||
fd3c8834da | |||
3d481178d7 | |||
d0cb112f4b | |||
c58445c772 | |||
f7dd376596 | |||
5b3b6f7f48 | |||
0c2af58b8d | |||
0e0040e78d | |||
8ba5ea0ecf | |||
dbfd844ff2 | |||
452478e779 | |||
6e06720e84 | |||
51baa35df1 | |||
6e2972673e | |||
0ac7e342f0 | |||
2a9852fa94 | |||
250cbf593b | |||
6fbdbaae80 | |||
5ccdaeb09e | |||
b0b64eb404 | |||
831eeca7c8 | |||
2c5e062c2b | |||
c83d03c298 | |||
485d89d5c8 | |||
a997ca746f | |||
572e281566 | |||
8674b81b3a | |||
bce35c708a | |||
ccbf116fd6 | |||
f26d81200b | |||
71173aa010 | |||
4ee3d6aad4 | |||
4ffe988c30 | |||
df354acf97 | |||
6662eef2fd | |||
dcdfaf36f1 | |||
ae7e31717a | |||
fe7ecc377a | |||
29b1c8e1cb | |||
ae906eed8f | |||
07ce3fb8ea | |||
1d7d6c6f99 | |||
f490141217 | |||
0e748c6871 | |||
f7e7fab6f2 | |||
fbde7df487 | |||
914b6fa966 | |||
69096b09f3 | |||
da5dd4d173 | |||
6b5dc3b7d5 | |||
d31ac49ead | |||
60afcd069a | |||
a39139c610 | |||
42370b5eb8 | |||
b99a09d73b | |||
b8e06f6365 | |||
f552338822 | |||
6373379da6 | |||
ff11bcdb7c | |||
c2bb6b3068 | |||
259617f68d | |||
ab08aa9c9e | |||
7c610ee7c9 | |||
23deaedd39 | |||
538d39a704 | |||
b961abb52b | |||
b32b12b208 | |||
e88c2be013 | |||
d5ddd0a9dd | |||
dff36a18a2 | |||
fb78d83ec3 | |||
d23700b919 | |||
92db56c0cb | |||
4c6163baa0 | |||
f46388fa63 | |||
aba1728bc3 | |||
ddb9ed39a5 | |||
a242f61b8e | |||
d19de30d9c | |||
6d05163c9f | |||
a41cd17092 | |||
ec7324e972 | |||
7f5dd13422 | |||
7f4ad7ff46 | |||
60200689f4 | |||
af3797e16c | |||
d4743fb10d | |||
38b083e117 | |||
fece5adc2a | |||
7bfb4126d7 | |||
2abe584bc5 | |||
ee9223b1b9 | |||
d2a16ff8f6 | |||
f1e5059682 | |||
11977f4f77 | |||
461eac46b9 | |||
75af664511 | |||
174298b497 | |||
9b392a0601 | |||
bf9de3bcf6 | |||
189df1280c | |||
8807c327d1 | |||
d627ec013e | |||
d0bbc67b27 | |||
453557948e | |||
4033692dcb | |||
236f28c53c | |||
b2bfed8937 | |||
71adbdd7d7 | |||
573df2e747 | |||
aa9cb6d052 | |||
8358e0ff48 | |||
1687ec85de | |||
d5fc0a4bda | |||
3bcff77947 | |||
1521842d7a | |||
b04813e472 | |||
9e41d93184 |
12
README.md
@ -2,14 +2,15 @@
|
||||
|
||||
[](https://drone.io/github.com/astaxie/beego/latest)
|
||||
|
||||
beego is a Go Framework which is inspired from tornado and sinatra.
|
||||
beego is a Go Framework inspired by tornado and sinatra.
|
||||
|
||||
It is a simply & powerful web framework.
|
||||
It is a simple & powerful web framework.
|
||||
|
||||
More info [beego.me](http://beego.me)
|
||||
|
||||
## Features
|
||||
|
||||
* RESTFul support
|
||||
* RESTful support
|
||||
* MVC architecture
|
||||
* Session support (store in memory, file, Redis or MySQL)
|
||||
* Cache support (store in memory, Redis or Memcache)
|
||||
@ -22,11 +23,11 @@ It is a simply & powerful web framework.
|
||||
|
||||
## Documentation
|
||||
|
||||
[English](https://github.com/astaxie/beego/tree/master/docs/en)
|
||||
[English](http://beego.me/docs/intro/)
|
||||
|
||||
[API](http://gowalker.org/github.com/astaxie/beego)
|
||||
|
||||
[中文文档](https://github.com/astaxie/beego/tree/master/docs/zh)
|
||||
[中文文档](http://beego.me/docs/intro/)
|
||||
|
||||
|
||||
## LICENSE
|
||||
@ -38,4 +39,5 @@ beego is licensed under the Apache Licence, Version 2.0
|
||||
## Use case
|
||||
|
||||
- Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker)
|
||||
- seocms: [seocms](https://github.com/chinakr/seocms)
|
||||
- CMS: [toropress](https://github.com/insionng/toropress)
|
||||
|
269
admin.go
Normal file
@ -0,0 +1,269 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/toolbox"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
// BeeAdminApp is the default AdminApp used by admin module.
|
||||
var BeeAdminApp *AdminApp
|
||||
|
||||
// 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.
|
||||
// usage:
|
||||
// func MyFilterMonitor(method, requestPath string, t time.Duration) bool {
|
||||
// if method == "POST" {
|
||||
// return false
|
||||
// }
|
||||
// if t.Nanoseconds() < 100 {
|
||||
// return false
|
||||
// }
|
||||
// if strings.HasPrefix(requestPath, "/astaxie") {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
// beego.FilterMonitorFunc = MyFilterMonitor.
|
||||
var FilterMonitorFunc func(string, string, time.Duration) bool
|
||||
|
||||
func init() {
|
||||
BeeAdminApp = &AdminApp{
|
||||
routers: make(map[string]http.HandlerFunc),
|
||||
}
|
||||
BeeAdminApp.Route("/", AdminIndex)
|
||||
BeeAdminApp.Route("/qps", QpsIndex)
|
||||
BeeAdminApp.Route("/prof", ProfIndex)
|
||||
BeeAdminApp.Route("/healthcheck", Healthcheck)
|
||||
BeeAdminApp.Route("/task", TaskStatus)
|
||||
BeeAdminApp.Route("/runtask", RunTask)
|
||||
BeeAdminApp.Route("/listconf", ListConf)
|
||||
FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
|
||||
}
|
||||
|
||||
// AdminIndex is the default http.Handler for admin module.
|
||||
// it matches url pattern "/".
|
||||
func AdminIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Write([]byte("Welcome to Admin Dashboard\n"))
|
||||
rw.Write([]byte("There are servral functions:\n"))
|
||||
rw.Write([]byte("1. Record all request and request time, http://localhost:8088/qps\n"))
|
||||
rw.Write([]byte("2. Get runtime profiling data by the pprof, http://localhost:8088/prof\n"))
|
||||
rw.Write([]byte("3. Get healthcheck result from http://localhost:8088/prof\n"))
|
||||
rw.Write([]byte("4. Get current task infomation from taskhttp://localhost:8088/task \n"))
|
||||
rw.Write([]byte("5. To run a task passed a param http://localhost:8088/runtask\n"))
|
||||
rw.Write([]byte("6. Get all confige & router infomation http://localhost:8088/listconf\n"))
|
||||
|
||||
}
|
||||
|
||||
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
|
||||
// it's registered with url pattern "/qbs" in admin module.
|
||||
func QpsIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
toolbox.StatisticsMap.GetMap(rw)
|
||||
}
|
||||
|
||||
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
|
||||
// it's registered with url pattern "/listconf" in admin module.
|
||||
func ListConf(rw http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
command := r.Form.Get("command")
|
||||
if command != "" {
|
||||
switch command {
|
||||
case "conf":
|
||||
fmt.Fprintln(rw, "list all beego's conf:")
|
||||
fmt.Fprintln(rw, "AppName:", AppName)
|
||||
fmt.Fprintln(rw, "AppPath:", AppPath)
|
||||
fmt.Fprintln(rw, "AppConfigPath:", AppConfigPath)
|
||||
fmt.Fprintln(rw, "StaticDir:", StaticDir)
|
||||
fmt.Fprintln(rw, "StaticExtensionsToGzip:", StaticExtensionsToGzip)
|
||||
fmt.Fprintln(rw, "HttpAddr:", HttpAddr)
|
||||
fmt.Fprintln(rw, "HttpPort:", HttpPort)
|
||||
fmt.Fprintln(rw, "HttpTLS:", HttpTLS)
|
||||
fmt.Fprintln(rw, "HttpCertFile:", HttpCertFile)
|
||||
fmt.Fprintln(rw, "HttpKeyFile:", HttpKeyFile)
|
||||
fmt.Fprintln(rw, "RecoverPanic:", RecoverPanic)
|
||||
fmt.Fprintln(rw, "AutoRender:", AutoRender)
|
||||
fmt.Fprintln(rw, "ViewsPath:", ViewsPath)
|
||||
fmt.Fprintln(rw, "RunMode:", RunMode)
|
||||
fmt.Fprintln(rw, "SessionOn:", SessionOn)
|
||||
fmt.Fprintln(rw, "SessionProvider:", SessionProvider)
|
||||
fmt.Fprintln(rw, "SessionName:", SessionName)
|
||||
fmt.Fprintln(rw, "SessionGCMaxLifetime:", SessionGCMaxLifetime)
|
||||
fmt.Fprintln(rw, "SessionSavePath:", SessionSavePath)
|
||||
fmt.Fprintln(rw, "SessionHashFunc:", SessionHashFunc)
|
||||
fmt.Fprintln(rw, "SessionHashKey:", SessionHashKey)
|
||||
fmt.Fprintln(rw, "SessionCookieLifeTime:", SessionCookieLifeTime)
|
||||
fmt.Fprintln(rw, "UseFcgi:", UseFcgi)
|
||||
fmt.Fprintln(rw, "MaxMemory:", MaxMemory)
|
||||
fmt.Fprintln(rw, "EnableGzip:", EnableGzip)
|
||||
fmt.Fprintln(rw, "DirectoryIndex:", DirectoryIndex)
|
||||
fmt.Fprintln(rw, "EnableHotUpdate:", EnableHotUpdate)
|
||||
fmt.Fprintln(rw, "HttpServerTimeOut:", HttpServerTimeOut)
|
||||
fmt.Fprintln(rw, "ErrorsShow:", ErrorsShow)
|
||||
fmt.Fprintln(rw, "XSRFKEY:", XSRFKEY)
|
||||
fmt.Fprintln(rw, "EnableXSRF:", EnableXSRF)
|
||||
fmt.Fprintln(rw, "XSRFExpire:", XSRFExpire)
|
||||
fmt.Fprintln(rw, "CopyRequestBody:", CopyRequestBody)
|
||||
fmt.Fprintln(rw, "TemplateLeft:", TemplateLeft)
|
||||
fmt.Fprintln(rw, "TemplateRight:", TemplateRight)
|
||||
fmt.Fprintln(rw, "BeegoServerName:", BeegoServerName)
|
||||
fmt.Fprintln(rw, "EnableAdmin:", EnableAdmin)
|
||||
fmt.Fprintln(rw, "AdminHttpAddr:", AdminHttpAddr)
|
||||
fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort)
|
||||
case "router":
|
||||
fmt.Fprintln(rw, "Print all router infomation:")
|
||||
for _, router := range BeeApp.Handlers.fixrouters {
|
||||
if router.hasMethod {
|
||||
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
|
||||
} else {
|
||||
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
|
||||
}
|
||||
}
|
||||
for _, router := range BeeApp.Handlers.routers {
|
||||
if router.hasMethod {
|
||||
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
|
||||
} else {
|
||||
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
|
||||
}
|
||||
}
|
||||
if BeeApp.Handlers.enableAuto {
|
||||
for controllerName, methodObj := range BeeApp.Handlers.autoRouter {
|
||||
fmt.Fprintln(rw, controllerName, "----")
|
||||
for methodName, obj := range methodObj {
|
||||
fmt.Fprintln(rw, " ", methodName, "-----", obj.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
case "filter":
|
||||
fmt.Fprintln(rw, "Print all filter infomation:")
|
||||
if BeeApp.Handlers.enableFilter {
|
||||
fmt.Fprintln(rw, "BeforeRouter:")
|
||||
if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok {
|
||||
for _, f := range bf {
|
||||
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(rw, "AfterStatic:")
|
||||
if bf, ok := BeeApp.Handlers.filters[AfterStatic]; ok {
|
||||
for _, f := range bf {
|
||||
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(rw, "BeforeExec:")
|
||||
if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok {
|
||||
for _, f := range bf {
|
||||
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(rw, "AfterExec:")
|
||||
if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok {
|
||||
for _, f := range bf {
|
||||
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(rw, "FinishRouter:")
|
||||
if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok {
|
||||
for _, f := range bf {
|
||||
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
rw.Write([]byte("command not support"))
|
||||
}
|
||||
} else {
|
||||
rw.Write([]byte("ListConf support this command:\n"))
|
||||
rw.Write([]byte("1. command=conf\n"))
|
||||
rw.Write([]byte("2. command=router\n"))
|
||||
rw.Write([]byte("3. command=filter\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// ProfIndex is a http.Handler for showing profile command.
|
||||
// it's in url pattern "/prof" in admin module.
|
||||
func ProfIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
command := r.Form.Get("command")
|
||||
if command != "" {
|
||||
toolbox.ProcessInput(command, rw)
|
||||
} else {
|
||||
rw.Write([]byte("request url like '/prof?command=lookup goroutine'\n"))
|
||||
rw.Write([]byte("the command have below types:\n"))
|
||||
rw.Write([]byte("1. lookup goroutine\n"))
|
||||
rw.Write([]byte("2. lookup heap\n"))
|
||||
rw.Write([]byte("3. lookup threadcreate\n"))
|
||||
rw.Write([]byte("4. lookup block\n"))
|
||||
rw.Write([]byte("5. start cpuprof\n"))
|
||||
rw.Write([]byte("6. stop cpuprof\n"))
|
||||
rw.Write([]byte("7. get memprof\n"))
|
||||
rw.Write([]byte("8. gc summary\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Healthcheck is a http.Handler calling health checking and showing the result.
|
||||
// it's in "/healthcheck" pattern in admin module.
|
||||
func Healthcheck(rw http.ResponseWriter, req *http.Request) {
|
||||
for name, h := range toolbox.AdminCheckList {
|
||||
if err := h.Check(); err != nil {
|
||||
fmt.Fprintf(rw, "%s : ok\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(rw, "%s : %s\n", name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
|
||||
// it's in "/task" pattern in admin module.
|
||||
func TaskStatus(rw http.ResponseWriter, req *http.Request) {
|
||||
for tname, tk := range toolbox.AdminTaskList {
|
||||
fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String())
|
||||
}
|
||||
}
|
||||
|
||||
// RunTask is a http.Handler to run a Task from the "query string.
|
||||
// the request url likes /runtask?taskname=sendmail.
|
||||
func RunTask(rw http.ResponseWriter, req *http.Request) {
|
||||
req.ParseForm()
|
||||
taskname := req.Form.Get("taskname")
|
||||
if t, ok := toolbox.AdminTaskList[taskname]; ok {
|
||||
err := t.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(rw, "%v", err)
|
||||
}
|
||||
fmt.Fprintf(rw, "%s run success,Now the Status is %s", t.GetStatus())
|
||||
} else {
|
||||
fmt.Fprintf(rw, "there's no task which named:%s", taskname)
|
||||
}
|
||||
}
|
||||
|
||||
// AdminApp is an http.HandlerFunc map used as BeeAdminApp.
|
||||
type AdminApp struct {
|
||||
routers map[string]http.HandlerFunc
|
||||
}
|
||||
|
||||
// Route adds http.HandlerFunc to AdminApp with url pattern.
|
||||
func (admin *AdminApp) Route(pattern string, f http.HandlerFunc) {
|
||||
admin.routers[pattern] = f
|
||||
}
|
||||
|
||||
// Run AdminApp http server.
|
||||
// Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
|
||||
func (admin *AdminApp) Run() {
|
||||
if len(toolbox.AdminTaskList) > 0 {
|
||||
toolbox.StartTask()
|
||||
}
|
||||
addr := AdminHttpAddr
|
||||
|
||||
if AdminHttpPort != 0 {
|
||||
addr = fmt.Sprintf("%s:%d", AdminHttpAddr, AdminHttpPort)
|
||||
}
|
||||
for p, f := range admin.routers {
|
||||
http.Handle(p, f)
|
||||
}
|
||||
err := http.ListenAndServe(addr, nil)
|
||||
if err != nil {
|
||||
BeeLogger.Critical("Admin ListenAndServe: ", err)
|
||||
}
|
||||
}
|
164
app.go
Normal file
@ -0,0 +1,164 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
|
||||
// FilterFunc defines filter function type.
|
||||
type FilterFunc func(*context.Context)
|
||||
|
||||
// App defines beego application with a new PatternServeMux.
|
||||
type App struct {
|
||||
Handlers *ControllerRegistor
|
||||
}
|
||||
|
||||
// NewApp returns a new beego application.
|
||||
func NewApp() *App {
|
||||
cr := NewControllerRegistor()
|
||||
app := &App{Handlers: cr}
|
||||
return app
|
||||
}
|
||||
|
||||
// Run beego application.
|
||||
func (app *App) Run() {
|
||||
addr := HttpAddr
|
||||
|
||||
if HttpPort != 0 {
|
||||
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
|
||||
}
|
||||
|
||||
BeeLogger.Info("Running on %s", addr)
|
||||
|
||||
var (
|
||||
err error
|
||||
l net.Listener
|
||||
)
|
||||
|
||||
if UseFcgi {
|
||||
if HttpPort == 0 {
|
||||
l, err = net.Listen("unix", addr)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", addr)
|
||||
}
|
||||
if err != nil {
|
||||
BeeLogger.Critical("Listen: ", err)
|
||||
}
|
||||
err = fcgi.Serve(l, app.Handlers)
|
||||
} else {
|
||||
if EnableHotUpdate {
|
||||
server := &http.Server{
|
||||
Handler: app.Handlers,
|
||||
ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
|
||||
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
|
||||
}
|
||||
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if nil != err {
|
||||
BeeLogger.Critical("ResolveTCPAddr:", err)
|
||||
}
|
||||
l, err = GetInitListener(laddr)
|
||||
theStoppable = newStoppable(l)
|
||||
err = server.Serve(theStoppable)
|
||||
theStoppable.wg.Wait()
|
||||
CloseSelf()
|
||||
} else {
|
||||
s := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: app.Handlers,
|
||||
ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
|
||||
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
|
||||
}
|
||||
if HttpTLS {
|
||||
err = s.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
|
||||
} else {
|
||||
err = s.ListenAndServe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
BeeLogger.Critical("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Router adds a url-patterned controller handler.
|
||||
// The path argument supports regex rules and specific placeholders.
|
||||
// The c argument needs a controller handler implemented beego.ControllerInterface.
|
||||
// The mapping methods argument only need one string to define custom router rules.
|
||||
// usage:
|
||||
// simple router
|
||||
// beego.Router("/admin", &admin.UserController{})
|
||||
// beego.Router("/admin/index", &admin.ArticleController{})
|
||||
//
|
||||
// regex router
|
||||
//
|
||||
// beego.Router(“/api/:id([0-9]+)“, &controllers.RController{})
|
||||
//
|
||||
// custom rules
|
||||
// beego.Router("/api/list",&RestController{},"*:ListFood")
|
||||
// beego.Router("/api/create",&RestController{},"post:CreateFood")
|
||||
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
|
||||
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
|
||||
func (app *App) Router(path string, c ControllerInterface, mappingMethods ...string) *App {
|
||||
app.Handlers.Add(path, c, mappingMethods...)
|
||||
return app
|
||||
}
|
||||
|
||||
// AutoRouter adds beego-defined controller handler.
|
||||
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
|
||||
// visit the url /main/list to exec List function or /main/page to exec Page function.
|
||||
func (app *App) AutoRouter(c ControllerInterface) *App {
|
||||
app.Handlers.AddAuto(c)
|
||||
return app
|
||||
}
|
||||
|
||||
// UrlFor creates a url with another registered controller handler with params.
|
||||
// The endpoint is formed as path.controller.name to defined the controller method which will run.
|
||||
// The values need key-pair data to assign into controller method.
|
||||
func (app *App) UrlFor(endpoint string, values ...string) string {
|
||||
return app.Handlers.UrlFor(endpoint, values...)
|
||||
}
|
||||
|
||||
// [Deprecated] use InsertFilter.
|
||||
// Filter adds a FilterFunc under pattern condition and named action.
|
||||
// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter.
|
||||
func (app *App) Filter(pattern, action string, filter FilterFunc) *App {
|
||||
app.Handlers.AddFilter(pattern, action, filter)
|
||||
return app
|
||||
}
|
||||
|
||||
// InsertFilter adds a FilterFunc with pattern condition and action constant.
|
||||
// The pos means action constant including
|
||||
// beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
|
||||
func (app *App) InsertFilter(pattern string, pos int, filter FilterFunc) *App {
|
||||
app.Handlers.InsertFilter(pattern, pos, filter)
|
||||
return app
|
||||
}
|
||||
|
||||
// SetViewsPath sets view directory path in beego application.
|
||||
// it returns beego application self.
|
||||
func (app *App) SetViewsPath(path string) *App {
|
||||
ViewsPath = path
|
||||
return app
|
||||
}
|
||||
|
||||
// SetStaticPath sets static directory path and proper url pattern in beego application.
|
||||
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
|
||||
// it returns beego application self.
|
||||
func (app *App) SetStaticPath(url string, path string) *App {
|
||||
StaticDir[url] = path
|
||||
return app
|
||||
}
|
||||
|
||||
// DelStaticPath removes the static folder setting in this url pattern in beego application.
|
||||
// it returns beego application self.
|
||||
func (app *App) DelStaticPath(url string) *App {
|
||||
delete(StaticDir, url)
|
||||
return app
|
||||
}
|
242
beego.go
@ -1,221 +1,133 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/session"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/middleware"
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
const VERSION = "0.7.0"
|
||||
// beego web framework version.
|
||||
const VERSION = "1.0.1"
|
||||
|
||||
var (
|
||||
BeeApp *App
|
||||
AppName string
|
||||
AppPath string
|
||||
AppConfigPath string
|
||||
StaticDir map[string]string
|
||||
TemplateCache map[string]*template.Template
|
||||
HttpAddr string
|
||||
HttpPort int
|
||||
RecoverPanic bool
|
||||
AutoRender bool
|
||||
PprofOn bool
|
||||
ViewsPath string
|
||||
RunMode string //"dev" or "prod"
|
||||
AppConfig *Config
|
||||
//related to session
|
||||
GlobalSessions *session.Manager //GlobalSessions
|
||||
SessionOn bool // wheather auto start session,default is false
|
||||
SessionProvider string // default session provider memory mysql redis
|
||||
SessionName string // sessionName cookie's name
|
||||
SessionGCMaxLifetime int64 // session's gc maxlifetime
|
||||
SessionSavePath string // session savepath if use mysql/redis/file this set to the connectinfo
|
||||
UseFcgi bool
|
||||
MaxMemory int64
|
||||
EnableGzip bool // enable gzip
|
||||
DirectoryIndex bool //ebable DirectoryIndex default is false
|
||||
)
|
||||
|
||||
func init() {
|
||||
os.Chdir(path.Dir(os.Args[0]))
|
||||
BeeApp = NewApp()
|
||||
AppPath, _ = os.Getwd()
|
||||
StaticDir = make(map[string]string)
|
||||
TemplateCache = make(map[string]*template.Template)
|
||||
HttpAddr = ""
|
||||
HttpPort = 8080
|
||||
AppName = "beego"
|
||||
RunMode = "dev" //default runmod
|
||||
AutoRender = true
|
||||
RecoverPanic = true
|
||||
PprofOn = false
|
||||
ViewsPath = "views"
|
||||
SessionOn = false
|
||||
SessionProvider = "memory"
|
||||
SessionName = "beegosessionID"
|
||||
SessionGCMaxLifetime = 3600
|
||||
SessionSavePath = ""
|
||||
UseFcgi = false
|
||||
MaxMemory = 1 << 26 //64MB
|
||||
EnableGzip = false
|
||||
StaticDir["/static"] = "static"
|
||||
AppConfigPath = path.Join(AppPath, "conf", "app.conf")
|
||||
ParseConfig()
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Handlers *ControllerRegistor
|
||||
}
|
||||
|
||||
// New returns a new PatternServeMux.
|
||||
func NewApp() *App {
|
||||
cr := NewControllerRegistor()
|
||||
app := &App{Handlers: cr}
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) Run() {
|
||||
addr := fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
|
||||
var (
|
||||
err error
|
||||
l net.Listener
|
||||
)
|
||||
if UseFcgi {
|
||||
l, err = net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
BeeLogger.Fatal("Listen: ", err)
|
||||
}
|
||||
err = fcgi.Serve(l, app.Handlers)
|
||||
} else {
|
||||
server := &http.Server{Handler: app.Handlers}
|
||||
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if nil != err {
|
||||
BeeLogger.Fatal("ResolveTCPAddr:", err)
|
||||
}
|
||||
l, err = GetInitListner(laddr)
|
||||
theStoppable = newStoppable(l)
|
||||
err = server.Serve(theStoppable)
|
||||
theStoppable.wg.Wait()
|
||||
CloseSelf()
|
||||
}
|
||||
if err != nil {
|
||||
BeeLogger.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) Router(path string, c ControllerInterface) *App {
|
||||
app.Handlers.Add(path, c)
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) Filter(filter http.HandlerFunc) *App {
|
||||
app.Handlers.Filter(filter)
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) FilterParam(param string, filter http.HandlerFunc) *App {
|
||||
app.Handlers.FilterParam(param, filter)
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) FilterPrefixPath(path string, filter http.HandlerFunc) *App {
|
||||
app.Handlers.FilterPrefixPath(path, filter)
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) SetViewsPath(path string) *App {
|
||||
ViewsPath = path
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) SetStaticPath(url string, path string) *App {
|
||||
StaticDir[url] = path
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) ErrorLog(ctx *Context) {
|
||||
BeeLogger.Printf("[ERR] host: '%s', request: '%s %s', proto: '%s', ua: '%s', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr)
|
||||
}
|
||||
|
||||
func (app *App) AccessLog(ctx *Context) {
|
||||
BeeLogger.Printf("[ACC] host: '%s', request: '%s %s', proto: '%s', ua: '%s', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr)
|
||||
}
|
||||
|
||||
func RegisterController(path string, c ControllerInterface) *App {
|
||||
BeeApp.Router(path, c)
|
||||
// Router adds a patterned controller handler to BeeApp.
|
||||
// it's an alias method of App.Router.
|
||||
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
|
||||
BeeApp.Router(rootpath, c, mappingMethods...)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func Router(path string, c ControllerInterface) *App {
|
||||
BeeApp.Router(path, c)
|
||||
// RESTRouter adds a restful controller handler to BeeApp.
|
||||
// its' controller implements beego.ControllerInterface and
|
||||
// defines a param "pattern/:objectId" to visit each resource.
|
||||
func RESTRouter(rootpath string, c ControllerInterface) *App {
|
||||
Router(rootpath, c)
|
||||
Router(path.Join(rootpath, ":objectId"), c)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func RouterHandler(path string, c http.Handler) *App {
|
||||
BeeApp.Handlers.AddHandler(path, c)
|
||||
// AutoRouter adds defined controller handler to BeeApp.
|
||||
// it's same to App.AutoRouter.
|
||||
func AutoRouter(c ControllerInterface) *App {
|
||||
BeeApp.AutoRouter(c)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
// ErrorHandler registers http.HandlerFunc to each http err code string.
|
||||
// usage:
|
||||
// beego.ErrorHandler("404",NotFound)
|
||||
// beego.ErrorHandler("500",InternalServerError)
|
||||
func Errorhandler(err string, h http.HandlerFunc) *App {
|
||||
ErrorMaps[err] = h
|
||||
middleware.Errorhandler(err, h)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
// SetViewsPath sets view directory to BeeApp.
|
||||
// it's alias of App.SetViewsPath.
|
||||
func SetViewsPath(path string) *App {
|
||||
BeeApp.SetViewsPath(path)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
// SetStaticPath sets static directory and url prefix to BeeApp.
|
||||
// it's alias of App.SetStaticPath.
|
||||
func SetStaticPath(url string, path string) *App {
|
||||
if !strings.HasPrefix(url, "/") {
|
||||
url = "/" + url
|
||||
}
|
||||
StaticDir[url] = path
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func Filter(filter http.HandlerFunc) *App {
|
||||
BeeApp.Filter(filter)
|
||||
// DelStaticPath removes the static folder setting in this url pattern in beego application.
|
||||
// it's alias of App.DelStaticPath.
|
||||
func DelStaticPath(url string) *App {
|
||||
delete(StaticDir, url)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func FilterParam(param string, filter http.HandlerFunc) *App {
|
||||
BeeApp.FilterParam(param, filter)
|
||||
// [Deprecated] use InsertFilter.
|
||||
// Filter adds a FilterFunc under pattern condition and named action.
|
||||
// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter.
|
||||
// it's alias of App.Filter.
|
||||
func AddFilter(pattern, action string, filter FilterFunc) *App {
|
||||
BeeApp.Filter(pattern, action, filter)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func FilterPrefixPath(path string, filter http.HandlerFunc) *App {
|
||||
BeeApp.FilterPrefixPath(path, filter)
|
||||
// InsertFilter adds a FilterFunc with pattern condition and action constant.
|
||||
// The pos means action constant including
|
||||
// beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
|
||||
// it's alias of App.InsertFilter.
|
||||
func InsertFilter(pattern string, pos int, filter FilterFunc) *App {
|
||||
BeeApp.InsertFilter(pattern, pos, filter)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
// Run beego application.
|
||||
// it's alias of App.Run.
|
||||
func Run() {
|
||||
if AppConfigPath != path.Join(AppPath, "conf", "app.conf") {
|
||||
// if AppConfigPath not In the conf/app.conf reParse config
|
||||
if AppConfigPath != filepath.Join(AppPath, "conf", "app.conf") {
|
||||
err := ParseConfig()
|
||||
if err != nil {
|
||||
if RunMode == "dev" {
|
||||
Warn(err)
|
||||
// configuration is critical to app, panic here if parse failed
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if PprofOn {
|
||||
BeeApp.Router(`/debug/pprof`, &ProfController{})
|
||||
BeeApp.Router(`/debug/pprof/:pp([\w]+)`, &ProfController{})
|
||||
}
|
||||
|
||||
//init mime
|
||||
initMime()
|
||||
|
||||
if SessionOn {
|
||||
GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime, SessionSavePath)
|
||||
GlobalSessions, _ = session.NewManager(SessionProvider,
|
||||
SessionName,
|
||||
SessionGCMaxLifetime,
|
||||
SessionSavePath,
|
||||
HttpTLS,
|
||||
SessionHashFunc,
|
||||
SessionHashKey,
|
||||
SessionCookieLifeTime)
|
||||
go GlobalSessions.GC()
|
||||
}
|
||||
|
||||
err := BuildTemplate(ViewsPath)
|
||||
if err != nil {
|
||||
if RunMode == "dev" {
|
||||
Warn(err)
|
||||
}
|
||||
}
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
registerErrorHander()
|
||||
|
||||
middleware.VERSION = VERSION
|
||||
middleware.AppName = AppName
|
||||
middleware.RegisterErrorHander()
|
||||
|
||||
if EnableAdmin {
|
||||
go BeeAdminApp.Run()
|
||||
}
|
||||
|
||||
BeeApp.Run()
|
||||
}
|
||||
|
128
cache.go
@ -1,128 +0,0 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultEvery int = 60 // 1 minute
|
||||
)
|
||||
|
||||
type BeeItem struct {
|
||||
val interface{}
|
||||
Lastaccess time.Time
|
||||
expired int
|
||||
}
|
||||
|
||||
func (itm *BeeItem) Access() interface{} {
|
||||
itm.Lastaccess = time.Now()
|
||||
return itm.val
|
||||
}
|
||||
|
||||
type BeeCache struct {
|
||||
lock sync.RWMutex
|
||||
dur time.Duration
|
||||
items map[string]*BeeItem
|
||||
Every int // Run an expiration check Every seconds
|
||||
}
|
||||
|
||||
// NewDefaultCache returns a new FileCache with sane defaults.
|
||||
func NewBeeCache() *BeeCache {
|
||||
cache := BeeCache{dur: time.Since(time.Now()),
|
||||
Every: DefaultEvery}
|
||||
return &cache
|
||||
}
|
||||
|
||||
func (bc *BeeCache) Get(name string) interface{} {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
itm, ok := bc.items[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return itm.Access()
|
||||
}
|
||||
|
||||
func (bc *BeeCache) Put(name string, value interface{}, expired int) error {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
t := BeeItem{val: value, Lastaccess: time.Now(), expired: expired}
|
||||
if _, ok := bc.items[name]; ok {
|
||||
return errors.New("the key is exist")
|
||||
} else {
|
||||
bc.items[name] = &t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BeeCache) Delete(name string) (ok bool, err error) {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
if _, ok = bc.items[name]; !ok {
|
||||
return
|
||||
}
|
||||
delete(bc.items, name)
|
||||
_, valid := bc.items[name]
|
||||
if valid {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bc *BeeCache) IsExist(name string) bool {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
_, ok := bc.items[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Start activates the file cache; it will
|
||||
func (bc *BeeCache) Start() error {
|
||||
dur, err := time.ParseDuration(fmt.Sprintf("%ds", bc.Every))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.dur = dur
|
||||
bc.items = make(map[string]*BeeItem, 0)
|
||||
go bc.vaccuum()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BeeCache) vaccuum() {
|
||||
if bc.Every < 1 {
|
||||
return
|
||||
}
|
||||
for {
|
||||
<-time.After(time.Duration(bc.dur))
|
||||
if bc.items == nil {
|
||||
return
|
||||
}
|
||||
for name, _ := range bc.items {
|
||||
bc.item_expired(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// item_expired returns true if an item is expired.
|
||||
func (bc *BeeCache) item_expired(name string) bool {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
itm, ok := bc.items[name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
dur := time.Now().Sub(itm.Lastaccess)
|
||||
sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds()))
|
||||
if err != nil {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
} else if sec >= itm.expired {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
31
cache/cache.go
vendored
@ -4,12 +4,32 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Cache interface contains all behaviors for cache adapter.
|
||||
// usage:
|
||||
// cache.Register("file",cache.NewFileCache()) // this operation is run in init method of file.go.
|
||||
// c := cache.NewCache("file","{....}")
|
||||
// c.Put("key",value,3600)
|
||||
// v := c.Get("key")
|
||||
//
|
||||
// c.Incr("counter") // now is 1
|
||||
// c.Incr("counter") // now is 2
|
||||
// count := c.Get("counter").(int)
|
||||
type Cache interface {
|
||||
// get cached value by key.
|
||||
Get(key string) interface{}
|
||||
Put(key string, val interface{}, timeout int) error
|
||||
// set cached value with key and expire time.
|
||||
Put(key string, val interface{}, timeout int64) error
|
||||
// delete cached value by key.
|
||||
Delete(key string) error
|
||||
// increase cached int value by key, as a counter.
|
||||
Incr(key string) error
|
||||
// decrease cached int value by key, as a counter.
|
||||
Decr(key string) error
|
||||
// check cached value is existed or not.
|
||||
IsExist(key string) bool
|
||||
// clear all cache.
|
||||
ClearAll() error
|
||||
// start gc routine via config string setting.
|
||||
StartAndGC(config string) error
|
||||
}
|
||||
|
||||
@ -28,12 +48,17 @@ func Register(name string, adapter Cache) {
|
||||
adapters[name] = adapter
|
||||
}
|
||||
|
||||
// config need to be correct JSON as string: {"interval":360}
|
||||
// Create a new cache driver by adapter and config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
// it will start gc automatically.
|
||||
func NewCache(adapterName, config string) (Cache, error) {
|
||||
adapter, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cache: unknown adaptername %q (forgotten import?)", adapterName)
|
||||
}
|
||||
adapter.StartAndGC(config)
|
||||
err := adapter.StartAndGC(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adapter, nil
|
||||
}
|
||||
|
19
cache/cache_test.go
vendored
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_cache(t *testing.T) {
|
||||
bm, err := NewCache("memory", `{"interval":60}`)
|
||||
bm, err := NewCache("memory", `{"interval":20}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
@ -21,7 +21,7 @@ func Test_cache(t *testing.T) {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
time.Sleep(70 * time.Second)
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("check err")
|
||||
@ -31,6 +31,21 @@ func Test_cache(t *testing.T) {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
|
||||
if err = bm.Incr("astaxie"); err != nil {
|
||||
t.Error("Incr Error", err)
|
||||
}
|
||||
|
||||
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
if err = bm.Decr("astaxie"); err != nil {
|
||||
t.Error("Incr Error", err)
|
||||
}
|
||||
|
||||
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||
t.Error("get err")
|
||||
}
|
||||
bm.Delete("astaxie")
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("delete err")
|
||||
|
110
cache/conv.go
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// convert interface to string.
|
||||
func GetString(v interface{}) string {
|
||||
switch result := v.(type) {
|
||||
case string:
|
||||
return result
|
||||
case []byte:
|
||||
return string(result)
|
||||
default:
|
||||
if v == nil {
|
||||
return ""
|
||||
} else {
|
||||
return fmt.Sprintf("%v", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert interface to int.
|
||||
func GetInt(v interface{}) int {
|
||||
switch result := v.(type) {
|
||||
case int:
|
||||
return result
|
||||
case int32:
|
||||
return int(result)
|
||||
case int64:
|
||||
return int(result)
|
||||
default:
|
||||
d := GetString(v)
|
||||
if d != "" {
|
||||
value, err := strconv.Atoi(d)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// convert interface to int64.
|
||||
func GetInt64(v interface{}) int64 {
|
||||
switch result := v.(type) {
|
||||
case int:
|
||||
return int64(result)
|
||||
case int32:
|
||||
return int64(result)
|
||||
case int64:
|
||||
return result
|
||||
default:
|
||||
d := GetString(v)
|
||||
if d != "" {
|
||||
result, err := strconv.ParseInt(d, 10, 64)
|
||||
if err == nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// convert interface to float64.
|
||||
func GetFloat64(v interface{}) float64 {
|
||||
switch result := v.(type) {
|
||||
case float64:
|
||||
return result
|
||||
default:
|
||||
d := GetString(v)
|
||||
if d != "" {
|
||||
value, err := strconv.ParseFloat(d, 64)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// convert interface to bool.
|
||||
func GetBool(v interface{}) bool {
|
||||
switch result := v.(type) {
|
||||
case bool:
|
||||
return result
|
||||
default:
|
||||
d := GetString(v)
|
||||
if d != "" {
|
||||
result, err := strconv.ParseBool(d)
|
||||
if err == nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// convert interface to byte slice.
|
||||
func getByteArray(v interface{}) []byte {
|
||||
switch result := v.(type) {
|
||||
case []byte:
|
||||
return result
|
||||
case string:
|
||||
return []byte(result)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
144
cache/conv_test.go
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetString(t *testing.T) {
|
||||
var t1 = "test1"
|
||||
if "test1" != GetString(t1) {
|
||||
t.Error("get string from string error")
|
||||
}
|
||||
var t2 = []byte("test2")
|
||||
if "test2" != GetString(t2) {
|
||||
t.Error("get string from byte array error")
|
||||
}
|
||||
var t3 int = 1
|
||||
if "1" != GetString(t3) {
|
||||
t.Error("get string from int error")
|
||||
}
|
||||
var t4 int64 = 1
|
||||
if "1" != GetString(t4) {
|
||||
t.Error("get string from int64 error")
|
||||
}
|
||||
var t5 float64 = 1.1
|
||||
if "1.1" != GetString(t5) {
|
||||
t.Error("get string from float64 error")
|
||||
}
|
||||
|
||||
if "" != GetString(nil) {
|
||||
t.Error("get string from nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInt(t *testing.T) {
|
||||
var t1 int = 1
|
||||
if 1 != GetInt(t1) {
|
||||
t.Error("get int from int error")
|
||||
}
|
||||
var t2 int32 = 32
|
||||
if 32 != GetInt(t2) {
|
||||
t.Error("get int from int32 error")
|
||||
}
|
||||
var t3 int64 = 64
|
||||
if 64 != GetInt(t3) {
|
||||
t.Error("get int from int64 error")
|
||||
}
|
||||
var t4 = "128"
|
||||
if 128 != GetInt(t4) {
|
||||
t.Error("get int from num string error")
|
||||
}
|
||||
if 0 != GetInt(nil) {
|
||||
t.Error("get int from nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInt64(t *testing.T) {
|
||||
var i int64 = 1
|
||||
var t1 int = 1
|
||||
if i != GetInt64(t1) {
|
||||
t.Error("get int64 from int error")
|
||||
}
|
||||
var t2 int32 = 1
|
||||
if i != GetInt64(t2) {
|
||||
t.Error("get int64 from int32 error")
|
||||
}
|
||||
var t3 int64 = 1
|
||||
if i != GetInt64(t3) {
|
||||
t.Error("get int64 from int64 error")
|
||||
}
|
||||
var t4 = "1"
|
||||
if i != GetInt64(t4) {
|
||||
t.Error("get int64 from num string error")
|
||||
}
|
||||
if 0 != GetInt64(nil) {
|
||||
t.Error("get int64 from nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFloat64(t *testing.T) {
|
||||
var f float64 = 1.11
|
||||
var t1 float32 = 1.11
|
||||
if f != GetFloat64(t1) {
|
||||
t.Error("get float64 from float32 error")
|
||||
}
|
||||
var t2 float64 = 1.11
|
||||
if f != GetFloat64(t2) {
|
||||
t.Error("get float64 from float64 error")
|
||||
}
|
||||
var t3 = "1.11"
|
||||
if f != GetFloat64(t3) {
|
||||
t.Error("get float64 from string error")
|
||||
}
|
||||
|
||||
var f2 float64 = 1
|
||||
var t4 int = 1
|
||||
if f2 != GetFloat64(t4) {
|
||||
t.Error("get float64 from int error")
|
||||
}
|
||||
|
||||
if 0 != GetFloat64(nil) {
|
||||
t.Error("get float64 from nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBool(t *testing.T) {
|
||||
var t1 = true
|
||||
if true != GetBool(t1) {
|
||||
t.Error("get bool from bool error")
|
||||
}
|
||||
var t2 = "true"
|
||||
if true != GetBool(t2) {
|
||||
t.Error("get bool from string error")
|
||||
}
|
||||
if false != GetBool(nil) {
|
||||
t.Error("get bool from nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByteArray(t *testing.T) {
|
||||
var b = []byte("test")
|
||||
var t1 = []byte("test")
|
||||
if !byteArrayEquals(b, getByteArray(t1)) {
|
||||
t.Error("get byte array from byte array error")
|
||||
}
|
||||
var t2 = "test"
|
||||
if !byteArrayEquals(b, getByteArray(t2)) {
|
||||
t.Error("get byte array from string error")
|
||||
}
|
||||
if nil != getByteArray(nil) {
|
||||
t.Error("get byte array from nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func byteArrayEquals(a []byte, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
276
cache/file.go
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
/**
|
||||
* package: file
|
||||
* User: gouki
|
||||
* Date: 2013-10-22 - 14:22
|
||||
*/
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("file", NewFileCache())
|
||||
}
|
||||
|
||||
// FileCacheItem is basic unit of file cache adapter.
|
||||
// it contains data and expire time.
|
||||
type FileCacheItem struct {
|
||||
Data interface{}
|
||||
Lastaccess int64
|
||||
Expired int64
|
||||
}
|
||||
|
||||
var (
|
||||
FileCachePath string = "cache" // cache directory
|
||||
FileCacheFileSuffix string = ".bin" // cache file suffix
|
||||
FileCacheDirectoryLevel int = 2 // cache file deep level if auto generated cache files.
|
||||
FileCacheEmbedExpiry int64 = 0 // cache expire time, default is no expire forever.
|
||||
)
|
||||
|
||||
// FileCache is cache adapter for file storage.
|
||||
type FileCache struct {
|
||||
CachePath string
|
||||
FileSuffix string
|
||||
DirectoryLevel int
|
||||
EmbedExpiry int
|
||||
}
|
||||
|
||||
// Create new file cache with default directory and suffix.
|
||||
// the level and expiry need set in method StartAndGC as config string.
|
||||
func NewFileCache() *FileCache {
|
||||
return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
|
||||
}
|
||||
|
||||
// Start and begin gc for file cache.
|
||||
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
|
||||
func (this *FileCache) StartAndGC(config string) error {
|
||||
|
||||
var cfg map[string]string
|
||||
json.Unmarshal([]byte(config), &cfg)
|
||||
//fmt.Println(cfg)
|
||||
if _, ok := cfg["CachePath"]; !ok {
|
||||
cfg["CachePath"] = FileCachePath
|
||||
}
|
||||
if _, ok := cfg["FileSuffix"]; !ok {
|
||||
cfg["FileSuffix"] = FileCacheFileSuffix
|
||||
}
|
||||
if _, ok := cfg["DirectoryLevel"]; !ok {
|
||||
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
|
||||
}
|
||||
if _, ok := cfg["EmbedExpiry"]; !ok {
|
||||
cfg["EmbedExpiry"] = strconv.FormatInt(FileCacheEmbedExpiry, 10)
|
||||
}
|
||||
this.CachePath = cfg["CachePath"]
|
||||
this.FileSuffix = cfg["FileSuffix"]
|
||||
this.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
|
||||
this.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
|
||||
|
||||
this.Init()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init will make new dir for file cache if not exist.
|
||||
func (this *FileCache) Init() {
|
||||
app := filepath.Dir(os.Args[0])
|
||||
this.CachePath = filepath.Join(app, this.CachePath)
|
||||
ok, err := exists(this.CachePath)
|
||||
if err != nil { // print error
|
||||
//fmt.Println(err)
|
||||
}
|
||||
if !ok {
|
||||
if err = os.Mkdir(this.CachePath, os.ModePerm); err != nil {
|
||||
//fmt.Println(err);
|
||||
}
|
||||
}
|
||||
//fmt.Println(this.getCacheFileName("123456"));
|
||||
}
|
||||
|
||||
// get cached file name. it's md5 encoded.
|
||||
func (this *FileCache) getCacheFileName(key string) string {
|
||||
m := md5.New()
|
||||
io.WriteString(m, key)
|
||||
keyMd5 := hex.EncodeToString(m.Sum(nil))
|
||||
cachePath := this.CachePath
|
||||
//fmt.Println("cachepath : " , cachePath)
|
||||
//fmt.Println("md5" , keyMd5);
|
||||
switch this.DirectoryLevel {
|
||||
case 2:
|
||||
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
|
||||
case 1:
|
||||
cachePath = filepath.Join(cachePath, keyMd5[0:2])
|
||||
}
|
||||
|
||||
ok, err := exists(cachePath)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
}
|
||||
if !ok {
|
||||
if err = os.MkdirAll(cachePath, os.ModePerm); err != nil {
|
||||
//fmt.Println(err);
|
||||
}
|
||||
}
|
||||
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, this.FileSuffix))
|
||||
}
|
||||
|
||||
// Get value from file cache.
|
||||
// if non-exist or expired, return empty string.
|
||||
func (this *FileCache) Get(key string) interface{} {
|
||||
filename := this.getCacheFileName(key)
|
||||
filedata, err := File_get_contents(filename)
|
||||
//fmt.Println("get length:" , len(filedata));
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var to FileCacheItem
|
||||
Gob_decode([]byte(filedata), &to)
|
||||
if to.Expired < time.Now().Unix() {
|
||||
return ""
|
||||
}
|
||||
return to.Data
|
||||
}
|
||||
|
||||
// Put value into file cache.
|
||||
// timeout means how long to keep this file, unit of second.
|
||||
func (this *FileCache) Put(key string, val interface{}, timeout int64) error {
|
||||
filename := this.getCacheFileName(key)
|
||||
var item FileCacheItem
|
||||
item.Data = val
|
||||
if timeout == FileCacheEmbedExpiry {
|
||||
item.Expired = time.Now().Unix() + (86400 * 365 * 10) //10年
|
||||
} else {
|
||||
item.Expired = time.Now().Unix() + timeout
|
||||
}
|
||||
item.Lastaccess = time.Now().Unix()
|
||||
data, err := Gob_encode(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = File_put_contents(filename, data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete file cache value.
|
||||
func (this *FileCache) Delete(key string) error {
|
||||
filename := this.getCacheFileName(key)
|
||||
if ok, _ := exists(filename); ok {
|
||||
return os.Remove(filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increase cached int value.
|
||||
// this value is saving forever unless Delete.
|
||||
func (this *FileCache) Incr(key string) error {
|
||||
data := this.Get(key)
|
||||
var incr int
|
||||
fmt.Println(reflect.TypeOf(data).Name())
|
||||
if reflect.TypeOf(data).Name() != "int" {
|
||||
incr = 0
|
||||
} else {
|
||||
incr = data.(int) + 1
|
||||
}
|
||||
this.Put(key, incr, FileCacheEmbedExpiry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrease cached int value.
|
||||
func (this *FileCache) Decr(key string) error {
|
||||
data := this.Get(key)
|
||||
var decr int
|
||||
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
|
||||
decr = 0
|
||||
} else {
|
||||
decr = data.(int) - 1
|
||||
}
|
||||
this.Put(key, decr, FileCacheEmbedExpiry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check value is exist.
|
||||
func (this *FileCache) IsExist(key string) bool {
|
||||
filename := this.getCacheFileName(key)
|
||||
ret, _ := exists(filename)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Clean cached files.
|
||||
// not implemented.
|
||||
func (this *FileCache) ClearAll() error {
|
||||
//this.CachePath .递归删除
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check file exist.
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Get bytes to file.
|
||||
// if non-exist, create this file.
|
||||
func File_get_contents(filename string) ([]byte, error) {
|
||||
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
defer f.Close()
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
data := make([]byte, stat.Size())
|
||||
result, err := f.Read(data)
|
||||
if int64(result) == stat.Size() {
|
||||
return data, err
|
||||
}
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
// Put bytes to file.
|
||||
// if non-exist, create this file.
|
||||
func File_put_contents(filename string, content []byte) error {
|
||||
fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
_, err = fp.Write(content)
|
||||
return err
|
||||
}
|
||||
|
||||
// Gob encodes file cache item.
|
||||
func Gob_encode(data interface{}) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// Gob decodes file cache item.
|
||||
func Gob_decode(data []byte, to interface{}) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := gob.NewDecoder(buf)
|
||||
return dec.Decode(&to)
|
||||
}
|
38
cache/memcache.go
vendored
@ -1,34 +1,43 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"code.google.com/p/vitess/go/memcache"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/beego/memcache"
|
||||
)
|
||||
|
||||
// Memcache adapter.
|
||||
type MemcacheCache struct {
|
||||
c *memcache.Connection
|
||||
conninfo string
|
||||
}
|
||||
|
||||
// create new memcache adapter.
|
||||
func NewMemCache() *MemcacheCache {
|
||||
return &MemcacheCache{}
|
||||
}
|
||||
|
||||
// get value from memcache.
|
||||
func (rc *MemcacheCache) Get(key string) interface{} {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
}
|
||||
v, _, err := rc.c.Get(key)
|
||||
v, err := rc.c.Get(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var contain interface{}
|
||||
contain = v
|
||||
if len(v) > 0 {
|
||||
contain = string(v[0].Value)
|
||||
} else {
|
||||
contain = nil
|
||||
}
|
||||
return contain
|
||||
}
|
||||
|
||||
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int) error {
|
||||
// put value to memcache. only support string.
|
||||
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
}
|
||||
@ -43,6 +52,7 @@ func (rc *MemcacheCache) Put(key string, val interface{}, timeout int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete value in memcache.
|
||||
func (rc *MemcacheCache) Delete(key string) error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
@ -51,11 +61,24 @@ func (rc *MemcacheCache) Delete(key string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// [Not Support]
|
||||
// increase counter.
|
||||
func (rc *MemcacheCache) Incr(key string) error {
|
||||
return errors.New("not support in memcache")
|
||||
}
|
||||
|
||||
// [Not Support]
|
||||
// decrease counter.
|
||||
func (rc *MemcacheCache) Decr(key string) error {
|
||||
return errors.New("not support in memcache")
|
||||
}
|
||||
|
||||
// check value exists in memcache.
|
||||
func (rc *MemcacheCache) IsExist(key string) bool {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
}
|
||||
v, _, err := rc.c.Get(key)
|
||||
v, err := rc.c.Get(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@ -67,6 +90,7 @@ func (rc *MemcacheCache) IsExist(key string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// clear all cached in memcache.
|
||||
func (rc *MemcacheCache) ClearAll() error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
@ -75,6 +99,9 @@ func (rc *MemcacheCache) ClearAll() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// start memcache adapter.
|
||||
// config string is like {"conn":"connection info"}.
|
||||
// if connecting error, return.
|
||||
func (rc *MemcacheCache) StartAndGC(config string) error {
|
||||
var cf map[string]string
|
||||
json.Unmarshal([]byte(config), &cf)
|
||||
@ -89,6 +116,7 @@ func (rc *MemcacheCache) StartAndGC(config string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// connect to memcache and keep the connection.
|
||||
func (rc *MemcacheCache) connectInit() *memcache.Connection {
|
||||
c, err := memcache.Connect(rc.conninfo)
|
||||
if err != nil {
|
||||
|
122
cache/memory.go
vendored
@ -4,39 +4,39 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// clock time of recycling the expired cache items in memory.
|
||||
DefaultEvery int = 60 // 1 minute
|
||||
)
|
||||
|
||||
// Memory cache item.
|
||||
type MemoryItem struct {
|
||||
val interface{}
|
||||
Lastaccess time.Time
|
||||
expired int
|
||||
}
|
||||
|
||||
func (itm *MemoryItem) Access() interface{} {
|
||||
itm.Lastaccess = time.Now()
|
||||
return itm.val
|
||||
expired int64
|
||||
}
|
||||
|
||||
// Memory cache adapter.
|
||||
// it contains a RW locker for safe map storage.
|
||||
type MemoryCache struct {
|
||||
lock sync.RWMutex
|
||||
dur time.Duration
|
||||
items map[string]*MemoryItem
|
||||
Every int // Run an expiration check Every seconds
|
||||
Every int // run an expiration check Every cloc; time
|
||||
}
|
||||
|
||||
// NewDefaultCache returns a new FileCache with sane defaults.
|
||||
// NewMemoryCache returns a new MemoryCache.
|
||||
func NewMemoryCache() *MemoryCache {
|
||||
cache := MemoryCache{items: make(map[string]*MemoryItem)}
|
||||
return &cache
|
||||
}
|
||||
|
||||
// Get cache from memory.
|
||||
// if non-existed or expired, return nil.
|
||||
func (bc *MemoryCache) Get(name string) interface{} {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
@ -44,21 +44,27 @@ func (bc *MemoryCache) Get(name string) interface{} {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return itm.Access()
|
||||
if (time.Now().Unix() - itm.Lastaccess.Unix()) > itm.expired {
|
||||
go bc.Delete(name)
|
||||
return nil
|
||||
}
|
||||
return itm.val
|
||||
}
|
||||
|
||||
func (bc *MemoryCache) Put(name string, value interface{}, expired int) error {
|
||||
// Put cache to memory.
|
||||
func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
t := MemoryItem{val: value, Lastaccess: time.Now(), expired: expired}
|
||||
if _, ok := bc.items[name]; ok {
|
||||
return errors.New("the key is exist")
|
||||
} else {
|
||||
bc.items[name] = &t
|
||||
t := MemoryItem{
|
||||
val: value,
|
||||
Lastaccess: time.Now(),
|
||||
expired: expired,
|
||||
}
|
||||
bc.items[name] = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Delete cache in memory.
|
||||
func (bc *MemoryCache) Delete(name string) error {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
@ -73,6 +79,74 @@ func (bc *MemoryCache) Delete(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increase cache counter in memory.
|
||||
// it supports int,int64,int32,uint,uint64,uint32.
|
||||
func (bc *MemoryCache) Incr(key string) error {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
itm, ok := bc.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch itm.val.(type) {
|
||||
case int:
|
||||
itm.val = itm.val.(int) + 1
|
||||
case int64:
|
||||
itm.val = itm.val.(int64) + 1
|
||||
case int32:
|
||||
itm.val = itm.val.(int32) + 1
|
||||
case uint:
|
||||
itm.val = itm.val.(uint) + 1
|
||||
case uint32:
|
||||
itm.val = itm.val.(uint32) + 1
|
||||
case uint64:
|
||||
itm.val = itm.val.(uint64) + 1
|
||||
default:
|
||||
return errors.New("item val is not int int64 int32")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrease counter in memory.
|
||||
func (bc *MemoryCache) Decr(key string) error {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
itm, ok := bc.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch itm.val.(type) {
|
||||
case int:
|
||||
itm.val = itm.val.(int) - 1
|
||||
case int64:
|
||||
itm.val = itm.val.(int64) - 1
|
||||
case int32:
|
||||
itm.val = itm.val.(int32) - 1
|
||||
case uint:
|
||||
if itm.val.(uint) > 0 {
|
||||
itm.val = itm.val.(uint) - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint32:
|
||||
if itm.val.(uint32) > 0 {
|
||||
itm.val = itm.val.(uint32) - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint64:
|
||||
if itm.val.(uint64) > 0 {
|
||||
itm.val = itm.val.(uint64) - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
default:
|
||||
return errors.New("item val is not int int64 int32")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// check cache exist in memory.
|
||||
func (bc *MemoryCache) IsExist(name string) bool {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
@ -80,6 +154,7 @@ func (bc *MemoryCache) IsExist(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// delete all cache in memory.
|
||||
func (bc *MemoryCache) ClearAll() error {
|
||||
bc.lock.Lock()
|
||||
defer bc.lock.Unlock()
|
||||
@ -87,11 +162,11 @@ func (bc *MemoryCache) ClearAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start activates the file cache; it will
|
||||
// start memory cache. it will check expiration in every clock time.
|
||||
func (bc *MemoryCache) StartAndGC(config string) error {
|
||||
var cf map[string]int
|
||||
json.Unmarshal([]byte(config), &cf)
|
||||
if _, ok := cf["every"]; !ok {
|
||||
if _, ok := cf["interval"]; !ok {
|
||||
cf = make(map[string]int)
|
||||
cf["interval"] = DefaultEvery
|
||||
}
|
||||
@ -105,12 +180,13 @@ func (bc *MemoryCache) StartAndGC(config string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check expiration.
|
||||
func (bc *MemoryCache) vaccuum() {
|
||||
if bc.Every < 1 {
|
||||
return
|
||||
}
|
||||
for {
|
||||
<-time.After(time.Duration(bc.dur) * time.Second)
|
||||
<-time.After(bc.dur)
|
||||
if bc.items == nil {
|
||||
return
|
||||
}
|
||||
@ -128,12 +204,8 @@ func (bc *MemoryCache) item_expired(name string) bool {
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
dur := time.Now().Sub(itm.Lastaccess)
|
||||
sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds()))
|
||||
if err != nil {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
} else if sec >= itm.expired {
|
||||
sec := time.Now().Unix() - itm.Lastaccess.Unix()
|
||||
if sec >= itm.expired {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
}
|
||||
|
93
cache/redis.go
vendored
@ -3,26 +3,35 @@ package cache
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
|
||||
"github.com/beego/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
// the collection name of redis for cache adapter.
|
||||
DefaultKey string = "beecacheRedis"
|
||||
)
|
||||
|
||||
// Redis cache adapter.
|
||||
type RedisCache struct {
|
||||
c redis.Conn
|
||||
conninfo string
|
||||
key string
|
||||
}
|
||||
|
||||
// create new redis cache with default collection name.
|
||||
func NewRedisCache() *RedisCache {
|
||||
return &RedisCache{key: DefaultKey}
|
||||
}
|
||||
|
||||
// Get cache from redis.
|
||||
func (rc *RedisCache) Get(key string) interface{} {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
v, err := rc.c.Do("HGET", rc.key, key)
|
||||
if err != nil {
|
||||
@ -31,25 +40,41 @@ func (rc *RedisCache) Get(key string) interface{} {
|
||||
return v
|
||||
}
|
||||
|
||||
func (rc *RedisCache) Put(key string, val interface{}, timeout int) error {
|
||||
// put cache to redis.
|
||||
// timeout is ignored.
|
||||
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.c.Do("HSET", rc.key, key, val)
|
||||
return err
|
||||
}
|
||||
|
||||
// delete cache in redis.
|
||||
func (rc *RedisCache) Delete(key string) error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.c.Do("HDEL", rc.key, key)
|
||||
return err
|
||||
}
|
||||
|
||||
// check cache exist in redis.
|
||||
func (rc *RedisCache) IsExist(key string) bool {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
v, err := redis.Bool(rc.c.Do("HEXISTS", rc.key, key))
|
||||
if err != nil {
|
||||
@ -58,14 +83,55 @@ func (rc *RedisCache) IsExist(key string) bool {
|
||||
return v
|
||||
}
|
||||
|
||||
// increase counter in redis.
|
||||
func (rc *RedisCache) Incr(key string) error {
|
||||
if rc.c == nil {
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, 1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decrease counter in redis.
|
||||
func (rc *RedisCache) Decr(key string) error {
|
||||
if rc.c == nil {
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, -1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean all cache in redis. delete this redis collection.
|
||||
func (rc *RedisCache) ClearAll() error {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.c.Do("DEL", rc.key)
|
||||
return err
|
||||
}
|
||||
|
||||
// start redis cache adapter.
|
||||
// config is like {"key":"collection key","conn":"connection info"}
|
||||
// the cache item in redis are stored forever,
|
||||
// so no gc operation.
|
||||
func (rc *RedisCache) StartAndGC(config string) error {
|
||||
var cf map[string]string
|
||||
json.Unmarshal([]byte(config), &cf)
|
||||
@ -77,19 +143,24 @@ func (rc *RedisCache) StartAndGC(config string) error {
|
||||
}
|
||||
rc.key = cf["key"]
|
||||
rc.conninfo = cf["conn"]
|
||||
rc.c = rc.connectInit()
|
||||
var err error
|
||||
rc.c, err = rc.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rc.c == nil {
|
||||
return errors.New("dial tcp conn error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *RedisCache) connectInit() redis.Conn {
|
||||
// connect to redis.
|
||||
func (rc *RedisCache) connectInit() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", rc.conninfo)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
return c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
421
config.go
@ -1,181 +1,314 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/astaxie/beego/config"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
var (
|
||||
bComment = []byte{'#'}
|
||||
bEmpty = []byte{}
|
||||
bEqual = []byte{'='}
|
||||
bDQuote = []byte{'"'}
|
||||
BeeApp *App // beego application
|
||||
AppName string
|
||||
AppPath string
|
||||
AppConfigPath string
|
||||
StaticDir map[string]string
|
||||
TemplateCache map[string]*template.Template // template caching map
|
||||
StaticExtensionsToGzip []string // files with should be compressed with gzip (.js,.css,etc)
|
||||
HttpAddr string
|
||||
HttpPort int
|
||||
HttpTLS bool
|
||||
HttpCertFile string
|
||||
HttpKeyFile string
|
||||
RecoverPanic bool // flag of auto recover panic
|
||||
AutoRender bool // flag of render template automatically
|
||||
ViewsPath string
|
||||
RunMode string // run mode, "dev" or "prod"
|
||||
AppConfig config.ConfigContainer
|
||||
GlobalSessions *session.Manager // global session mananger
|
||||
SessionOn bool // flag of starting session auto. default is false.
|
||||
SessionProvider string // default session provider, memory, mysql , redis ,etc.
|
||||
SessionName string // the cookie name when saving session id into cookie.
|
||||
SessionGCMaxLifetime int64 // session gc time for auto cleaning expired session.
|
||||
SessionSavePath string // if use mysql/redis/file provider, define save path to connection info.
|
||||
SessionHashFunc string // session hash generation func.
|
||||
SessionHashKey string // session hash salt string.
|
||||
SessionCookieLifeTime int // the life time of session id in cookie.
|
||||
UseFcgi bool
|
||||
MaxMemory int64
|
||||
EnableGzip bool // flag of enable gzip
|
||||
DirectoryIndex bool // flag of display directory index. default is false.
|
||||
EnableHotUpdate bool // flag of hot update checking by app self. default is false.
|
||||
HttpServerTimeOut int64
|
||||
ErrorsShow bool // flag of show errors in page. if true, show error and trace info in page rendered with error template.
|
||||
XSRFKEY string // xsrf hash salt string.
|
||||
EnableXSRF bool // flag of enable xsrf.
|
||||
XSRFExpire int // the expiry of xsrf value.
|
||||
CopyRequestBody bool // flag of copy raw request body in context.
|
||||
TemplateLeft string
|
||||
TemplateRight string
|
||||
BeegoServerName string // beego server name exported in response header.
|
||||
EnableAdmin bool // flag of enable admin module to log every request info.
|
||||
AdminHttpAddr string // http server configurations for admin module.
|
||||
AdminHttpPort int
|
||||
)
|
||||
|
||||
// A Config represents the configuration.
|
||||
type Config struct {
|
||||
filename string
|
||||
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
|
||||
data map[string]string // key: value
|
||||
offset map[string]int64 // key: offset; for editing.
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// ParseFile creates a new Config and parses the file configuration from the
|
||||
// named file.
|
||||
func LoadConfig(name string) (*Config, error) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
file.Name(),
|
||||
make(map[int][]string),
|
||||
make(map[string]string),
|
||||
make(map[string]int64),
|
||||
sync.RWMutex{},
|
||||
}
|
||||
cfg.Lock()
|
||||
defer cfg.Unlock()
|
||||
defer file.Close()
|
||||
|
||||
var comment bytes.Buffer
|
||||
buf := bufio.NewReader(file)
|
||||
|
||||
for nComment, off := 0, int64(1); ; {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
}
|
||||
|
||||
off += int64(len(line))
|
||||
|
||||
if bytes.HasPrefix(line, bComment) {
|
||||
line = bytes.TrimLeft(line, "#")
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
comment.Write(line)
|
||||
comment.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
if comment.Len() != 0 {
|
||||
cfg.comment[nComment] = []string{comment.String()}
|
||||
comment.Reset()
|
||||
nComment++
|
||||
}
|
||||
|
||||
val := bytes.SplitN(line, bEqual, 2)
|
||||
if bytes.HasPrefix([]byte(strings.TrimSpace(string(val[1]))), bDQuote) {
|
||||
val[1] = bytes.Trim([]byte(strings.TrimSpace(string(val[1]))), `"`)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(string(val[0]))
|
||||
cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
|
||||
cfg.data[key] = strings.TrimSpace(string(val[1]))
|
||||
cfg.offset[key] = off
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *Config) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.data[key])
|
||||
}
|
||||
|
||||
// Int returns the integer value for a given key.
|
||||
func (c *Config) Int(key string) (int, error) {
|
||||
return strconv.Atoi(c.data[key])
|
||||
}
|
||||
|
||||
func (c *Config) Int64(key string) (int64, error) {
|
||||
return strconv.ParseInt(c.data[key], 10, 64)
|
||||
}
|
||||
|
||||
// Float returns the float value for a given key.
|
||||
func (c *Config) Float(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.data[key], 64)
|
||||
}
|
||||
|
||||
// String returns the string value for a given key.
|
||||
func (c *Config) String(key string) string {
|
||||
return c.data[key]
|
||||
}
|
||||
|
||||
// WriteValue writes a new value for key.
|
||||
func (c *Config) SetValue(key, value string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if _, found := c.data[key]; !found {
|
||||
return errors.New("key not found: " + key)
|
||||
}
|
||||
|
||||
c.data[key] = value
|
||||
return nil
|
||||
func init() {
|
||||
// create beego application
|
||||
BeeApp = NewApp()
|
||||
|
||||
// initialize default configurations
|
||||
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
os.Chdir(AppPath)
|
||||
|
||||
StaticDir = make(map[string]string)
|
||||
StaticDir["/static"] = "static"
|
||||
|
||||
StaticExtensionsToGzip = []string{".css", ".js"}
|
||||
|
||||
TemplateCache = make(map[string]*template.Template)
|
||||
|
||||
// set this to 0.0.0.0 to make this app available to externally
|
||||
HttpAddr = ""
|
||||
HttpPort = 8080
|
||||
|
||||
AppName = "beego"
|
||||
|
||||
RunMode = "dev" //default runmod
|
||||
|
||||
AutoRender = true
|
||||
|
||||
RecoverPanic = true
|
||||
|
||||
ViewsPath = "views"
|
||||
|
||||
SessionOn = false
|
||||
SessionProvider = "memory"
|
||||
SessionName = "beegosessionID"
|
||||
SessionGCMaxLifetime = 3600
|
||||
SessionSavePath = ""
|
||||
SessionHashFunc = "sha1"
|
||||
SessionHashKey = "beegoserversessionkey"
|
||||
SessionCookieLifeTime = 0 //set cookie default is the brower life
|
||||
|
||||
UseFcgi = false
|
||||
|
||||
MaxMemory = 1 << 26 //64MB
|
||||
|
||||
EnableGzip = false
|
||||
|
||||
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
||||
|
||||
HttpServerTimeOut = 0
|
||||
|
||||
ErrorsShow = true
|
||||
|
||||
XSRFKEY = "beegoxsrf"
|
||||
XSRFExpire = 0
|
||||
|
||||
TemplateLeft = "{{"
|
||||
TemplateRight = "}}"
|
||||
|
||||
BeegoServerName = "beegoServer"
|
||||
|
||||
EnableAdmin = false
|
||||
AdminHttpAddr = "127.0.0.1"
|
||||
AdminHttpPort = 8088
|
||||
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
// init BeeLogger
|
||||
BeeLogger = logs.NewLogger(10000)
|
||||
BeeLogger.SetLogger("console", "")
|
||||
|
||||
err := ParseConfig()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
// for init if doesn't have app.conf will not panic
|
||||
Info(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseConfig parsed default config file.
|
||||
// now only support ini, next will support json.
|
||||
func ParseConfig() (err error) {
|
||||
AppConfig, err = LoadConfig(AppConfigPath)
|
||||
AppConfig, err = config.NewConfig("ini", AppConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
HttpAddr = AppConfig.String("httpaddr")
|
||||
if v, err := AppConfig.Int("httpport"); err == nil {
|
||||
HttpAddr = AppConfig.String("HttpAddr")
|
||||
|
||||
if v, err := AppConfig.Int("HttpPort"); err == nil {
|
||||
HttpPort = v
|
||||
}
|
||||
if v, err := AppConfig.Int64("maxmemory"); err == nil {
|
||||
MaxMemory = v
|
||||
|
||||
if maxmemory, err := AppConfig.Int64("MaxMemory"); err == nil {
|
||||
MaxMemory = maxmemory
|
||||
}
|
||||
AppName = AppConfig.String("appname")
|
||||
if runmode := AppConfig.String("runmode"); runmode != "" {
|
||||
|
||||
if appname := AppConfig.String("AppName"); appname != "" {
|
||||
AppName = appname
|
||||
}
|
||||
|
||||
if runmode := AppConfig.String("RunMode"); runmode != "" {
|
||||
RunMode = runmode
|
||||
}
|
||||
if ar, err := AppConfig.Bool("autorender"); err == nil {
|
||||
AutoRender = ar
|
||||
|
||||
if autorender, err := AppConfig.Bool("AutoRender"); err == nil {
|
||||
AutoRender = autorender
|
||||
}
|
||||
if ar, err := AppConfig.Bool("autorecover"); err == nil {
|
||||
RecoverPanic = ar
|
||||
|
||||
if autorecover, err := AppConfig.Bool("RecoverPanic"); err == nil {
|
||||
RecoverPanic = autorecover
|
||||
}
|
||||
if ar, err := AppConfig.Bool("pprofon"); err == nil {
|
||||
PprofOn = ar
|
||||
}
|
||||
if views := AppConfig.String("viewspath"); views != "" {
|
||||
|
||||
if views := AppConfig.String("ViewsPath"); views != "" {
|
||||
ViewsPath = views
|
||||
}
|
||||
if ar, err := AppConfig.Bool("sessionon"); err == nil {
|
||||
SessionOn = ar
|
||||
|
||||
if sessionon, err := AppConfig.Bool("SessionOn"); err == nil {
|
||||
SessionOn = sessionon
|
||||
}
|
||||
if ar := AppConfig.String("sessionprovider"); ar != "" {
|
||||
SessionProvider = ar
|
||||
|
||||
if sessProvider := AppConfig.String("SessionProvider"); sessProvider != "" {
|
||||
SessionProvider = sessProvider
|
||||
}
|
||||
if ar := AppConfig.String("sessionname"); ar != "" {
|
||||
SessionName = ar
|
||||
|
||||
if sessName := AppConfig.String("SessionName"); sessName != "" {
|
||||
SessionName = sessName
|
||||
}
|
||||
if ar := AppConfig.String("sessionsavepath"); ar != "" {
|
||||
SessionSavePath = ar
|
||||
|
||||
if sesssavepath := AppConfig.String("SessionSavePath"); sesssavepath != "" {
|
||||
SessionSavePath = sesssavepath
|
||||
}
|
||||
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err == nil && ar != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64)
|
||||
|
||||
if sesshashfunc := AppConfig.String("SessionHashFunc"); sesshashfunc != "" {
|
||||
SessionHashFunc = sesshashfunc
|
||||
}
|
||||
|
||||
if sesshashkey := AppConfig.String("SessionHashKey"); sesshashkey != "" {
|
||||
SessionHashKey = sesshashkey
|
||||
}
|
||||
|
||||
if sessMaxLifeTime, err := AppConfig.Int("SessionGCMaxLifetime"); err == nil && sessMaxLifeTime != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(sessMaxLifeTime), 10, 64)
|
||||
SessionGCMaxLifetime = int64val
|
||||
}
|
||||
if ar, err := AppConfig.Bool("usefcgi"); err == nil {
|
||||
UseFcgi = ar
|
||||
|
||||
if sesscookielifetime, err := AppConfig.Int("SessionCookieLifeTime"); err == nil && sesscookielifetime != 0 {
|
||||
SessionCookieLifeTime = sesscookielifetime
|
||||
}
|
||||
if ar, err := AppConfig.Bool("enablegzip"); err == nil {
|
||||
EnableGzip = ar
|
||||
|
||||
if usefcgi, err := AppConfig.Bool("UseFcgi"); err == nil {
|
||||
UseFcgi = usefcgi
|
||||
}
|
||||
if ar, err := AppConfig.Bool("directoryindex"); err == nil {
|
||||
DirectoryIndex = ar
|
||||
|
||||
if enablegzip, err := AppConfig.Bool("EnableGzip"); err == nil {
|
||||
EnableGzip = enablegzip
|
||||
}
|
||||
|
||||
if directoryindex, err := AppConfig.Bool("DirectoryIndex"); err == nil {
|
||||
DirectoryIndex = directoryindex
|
||||
}
|
||||
|
||||
if hotupdate, err := AppConfig.Bool("HotUpdate"); err == nil {
|
||||
EnableHotUpdate = hotupdate
|
||||
}
|
||||
|
||||
if timeout, err := AppConfig.Int64("HttpServerTimeOut"); err == nil {
|
||||
HttpServerTimeOut = timeout
|
||||
}
|
||||
|
||||
if errorsshow, err := AppConfig.Bool("ErrorsShow"); err == nil {
|
||||
ErrorsShow = errorsshow
|
||||
}
|
||||
|
||||
if copyrequestbody, err := AppConfig.Bool("CopyRequestBody"); err == nil {
|
||||
CopyRequestBody = copyrequestbody
|
||||
}
|
||||
|
||||
if xsrfkey := AppConfig.String("XSRFKEY"); xsrfkey != "" {
|
||||
XSRFKEY = xsrfkey
|
||||
}
|
||||
|
||||
if enablexsrf, err := AppConfig.Bool("EnableXSRF"); err == nil {
|
||||
EnableXSRF = enablexsrf
|
||||
}
|
||||
|
||||
if expire, err := AppConfig.Int("XSRFExpire"); err == nil {
|
||||
XSRFExpire = expire
|
||||
}
|
||||
|
||||
if tplleft := AppConfig.String("TemplateLeft"); tplleft != "" {
|
||||
TemplateLeft = tplleft
|
||||
}
|
||||
|
||||
if tplright := AppConfig.String("TemplateRight"); tplright != "" {
|
||||
TemplateRight = tplright
|
||||
}
|
||||
|
||||
if httptls, err := AppConfig.Bool("HttpTLS"); err == nil {
|
||||
HttpTLS = httptls
|
||||
}
|
||||
|
||||
if certfile := AppConfig.String("HttpCertFile"); certfile != "" {
|
||||
HttpCertFile = certfile
|
||||
}
|
||||
|
||||
if keyfile := AppConfig.String("HttpKeyFile"); keyfile != "" {
|
||||
HttpKeyFile = keyfile
|
||||
}
|
||||
|
||||
if serverName := AppConfig.String("BeegoServerName"); serverName != "" {
|
||||
BeegoServerName = serverName
|
||||
}
|
||||
|
||||
if sd := AppConfig.String("StaticDir"); sd != "" {
|
||||
for k := range StaticDir {
|
||||
delete(StaticDir, k)
|
||||
}
|
||||
sds := strings.Fields(sd)
|
||||
for _, v := range sds {
|
||||
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
|
||||
StaticDir["/"+url2fsmap[0]] = url2fsmap[1]
|
||||
} else {
|
||||
StaticDir["/"+url2fsmap[0]] = url2fsmap[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
|
||||
extensions := strings.Split(sgz, ",")
|
||||
if len(extensions) > 0 {
|
||||
StaticExtensionsToGzip = []string{}
|
||||
for _, ext := range extensions {
|
||||
if len(ext) == 0 {
|
||||
continue
|
||||
}
|
||||
extWithDot := ext
|
||||
if extWithDot[:1] != "." {
|
||||
extWithDot = "." + extWithDot
|
||||
}
|
||||
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
|
||||
EnableAdmin = enableadmin
|
||||
}
|
||||
|
||||
if adminhttpaddr := AppConfig.String("AdminHttpAddr"); adminhttpaddr != "" {
|
||||
AdminHttpAddr = adminhttpaddr
|
||||
}
|
||||
|
||||
if adminhttpport, err := AppConfig.Int("AdminHttpPort"); err == nil {
|
||||
AdminHttpPort = adminhttpport
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
44
config/config.go
Normal file
@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ConfigContainer interface {
|
||||
Set(key, val string) error
|
||||
String(key string) string
|
||||
Int(key string) (int, error)
|
||||
Int64(key string) (int64, error)
|
||||
Bool(key string) (bool, error)
|
||||
Float(key string) (float64, error)
|
||||
DIY(key string) (interface{}, error)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
Parse(key string) (ConfigContainer, error)
|
||||
}
|
||||
|
||||
var adapters = make(map[string]Config)
|
||||
|
||||
// Register makes a config adapter available by the adapter name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, adapter Config) {
|
||||
if adapter == nil {
|
||||
panic("config: Register adapter is nil")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic("config: Register called twice for adapter " + name)
|
||||
}
|
||||
adapters[name] = adapter
|
||||
}
|
||||
|
||||
// adapterNamer is ini/json/xml/yaml
|
||||
// filename is the config file path
|
||||
func NewConfig(adapterName, fileaname string) (ConfigContainer, error) {
|
||||
adapter, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
|
||||
}
|
||||
return adapter.Parse(fileaname)
|
||||
}
|
208
config/ini.go
Normal file
@ -0,0 +1,208 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
DEFAULT_SECTION = "default"
|
||||
bNumComment = []byte{'#'} // number sign
|
||||
bSemComment = []byte{';'} // semicolon
|
||||
bEmpty = []byte{}
|
||||
bEqual = []byte{'='}
|
||||
bDQuote = []byte{'"'}
|
||||
sectionStart = []byte{'['}
|
||||
sectionEnd = []byte{']'}
|
||||
)
|
||||
|
||||
type IniConfig struct {
|
||||
}
|
||||
|
||||
// ParseFile creates a new Config and parses the file configuration from the
|
||||
// named file.
|
||||
func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &IniConfigContainer{
|
||||
file.Name(),
|
||||
make(map[string]map[string]string),
|
||||
make(map[string]string),
|
||||
make(map[string]string),
|
||||
sync.RWMutex{},
|
||||
}
|
||||
cfg.Lock()
|
||||
defer cfg.Unlock()
|
||||
defer file.Close()
|
||||
|
||||
var comment bytes.Buffer
|
||||
buf := bufio.NewReader(file)
|
||||
section := DEFAULT_SECTION
|
||||
for {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
|
||||
var bComment []byte
|
||||
switch {
|
||||
case bytes.HasPrefix(line, bNumComment):
|
||||
bComment = bNumComment
|
||||
case bytes.HasPrefix(line, bSemComment):
|
||||
bComment = bSemComment
|
||||
}
|
||||
if bComment != nil {
|
||||
line = bytes.TrimLeft(line, string(bComment))
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
comment.Write(line)
|
||||
comment.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
|
||||
section = string(line[1 : len(line)-1])
|
||||
section = strings.ToLower(section) // section name case insensitive
|
||||
if comment.Len() > 0 {
|
||||
cfg.sectionComment[section] = comment.String()
|
||||
comment.Reset()
|
||||
}
|
||||
if _, ok := cfg.data[section]; !ok {
|
||||
cfg.data[section] = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
if _, ok := cfg.data[section]; !ok {
|
||||
cfg.data[section] = make(map[string]string)
|
||||
}
|
||||
keyval := bytes.SplitN(line, bEqual, 2)
|
||||
val := bytes.TrimSpace(keyval[1])
|
||||
if bytes.HasPrefix(val, bDQuote) {
|
||||
val = bytes.Trim(val, `"`)
|
||||
}
|
||||
|
||||
key := string(bytes.TrimSpace(keyval[0])) // key name case insensitive
|
||||
key = strings.ToLower(key)
|
||||
cfg.data[section][key] = string(val)
|
||||
if comment.Len() > 0 {
|
||||
cfg.keycomment[section+"."+key] = comment.String()
|
||||
comment.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// A Config represents the configuration.
|
||||
type IniConfigContainer struct {
|
||||
filename string
|
||||
data map[string]map[string]string //section=> key:val
|
||||
sectionComment map[string]string //sction : comment
|
||||
keycomment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
||||
key = strings.ToLower(key)
|
||||
return strconv.ParseBool(c.getdata(key))
|
||||
}
|
||||
|
||||
// Int returns the integer value for a given key.
|
||||
func (c *IniConfigContainer) Int(key string) (int, error) {
|
||||
key = strings.ToLower(key)
|
||||
return strconv.Atoi(c.getdata(key))
|
||||
}
|
||||
|
||||
func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
||||
key = strings.ToLower(key)
|
||||
return strconv.ParseInt(c.getdata(key), 10, 64)
|
||||
}
|
||||
|
||||
// Float returns the float value for a given key.
|
||||
func (c *IniConfigContainer) Float(key string) (float64, error) {
|
||||
key = strings.ToLower(key)
|
||||
return strconv.ParseFloat(c.getdata(key), 64)
|
||||
}
|
||||
|
||||
// String returns the string value for a given key.
|
||||
func (c *IniConfigContainer) String(key string) string {
|
||||
key = strings.ToLower(key)
|
||||
return c.getdata(key)
|
||||
}
|
||||
|
||||
// WriteValue writes a new value for key.
|
||||
func (c *IniConfigContainer) Set(key, value string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if len(key) == 0 {
|
||||
return errors.New("key is empty")
|
||||
}
|
||||
|
||||
var section, k string
|
||||
key = strings.ToLower(key)
|
||||
sectionkey := strings.Split(key, "::")
|
||||
if len(sectionkey) >= 2 {
|
||||
section = sectionkey[0]
|
||||
k = sectionkey[1]
|
||||
} else {
|
||||
section = DEFAULT_SECTION
|
||||
k = sectionkey[0]
|
||||
}
|
||||
if _, ok := c.data[section]; !ok {
|
||||
c.data[section] = make(map[string]string)
|
||||
}
|
||||
c.data[section][k] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||
key = strings.ToLower(key)
|
||||
if v, ok := c.data[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return v, errors.New("key not find")
|
||||
}
|
||||
|
||||
//section.key or key
|
||||
func (c *IniConfigContainer) getdata(key string) string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if len(key) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var section, k string
|
||||
key = strings.ToLower(key)
|
||||
sectionkey := strings.Split(key, "::")
|
||||
if len(sectionkey) >= 2 {
|
||||
section = sectionkey[0]
|
||||
k = sectionkey[1]
|
||||
} else {
|
||||
section = DEFAULT_SECTION
|
||||
k = sectionkey[0]
|
||||
}
|
||||
if v, ok := c.data[section]; ok {
|
||||
if vv, o := v[k]; o {
|
||||
return vv
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("ini", &IniConfig{})
|
||||
}
|
81
config/ini_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var inicontext = `
|
||||
;comment one
|
||||
#comment two
|
||||
appname = beeapi
|
||||
httpport = 8080
|
||||
mysqlport = 3600
|
||||
PI = 3.1415976
|
||||
runmode = "dev"
|
||||
autorender = false
|
||||
copyrequestbody = true
|
||||
[demo]
|
||||
key1="asta"
|
||||
key2 = "xie"
|
||||
CaseInsensitive = true
|
||||
`
|
||||
|
||||
func TestIni(t *testing.T) {
|
||||
f, err := os.Create("testini.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.WriteString(inicontext)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove("testini.conf")
|
||||
iniconf, err := NewConfig("ini", "testini.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := iniconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = iniconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if iniconf.String("demo::key1") != "asta" {
|
||||
t.Fatal("get demo.key1 error")
|
||||
}
|
||||
if iniconf.String("demo::key2") != "xie" {
|
||||
t.Fatal("get demo.key2 error")
|
||||
}
|
||||
if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true {
|
||||
t.Fatal("get demo.caseinsensitive error")
|
||||
}
|
||||
}
|
153
config/json.go
Normal file
@ -0,0 +1,153 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type JsonConfig struct {
|
||||
}
|
||||
|
||||
func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
x := &JsonConfigContainer{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
content, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(content, &x.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type JsonConfigContainer struct {
|
||||
data map[string]interface{}
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) Bool(key string) (bool, error) {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(bool); ok {
|
||||
return v, nil
|
||||
} else {
|
||||
return false, errors.New("not bool value")
|
||||
}
|
||||
} else {
|
||||
return false, errors.New("not exist key:" + key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) Int(key string) (int, error) {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(float64); ok {
|
||||
return int(v), nil
|
||||
} else {
|
||||
return 0, errors.New("not int value")
|
||||
}
|
||||
} else {
|
||||
return 0, errors.New("not exist key:" + key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) Int64(key string) (int64, error) {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(float64); ok {
|
||||
return int64(v), nil
|
||||
} else {
|
||||
return 0, errors.New("not int64 value")
|
||||
}
|
||||
} else {
|
||||
return 0, errors.New("not exist key:" + key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) Float(key string) (float64, error) {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(float64); ok {
|
||||
return v, nil
|
||||
} else {
|
||||
return 0.0, errors.New("not float64 value")
|
||||
}
|
||||
} else {
|
||||
return 0.0, errors.New("not exist key:" + key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) String(key string) string {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(string); ok {
|
||||
return v
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) Set(key, val string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||
val := c.getdata(key)
|
||||
if val != nil {
|
||||
return val, nil
|
||||
} else {
|
||||
return nil, errors.New("not exist key")
|
||||
}
|
||||
}
|
||||
|
||||
//section.key or key
|
||||
func (c *JsonConfigContainer) getdata(key string) interface{} {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if len(key) == 0 {
|
||||
return nil
|
||||
}
|
||||
sectionkey := strings.Split(key, "::")
|
||||
if len(sectionkey) >= 2 {
|
||||
cruval, ok := c.data[sectionkey[0]]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for _, key := range sectionkey[1:] {
|
||||
if v, ok := cruval.(map[string]interface{}); !ok {
|
||||
return nil
|
||||
} else if cruval, ok = v[key]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return cruval
|
||||
} else {
|
||||
if v, ok := c.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("json", &JsonConfig{})
|
||||
}
|
97
config/json_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var jsoncontext = `{
|
||||
"appname": "beeapi",
|
||||
"httpport": 8080,
|
||||
"mysqlport": 3600,
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
f, err := os.Create("testjson.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.WriteString(jsoncontext)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove("testjson.conf")
|
||||
jsonconf, err := NewConfig("json", "testjson.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = jsonconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if jsonconf.String("database::host") != "host" {
|
||||
t.Fatal("get database::host error")
|
||||
}
|
||||
if jsonconf.String("database::conns::connectioninfo") != "info" {
|
||||
t.Fatal("get database::conns::connectioninfo error")
|
||||
}
|
||||
if maxconnection, err := jsonconf.Int("database::conns::maxconnection"); err != nil || maxconnection != 12 {
|
||||
t.Fatal("get database::conns::maxconnection error")
|
||||
}
|
||||
if db, err := jsonconf.DIY("database"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m, ok := db.(map[string]interface{}); !ok {
|
||||
t.Log(db)
|
||||
t.Fatal("db not map[string]interface{}")
|
||||
} else {
|
||||
if m["host"].(string) != "host" {
|
||||
t.Fatal("get host err")
|
||||
}
|
||||
}
|
||||
}
|
83
config/xml.go
Normal file
@ -0,0 +1,83 @@
|
||||
//xml parse should incluce in <config></config> tags
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/beego/x2j"
|
||||
)
|
||||
|
||||
type XMLConfig struct {
|
||||
}
|
||||
|
||||
func (xmls *XMLConfig) Parse(filename string) (ConfigContainer, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
x := &XMLConfigContainer{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
content, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := x2j.DocToMap(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x.data = d["config"].(map[string]interface{})
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type XMLConfigContainer struct {
|
||||
data map[string]interface{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.data[key].(string))
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) Int(key string) (int, error) {
|
||||
return strconv.Atoi(c.data[key].(string))
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) Int64(key string) (int64, error) {
|
||||
return strconv.ParseInt(c.data[key].(string), 10, 64)
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) Float(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.data[key].(string), 64)
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) String(key string) string {
|
||||
if v, ok := c.data[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) Set(key, val string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||
if v, ok := c.data[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("not exist key")
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("xml", &XMLConfig{})
|
||||
}
|
69
config/xml_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//xml parse should incluce in <config></config> tags
|
||||
var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<appname>beeapi</appname>
|
||||
<httpport>8080</httpport>
|
||||
<mysqlport>3600</mysqlport>
|
||||
<PI>3.1415976</PI>
|
||||
<runmode>dev</runmode>
|
||||
<autorender>false</autorender>
|
||||
<copyrequestbody>true</copyrequestbody>
|
||||
</config>
|
||||
`
|
||||
|
||||
func TestXML(t *testing.T) {
|
||||
f, err := os.Create("testxml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.WriteString(xmlcontext)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove("testxml.conf")
|
||||
xmlconf, err := NewConfig("xml", "testxml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := xmlconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = xmlconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
}
|
127
config/yaml.go
Normal file
@ -0,0 +1,127 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/beego/goyaml2"
|
||||
)
|
||||
|
||||
type YAMLConfig struct {
|
||||
}
|
||||
|
||||
func (yaml *YAMLConfig) Parse(filename string) (ConfigContainer, error) {
|
||||
y := &YAMLConfigContainer{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
cnf, err := ReadYmlReader(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y.data = cnf
|
||||
return y, nil
|
||||
}
|
||||
|
||||
// 从Reader读取YAML
|
||||
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
||||
err = nil
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = nil
|
||||
buf, err := ioutil.ReadAll(f)
|
||||
if err != nil || len(buf) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
if string(buf[0:1]) == "{" {
|
||||
log.Println("Look lile a Json, try it")
|
||||
err = json.Unmarshal(buf, &cnf)
|
||||
if err == nil {
|
||||
log.Println("It is Json Map")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_map, _err := goyaml2.Read(bytes.NewBuffer(buf))
|
||||
if _err != nil {
|
||||
log.Println("Goyaml2 ERR>", string(buf), _err)
|
||||
//err = goyaml.Unmarshal(buf, &cnf)
|
||||
err = _err
|
||||
return
|
||||
}
|
||||
if _map == nil {
|
||||
log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
|
||||
}
|
||||
cnf, ok := _map.(map[string]interface{})
|
||||
if !ok {
|
||||
log.Println("Not a Map? >> ", string(buf), _map)
|
||||
cnf = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type YAMLConfigContainer struct {
|
||||
data map[string]interface{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) Bool(key string) (bool, error) {
|
||||
if v, ok := c.data[key].(bool); ok {
|
||||
return v, nil
|
||||
}
|
||||
return false, errors.New("not bool value")
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) Int(key string) (int, error) {
|
||||
if v, ok := c.data[key].(int64); ok {
|
||||
return int(v), nil
|
||||
}
|
||||
return 0, errors.New("not int value")
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) Int64(key string) (int64, error) {
|
||||
if v, ok := c.data[key].(int64); ok {
|
||||
return v, nil
|
||||
}
|
||||
return 0, errors.New("not bool value")
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) Float(key string) (float64, error) {
|
||||
if v, ok := c.data[key].(float64); ok {
|
||||
return v, nil
|
||||
}
|
||||
return 0.0, errors.New("not float64 value")
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) String(key string) string {
|
||||
if v, ok := c.data[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) Set(key, val string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||
if v, ok := c.data[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("not exist key")
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("yaml", &YAMLConfig{})
|
||||
}
|
66
config/yaml_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var yamlcontext = `
|
||||
"appname": beeapi
|
||||
"httpport": 8080
|
||||
"mysqlport": 3600
|
||||
"PI": 3.1415976
|
||||
"runmode": dev
|
||||
"autorender": false
|
||||
"copyrequestbody": true
|
||||
`
|
||||
|
||||
func TestYaml(t *testing.T) {
|
||||
f, err := os.Create("testyaml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.WriteString(yamlcontext)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove("testyaml.conf")
|
||||
yamlconf, err := NewConfig("yaml", "testyaml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if yamlconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if yamlconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := yamlconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = yamlconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if yamlconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
}
|
71
context.go
@ -1,71 +0,0 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
ResponseWriter http.ResponseWriter
|
||||
Request *http.Request
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
func (ctx *Context) WriteString(content string) {
|
||||
ctx.ResponseWriter.Write([]byte(content))
|
||||
}
|
||||
|
||||
func (ctx *Context) Abort(status int, body string) {
|
||||
ctx.ResponseWriter.WriteHeader(status)
|
||||
ctx.ResponseWriter.Write([]byte(body))
|
||||
}
|
||||
|
||||
func (ctx *Context) Redirect(status int, url_ string) {
|
||||
ctx.ResponseWriter.Header().Set("Location", url_)
|
||||
ctx.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (ctx *Context) NotModified() {
|
||||
ctx.ResponseWriter.WriteHeader(304)
|
||||
}
|
||||
|
||||
func (ctx *Context) NotFound(message string) {
|
||||
ctx.ResponseWriter.WriteHeader(404)
|
||||
ctx.ResponseWriter.Write([]byte(message))
|
||||
}
|
||||
|
||||
//Sets the content type by extension, as defined in the mime package.
|
||||
//For example, ctx.ContentType("json") sets the content-type to "application/json"
|
||||
func (ctx *Context) ContentType(ext string) {
|
||||
if !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
ctype := mime.TypeByExtension(ext)
|
||||
if ctype != "" {
|
||||
ctx.ResponseWriter.Header().Set("Content-Type", ctype)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *Context) SetHeader(hdr string, val string, unique bool) {
|
||||
if unique {
|
||||
ctx.ResponseWriter.Header().Set(hdr, val)
|
||||
} else {
|
||||
ctx.ResponseWriter.Header().Add(hdr, val)
|
||||
}
|
||||
}
|
||||
|
||||
//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
|
||||
func (ctx *Context) SetCookie(name string, value string, age int64) {
|
||||
var utctime time.Time
|
||||
if age == 0 {
|
||||
// 2^31 - 1 seconds (roughly 2038)
|
||||
utctime = time.Unix(2147483647, 0)
|
||||
} else {
|
||||
utctime = time.Unix(time.Now().Unix()+age, 0)
|
||||
}
|
||||
cookie := fmt.Sprintf("%s=%s; Expires=%s; Path=/", name, value, webTime(utctime))
|
||||
ctx.SetHeader("Set-Cookie", cookie, true)
|
||||
}
|
48
context/context.go
Normal file
@ -0,0 +1,48 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego/middleware"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Input *BeegoInput
|
||||
Output *BeegoOutput
|
||||
Request *http.Request
|
||||
ResponseWriter http.ResponseWriter
|
||||
}
|
||||
|
||||
func (ctx *Context) Redirect(status int, localurl string) {
|
||||
ctx.Output.Header("Location", localurl)
|
||||
ctx.Output.SetStatus(status)
|
||||
}
|
||||
|
||||
func (ctx *Context) Abort(status int, body string) {
|
||||
ctx.Output.SetStatus(status)
|
||||
// first panic from ErrorMaps, is is user defined error functions.
|
||||
if _, ok := middleware.ErrorMaps[body]; ok {
|
||||
panic(body)
|
||||
}
|
||||
// second panic from HTTPExceptionMaps, it is system defined functions.
|
||||
if e, ok := middleware.HTTPExceptionMaps[status]; ok {
|
||||
if len(body) >= 1 {
|
||||
e.Description = body
|
||||
}
|
||||
panic(e)
|
||||
}
|
||||
// last panic user string
|
||||
panic(body)
|
||||
}
|
||||
|
||||
func (ctx *Context) WriteString(content string) {
|
||||
ctx.Output.Body([]byte(content))
|
||||
}
|
||||
|
||||
func (ctx *Context) GetCookie(key string) string {
|
||||
return ctx.Input.Cookie(key)
|
||||
}
|
||||
|
||||
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
|
||||
ctx.Output.Cookie(name, value, others...)
|
||||
}
|
183
context/input.go
Normal file
@ -0,0 +1,183 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
type BeegoInput struct {
|
||||
CruSession session.SessionStore
|
||||
Params map[string]string
|
||||
Data map[interface{}]interface{}
|
||||
Request *http.Request
|
||||
RequestBody []byte
|
||||
}
|
||||
|
||||
func NewInput(req *http.Request) *BeegoInput {
|
||||
return &BeegoInput{
|
||||
Params: make(map[string]string),
|
||||
Data: make(map[interface{}]interface{}),
|
||||
Request: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Protocol() string {
|
||||
return input.Request.Proto
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Uri() string {
|
||||
return input.Request.RequestURI
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Url() string {
|
||||
return input.Request.URL.String()
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Site() string {
|
||||
return input.Scheme() + "://" + input.Domain()
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Scheme() string {
|
||||
if input.Request.URL.Scheme != "" {
|
||||
return input.Request.URL.Scheme
|
||||
} else if input.Request.TLS == nil {
|
||||
return "http"
|
||||
} else {
|
||||
return "https"
|
||||
}
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Domain() string {
|
||||
return input.Host()
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Host() string {
|
||||
if input.Request.Host != "" {
|
||||
hostParts := strings.Split(input.Request.Host, ":")
|
||||
if len(hostParts) > 0 {
|
||||
return hostParts[0]
|
||||
}
|
||||
return input.Request.Host
|
||||
}
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Method() string {
|
||||
return input.Request.Method
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Is(method string) bool {
|
||||
return input.Method() == method
|
||||
}
|
||||
|
||||
func (input *BeegoInput) IsAjax() bool {
|
||||
return input.Header("X-Requested-With") == "XMLHttpRequest"
|
||||
}
|
||||
|
||||
func (input *BeegoInput) IsSecure() bool {
|
||||
return input.Scheme() == "https"
|
||||
}
|
||||
|
||||
func (input *BeegoInput) IsWebsocket() bool {
|
||||
return input.Header("Upgrade") == "websocket"
|
||||
}
|
||||
|
||||
func (input *BeegoInput) IsUpload() bool {
|
||||
return input.Request.MultipartForm != nil
|
||||
}
|
||||
|
||||
func (input *BeegoInput) IP() string {
|
||||
ips := input.Proxy()
|
||||
if len(ips) > 0 && ips[0] != "" {
|
||||
return ips[0]
|
||||
}
|
||||
ip := strings.Split(input.Request.RemoteAddr, ":")
|
||||
if len(ip) > 0 {
|
||||
if ip[0] != "["{
|
||||
return ip[0]
|
||||
}
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Proxy() []string {
|
||||
if ips := input.Header("X-Forwarded-For"); ips != "" {
|
||||
return strings.Split(ips, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Refer() string {
|
||||
return input.Header("Referer")
|
||||
}
|
||||
|
||||
func (input *BeegoInput) SubDomains() string {
|
||||
parts := strings.Split(input.Host(), ".")
|
||||
return strings.Join(parts[len(parts)-2:], ".")
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Port() int {
|
||||
parts := strings.Split(input.Request.Host, ":")
|
||||
if len(parts) == 2 {
|
||||
port, _ := strconv.Atoi(parts[1])
|
||||
return port
|
||||
}
|
||||
return 80
|
||||
}
|
||||
|
||||
func (input *BeegoInput) UserAgent() string {
|
||||
return input.Header("User-Agent")
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Param(key string) string {
|
||||
if v, ok := input.Params[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Query(key string) string {
|
||||
input.Request.ParseForm()
|
||||
return input.Request.Form.Get(key)
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Header(key string) string {
|
||||
return input.Request.Header.Get(key)
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Cookie(key string) string {
|
||||
ck, err := input.Request.Cookie(key)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ck.Value
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Session(key interface{}) interface{} {
|
||||
return input.CruSession.Get(key)
|
||||
}
|
||||
|
||||
func (input *BeegoInput) Body() []byte {
|
||||
requestbody, _ := ioutil.ReadAll(input.Request.Body)
|
||||
input.Request.Body.Close()
|
||||
bf := bytes.NewBuffer(requestbody)
|
||||
input.Request.Body = ioutil.NopCloser(bf)
|
||||
input.RequestBody = requestbody
|
||||
return requestbody
|
||||
}
|
||||
|
||||
func (input *BeegoInput) GetData(key interface{}) interface{} {
|
||||
if v, ok := input.Data[key]; ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (input *BeegoInput) SetData(key, val interface{}) {
|
||||
input.Data[key] = val
|
||||
}
|
259
context/output.go
Normal file
@ -0,0 +1,259 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BeegoOutput struct {
|
||||
Context *Context
|
||||
Status int
|
||||
EnableGzip bool
|
||||
}
|
||||
|
||||
func NewOutput() *BeegoOutput {
|
||||
return &BeegoOutput{}
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Header(key, val string) {
|
||||
output.Context.ResponseWriter.Header().Set(key, val)
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Body(content []byte) {
|
||||
output_writer := output.Context.ResponseWriter.(io.Writer)
|
||||
if output.EnableGzip == true && output.Context.Input.Header("Accept-Encoding") != "" {
|
||||
splitted := strings.SplitN(output.Context.Input.Header("Accept-Encoding"), ",", -1)
|
||||
encodings := make([]string, len(splitted))
|
||||
|
||||
for i, val := range splitted {
|
||||
encodings[i] = strings.TrimSpace(val)
|
||||
}
|
||||
for _, val := range encodings {
|
||||
if val == "gzip" {
|
||||
output.Header("Content-Encoding", "gzip")
|
||||
output_writer, _ = gzip.NewWriterLevel(output.Context.ResponseWriter, gzip.BestSpeed)
|
||||
|
||||
break
|
||||
} else if val == "deflate" {
|
||||
output.Header("Content-Encoding", "deflate")
|
||||
output_writer, _ = flate.NewWriter(output.Context.ResponseWriter, flate.BestSpeed)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output.Header("Content-Length", strconv.Itoa(len(content)))
|
||||
}
|
||||
output_writer.Write(content)
|
||||
switch output_writer.(type) {
|
||||
case *gzip.Writer:
|
||||
output_writer.(*gzip.Writer).Close()
|
||||
case *flate.Writer:
|
||||
output_writer.(*flate.Writer).Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
|
||||
if len(others) > 0 {
|
||||
switch others[0].(type) {
|
||||
case int:
|
||||
if others[0].(int) > 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int))
|
||||
} else if others[0].(int) < 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=0")
|
||||
}
|
||||
case int64:
|
||||
if others[0].(int64) > 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int64))
|
||||
} else if others[0].(int64) < 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=0")
|
||||
}
|
||||
case int32:
|
||||
if others[0].(int32) > 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int32))
|
||||
} else if others[0].(int32) < 0 {
|
||||
fmt.Fprintf(&b, "; Max-Age=0")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(others) > 1 {
|
||||
fmt.Fprintf(&b, "; Path=%s", sanitizeValue(others[1].(string)))
|
||||
}
|
||||
if len(others) > 2 {
|
||||
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(others[2].(string)))
|
||||
}
|
||||
if len(others) > 3 {
|
||||
fmt.Fprintf(&b, "; Secure")
|
||||
}
|
||||
if len(others) > 4 {
|
||||
fmt.Fprintf(&b, "; HttpOnly")
|
||||
}
|
||||
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
|
||||
}
|
||||
|
||||
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
|
||||
|
||||
func sanitizeName(n string) string {
|
||||
return cookieNameSanitizer.Replace(n)
|
||||
}
|
||||
|
||||
var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
|
||||
|
||||
func sanitizeValue(v string) string {
|
||||
return cookieValueSanitizer.Replace(v)
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) error {
|
||||
output.Header("Content-Type", "application/json;charset=UTF-8")
|
||||
var content []byte
|
||||
var err error
|
||||
if hasIndent {
|
||||
content, err = json.MarshalIndent(data, "", " ")
|
||||
} else {
|
||||
content, err = json.Marshal(data)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
if coding {
|
||||
content = []byte(stringsToJson(string(content)))
|
||||
}
|
||||
output.Body(content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
|
||||
output.Header("Content-Type", "application/javascript;charset=UTF-8")
|
||||
var content []byte
|
||||
var err error
|
||||
if hasIndent {
|
||||
content, err = json.MarshalIndent(data, "", " ")
|
||||
} else {
|
||||
content, err = json.Marshal(data)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
callback := output.Context.Input.Query("callback")
|
||||
if callback == "" {
|
||||
return errors.New(`"callback" parameter required`)
|
||||
}
|
||||
callback_content := bytes.NewBufferString(" " + template.JSEscapeString(callback))
|
||||
callback_content.WriteString("(")
|
||||
callback_content.Write(content)
|
||||
callback_content.WriteString(");\r\n")
|
||||
output.Body(callback_content.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error {
|
||||
output.Header("Content-Type", "application/xml;charset=UTF-8")
|
||||
var content []byte
|
||||
var err error
|
||||
if hasIndent {
|
||||
content, err = xml.MarshalIndent(data, "", " ")
|
||||
} else {
|
||||
content, err = xml.Marshal(data)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
output.Body(content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Download(file string) {
|
||||
output.Header("Content-Description", "File Transfer")
|
||||
output.Header("Content-Type", "application/octet-stream")
|
||||
output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))
|
||||
output.Header("Content-Transfer-Encoding", "binary")
|
||||
output.Header("Expires", "0")
|
||||
output.Header("Cache-Control", "must-revalidate")
|
||||
output.Header("Pragma", "public")
|
||||
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) ContentType(ext string) {
|
||||
if !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
ctype := mime.TypeByExtension(ext)
|
||||
if ctype != "" {
|
||||
output.Header("Content-Type", ctype)
|
||||
}
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) SetStatus(status int) {
|
||||
output.Context.ResponseWriter.WriteHeader(status)
|
||||
output.Status = status
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsCachable(status int) bool {
|
||||
return output.Status >= 200 && output.Status < 300 || output.Status == 304
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsEmpty(status int) bool {
|
||||
return output.Status == 201 || output.Status == 204 || output.Status == 304
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsOk(status int) bool {
|
||||
return output.Status == 200
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsSuccessful(status int) bool {
|
||||
return output.Status >= 200 && output.Status < 300
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsRedirect(status int) bool {
|
||||
return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsForbidden(status int) bool {
|
||||
return output.Status == 403
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsNotFound(status int) bool {
|
||||
return output.Status == 404
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsClientError(status int) bool {
|
||||
return output.Status >= 400 && output.Status < 500
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) IsServerError(status int) bool {
|
||||
return output.Status >= 500 && output.Status < 600
|
||||
}
|
||||
|
||||
func stringsToJson(str string) string {
|
||||
rs := []rune(str)
|
||||
jsons := ""
|
||||
for _, r := range rs {
|
||||
rint := int(r)
|
||||
if rint < 128 {
|
||||
jsons += string(r)
|
||||
} else {
|
||||
jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
|
||||
}
|
||||
}
|
||||
return jsons
|
||||
}
|
||||
|
||||
func (output *BeegoOutput) Session(name interface{}, value interface{}) {
|
||||
output.Context.Input.CruSession.Set(name, value)
|
||||
}
|
383
controller.go
@ -2,12 +2,12 @@ package beego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/astaxie/beego/session"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -15,23 +15,41 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
var (
|
||||
// custom error when user stop request handler manually.
|
||||
USERSTOPRUN = errors.New("User stop run")
|
||||
)
|
||||
|
||||
// Controller defines some basic http request handler operations, such as
|
||||
// http context, template and view, session and xsrf.
|
||||
type Controller struct {
|
||||
Ctx *Context
|
||||
Ctx *context.Context
|
||||
Data map[interface{}]interface{}
|
||||
ChildName string
|
||||
controllerName string
|
||||
actionName string
|
||||
TplNames string
|
||||
Layout string
|
||||
LayoutSections map[string]string // the key is the section name and the value is the template name
|
||||
TplExt string
|
||||
_xsrf_token string
|
||||
gotofunc string
|
||||
CruSession session.SessionStore
|
||||
XSRFExpire int
|
||||
AppController interface{}
|
||||
}
|
||||
|
||||
// ControllerInterface is an interface to uniform all controller handler.
|
||||
type ControllerInterface interface {
|
||||
Init(ct *Context, cn string)
|
||||
Init(ct *context.Context, controllerName, actionName string, app interface{})
|
||||
Prepare()
|
||||
Get()
|
||||
Post()
|
||||
@ -42,154 +60,151 @@ type ControllerInterface interface {
|
||||
Options()
|
||||
Finish()
|
||||
Render() error
|
||||
XsrfToken() string
|
||||
CheckXsrfCookie() bool
|
||||
}
|
||||
|
||||
func (c *Controller) Init(ctx *Context, cn string) {
|
||||
// Init generates default values of controller operations.
|
||||
func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) {
|
||||
c.Data = make(map[interface{}]interface{})
|
||||
c.Layout = ""
|
||||
c.TplNames = ""
|
||||
c.ChildName = cn
|
||||
c.controllerName = controllerName
|
||||
c.actionName = actionName
|
||||
c.Ctx = ctx
|
||||
c.TplExt = "tpl"
|
||||
|
||||
c.AppController = app
|
||||
}
|
||||
|
||||
// Prepare runs after Init before request function execution.
|
||||
func (c *Controller) Prepare() {
|
||||
|
||||
}
|
||||
|
||||
// Finish runs after request function execution.
|
||||
func (c *Controller) Finish() {
|
||||
|
||||
}
|
||||
|
||||
func (c *Controller) Destructor() {
|
||||
if c.CruSession != nil {
|
||||
c.CruSession.SessionRelease()
|
||||
}
|
||||
}
|
||||
|
||||
// Get adds a request function to handle GET request.
|
||||
func (c *Controller) Get() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Post adds a request function to handle POST request.
|
||||
func (c *Controller) Post() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Delete adds a request function to handle DELETE request.
|
||||
func (c *Controller) Delete() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Put adds a request function to handle PUT request.
|
||||
func (c *Controller) Put() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Head adds a request function to handle HEAD request.
|
||||
func (c *Controller) Head() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Patch adds a request function to handle PATCH request.
|
||||
func (c *Controller) Patch() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Options adds a request function to handle OPTIONS request.
|
||||
func (c *Controller) Options() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Render sends the response with rendered template bytes as text/html type.
|
||||
func (c *Controller) Render() error {
|
||||
rb, err := c.RenderBytes()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
output_writer := c.Ctx.ResponseWriter.(io.Writer)
|
||||
if EnableGzip == true && c.Ctx.Request.Header.Get("Accept-Encoding") != "" {
|
||||
splitted := strings.SplitN(c.Ctx.Request.Header.Get("Accept-Encoding"), ",", -1)
|
||||
encodings := make([]string, len(splitted))
|
||||
|
||||
for i, val := range splitted {
|
||||
encodings[i] = strings.TrimSpace(val)
|
||||
}
|
||||
for _, val := range encodings {
|
||||
if val == "gzip" {
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Encoding", "gzip")
|
||||
output_writer, _ = gzip.NewWriterLevel(c.Ctx.ResponseWriter, gzip.BestSpeed)
|
||||
|
||||
break
|
||||
} else if val == "deflate" {
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Encoding", "deflate")
|
||||
output_writer, _ = zlib.NewWriterLevel(c.Ctx.ResponseWriter, zlib.BestSpeed)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.Ctx.SetHeader("Content-Length", strconv.Itoa(len(rb)), true)
|
||||
}
|
||||
output_writer.Write(rb)
|
||||
switch output_writer.(type) {
|
||||
case *gzip.Writer:
|
||||
output_writer.(*gzip.Writer).Close()
|
||||
case *zlib.Writer:
|
||||
output_writer.(*zlib.Writer).Close()
|
||||
case io.WriteCloser:
|
||||
output_writer.(io.WriteCloser).Close()
|
||||
}
|
||||
return nil
|
||||
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Ctx.Output.Body(rb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderString returns the rendered template string. Do not send out response.
|
||||
func (c *Controller) RenderString() (string, error) {
|
||||
b, e := c.RenderBytes()
|
||||
return string(b), e
|
||||
}
|
||||
|
||||
// RenderBytes returns the bytes of renderd tempate string. Do not send out response.
|
||||
func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
//if the controller has set layout, then first get the tplname's content set the content to the layout
|
||||
if c.Layout != "" {
|
||||
if c.TplNames == "" {
|
||||
c.TplNames = c.ChildName + "/" + c.Ctx.Request.Method + "." + c.TplExt
|
||||
c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||
}
|
||||
if RunMode == "dev" {
|
||||
BuildTemplate(ViewsPath)
|
||||
}
|
||||
subdir := path.Dir(c.TplNames)
|
||||
_, file := path.Split(c.TplNames)
|
||||
newbytes := bytes.NewBufferString("")
|
||||
if _, ok := BeeTemplates[subdir]; !ok {
|
||||
if _, ok := BeeTemplates[c.TplNames]; !ok {
|
||||
panic("can't find templatefile in the path:" + c.TplNames)
|
||||
return []byte{}, errors.New("can't find templatefile in the path:" + c.TplNames)
|
||||
}
|
||||
BeeTemplates[subdir].ExecuteTemplate(newbytes, file, c.Data)
|
||||
tplcontent, _ := ioutil.ReadAll(newbytes)
|
||||
c.Data["LayoutContent"] = template.HTML(string(tplcontent))
|
||||
subdir = path.Dir(c.Layout)
|
||||
_, file = path.Split(c.Layout)
|
||||
ibytes := bytes.NewBufferString("")
|
||||
err := BeeTemplates[subdir].ExecuteTemplate(ibytes, file, c.Data)
|
||||
err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes, c.TplNames, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
tplcontent, _ := ioutil.ReadAll(newbytes)
|
||||
c.Data["LayoutContent"] = template.HTML(string(tplcontent))
|
||||
|
||||
if c.LayoutSections != nil {
|
||||
for sectionName, sectionTpl := range c.LayoutSections {
|
||||
if (sectionTpl == "") {
|
||||
c.Data[sectionName] = ""
|
||||
continue
|
||||
}
|
||||
|
||||
sectionBytes := bytes.NewBufferString("")
|
||||
err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes, sectionTpl, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
sectionContent, _ := ioutil.ReadAll(sectionBytes)
|
||||
c.Data[sectionName] = template.HTML(string(sectionContent))
|
||||
}
|
||||
}
|
||||
|
||||
ibytes := bytes.NewBufferString("")
|
||||
err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes, c.Layout, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
icontent, _ := ioutil.ReadAll(ibytes)
|
||||
return icontent, nil
|
||||
} else {
|
||||
if c.TplNames == "" {
|
||||
c.TplNames = c.ChildName + "/" + c.Ctx.Request.Method + "." + c.TplExt
|
||||
c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||
}
|
||||
if RunMode == "dev" {
|
||||
BuildTemplate(ViewsPath)
|
||||
}
|
||||
subdir := path.Dir(c.TplNames)
|
||||
_, file := path.Split(c.TplNames)
|
||||
ibytes := bytes.NewBufferString("")
|
||||
if _, ok := BeeTemplates[subdir]; !ok {
|
||||
if _, ok := BeeTemplates[c.TplNames]; !ok {
|
||||
panic("can't find templatefile in the path:" + c.TplNames)
|
||||
return []byte{}, errors.New("can't find templatefile in the path:" + c.TplNames)
|
||||
}
|
||||
err := BeeTemplates[subdir].ExecuteTemplate(ibytes, file, c.Data)
|
||||
err := BeeTemplates[c.TplNames].ExecuteTemplate(ibytes, c.TplNames, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
icontent, _ := ioutil.ReadAll(ibytes)
|
||||
return icontent, nil
|
||||
@ -197,56 +212,78 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// Redirect sends the redirection response to url with status code.
|
||||
func (c *Controller) Redirect(url string, code int) {
|
||||
c.Ctx.Redirect(code, url)
|
||||
}
|
||||
|
||||
// Aborts stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
||||
func (c *Controller) Abort(code string) {
|
||||
panic(code)
|
||||
}
|
||||
|
||||
func (c *Controller) ServeJson() {
|
||||
content, err := json.MarshalIndent(c.Data["json"], "", " ")
|
||||
if err != nil {
|
||||
http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
status, err := strconv.Atoi(code)
|
||||
if err == nil {
|
||||
c.Ctx.Abort(status, code)
|
||||
} else {
|
||||
c.Ctx.Abort(200, code)
|
||||
}
|
||||
c.Ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
c.Ctx.ResponseWriter.Write(content)
|
||||
}
|
||||
|
||||
// StopRun makes panic of USERSTOPRUN error and go to recover function if defined.
|
||||
func (c *Controller) StopRun() {
|
||||
panic(USERSTOPRUN)
|
||||
}
|
||||
|
||||
// UrlFor does another controller handler in this request function.
|
||||
// it goes to this controller method if endpoint is not clear.
|
||||
func (c *Controller) UrlFor(endpoint string, values ...string) string {
|
||||
if len(endpoint) <= 0 {
|
||||
return ""
|
||||
}
|
||||
if endpoint[0] == '.' {
|
||||
return UrlFor(reflect.Indirect(reflect.ValueOf(c.AppController)).Type().Name()+endpoint, values...)
|
||||
} else {
|
||||
return UrlFor(endpoint, values...)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ServeJson sends a json response with encoding charset.
|
||||
func (c *Controller) ServeJson(encoding ...bool) {
|
||||
var hasIndent bool
|
||||
var hasencoding bool
|
||||
if RunMode == "prod" {
|
||||
hasIndent = false
|
||||
} else {
|
||||
hasIndent = true
|
||||
}
|
||||
if len(encoding) > 0 && encoding[0] == true {
|
||||
hasencoding = true
|
||||
}
|
||||
c.Ctx.Output.Json(c.Data["json"], hasIndent, hasencoding)
|
||||
}
|
||||
|
||||
// ServeJsonp sends a jsonp response.
|
||||
func (c *Controller) ServeJsonp() {
|
||||
content, err := json.MarshalIndent(c.Data["jsonp"], "", " ")
|
||||
if err != nil {
|
||||
http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
var hasIndent bool
|
||||
if RunMode == "prod" {
|
||||
hasIndent = false
|
||||
} else {
|
||||
hasIndent = true
|
||||
}
|
||||
callback := c.Ctx.Request.Form.Get("callback")
|
||||
if callback == "" {
|
||||
http.Error(c.Ctx.ResponseWriter, `"callback" parameter required`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
callback_content := bytes.NewBufferString(callback)
|
||||
callback_content.WriteString("(")
|
||||
callback_content.Write(content)
|
||||
callback_content.WriteString(");\r\n")
|
||||
c.Ctx.SetHeader("Content-Length", strconv.Itoa(callback_content.Len()), true)
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
c.Ctx.ResponseWriter.Write(callback_content.Bytes())
|
||||
c.Ctx.Output.Jsonp(c.Data["jsonp"], hasIndent)
|
||||
}
|
||||
|
||||
// ServeXml sends xml response.
|
||||
func (c *Controller) ServeXml() {
|
||||
content, err := xml.Marshal(c.Data["xml"])
|
||||
if err != nil {
|
||||
http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
var hasIndent bool
|
||||
if RunMode == "prod" {
|
||||
hasIndent = false
|
||||
} else {
|
||||
hasIndent = true
|
||||
}
|
||||
c.Ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/xml;charset=UTF-8")
|
||||
c.Ctx.ResponseWriter.Write(content)
|
||||
c.Ctx.Output.Xml(c.Data["xml"], hasIndent)
|
||||
}
|
||||
|
||||
// Input returns the input data map from POST or PUT request body and query string.
|
||||
func (c *Controller) Input() url.Values {
|
||||
ct := c.Ctx.Request.Header.Get("Content-Type")
|
||||
if strings.Contains(ct, "multipart/form-data") {
|
||||
@ -257,12 +294,20 @@ func (c *Controller) Input() url.Values {
|
||||
return c.Ctx.Request.Form
|
||||
}
|
||||
|
||||
// ParseForm maps input data map to obj struct.
|
||||
func (c *Controller) ParseForm(obj interface{}) error {
|
||||
return ParseForm(c.Input(), obj)
|
||||
}
|
||||
|
||||
// GetString returns the input value by key string.
|
||||
func (c *Controller) GetString(key string) string {
|
||||
return c.Input().Get(key)
|
||||
}
|
||||
|
||||
// GetStrings returns the input string slice by key string.
|
||||
// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection.
|
||||
func (c *Controller) GetStrings(key string) []string {
|
||||
r := c.Ctx.Request;
|
||||
r := c.Ctx.Request
|
||||
if r.Form == nil {
|
||||
return []string{}
|
||||
}
|
||||
@ -273,18 +318,29 @@ func (c *Controller) GetStrings(key string) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// GetInt returns input value as int64.
|
||||
func (c *Controller) GetInt(key string) (int64, error) {
|
||||
return strconv.ParseInt(c.Input().Get(key), 10, 64)
|
||||
}
|
||||
|
||||
// GetBool returns input value as bool.
|
||||
func (c *Controller) GetBool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.Input().Get(key))
|
||||
}
|
||||
|
||||
// GetFloat returns input value as float64.
|
||||
func (c *Controller) GetFloat(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.Input().Get(key), 64)
|
||||
}
|
||||
|
||||
// GetFile returns the file data in file upload field named as key.
|
||||
// it returns the first one of multi-uploaded files.
|
||||
func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
||||
return c.Ctx.Request.FormFile(key)
|
||||
}
|
||||
|
||||
// SaveToFile saves uploaded file to new path.
|
||||
// it only operates the first one of mutil-upload form file field.
|
||||
func (c *Controller) SaveToFile(fromfile, tofile string) error {
|
||||
file, _, err := c.Ctx.Request.FormFile(fromfile)
|
||||
if err != nil {
|
||||
@ -300,13 +356,15 @@ func (c *Controller) SaveToFile(fromfile, tofile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartSession starts session and load old session data info this controller.
|
||||
func (c *Controller) StartSession() session.SessionStore {
|
||||
if c.CruSession == nil {
|
||||
c.CruSession = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
c.CruSession = c.Ctx.Input.CruSession
|
||||
}
|
||||
return c.CruSession
|
||||
}
|
||||
|
||||
// SetSession puts value into session.
|
||||
func (c *Controller) SetSession(name interface{}, value interface{}) {
|
||||
if c.CruSession == nil {
|
||||
c.StartSession()
|
||||
@ -314,6 +372,7 @@ func (c *Controller) SetSession(name interface{}, value interface{}) {
|
||||
c.CruSession.Set(name, value)
|
||||
}
|
||||
|
||||
// GetSession gets value from session.
|
||||
func (c *Controller) GetSession(name interface{}) interface{} {
|
||||
if c.CruSession == nil {
|
||||
c.StartSession()
|
||||
@ -321,9 +380,125 @@ func (c *Controller) GetSession(name interface{}) interface{} {
|
||||
return c.CruSession.Get(name)
|
||||
}
|
||||
|
||||
// SetSession removes value from session.
|
||||
func (c *Controller) DelSession(name interface{}) {
|
||||
if c.CruSession == nil {
|
||||
c.StartSession()
|
||||
}
|
||||
c.CruSession.Delete(name)
|
||||
}
|
||||
|
||||
// SessionRegenerateID regenerates session id for this session.
|
||||
// the session data have no changes.
|
||||
func (c *Controller) SessionRegenerateID() {
|
||||
c.CruSession = GlobalSessions.SessionRegenerateId(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
c.Ctx.Input.CruSession = c.CruSession
|
||||
}
|
||||
|
||||
// DestroySession cleans session data and session cookie.
|
||||
func (c *Controller) DestroySession() {
|
||||
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
}
|
||||
|
||||
// IsAjax returns this request is ajax or not.
|
||||
func (c *Controller) IsAjax() bool {
|
||||
return c.Ctx.Input.IsAjax()
|
||||
}
|
||||
|
||||
// GetSecureCookie returns decoded cookie value from encoded browser cookie values.
|
||||
func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) {
|
||||
val := c.Ctx.GetCookie(key)
|
||||
if val == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(val, "|", 3)
|
||||
|
||||
if len(parts) != 3 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
vs := parts[0]
|
||||
timestamp := parts[1]
|
||||
sig := parts[2]
|
||||
|
||||
h := hmac.New(sha1.New, []byte(Secret))
|
||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
|
||||
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
||||
return "", false
|
||||
}
|
||||
res, _ := base64.URLEncoding.DecodeString(vs)
|
||||
return string(res), true
|
||||
}
|
||||
|
||||
// SetSecureCookie puts value into cookie after encoded the value.
|
||||
func (c *Controller) SetSecureCookie(Secret, name, val string, age int64) {
|
||||
vs := base64.URLEncoding.EncodeToString([]byte(val))
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
h := hmac.New(sha1.New, []byte(Secret))
|
||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
||||
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
||||
c.Ctx.SetCookie(name, cookie, age, "/")
|
||||
}
|
||||
|
||||
// XsrfToken creates a xsrf token string and returns.
|
||||
func (c *Controller) XsrfToken() string {
|
||||
if c._xsrf_token == "" {
|
||||
token, ok := c.GetSecureCookie(XSRFKEY, "_xsrf")
|
||||
if !ok {
|
||||
var expire int64
|
||||
if c.XSRFExpire > 0 {
|
||||
expire = int64(c.XSRFExpire)
|
||||
} else {
|
||||
expire = int64(XSRFExpire)
|
||||
}
|
||||
token = getRandomString(15)
|
||||
c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire)
|
||||
}
|
||||
c._xsrf_token = token
|
||||
}
|
||||
return c._xsrf_token
|
||||
}
|
||||
|
||||
// CheckXsrfCookie checks xsrf token in this request is valid or not.
|
||||
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
|
||||
// or in form field value named as "_xsrf".
|
||||
func (c *Controller) CheckXsrfCookie() bool {
|
||||
token := c.GetString("_xsrf")
|
||||
if token == "" {
|
||||
token = c.Ctx.Request.Header.Get("X-Xsrftoken")
|
||||
}
|
||||
if token == "" {
|
||||
token = c.Ctx.Request.Header.Get("X-Csrftoken")
|
||||
}
|
||||
if token == "" {
|
||||
c.Ctx.Abort(403, "'_xsrf' argument missing from POST")
|
||||
} else if c._xsrf_token != token {
|
||||
c.Ctx.Abort(403, "XSRF cookie does not match POST argument")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// XsrfFormHtml writes an input field contains xsrf token value.
|
||||
func (c *Controller) XsrfFormHtml() string {
|
||||
return "<input type=\"hidden\" name=\"_xsrf\" value=\"" +
|
||||
c._xsrf_token + "\"/>"
|
||||
}
|
||||
|
||||
// GetControllerAndAction gets the executing controller name and action name.
|
||||
func (c *Controller) GetControllerAndAction() (controllerName, actionName string) {
|
||||
return c.controllerName, c.actionName
|
||||
}
|
||||
|
||||
// getRandomString returns random string.
|
||||
func getRandomString(n int) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
## What is hot update?
|
||||
|
||||
If you have used nginx, you may know that nginx supports hot update, which means you can update your nginx without stopping and restarting it. It serves old connections with old version, and accepts new connections with new version. Notice that hot compiling is different from hot update, where hot compiling is monitoring your source files and recompile them when the content changes, it requires stop and restart your applications, `bee start` is a tool for hot compiling.
|
||||
|
||||
|
||||
## Is hot update necessary?
|
||||
|
||||
Some people says that hot update is not as useful as its cool name. In my opinion, this is absolutely necessary because zero-down server is our goal for our services. Even though sometimes some errors or hardware problems may occur, but it belongs to design of high availability, don't mix them up. Service update is a known issue, so we need to fix this problem.
|
||||
|
||||
|
||||
## How Beego support hot update?
|
||||
|
||||
The basic principle of hot update: main process fork a process, and child process execute corresponding programs. So what happens? We know that after forked a process, main process will have all handles, data and stack, etc, but all handles are saved in `CloseOnExec`, so all copied handles will be closed when you execute it unless you clarify this, and we need child process to reuse the handle of `net.Listener`. Once a process calls exec functions, it is "dead", system replaces it with new code. The only thing it left is the process ID, which is the same number but it is a new program after executed.
|
||||
|
||||
Therefore, the first thing we need to do is that let child process fork main process and through `os.StartProcess` to append files that contains handle that is going to be inherited.
|
||||
|
||||
The second step is that we hope child process can start listening from same handle, so we can use `net.FileListener` to achieve this goal. Here we also need FD of this file, so we need to add a environment variable to set this FD before we start child process.
|
||||
|
||||
The final step is that we want to serve old connections with old version of application, and serve new connections with new version. So how can we know if there is any old connections? To do this, we have to record all connections, then we are able to know. Another problem is that how to let new application to accept connection? Because two versions of applications are listening to same port, they get connection request randomly, so we just close old accept function, and it will get error in `l.Accept`.
|
||||
|
||||
Above are three problems that we need to solve, you can see my code logic for specific implementation.
|
||||
|
||||
|
||||
## Show time
|
||||
|
||||
1. Write code in your Get method:
|
||||
|
||||
func (this *MainController) Get() {
|
||||
a, _ := this.GetInt("sleep")
|
||||
time.Sleep(time.Duration(a) * time.Second)
|
||||
this.Ctx.WriteString("ospid:" + strconv.Itoa(os.Getpid()))
|
||||
}
|
||||
|
||||
2. Open two terminals:
|
||||
|
||||
One execute: ` ps -ef|grep <application name>`
|
||||
|
||||
Another one execute: `curl "http://127.0.0.1:8080/?sleep=20"`
|
||||
|
||||
3. Hot update
|
||||
|
||||
`kill -HUP <PID>`
|
||||
|
||||
4. Open a terminal to request connection: `curl "http://127.0.0.1:8080/?sleep=0"`
|
||||
|
||||
As you will see, the first request will wait for 20 seconds, but it's served by old process; after hot update, the first request will print old process ID, but the second request will print new process ID.
|
@ -1,26 +0,0 @@
|
||||
# Installation
|
||||
|
||||
Beego is a simple web framework, but it uses many third-party packages, so you have to install all dependency packages also.
|
||||
|
||||
- Before anything you do, you have to check that you installed Go in your computer, see more detail about Go installation in my book: [Chapter 1](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/01.1.md)
|
||||
- Use `go get ` to install Beego:
|
||||
|
||||
go get github.com/astaxie/beego
|
||||
|
||||
- Install bee tools for fast-develop Beego applications:
|
||||
|
||||
go get github.com/astaxie/bee
|
||||
|
||||
Good job, you're ready to Beego with powerful bee tools!
|
||||
|
||||

|
||||
|
||||
Beego has following dependency packages:
|
||||
|
||||
- Session module: [github.com/astaxie/beego/session](https://github.com/astaxie/beego/session)
|
||||
- To support redis engine: [github.com/garyburd/redigo/redis](https://github.com/garyburd/redigo/redis)
|
||||
- To support mysql engine: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
|
||||
- To support markdown as template function: [github.com/russross/blackfriday](https://github.com/russross/blackfriday)
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Quick start](Quickstart.md)
|
@ -1,60 +0,0 @@
|
||||
# Beego
|
||||
|
||||
Beego is a lightweight, open source, non-blocking and scalable web framework for the Go programming language. It's like tornado in Python. This web framework has already been using for building web server and tools in SNDA's CDN system. Documentation and downloads available at [http://astaxie.github.com/beego](http://astaxie.github.com/beego)
|
||||
|
||||
It has following main features:
|
||||
|
||||
- Supports MVC model, you only need to focus on logic and implementation methods.
|
||||
- Supports websocket, use customized handlers to integrate sockjs.
|
||||
- Supports customized router rules, including regex and semanteme.
|
||||
- Session integration, supports memory, file, redis, mysql, etc.
|
||||
- Automated parsing user form, you can get data very easy.
|
||||
- Log level system, easy to record debugging and deployment logs.
|
||||
- Use configuration file (.ini) to customized your system.
|
||||
- Use built-in templates in Go, and it provides much more useful functions which are commonly used in web development.
|
||||
|
||||
The working principles of Beego as follows:
|
||||
|
||||

|
||||
|
||||
Beego is licensed under the Apache Licence, Version 2.0
|
||||
(http://www.apache.org/licenses/LICENSE-2.0.html).
|
||||
|
||||
|
||||
# Simple example
|
||||
|
||||
The following example prints string "Hello world" to your browser, it shows how easy to build a web application with Beego.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Ctx.WriteString("hello world")
|
||||
}
|
||||
|
||||
func main() {
|
||||
beego.Router("/", &MainController{})
|
||||
beego.Run()
|
||||
}
|
||||
|
||||
|
||||
# Handbook
|
||||
|
||||
- [Purposes](Why.md)
|
||||
- [Installation](Install.md)
|
||||
- [Quick start](Quickstart.md)
|
||||
- [Step by step](Tutorial.md)
|
||||
- [Real world usage](Application.md)
|
||||
- [Hot update](HotUpdate.md)
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
[Go Walker](http://gowalker.org/github.com/astaxie/beego)
|
@ -1,36 +0,0 @@
|
||||
## Supervisord
|
||||
|
||||
[Supervisord](http://supervisord.org/) will make sure your web app is always up.
|
||||
|
||||
1. Installation
|
||||
|
||||
wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
|
||||
|
||||
sh setuptools-0.6c11-py2.7.egg
|
||||
|
||||
easy_install supervisor
|
||||
|
||||
echo_supervisord_conf >/etc/supervisord.conf
|
||||
|
||||
mkdir /etc/supervisord.conf.d
|
||||
|
||||
2. Configure `/etc/supervisord.conf`
|
||||
|
||||
[include]
|
||||
files = /etc/supervisord.conf.d/*.conf
|
||||
|
||||
3. Add new application
|
||||
|
||||
cd /etc/supervisord.conf.d
|
||||
vim beepkg.conf
|
||||
|
||||
Configuration file:
|
||||
|
||||
[program:beepkg]
|
||||
directory = /opt/app/beepkg
|
||||
command = /opt/app/beepkg/beepkg
|
||||
autostart = true
|
||||
startsecs = 5
|
||||
user = root
|
||||
redirect_stderr = true
|
||||
stdout_logfile = /var/log/supervisord/beepkg.log
|
@ -1,19 +0,0 @@
|
||||
# 一步一步跟我写博客
|
||||
|
||||
|
||||
## 创建项目
|
||||
|
||||
|
||||
## 数据库结构设计
|
||||
|
||||
|
||||
## 控制器设计
|
||||
|
||||
|
||||
## 模板设计
|
||||
|
||||
|
||||
## 用户登陆退出
|
||||
|
||||
|
||||
## 数据库操作
|
@ -1,20 +0,0 @@
|
||||
# Design purposes and ideas
|
||||
|
||||
People may ask me why I want to build a new web framework rather than use other good ones. I know there are many excellent web frameworks on the internet and almost all of them are open source, and I have my reasons to do this.
|
||||
|
||||
Remember when I was writing the book about how to build web applications with Go, I just wanted to tell people what were my valuable experiences with Go in web development, especially I have been working with PHP and Python for almost ten years. At first, I didn't realize that a small web framework can give great help to web developers when they are learning to build web applications in a new programming language, and it also helps people more by studying its source code. Finally, I decided to write a open source web framework called Beego as supporting materiel for my book.
|
||||
|
||||
I used to use CI in PHP and tornado in Python, there are both lightweight, so they has following advantages:
|
||||
|
||||
1. Save time for handling general problems, I only need to care about logic part.
|
||||
2. Learn more about languages by studying their source code, it's not hard to read and understand them because they are both lightweight frameworks.
|
||||
3. It's quite easy to make secondary development of these frameworks for specific purposes.
|
||||
|
||||
Those reasons are my original intention of implementing Beego, and used two chapters in my book to introduce and design this lightweight web framework in Go.
|
||||
|
||||
Then I started to design logic execution of Beego. Because Go and Python have somewhat similar, I referenced some ideas from tornado to design Beego. As you can see, there is no different between Beego and tornado in RESTful processing; they both use GET, POST or some other methods to implement RESTful. I took some ideas from [https://github.com/drone/routes](https://github.com/drone/routes) at the beginning of designing routes. It uses regular expression in route rules processing, which is an excellent idea that to make up for the default Mux router function in Go. However, I have to design my own interface in order to implement RESTful and use inherited ideas in Python.
|
||||
|
||||
The controller is the most important part of whole MVC model, and Beego uses the interface and ideas I said above for the controller. Although I haven't decided to have to design the model part, everyone is welcome to implement data management by referencing Beedb, my another open source project. I simply adopt Go built-in template engine for the view part, but add more commonly used functions as template functions. This is how a simple web framework looks like, but I'll keep working on form processing, session handling, log recording, configuration, automated operation, etc, to build a simple but complete web framework.
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Installation](Install.md)
|
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 68 KiB |
@ -1,23 +0,0 @@
|
||||
# beego案例
|
||||
|
||||
- 短域名服务
|
||||
|
||||
使用beego开发了一个类似bitly的短域名服务,提供盛大内部项目使用,目前一台机器:32G内存、8核、centos64
|
||||
redis数据库,数据量在1500w多点,从2012年8月份运行至今没有出现过问题
|
||||
- 政府合作项目
|
||||
|
||||
使用beego提供系统级别服务,监控继承电路板信号,智能分析nginx配置,提供大数据量的下载调度
|
||||
|
||||
- 内部监控系统
|
||||
目前这个项目还在开发中,主要是利用beego做两个服务,一个是服务器端,收集信息,一个是客户端,收集信息并上报给服务器端,如果和服务端断开,那么本地可以暂存数据,防止数据丢失,同时还支持类似pupput的功能,支持程序自动更新(重启功能,不支持热更新)
|
||||
|
||||
- 日志分析系统
|
||||
以前采用hadoop来分析日志的来源和省份信息,发现hadoop分析这么大的数据,性能不是很好,延迟比较大,目前采用自己的一套架构,squid日志每小时分割上报,每小时对日志进行分割,然后进行UV、PV、省份、运营商、浏览器、数据量等的分析,同时把日志进行按域名分割,提供用户原始日志下载
|
||||
|
||||
- 下载分发系统
|
||||
架构暂时不好公布,我们提供文件下载的智能分发,但是从文件上传到下发到每一台服务器,以前采用BT的方式,性能不是很好,目前采用新的架构,性能提升十几倍
|
||||
|
||||
- 基于微信的提醒助手
|
||||
开源在github上
|
||||
|
||||
- 视频直播调度器
|
@ -1,45 +0,0 @@
|
||||
## 热升级是什么?
|
||||
|
||||
热升级是什么呢?了解nginx的同学都知道,nginx是支持热升级的,可以用老进程服务先前链接的链接,使用新进程服务新的链接,即在不停止服务的情况下完成系统的升级与运行参数修改。那么热升级和热编译是不同的概念,热编译是通过监控文件的变化重新编译,然后重启进程,例如bee start就是这样的工具
|
||||
|
||||
|
||||
## 热升级有必要吗?
|
||||
|
||||
很多人认为HTTP的应用有必要支持热升级吗?那么我可以很负责的说非常有必要,不中断服务始终是我们所追求的目标,虽然很多人说可能服务器会坏掉等等,这个是属于高可用的设计范畴,不要搞混了,这个是可预知的问题,所以我们需要避免这样的升级带来的用户不可用。你还在为以前升级搞到凌晨升级而烦恼嘛?那么现在就赶紧拥抱热升级吧。
|
||||
|
||||
|
||||
## beego如何支持热升级
|
||||
热升级的原理基本上就是:主进程fork一个进程,然后子进程exec相应的程序。那么这个过程中发生了什么呢?我们知道进程fork之后会把主进程的所有句柄、数据和堆栈继承过来、但是里面所有的句柄存在一个叫做CloseOnExec,也就是执行exec的时候,copy的所有的句柄都被关闭了,除非特别申明,而我们期望的是子进程能够复用主进程的net.Listener的句柄。一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
|
||||
|
||||
那么我们要做的第一步就是让子进程继承主进程的这个句柄,我们可以通过os.StartProcess的参数来附加Files,把需要继承的句柄写在里面。
|
||||
|
||||
第二步就是我们希望子进程能够从这个句柄启动监听,还好Go里面支持net.FileListener,直接从句柄来监听,但是我们需要子进程知道这个FD,所以在启动子进程的时候我们设置了一个环境变量设置这个FD。
|
||||
|
||||
第三步就是我们期望老的链接继续服务完,而新的链接采用新的进程,这里面有两个细节,第一就是老的链接继续服务,那么我们怎么知道有老链接存在?所以我们必须每次接收一个链接记录一下,这样我们就知道还存在没有服务完的链接,第二就是怎么让老进程停止接收数据,让新进程接收数据呢?大家都监听在同一个端口,理论上是随机来接收的,所以这里我们只要关闭老的链接的接收就行,这样就会使得在l.Accept的时候报错。
|
||||
|
||||
上面是我们需要解决的三个方面的问题,具体的实现大家可以看我实现的代码逻辑。
|
||||
|
||||
|
||||
## 如何演示热升级
|
||||
|
||||
1. 编写代码,在beego应用的控制器中Get方法实现大概如下:
|
||||
|
||||
func (this *MainController) Get() {
|
||||
a, _ := this.GetInt("sleep")
|
||||
time.Sleep(time.Duration(a) * time.Second)
|
||||
this.Ctx.WriteString("ospid:" + strconv.Itoa(os.Getpid()))
|
||||
}
|
||||
|
||||
2. 打开两个终端
|
||||
|
||||
一个终端输入:` ps -ef|grep 应用名`
|
||||
|
||||
一个终端输入请求:`curl "http://127.0.0.1:8080/?sleep=20"`
|
||||
|
||||
3. 热升级
|
||||
|
||||
`kill -HUP 进程ID`
|
||||
|
||||
4. 打开一个终端输入请求:`curl "http://127.0.0.1:8080/?sleep=0"`
|
||||
|
||||
我们可以看到这样的结果,第一个请求等待20s,但是处理他的是老的进程,热升级之后,第一个请求还在执行,最后会输出老的进程ID,而第二次请求,输出的是新的进程ID
|
@ -1,32 +0,0 @@
|
||||
# 安装入门
|
||||
|
||||
beego虽然是一个简单的框架,但是其中用到了很多第三方的包,所以在你安装beego的过程中Go会自动安装其他关联的包。
|
||||
|
||||
- 当然第一步你需要安装Go,如何安装Go请参考我的书[第一章](https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/01.1.md)
|
||||
|
||||
- 安装beego
|
||||
|
||||
go get github.com/astaxie/beego
|
||||
|
||||
- 安装bee工具,这个工具可以用来快速的建立beego的应用
|
||||
|
||||
go get github.com/astaxie/bee
|
||||
|
||||
这样就完成了beego的安装,你就可以开始开发了,可以通过bee工具来创建beego项目
|
||||
|
||||

|
||||
|
||||
>beego依赖的第三方包有如下:
|
||||
|
||||
> - session模块:github.com/astaxie/beego/session
|
||||
|
||||
|
||||
> - session模块中支持redis引擎:github.com/garyburd/redigo/redis
|
||||
|
||||
> - session模块中支持mysql引擎:github.com/go-sql-driver/mysql
|
||||
|
||||
> - 模板函数中支持markdown转化:github.com/russross/blackfriday
|
||||
|
||||
|
||||
- [beego介绍](README.md)
|
||||
- [快速入门](Quickstart.md)
|
@ -1,52 +0,0 @@
|
||||
# beego介绍
|
||||
beego是一个类似tornado的Go应用框架,采用了RESTFul的方式来实现应用框架,是一个超轻量级的框架,主要有如下的特点:
|
||||
|
||||
- 支持MVC的方式,用户只需要关注逻辑,实现对应method的方法即可
|
||||
- 支持websocket,通过自定义Handler实现集成sockjs等方式实现
|
||||
- 支持自定义路由,支持各种方式的路由,正则、语意均支持,类似sinatra
|
||||
- session集成,支持memory、file、redis、mysql等存储
|
||||
- 表单处理自动化解析,用户可以很方便的获取数据
|
||||
- 日志分级系统,用户可以很方便的调试和应用日志记录
|
||||
- 自定义配置文件,支持ini格式的文本配置,可以方便的在系统中调参数
|
||||
- 采用了Go内置的模板,集成实现了很多Web开发中常用的函数
|
||||
|
||||
执行过程如下所示:
|
||||

|
||||
|
||||
# beego简单例子
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Ctx.WriteString("hello world")
|
||||
}
|
||||
|
||||
func main() {
|
||||
beego.Router("/", &MainController{})
|
||||
beego.Run()
|
||||
}
|
||||
|
||||
|
||||
# beego 指南
|
||||
|
||||
* [为什么设计beego](Why.md)
|
||||
* [安装入门](Install.md)
|
||||
* [快速入门](Quickstart.md)
|
||||
* [一步一步开发应用](Tutorial.md)
|
||||
* [beego案例](Application.md)
|
||||
* [热升级](HotUpdate.md)
|
||||
|
||||
|
||||
# API接口
|
||||
|
||||
API对于我们平时开发应用非常有用,用于查询一些开发的函数,godoc做的非常好了
|
||||
|
||||
[Go Walker](http://gowalker.org/github.com/astaxie/beego)
|
@ -1,34 +0,0 @@
|
||||
## supervisord安装
|
||||
|
||||
1. setuptools安装
|
||||
|
||||
wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
|
||||
|
||||
sh setuptools-0.6c11-py2.7.egg
|
||||
|
||||
easy_install supervisor
|
||||
|
||||
echo_supervisord_conf >/etc/supervisord.conf
|
||||
|
||||
mkdir /etc/supervisord.conf.d
|
||||
|
||||
2. 修改配置/etc/supervisord.conf
|
||||
|
||||
[include]
|
||||
files = /etc/supervisord.conf.d/*.conf
|
||||
|
||||
3. 新建管理的应用
|
||||
|
||||
cd /etc/supervisord.conf.d
|
||||
vim beepkg.conf
|
||||
|
||||
配置文件:
|
||||
|
||||
[program:beepkg]
|
||||
directory = /opt/app/beepkg
|
||||
command = /opt/app/beepkg/beepkg
|
||||
autostart = true
|
||||
startsecs = 5
|
||||
user = root
|
||||
redirect_stderr = true
|
||||
stdout_logfile = /var/log/supervisord/beepkg.log
|
@ -1,19 +0,0 @@
|
||||
# 一步一步跟我写博客
|
||||
|
||||
|
||||
## 创建项目
|
||||
|
||||
|
||||
## 数据库结构设计
|
||||
|
||||
|
||||
## 控制器设计
|
||||
|
||||
|
||||
## 模板设计
|
||||
|
||||
|
||||
## 用户登陆退出
|
||||
|
||||
|
||||
## 数据库操作
|
@ -1,20 +0,0 @@
|
||||
# 为什么设计beego和设计的思路
|
||||
|
||||
很多人会问为什么有那么多框架了,还要去实现一个框架呢?是不是大家都有自己实现框架的情节,我可以肯定的说不是,我说一下为什么设计beego的初衷
|
||||
|
||||
还记得当初写书的时候,我纯粹只是想把自己在学习Go语言中的一些体会写出来,由于我以前主要从事PHP和python的Web开发,所以想写一本Go如何来做Web实战的经验,刚开始的时候书的目录里面根本就没有框架实现这些章节,是写到后来发现其实对于Web开发者来说,一个微型的框架是非常有利于大家学习一个语言和快速进行应用开发的。
|
||||
|
||||
我以前经常用PHP的CI框架和python的tornado框架,这些框架都是非常轻量级的,轻量级就有利于我们:
|
||||
|
||||
- 第一节约我开发中一些常见问题的处理,用户只需要关注逻辑层面的东西
|
||||
- 第二轻量级以至于他们的代码也是非常清晰的,我们可以通过阅读他们的源码来学习和体会这门语言的一些细节
|
||||
- 第三对于项目开发者来说可以基于这些框架进行改造以适应自己的项目,从而实现二次框架的创造
|
||||
|
||||
所以基于上面这些的考虑,我就想实现一个类似这些语言的轻量级框架,所以我就在书的最后设计了两个章节来介绍和实现beego框架,这就是当初写beego框架的初衷。
|
||||
|
||||
有了这个初衷之后我就开始设计beego的执行逻辑,由于Go语言和python的思路比较接近,所以我就参考了tornado的思路来设计beego,你可以看到beego的RESTful处理完全和tornado的处理是一模一样的,通过controller层的Get、Post等方法来实现RESTFul。刚开始的时候路由参考的是[https://github.com/drone/routes](https://github.com/drone/routes),这个的正则处理我觉得非常好,弥补了Go语言默认Mux中的路由功能,但是由于要采用RESTFul方式,所以我自己设计了一个接口,实现python中的继承思想。
|
||||
|
||||
整个的MVC逻辑中C是最重要的部分,这一块采用了我上面说的接口方式,M模块目前我还没想好怎么做,但是大家可以参考我的另一个开源项目beedb来实现数据的管理,V这一块目前采用了Go语言自带的模板引擎,但是实现了很多方便的模板函数。这样一个简易的框架就完成了,然后我就不断的完善周边的功能,包括表单处理、session处理、日志处理、配置处理、自动化运行等功能。
|
||||
|
||||
- [beego介绍](README.md)
|
||||
- [安装入门](Install.md)
|
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 68 KiB |
5
example/beeapi/conf/app.conf
Normal file
@ -0,0 +1,5 @@
|
||||
appname = beeapi
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
autorender = false
|
||||
copyrequestbody = true
|
56
example/beeapi/controllers/default.go
Normal file
@ -0,0 +1,56 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/example/beeapi/models"
|
||||
)
|
||||
|
||||
type ObjectController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *ObjectController) Post() {
|
||||
var ob models.Object
|
||||
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
|
||||
objectid := models.AddOne(ob)
|
||||
this.Data["json"] = map[string]string{"ObjectId": objectid}
|
||||
this.ServeJson()
|
||||
}
|
||||
|
||||
func (this *ObjectController) Get() {
|
||||
objectId := this.Ctx.Input.Params[":objectId"]
|
||||
if objectId != "" {
|
||||
ob, err := models.GetOne(objectId)
|
||||
if err != nil {
|
||||
this.Data["json"] = err
|
||||
} else {
|
||||
this.Data["json"] = ob
|
||||
}
|
||||
} else {
|
||||
obs := models.GetAll()
|
||||
this.Data["json"] = obs
|
||||
}
|
||||
this.ServeJson()
|
||||
}
|
||||
|
||||
func (this *ObjectController) Put() {
|
||||
objectId := this.Ctx.Input.Params[":objectId"]
|
||||
var ob models.Object
|
||||
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
|
||||
|
||||
err := models.Update(objectId, ob.Score)
|
||||
if err != nil {
|
||||
this.Data["json"] = err
|
||||
} else {
|
||||
this.Data["json"] = "update success!"
|
||||
}
|
||||
this.ServeJson()
|
||||
}
|
||||
|
||||
func (this *ObjectController) Delete() {
|
||||
objectId := this.Ctx.Input.Params[":objectId"]
|
||||
models.Delete(objectId)
|
||||
this.Data["json"] = "delete success!"
|
||||
this.ServeJson()
|
||||
}
|
20
example/beeapi/main.go
Normal file
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/example/beeapi/controllers"
|
||||
)
|
||||
|
||||
// Objects
|
||||
|
||||
// URL HTTP Verb Functionality
|
||||
// /object POST Creating Objects
|
||||
// /object/<objectId> GET Retrieving Objects
|
||||
// /object/<objectId> PUT Updating Objects
|
||||
// /object GET Queries
|
||||
// /object/<objectId> DELETE Deleting Objects
|
||||
|
||||
func main() {
|
||||
beego.RESTRouter("/object", &controllers.ObjectController{})
|
||||
beego.Run()
|
||||
}
|
52
example/beeapi/models/object.go
Normal file
@ -0,0 +1,52 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Objects map[string]*Object
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
ObjectId string
|
||||
Score int64
|
||||
PlayerName string
|
||||
}
|
||||
|
||||
func init() {
|
||||
Objects = make(map[string]*Object)
|
||||
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
|
||||
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
|
||||
}
|
||||
|
||||
func AddOne(object Object) (ObjectId string) {
|
||||
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
Objects[object.ObjectId] = &object
|
||||
return object.ObjectId
|
||||
}
|
||||
|
||||
func GetOne(ObjectId string) (object *Object, err error) {
|
||||
if v, ok := Objects[ObjectId]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("ObjectId Not Exist")
|
||||
}
|
||||
|
||||
func GetAll() map[string]*Object {
|
||||
return Objects
|
||||
}
|
||||
|
||||
func Update(ObjectId string, Score int64) (err error) {
|
||||
if v, ok := Objects[ObjectId]; ok {
|
||||
v.Score = Score
|
||||
return nil
|
||||
}
|
||||
return errors.New("ObjectId Not Exist")
|
||||
}
|
||||
|
||||
func Delete(ObjectId string) {
|
||||
delete(Objects, ObjectId)
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/fzzy/sockjs-go/sockjs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var users *sockjs.SessionPool = sockjs.NewSessionPool()
|
||||
|
||||
func chatHandler(s sockjs.Session) {
|
||||
users.Add(s)
|
||||
defer users.Remove(s)
|
||||
|
||||
for {
|
||||
m := s.Receive()
|
||||
if m == nil {
|
||||
break
|
||||
}
|
||||
fullAddr := s.Info().RemoteAddr
|
||||
addr := fullAddr[:strings.LastIndex(fullAddr, ":")]
|
||||
m = []byte(fmt.Sprintf("%s: %s", addr, m))
|
||||
users.Broadcast(m)
|
||||
}
|
||||
}
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (m *MainController) Get() {
|
||||
m.TplNames = "index.html"
|
||||
}
|
||||
|
||||
func main() {
|
||||
conf := sockjs.NewConfig()
|
||||
sockjshandler := sockjs.NewHandler("/chat", chatHandler, conf)
|
||||
beego.Router("/", &MainController{})
|
||||
beego.RouterHandler("/chat/:info(.*)", sockjshandler)
|
||||
beego.Run()
|
||||
}
|
3
example/chat/conf/app.conf
Normal file
@ -0,0 +1,3 @@
|
||||
appname = chat
|
||||
httpport = 8080
|
||||
runmode = dev
|
14
example/chat/controllers/default.go
Normal file
@ -0,0 +1,14 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Data["host"] = this.Ctx.Request.Host
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
169
example/chat/controllers/ws.go
Normal file
@ -0,0 +1,169 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/garyburd/go-websocket/websocket"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the client.
|
||||
writeWait = 10 * time.Second
|
||||
|
||||
// Time allowed to read the next message from the client.
|
||||
readWait = 60 * time.Second
|
||||
|
||||
// Send pings to client with this period. Must be less than readWait.
|
||||
pingPeriod = (readWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from client.
|
||||
maxMessageSize = 512
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
go h.run()
|
||||
}
|
||||
|
||||
// connection is an middleman between the websocket connection and the hub.
|
||||
type connection struct {
|
||||
username string
|
||||
|
||||
// The websocket connection.
|
||||
ws *websocket.Conn
|
||||
|
||||
// Buffered channel of outbound messages.
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the hub.
|
||||
func (c *connection) readPump() {
|
||||
defer func() {
|
||||
h.unregister <- c
|
||||
c.ws.Close()
|
||||
}()
|
||||
c.ws.SetReadLimit(maxMessageSize)
|
||||
c.ws.SetReadDeadline(time.Now().Add(readWait))
|
||||
for {
|
||||
op, r, err := c.ws.NextReader()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch op {
|
||||
case websocket.OpPong:
|
||||
c.ws.SetReadDeadline(time.Now().Add(readWait))
|
||||
case websocket.OpText:
|
||||
message, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
h.broadcast <- []byte(c.username + "_" + time.Now().Format("15:04:05") + ":" + string(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a message with the given opCode and payload.
|
||||
func (c *connection) write(opCode int, payload []byte) error {
|
||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.ws.WriteMessage(opCode, payload)
|
||||
}
|
||||
|
||||
// writePump pumps messages from the hub to the websocket connection.
|
||||
func (c *connection) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.ws.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
c.write(websocket.OpClose, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.write(websocket.OpText, message); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.OpPing, []byte{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type hub struct {
|
||||
// Registered connections.
|
||||
connections map[*connection]bool
|
||||
|
||||
// Inbound messages from the connections.
|
||||
broadcast chan []byte
|
||||
|
||||
// Register requests from the connections.
|
||||
register chan *connection
|
||||
|
||||
// Unregister requests from connections.
|
||||
unregister chan *connection
|
||||
}
|
||||
|
||||
var h = &hub{
|
||||
broadcast: make(chan []byte, maxMessageSize),
|
||||
register: make(chan *connection, 1),
|
||||
unregister: make(chan *connection, 1),
|
||||
connections: make(map[*connection]bool),
|
||||
}
|
||||
|
||||
func (h *hub) run() {
|
||||
for {
|
||||
select {
|
||||
case c := <-h.register:
|
||||
h.connections[c] = true
|
||||
case c := <-h.unregister:
|
||||
delete(h.connections, c)
|
||||
close(c.send)
|
||||
case m := <-h.broadcast:
|
||||
for c := range h.connections {
|
||||
select {
|
||||
case c.send <- m:
|
||||
default:
|
||||
close(c.send)
|
||||
delete(h.connections, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WSController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *WSController) Get() {
|
||||
ws, err := websocket.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request.Header, nil, 1024, 1024)
|
||||
if _, ok := err.(websocket.HandshakeError); ok {
|
||||
http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)
|
||||
return
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
c := &connection{send: make(chan []byte, 256), ws: ws, username: randomString(10)}
|
||||
h.register <- c
|
||||
go c.writePump()
|
||||
c.readPump()
|
||||
}
|
||||
|
||||
func randomString(l int) string {
|
||||
bytes := make([]byte, l)
|
||||
for i := 0; i < l; i++ {
|
||||
bytes[i] = byte(randInt(65, 90))
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func randInt(min int, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
12
example/chat/main.go
Normal file
@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/example/chat/controllers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
beego.Router("/", &controllers.MainController{})
|
||||
beego.Router("/ws", &controllers.WSController{})
|
||||
beego.Run()
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script src="http://cdn.sockjs.org/sockjs-0.3.4.min.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
var conn = null;
|
||||
|
||||
function log(msg) {
|
||||
var control = $('#log');
|
||||
control.html(control.html() + msg + '<br/>');
|
||||
control.scrollTop(control.scrollTop() + 1000);
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (conn != null) {
|
||||
log('Disconnecting...');
|
||||
|
||||
conn.close();
|
||||
conn = null;
|
||||
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
function updateUi() {
|
||||
if (conn == null || conn.readyState != SockJS.OPEN) {
|
||||
$('#status').text('disconnected');
|
||||
$('#connect').text('Connect');
|
||||
} else {
|
||||
$('#status').text('connected (' + conn.protocol + ')');
|
||||
$('#connect').text('Disconnect');
|
||||
}
|
||||
}
|
||||
|
||||
$('form').submit(function() {
|
||||
var text = $('#message').val();
|
||||
conn.send(text);
|
||||
$('#message').val('').focus();
|
||||
return false;
|
||||
});
|
||||
|
||||
conn = new SockJS('http://' + window.location.host + '/chat');
|
||||
log('Connecting...');
|
||||
|
||||
conn.onopen = function() {
|
||||
log('Connected.');
|
||||
updateUi();
|
||||
};
|
||||
|
||||
conn.onmessage = function(e) {
|
||||
log(e.data);
|
||||
};
|
||||
|
||||
conn.onclose = function() {
|
||||
log('Disconnected.');
|
||||
conn = null;
|
||||
updateUi();
|
||||
};
|
||||
|
||||
$('#message').val('').focus();
|
||||
});
|
||||
</script>
|
||||
<title>Sockjs-go chat</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sockjs-go chat</h1>
|
||||
|
||||
<div>
|
||||
Status: <span id="status">disconnected</span>
|
||||
</div>
|
||||
<div id="log" style="width: 60em; height: 20em; overflow:auto; border: 1px solid black">
|
||||
</div>
|
||||
<form id="chatform">
|
||||
<label for="message">Message:</label>
|
||||
<input id="message" type="text" />
|
||||
<input type="submit" value="Send" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
92
example/chat/views/index.tpl
Normal file
@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Chat Example</title>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
|
||||
var conn;
|
||||
var msg = $("#msg");
|
||||
var log = $("#log");
|
||||
|
||||
function appendLog(msg) {
|
||||
var d = log[0]
|
||||
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
|
||||
msg.appendTo(log)
|
||||
if (doScroll) {
|
||||
d.scrollTop = d.scrollHeight - d.clientHeight;
|
||||
}
|
||||
}
|
||||
|
||||
$("#form").submit(function() {
|
||||
if (!conn) {
|
||||
return false;
|
||||
}
|
||||
if (!msg.val()) {
|
||||
return false;
|
||||
}
|
||||
conn.send(msg.val());
|
||||
msg.val("");
|
||||
return false
|
||||
});
|
||||
|
||||
if (window["WebSocket"]) {
|
||||
conn = new WebSocket("ws://{{.host}}/ws");
|
||||
conn.onclose = function(evt) {
|
||||
appendLog($("<div><b>Connection closed.</b></div>"))
|
||||
}
|
||||
conn.onmessage = function(evt) {
|
||||
appendLog($("<div/>").text(evt.data))
|
||||
}
|
||||
} else {
|
||||
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: gray;
|
||||
}
|
||||
|
||||
#log {
|
||||
background: white;
|
||||
margin: 0;
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
left: 0.5em;
|
||||
right: 0.5em;
|
||||
bottom: 3em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#form {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<form id="form">
|
||||
<input type="submit" value="Send" />
|
||||
<input type="text" id="msg" size="64"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
148
filter.go
Normal file
@ -0,0 +1,148 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FilterRouter defines filter operation before controller handler execution.
|
||||
// it can match patterned url and do filter function when action arrives.
|
||||
type FilterRouter struct {
|
||||
pattern string
|
||||
regex *regexp.Regexp
|
||||
filterFunc FilterFunc
|
||||
hasregex bool
|
||||
params map[int]string
|
||||
parseParams map[string]string
|
||||
}
|
||||
|
||||
// ValidRouter check current request is valid for this filter.
|
||||
// if matched, returns parsed params in this request by defined filter router pattern.
|
||||
func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
|
||||
if mr.pattern == "" {
|
||||
return true, nil
|
||||
}
|
||||
if mr.pattern == "*" {
|
||||
return true, nil
|
||||
}
|
||||
if router == mr.pattern {
|
||||
return true, nil
|
||||
}
|
||||
if mr.hasregex {
|
||||
if !mr.regex.MatchString(router) {
|
||||
return false, nil
|
||||
}
|
||||
matches := mr.regex.FindStringSubmatch(router)
|
||||
if len(matches) > 0 {
|
||||
if len(matches[0]) == len(router) {
|
||||
params := make(map[string]string)
|
||||
for i, match := range matches[1:] {
|
||||
params[mr.params[i]] = match
|
||||
}
|
||||
return true, params
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func buildFilter(pattern string, filter FilterFunc) *FilterRouter {
|
||||
mr := new(FilterRouter)
|
||||
mr.params = make(map[int]string)
|
||||
mr.filterFunc = filter
|
||||
parts := strings.Split(pattern, "/")
|
||||
j := 0
|
||||
for i, part := range parts {
|
||||
if strings.HasPrefix(part, ":") {
|
||||
expr := "(.+)"
|
||||
//a user may choose to override the default expression
|
||||
// similar to expressjs: ‘/user/:id([0-9]+)’
|
||||
if index := strings.Index(part, "("); index != -1 {
|
||||
expr = part[index:]
|
||||
part = part[:index]
|
||||
//match /user/:id:int ([0-9]+)
|
||||
//match /post/:username:string ([\w]+)
|
||||
} else if lindex := strings.LastIndex(part, ":"); lindex != 0 {
|
||||
switch part[lindex:] {
|
||||
case ":int":
|
||||
expr = "([0-9]+)"
|
||||
part = part[:lindex]
|
||||
case ":string":
|
||||
expr = `([\w]+)`
|
||||
part = part[:lindex]
|
||||
}
|
||||
}
|
||||
mr.params[j] = part
|
||||
parts[i] = expr
|
||||
j++
|
||||
}
|
||||
if strings.HasPrefix(part, "*") {
|
||||
expr := "(.+)"
|
||||
if part == "*.*" {
|
||||
mr.params[j] = ":path"
|
||||
parts[i] = "([^.]+).([^.]+)"
|
||||
j++
|
||||
mr.params[j] = ":ext"
|
||||
j++
|
||||
} else {
|
||||
mr.params[j] = ":splat"
|
||||
parts[i] = expr
|
||||
j++
|
||||
}
|
||||
}
|
||||
//url like someprefix:id(xxx).html
|
||||
if strings.Contains(part, ":") && strings.Contains(part, "(") && strings.Contains(part, ")") {
|
||||
var out []rune
|
||||
var start bool
|
||||
var startexp bool
|
||||
var param []rune
|
||||
var expt []rune
|
||||
for _, v := range part {
|
||||
if start {
|
||||
if v != '(' {
|
||||
param = append(param, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if startexp {
|
||||
if v != ')' {
|
||||
expt = append(expt, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v == ':' {
|
||||
param = make([]rune, 0)
|
||||
param = append(param, ':')
|
||||
start = true
|
||||
} else if v == '(' {
|
||||
startexp = true
|
||||
start = false
|
||||
mr.params[j] = string(param)
|
||||
j++
|
||||
expt = make([]rune, 0)
|
||||
expt = append(expt, '(')
|
||||
} else if v == ')' {
|
||||
startexp = false
|
||||
expt = append(expt, ')')
|
||||
out = append(out, expt...)
|
||||
} else {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
parts[i] = string(out)
|
||||
}
|
||||
}
|
||||
|
||||
if j != 0 {
|
||||
pattern = strings.Join(parts, "/")
|
||||
regex, regexErr := regexp.Compile(pattern)
|
||||
if regexErr != nil {
|
||||
//TODO add error handling here to avoid panic
|
||||
panic(regexErr)
|
||||
}
|
||||
mr.regex = regex
|
||||
mr.hasregex = true
|
||||
}
|
||||
mr.pattern = pattern
|
||||
return mr
|
||||
}
|
25
fiter_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
|
||||
var FilterUser = func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte("i am " + ctx.Input.Params[":last"] + ctx.Input.Params[":first"]))
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "/person/asta/Xie", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler := NewControllerRegistor()
|
||||
handler.AddFilter("/person/:last/:first", "AfterStatic", FilterUser)
|
||||
handler.Add("/person/:last/:first", &TestController{})
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Body.String() != "i am astaXie" {
|
||||
t.Errorf("user define func can't run")
|
||||
}
|
||||
}
|
83
flash.go
Normal file
@ -0,0 +1,83 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// the separation string when encoding flash data.
|
||||
const BEEGO_FLASH_SEP = "#BEEGOFLASH#"
|
||||
|
||||
// FlashData is a tools to maintain data when using across request.
|
||||
type FlashData struct {
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
// NewFlash return a new empty FlashData struct.
|
||||
func NewFlash() *FlashData {
|
||||
return &FlashData{
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Notice writes notice message to flash.
|
||||
func (fd *FlashData) Notice(msg string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
fd.Data["notice"] = msg
|
||||
} else {
|
||||
fd.Data["notice"] = fmt.Sprintf(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warning writes warning message to flash.
|
||||
func (fd *FlashData) Warning(msg string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
fd.Data["warning"] = msg
|
||||
} else {
|
||||
fd.Data["warning"] = fmt.Sprintf(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error writes error message to flash.
|
||||
func (fd *FlashData) Error(msg string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
fd.Data["error"] = msg
|
||||
} else {
|
||||
fd.Data["error"] = fmt.Sprintf(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Store does the saving operation of flash data.
|
||||
// the data are encoded and saved in cookie.
|
||||
func (fd *FlashData) Store(c *Controller) {
|
||||
c.Data["flash"] = fd.Data
|
||||
var flashValue string
|
||||
for key, value := range fd.Data {
|
||||
flashValue += "\x00" + key + BEEGO_FLASH_SEP + value + "\x00"
|
||||
}
|
||||
c.Ctx.SetCookie("BEEGO_FLASH", url.QueryEscape(flashValue), 0, "/")
|
||||
}
|
||||
|
||||
// ReadFromRequest parsed flash data from encoded values in cookie.
|
||||
func ReadFromRequest(c *Controller) *FlashData {
|
||||
flash := &FlashData{
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
if cookie, err := c.Ctx.Request.Cookie("BEEGO_FLASH"); err == nil {
|
||||
v, _ := url.QueryUnescape(cookie.Value)
|
||||
vals := strings.Split(v, "\x00")
|
||||
for _, v := range vals {
|
||||
if len(v) > 0 {
|
||||
kv := strings.Split(v, BEEGO_FLASH_SEP)
|
||||
if len(kv) == 2 {
|
||||
flash.Data[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
//read one time then delete it
|
||||
c.Ctx.SetCookie("BEEGO_FLASH", "", -1, "/")
|
||||
}
|
||||
c.Data["flash"] = flash.Data
|
||||
return flash
|
||||
}
|
62
httplib/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# httplib
|
||||
httplib is an libs help you to curl remote url.
|
||||
|
||||
# How to use?
|
||||
|
||||
## GET
|
||||
you can use Get to crawl data.
|
||||
|
||||
import "httplib"
|
||||
|
||||
str, err := httplib.Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## POST
|
||||
POST data to remote url
|
||||
|
||||
b:=httplib.Post("http://beego.me/")
|
||||
b.Param("username","astaxie")
|
||||
b.Param("password","123456")
|
||||
str, err := b.String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## set timeout
|
||||
you can set timeout in request.default is 60 seconds.
|
||||
|
||||
set Get timeout:
|
||||
|
||||
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
set post timeout:
|
||||
|
||||
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
- first param is connectTimeout.
|
||||
- second param is readWriteTimeout
|
||||
|
||||
## debug
|
||||
if you want to debug the request info, set the debug on
|
||||
|
||||
httplib.Get("http://beego.me/").Debug(true)
|
||||
|
||||
## support HTTPS client
|
||||
if request url is https. You can set the client support TSL:
|
||||
|
||||
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
|
||||
more info about the tls.Config please visit http://golang.org/pkg/crypto/tls/#Config
|
||||
|
||||
## set cookie
|
||||
some http request need setcookie. So set it like this:
|
||||
|
||||
cookie := &http.Cookie{}
|
||||
cookie.Name = "username"
|
||||
cookie.Value = "astaxie"
|
||||
httplib.Get("http://beego.me/").SetCookie(cookie)
|
||||
|
256
httplib/httplib.go
Normal file
@ -0,0 +1,256 @@
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultUserAgent = "beegoServer"
|
||||
|
||||
func Get(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "GET"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
|
||||
}
|
||||
|
||||
func Post(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "POST"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
|
||||
}
|
||||
|
||||
func Put(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "PUT"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
|
||||
}
|
||||
|
||||
func Delete(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "DELETE"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
|
||||
}
|
||||
|
||||
func Head(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "HEAD"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
|
||||
}
|
||||
|
||||
type BeegoHttpRequest struct {
|
||||
url string
|
||||
req *http.Request
|
||||
params map[string]string
|
||||
showdebug bool
|
||||
connectTimeout time.Duration
|
||||
readWriteTimeout time.Duration
|
||||
tlsClientConfig *tls.Config
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
|
||||
b.showdebug = isdebug
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
|
||||
b.connectTimeout = connectTimeout
|
||||
b.readWriteTimeout = readWriteTimeout
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest {
|
||||
b.tlsClientConfig = config
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
|
||||
b.req.Header.Set(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest {
|
||||
b.req.Header.Add("Cookie", cookie.String())
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest {
|
||||
b.params[key] = value
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
|
||||
switch t := data.(type) {
|
||||
case string:
|
||||
bf := bytes.NewBufferString(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
case []byte:
|
||||
bf := bytes.NewBuffer(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
||||
var paramBody string
|
||||
if len(b.params) > 0 {
|
||||
var buf bytes.Buffer
|
||||
for k, v := range b.params {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
paramBody = buf.String()
|
||||
paramBody = paramBody[0 : len(paramBody)-1]
|
||||
}
|
||||
|
||||
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||
if strings.Index(b.url, "?") != -1 {
|
||||
b.url += "&" + paramBody
|
||||
} else {
|
||||
b.url = b.url + "?" + paramBody
|
||||
}
|
||||
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
|
||||
b.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||
b.Body(paramBody)
|
||||
}
|
||||
|
||||
url, err := url.Parse(b.url)
|
||||
if url.Scheme == "" {
|
||||
b.url = "http://" + b.url
|
||||
url, err = url.Parse(b.url)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.req.URL = url
|
||||
if b.showdebug {
|
||||
dump, err := httputil.DumpRequest(b.req, true)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
println(string(dump))
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: b.tlsClientConfig,
|
||||
Dial: TimeoutDialer(b.connectTimeout, b.readWriteTimeout),
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(b.req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) String() (string, error) {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) ToFile(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) ToJson(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) ToXML(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) Response() (*http.Response, error) {
|
||||
return b.getResponse()
|
||||
}
|
||||
|
||||
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
|
||||
return func(netw, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(netw, addr, cTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||
return conn, nil
|
||||
}
|
||||
}
|
32
httplib/httplib_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetUrl(t *testing.T) {
|
||||
resp, err := Get("http://beego.me/").Debug(true).Response()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Body == nil {
|
||||
t.Fatal("body is nil")
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Fatal("data is no")
|
||||
}
|
||||
|
||||
str, err := Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(str) == 0 {
|
||||
t.Fatal("has no info")
|
||||
}
|
||||
}
|
226
log.go
@ -1,167 +1,11 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type FileLogWriter struct {
|
||||
*log.Logger
|
||||
// The opened file
|
||||
filename string
|
||||
|
||||
maxlines int
|
||||
maxlines_curlines int
|
||||
|
||||
// Rotate at size
|
||||
maxsize int
|
||||
maxsize_cursize int
|
||||
|
||||
// Rotate daily
|
||||
daily bool
|
||||
maxday int64
|
||||
daily_opendate int
|
||||
|
||||
rotate bool
|
||||
|
||||
startLock sync.Mutex //only one log can writer to the file
|
||||
}
|
||||
|
||||
func NewFileWriter(fname string, rotate bool) *FileLogWriter {
|
||||
w := &FileLogWriter{
|
||||
filename: fname,
|
||||
maxlines: 1000000,
|
||||
maxsize: 1 << 28, //256 MB
|
||||
daily: true,
|
||||
maxday: 7,
|
||||
rotate: rotate,
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Set rotate at linecount (chainable). Must be called before call StartLogger
|
||||
func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
|
||||
w.maxlines = maxlines
|
||||
return w
|
||||
}
|
||||
|
||||
// Set rotate at size (chainable). Must be called before call StartLogger
|
||||
func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
|
||||
w.maxsize = maxsize
|
||||
return w
|
||||
}
|
||||
|
||||
// Set rotate daily (chainable). Must be called before call StartLogger
|
||||
func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
|
||||
w.daily = daily
|
||||
return w
|
||||
}
|
||||
|
||||
// Set rotate daily's log keep for maxday,other will delete
|
||||
func (w *FileLogWriter) SetRotateMaxDay(maxday int64) *FileLogWriter {
|
||||
w.maxday = maxday
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) StartLogger() error {
|
||||
if err := w.DoRotate(false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) docheck(size int) {
|
||||
w.startLock.Lock()
|
||||
defer w.startLock.Unlock()
|
||||
if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
|
||||
(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
|
||||
(w.daily && time.Now().Day() != w.daily_opendate) {
|
||||
if err := w.DoRotate(true); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.maxlines_curlines++
|
||||
w.maxsize_cursize += size
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) Printf(format string, v ...interface{}) {
|
||||
// Perform the write
|
||||
str := fmt.Sprintf(format, v...)
|
||||
n := 24 + len(str) // 24 stand for the length "2013/06/23 21:00:22 [T] "
|
||||
|
||||
w.docheck(n)
|
||||
w.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) DoRotate(rotate bool) error {
|
||||
if rotate {
|
||||
_, err := os.Lstat(w.filename)
|
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fname := ""
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fname = w.filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
|
||||
_, err = os.Lstat(fname)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
|
||||
}
|
||||
|
||||
// Rename the file to its newfound home
|
||||
err = os.Rename(w.filename, fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", err)
|
||||
}
|
||||
go w.deleteOldLog()
|
||||
}
|
||||
}
|
||||
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Logger = log.New(fd, "", log.Ldate|log.Ltime)
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
if finfo.Size() > 0 {
|
||||
content, err := ioutil.ReadFile(w.filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
|
||||
|
||||
} else {
|
||||
w.maxlines_curlines = 0
|
||||
}
|
||||
BeeLogger = w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) deleteOldLog() {
|
||||
dir := path.Dir(w.filename)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.maxday) {
|
||||
os.Remove(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Log levels to control the logging output.
|
||||
const (
|
||||
LevelTrace = iota
|
||||
@ -172,88 +16,50 @@ const (
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
// logLevel controls the global log level used by the logger.
|
||||
var level = LevelTrace
|
||||
|
||||
// LogLevel returns the global log level and can be used in
|
||||
// own implementations of the logger interface.
|
||||
func Level() int {
|
||||
return level
|
||||
}
|
||||
|
||||
// SetLogLevel sets the global log level used by the simple
|
||||
// logger.
|
||||
func SetLevel(l int) {
|
||||
level = l
|
||||
}
|
||||
|
||||
type IBeeLogger interface {
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
Fatalln(v ...interface{})
|
||||
Flags() int
|
||||
Output(calldepth int, s string) error
|
||||
Panic(v ...interface{})
|
||||
Panicf(format string, v ...interface{})
|
||||
Panicln(v ...interface{})
|
||||
Prefix() string
|
||||
Print(v ...interface{})
|
||||
Printf(format string, v ...interface{})
|
||||
Println(v ...interface{})
|
||||
SetFlags(flag int)
|
||||
SetPrefix(prefix string)
|
||||
BeeLogger.SetLevel(l)
|
||||
}
|
||||
|
||||
// logger references the used application logger.
|
||||
var BeeLogger IBeeLogger
|
||||
|
||||
func init() {
|
||||
BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||
}
|
||||
var BeeLogger *logs.BeeLogger
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(l *log.Logger) {
|
||||
BeeLogger = l
|
||||
func SetLogger(adaptername string, config string) {
|
||||
BeeLogger.SetLogger(adaptername, config)
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
func Trace(v ...interface{}) {
|
||||
if level <= LevelTrace {
|
||||
BeeLogger.Printf("[T] %v\n", v)
|
||||
}
|
||||
BeeLogger.Trace(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(v ...interface{}) {
|
||||
if level <= LevelDebug {
|
||||
BeeLogger.Printf("[D] %v\n", v)
|
||||
}
|
||||
BeeLogger.Debug(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Info logs a message at info level.
|
||||
func Info(v ...interface{}) {
|
||||
if level <= LevelInfo {
|
||||
BeeLogger.Printf("[I] %v\n", v)
|
||||
}
|
||||
BeeLogger.Info(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warn(v ...interface{}) {
|
||||
if level <= LevelWarning {
|
||||
BeeLogger.Printf("[W] %v\n", v)
|
||||
}
|
||||
BeeLogger.Warn(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(v ...interface{}) {
|
||||
if level <= LevelError {
|
||||
BeeLogger.Printf("[E] %v\n", v)
|
||||
}
|
||||
BeeLogger.Error(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(v ...interface{}) {
|
||||
if level <= LevelCritical {
|
||||
BeeLogger.Printf("[C] %v\n", v)
|
||||
}
|
||||
BeeLogger.Critical(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func generateFmtStr(n int) string {
|
||||
return strings.Repeat("%v ", n)
|
||||
}
|
||||
|
63
logs/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
## logs
|
||||
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
|
||||
## How to install?
|
||||
|
||||
go get github.com/astaxie/beego/logs
|
||||
|
||||
|
||||
## What adapters are supported?
|
||||
|
||||
As of now this logs support console, file,smtp and conn.
|
||||
|
||||
|
||||
## How to use it?
|
||||
|
||||
First you must import it
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
Then init a Log (example with console adapter)
|
||||
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
|
||||
> the first params stand for how many channel
|
||||
|
||||
Use it like this:
|
||||
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
|
||||
|
||||
## File adapter
|
||||
|
||||
Configure file adapter like this:
|
||||
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
|
||||
|
||||
## Conn adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Info("info")
|
||||
|
||||
|
||||
## Smtp adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
98
logs/conn.go
Normal file
@ -0,0 +1,98 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ConnWriter struct {
|
||||
lg *log.Logger
|
||||
innerWriter io.WriteCloser
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
func NewConn() LoggerInterface {
|
||||
conn := new(ConnWriter)
|
||||
conn.Level = LevelTrace
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *ConnWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnWriter) WriteMsg(msg string, level int) error {
|
||||
if level < c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.neddedConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func (c *ConnWriter) Destroy() {
|
||||
if c.innerWriter == nil {
|
||||
return
|
||||
}
|
||||
c.innerWriter.Close()
|
||||
}
|
||||
|
||||
func (c *ConnWriter) connect() error {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
c.innerWriter = nil
|
||||
}
|
||||
|
||||
conn, err := net.Dial(c.Net, c.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnWriter) neddedConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
c.Reconnect = false
|
||||
return true
|
||||
}
|
||||
|
||||
if c.innerWriter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.ReconnectOnMsg
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("conn", NewConn)
|
||||
}
|
11
logs/conn_test.go
Normal file
@ -0,0 +1,11 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Info("info")
|
||||
}
|
47
logs/console.go
Normal file
@ -0,0 +1,47 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ConsoleWriter struct {
|
||||
lg *log.Logger
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
func NewConsole() LoggerInterface {
|
||||
cw := new(ConsoleWriter)
|
||||
cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||
cw.Level = LevelTrace
|
||||
return cw
|
||||
}
|
||||
|
||||
func (c *ConsoleWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
|
||||
if level < c.Level {
|
||||
return nil
|
||||
}
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConsoleWriter) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
func (c *ConsoleWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
}
|
30
logs/console_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsole(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
log2 := NewLogger(100)
|
||||
log2.SetLogger("console", `{"level":1}`)
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
}
|
||||
|
||||
func BenchmarkConsole(b *testing.B) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Trace("trace")
|
||||
}
|
||||
}
|
224
logs/file.go
Normal file
@ -0,0 +1,224 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileLogWriter struct {
|
||||
*log.Logger
|
||||
mw *MuxWriter
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
|
||||
Maxlines int `json:"maxlines"`
|
||||
maxlines_curlines int
|
||||
|
||||
// Rotate at size
|
||||
Maxsize int `json:"maxsize"`
|
||||
maxsize_cursize int
|
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"`
|
||||
Maxdays int64 `json:"maxdays`
|
||||
daily_opendate int
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
startLock sync.Mutex // Only one log can write to the file
|
||||
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
type MuxWriter struct {
|
||||
sync.Mutex
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
func (l *MuxWriter) Write(b []byte) (int, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.fd.Write(b)
|
||||
}
|
||||
|
||||
func (l *MuxWriter) SetFd(fd *os.File) {
|
||||
if l.fd != nil {
|
||||
l.fd.Close()
|
||||
}
|
||||
l.fd = fd
|
||||
}
|
||||
|
||||
func NewFileWriter() LoggerInterface {
|
||||
w := &FileLogWriter{
|
||||
Filename: "",
|
||||
Maxlines: 1000000,
|
||||
Maxsize: 1 << 28, //256 MB
|
||||
Daily: true,
|
||||
Maxdays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
}
|
||||
// use MuxWriter instead direct use os.File for lock write when rotate
|
||||
w.mw = new(MuxWriter)
|
||||
// set MuxWriter as Logger's io.Writer
|
||||
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
|
||||
return w
|
||||
}
|
||||
|
||||
// jsonconfig like this
|
||||
//{
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxlines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "daily":true,
|
||||
// "maxdays":15,
|
||||
// "rotate":true
|
||||
//}
|
||||
func (w *FileLogWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
err = w.StartLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) StartLogger() error {
|
||||
fd, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mw.SetFd(fd)
|
||||
err = w.initFd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) docheck(size int) {
|
||||
w.startLock.Lock()
|
||||
defer w.startLock.Unlock()
|
||||
if (w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
|
||||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
|
||||
(w.Daily && time.Now().Day() != w.daily_opendate) {
|
||||
if err := w.DoRotate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.maxlines_curlines++
|
||||
w.maxsize_cursize += size
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) WriteMsg(msg string, level int) error {
|
||||
if level < w.Level {
|
||||
return nil
|
||||
}
|
||||
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
|
||||
w.docheck(n)
|
||||
w.Logger.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) initFd() error {
|
||||
fd := w.mw.fd
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
if finfo.Size() > 0 {
|
||||
content, err := ioutil.ReadFile(w.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
|
||||
} else {
|
||||
w.maxlines_curlines = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) DoRotate() error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fname := ""
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
|
||||
_, err = os.Lstat(fname)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
|
||||
}
|
||||
|
||||
// block Logger's io.Writer
|
||||
w.mw.Lock()
|
||||
defer w.mw.Unlock()
|
||||
|
||||
fd := w.mw.fd
|
||||
fd.Close()
|
||||
|
||||
// close fd before rename
|
||||
// Rename the file to its newfound home
|
||||
err = os.Rename(w.Filename, fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", err)
|
||||
}
|
||||
|
||||
// re-start logger
|
||||
err = w.StartLogger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s\n", err)
|
||||
}
|
||||
|
||||
go w.deleteOldLog()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) Destroy() {
|
||||
w.mw.fd.Close()
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) Flush() {
|
||||
w.mw.fd.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", NewFileWriter)
|
||||
}
|
110
logs/file_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
log.Trace("test")
|
||||
log.Info("info")
|
||||
log.Debug("debug")
|
||||
log.Warn("warning")
|
||||
log.Error("error")
|
||||
log.Critical("critical")
|
||||
time.Sleep(time.Second * 4)
|
||||
f, err := os.Open("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
linenum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
linenum++
|
||||
}
|
||||
}
|
||||
if linenum != 6 {
|
||||
t.Fatal(linenum, "not line 6")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFile2(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test2.log","level":2}`)
|
||||
log.Trace("test")
|
||||
log.Info("info")
|
||||
log.Debug("debug")
|
||||
log.Warn("warning")
|
||||
log.Error("error")
|
||||
log.Critical("critical")
|
||||
time.Sleep(time.Second * 4)
|
||||
f, err := os.Open("test2.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
linenum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
linenum++
|
||||
}
|
||||
}
|
||||
if linenum != 4 {
|
||||
t.Fatal(linenum, "not line 4")
|
||||
}
|
||||
os.Remove("test2.log")
|
||||
}
|
||||
|
||||
func TestFileRotate(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Trace("test")
|
||||
log.Info("info")
|
||||
log.Debug("debug")
|
||||
log.Warn("warning")
|
||||
log.Error("error")
|
||||
log.Critical("critical")
|
||||
time.Sleep(time.Second * 4)
|
||||
rotatename := "test3.log" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)
|
||||
b, err := exists(rotatename)
|
||||
if !b || err != nil {
|
||||
t.Fatal("rotate not gen")
|
||||
}
|
||||
os.Remove(rotatename)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func BenchmarkFile(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Trace("trace")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
166
logs/log.go
Normal file
@ -0,0 +1,166 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
LevelTrace = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
type loggerType func() LoggerInterface
|
||||
|
||||
type LoggerInterface interface {
|
||||
Init(config string) error
|
||||
WriteMsg(msg string, level int) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
|
||||
var adapters = make(map[string]loggerType)
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, log loggerType) {
|
||||
if log == nil {
|
||||
panic("logs: Register provide is nil")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic("logs: Register called twice for provider " + name)
|
||||
}
|
||||
adapters[name] = log
|
||||
}
|
||||
|
||||
type BeeLogger struct {
|
||||
lock sync.Mutex
|
||||
level int
|
||||
msg chan *logMsg
|
||||
outputs map[string]LoggerInterface
|
||||
}
|
||||
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
}
|
||||
|
||||
// config need to be correct JSON as string: {"interval":360}
|
||||
func NewLogger(channellen int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.msg = make(chan *logMsg, channellen)
|
||||
bl.outputs = make(map[string]LoggerInterface)
|
||||
//bl.SetLogger("console", "") // default output to console
|
||||
go bl.StartLogger()
|
||||
return bl
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if log, ok := adapters[adaptername]; ok {
|
||||
lg := log()
|
||||
lg.Init(config)
|
||||
bl.outputs[adaptername] = lg
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) DelLogger(adaptername string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if lg, ok := bl.outputs[adaptername]; ok {
|
||||
lg.Destroy()
|
||||
delete(bl.outputs, adaptername)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
||||
if bl.level > loglevel {
|
||||
return nil
|
||||
}
|
||||
lm := new(logMsg)
|
||||
lm.level = loglevel
|
||||
lm.msg = msg
|
||||
bl.msg <- lm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) SetLevel(l int) {
|
||||
bl.level = l
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) StartLogger() {
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msg:
|
||||
for _, l := range bl.outputs {
|
||||
l.WriteMsg(bm.msg, bm.level)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[T] "+format, v...)
|
||||
bl.writerMsg(LevelTrace, msg)
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writerMsg(LevelDebug, msg)
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writerMsg(LevelInfo, msg)
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writerMsg(LevelWarn, msg)
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[E] "+format, v...)
|
||||
bl.writerMsg(LevelError, msg)
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("[C] "+format, v...)
|
||||
bl.writerMsg(LevelCritical, msg)
|
||||
}
|
||||
|
||||
//flush all chan data
|
||||
func (bl *BeeLogger) Flush() {
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Close() {
|
||||
for {
|
||||
if len(bl.msg) > 0 {
|
||||
bm := <-bl.msg
|
||||
for _, l := range bl.outputs {
|
||||
l.WriteMsg(bm.msg, bm.level)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
l.Destroy()
|
||||
}
|
||||
}
|
77
logs/smtp.go
Normal file
@ -0,0 +1,77 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
subjectPhrase = "Diagnostic message from server"
|
||||
)
|
||||
|
||||
// smtpWriter is used to send emails via given SMTP-server.
|
||||
type SmtpWriter struct {
|
||||
Username string `json:"Username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"Host"`
|
||||
Subject string `json:"subject"`
|
||||
RecipientAddresses []string `json:"sendTos"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
func NewSmtpWriter() LoggerInterface {
|
||||
return &SmtpWriter{Level: LevelTrace}
|
||||
}
|
||||
|
||||
func (s *SmtpWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmtpWriter) WriteMsg(msg string, level int) error {
|
||||
if level < s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
hp := strings.Split(s.Host, ":")
|
||||
|
||||
// Set up authentication information.
|
||||
auth := smtp.PlainAuth(
|
||||
"",
|
||||
s.Username,
|
||||
s.Password,
|
||||
hp[0],
|
||||
)
|
||||
// Connect to the server, authenticate, set the sender and recipient,
|
||||
// and send the email all in one step.
|
||||
content_type := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.Username + "<" + s.Username +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
err := smtp.SendMail(
|
||||
s.Host,
|
||||
auth,
|
||||
s.Username,
|
||||
s.RecipientAddresses,
|
||||
mailmsg,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SmtpWriter) Flush() {
|
||||
return
|
||||
}
|
||||
func (s *SmtpWriter) Destroy() {
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("smtp", NewSmtpWriter)
|
||||
}
|
13
logs/smtp_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSmtp(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
||||
}
|
222
memzipfile.go
Normal file
@ -0,0 +1,222 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
//"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo)
|
||||
|
||||
// OpenMemZipFile returns MemFile object with a compressed static file.
|
||||
// it's used for serve static file if gzip enable.
|
||||
func OpenMemZipFile(path string, zip string) (*MemFile, error) {
|
||||
osfile, e := os.Open(path)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
defer osfile.Close()
|
||||
|
||||
osfileinfo, e := osfile.Stat()
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
modtime := osfileinfo.ModTime()
|
||||
fileSize := osfileinfo.Size()
|
||||
|
||||
cfi, ok := gmfim[zip+":"+path]
|
||||
if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize {
|
||||
//fmt.Printf("read %s file %s from cache\n", zip, path)
|
||||
} else {
|
||||
//fmt.Printf("NOT read %s file %s from cache\n", zip, path)
|
||||
var content []byte
|
||||
if zip == "gzip" {
|
||||
//将文件内容压缩到zipbuf中
|
||||
var zipbuf bytes.Buffer
|
||||
gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
_, e = io.Copy(gzipwriter, osfile)
|
||||
gzipwriter.Close()
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
//读zipbuf到content
|
||||
content, e = ioutil.ReadAll(&zipbuf)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
} else if zip == "deflate" {
|
||||
//将文件内容压缩到zipbuf中
|
||||
var zipbuf bytes.Buffer
|
||||
deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
_, e = io.Copy(deflatewriter, osfile)
|
||||
deflatewriter.Close()
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
//将zipbuf读入到content
|
||||
content, e = ioutil.ReadAll(&zipbuf)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
} else {
|
||||
content, e = ioutil.ReadAll(osfile)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
|
||||
cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
|
||||
gmfim[zip+":"+path] = cfi
|
||||
//fmt.Printf("%s file %s to %d, cache it\n", zip, path, len(content))
|
||||
}
|
||||
return &MemFile{fi: cfi, offset: 0}, nil
|
||||
}
|
||||
|
||||
// MemFileInfo contains a compressed file bytes and file information.
|
||||
// it implements os.FileInfo interface.
|
||||
type MemFileInfo struct {
|
||||
os.FileInfo
|
||||
modTime time.Time
|
||||
content []byte
|
||||
contentSize int64
|
||||
fileSize int64
|
||||
}
|
||||
|
||||
// Name returns the compressed filename.
|
||||
func (fi *MemFileInfo) Name() string {
|
||||
return fi.Name()
|
||||
}
|
||||
|
||||
// Size returns the raw file content size, not compressed size.
|
||||
func (fi *MemFileInfo) Size() int64 {
|
||||
return fi.contentSize
|
||||
}
|
||||
|
||||
// Mode returns file mode.
|
||||
func (fi *MemFileInfo) Mode() os.FileMode {
|
||||
return fi.Mode()
|
||||
}
|
||||
|
||||
// ModTime returns the last modified time of raw file.
|
||||
func (fi *MemFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
|
||||
// IsDir returns the compressing file is a directory or not.
|
||||
func (fi *MemFileInfo) IsDir() bool {
|
||||
return fi.IsDir()
|
||||
}
|
||||
|
||||
// return nil. implement the os.FileInfo interface method.
|
||||
func (fi *MemFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemFile contains MemFileInfo and bytes offset when reading.
|
||||
// it implements io.Reader,io.ReadCloser and io.Seeker.
|
||||
type MemFile struct {
|
||||
fi *MemFileInfo
|
||||
offset int64
|
||||
}
|
||||
|
||||
// Close memfile.
|
||||
func (f *MemFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get os.FileInfo of memfile.
|
||||
func (f *MemFile) Stat() (os.FileInfo, error) {
|
||||
return f.fi, nil
|
||||
}
|
||||
|
||||
// read os.FileInfo of files in directory of memfile.
|
||||
// it returns empty slice.
|
||||
func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
infos := []os.FileInfo{}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// Read bytes from the compressed file bytes.
|
||||
func (f *MemFile) Read(p []byte) (n int, err error) {
|
||||
if len(f.fi.content)-int(f.offset) >= len(p) {
|
||||
n = len(p)
|
||||
} else {
|
||||
n = len(f.fi.content) - int(f.offset)
|
||||
err = io.EOF
|
||||
}
|
||||
copy(p, f.fi.content[f.offset:f.offset+int64(n)])
|
||||
f.offset += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
var errWhence = errors.New("Seek: invalid whence")
|
||||
var errOffset = errors.New("Seek: invalid offset")
|
||||
|
||||
// Read bytes from the compressed file bytes by seeker.
|
||||
func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
switch whence {
|
||||
default:
|
||||
return 0, errWhence
|
||||
case os.SEEK_SET:
|
||||
case os.SEEK_CUR:
|
||||
offset += f.offset
|
||||
case os.SEEK_END:
|
||||
offset += int64(len(f.fi.content))
|
||||
}
|
||||
if offset < 0 || int(offset) > len(f.fi.content) {
|
||||
return 0, errOffset
|
||||
}
|
||||
f.offset = offset
|
||||
return f.offset, nil
|
||||
}
|
||||
|
||||
// GetAcceptEncodingZip returns accept encoding format in http header.
|
||||
// zip is first, then deflate if both accepted.
|
||||
// If no accepted, return empty string.
|
||||
func GetAcceptEncodingZip(r *http.Request) string {
|
||||
ss := r.Header.Get("Accept-Encoding")
|
||||
ss = strings.ToLower(ss)
|
||||
if strings.Contains(ss, "gzip") {
|
||||
return "gzip"
|
||||
} else if strings.Contains(ss, "deflate") {
|
||||
return "deflate"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CloseZWriter closes the io.Writer after compressing static file.
|
||||
func CloseZWriter(zwriter io.Writer) {
|
||||
if zwriter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch zwriter.(type) {
|
||||
case *gzip.Writer:
|
||||
zwriter.(*gzip.Writer).Close()
|
||||
case *flate.Writer:
|
||||
zwriter.(*flate.Writer).Close()
|
||||
//其他情况不close, 保持和默认(非压缩)行为一致
|
||||
/*
|
||||
case io.WriteCloser:
|
||||
zwriter.(io.WriteCloser).Close()
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
package beego
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
AppName string
|
||||
VERSION string
|
||||
)
|
||||
var tpl = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -189,6 +194,7 @@ func NotFound(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>You like 404 pages" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
//rw.WriteHeader(http.StatusNotFound)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -204,6 +210,7 @@ func Unauthorized(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>Check the address for errors" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
//rw.WriteHeader(http.StatusUnauthorized)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -220,6 +227,7 @@ func Forbidden(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>You need to log in" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
//rw.WriteHeader(http.StatusForbidden)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -235,6 +243,7 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>Please try again later." +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
//rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -249,10 +258,19 @@ func InternalServerError(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>you should report the fault to the website administrator" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
//rw.WriteHeader(http.StatusInternalServerError)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
func registerErrorHander() {
|
||||
func SimpleServerError(rw http.ResponseWriter, r *http.Request) {
|
||||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func Errorhandler(err string, h http.HandlerFunc) {
|
||||
ErrorMaps[err] = h
|
||||
}
|
||||
|
||||
func RegisterErrorHander() {
|
||||
if _, ok := ErrorMaps["404"]; !ok {
|
||||
ErrorMaps["404"] = NotFound
|
||||
}
|
||||
@ -273,3 +291,27 @@ func registerErrorHander() {
|
||||
ErrorMaps["500"] = InternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
func Exception(errcode string, w http.ResponseWriter, r *http.Request, msg string) {
|
||||
if h, ok := ErrorMaps[errcode]; ok {
|
||||
isint, err := strconv.Atoi(errcode)
|
||||
if err != nil {
|
||||
isint = 500
|
||||
}
|
||||
w.WriteHeader(isint)
|
||||
h(w, r)
|
||||
return
|
||||
} else {
|
||||
isint, err := strconv.Atoi(errcode)
|
||||
if err != nil {
|
||||
isint = 500
|
||||
}
|
||||
if isint == 400 {
|
||||
msg = "404 page not found"
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(isint)
|
||||
fmt.Fprintln(w, msg)
|
||||
return
|
||||
}
|
||||
}
|
32
middleware/exceptions.go
Normal file
@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import "fmt"
|
||||
|
||||
type HTTPException struct {
|
||||
StatusCode int // http status code 4xx, 5xx
|
||||
Description string
|
||||
}
|
||||
|
||||
func (e *HTTPException) Error() string {
|
||||
// return `status description`, e.g. `400 Bad Request`
|
||||
return fmt.Sprintf("%d %s", e.StatusCode, e.Description)
|
||||
}
|
||||
|
||||
var HTTPExceptionMaps map[int]HTTPException
|
||||
|
||||
func init() {
|
||||
HTTPExceptionMaps = make(map[int]HTTPException)
|
||||
|
||||
// Normal 4XX HTTP Status
|
||||
HTTPExceptionMaps[400] = HTTPException{400, "Bad Request"}
|
||||
HTTPExceptionMaps[401] = HTTPException{401, "Unauthorized"}
|
||||
HTTPExceptionMaps[403] = HTTPException{403, "Forbidden"}
|
||||
HTTPExceptionMaps[404] = HTTPException{404, "Not Found"}
|
||||
HTTPExceptionMaps[405] = HTTPException{405, "Method Not Allowed"}
|
||||
|
||||
// Normal 5XX HTTP Status
|
||||
HTTPExceptionMaps[500] = HTTPException{500, "Internal Server Error"}
|
||||
HTTPExceptionMaps[502] = HTTPException{502, "Bad Gateway"}
|
||||
HTTPExceptionMaps[503] = HTTPException{503, "Service Unavailable"}
|
||||
HTTPExceptionMaps[504] = HTTPException{504, "Gateway Timeout"}
|
||||
}
|
84
middleware/i18n.go
Normal file
@ -0,0 +1,84 @@
|
||||
package middleware
|
||||
|
||||
//import (
|
||||
// "github.com/astaxie/beego/config"
|
||||
// "os"
|
||||
// "path"
|
||||
//)
|
||||
|
||||
//type Translation struct {
|
||||
// filetype string
|
||||
// CurrentLocal string
|
||||
// Locales map[string]map[string]string
|
||||
//}
|
||||
|
||||
//func NewLocale(filetype string) *Translation {
|
||||
// return &Translation{
|
||||
// filetype: filetype,
|
||||
// CurrentLocal: "zh",
|
||||
// Locales: make(map[string]map[string]string),
|
||||
// }
|
||||
//}
|
||||
|
||||
//func (t *Translation) loadTranslations(dirPath string) error {
|
||||
// dir, err := os.Open(dirPath)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer dir.Close()
|
||||
|
||||
// names, err := dir.Readdirnames(-1)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// for _, name := range names {
|
||||
// fullPath := path.Join(dirPath, name)
|
||||
|
||||
// fi, err := os.Stat(fullPath)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if fi.IsDir() {
|
||||
// continue
|
||||
// } else {
|
||||
// if err := t.loadTranslation(fullPath, name); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return nil
|
||||
//}
|
||||
|
||||
//func (t *Translation) loadTranslation(fullPath, locale string) error {
|
||||
|
||||
// sourceKey2Trans, ok := t.Locales[locale]
|
||||
// if !ok {
|
||||
// sourceKey2Trans = make(map[string]string)
|
||||
|
||||
// t.Locales[locale] = sourceKey2Trans
|
||||
// }
|
||||
|
||||
// for _, m := range trf.Messages {
|
||||
// if m.Translation != "" {
|
||||
// sourceKey2Trans[sourceKey(m.Source, m.Context)] = m.Translation
|
||||
// }
|
||||
// }
|
||||
|
||||
// return nil
|
||||
//}
|
||||
|
||||
//func (t *Translation) SetLocale(local string) {
|
||||
// t.CurrentLocal = local
|
||||
//}
|
||||
|
||||
//func (t *Translation) Translate(key string) string {
|
||||
// if ct, ok := t.Locales[t.CurrentLocal]; ok {
|
||||
// if v, o := ct[key]; o {
|
||||
// return v
|
||||
// }
|
||||
// }
|
||||
// return key
|
||||
//}
|
551
mime.go
Normal file
@ -0,0 +1,551 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"mime"
|
||||
)
|
||||
|
||||
var mimemaps map[string]string = map[string]string{
|
||||
".3dm": "x-world/x-3dmf",
|
||||
".3dmf": "x-world/x-3dmf",
|
||||
".7z": "application/x-7z-compressed",
|
||||
".a": "application/octet-stream",
|
||||
".aab": "application/x-authorware-bin",
|
||||
".aam": "application/x-authorware-map",
|
||||
".aas": "application/x-authorware-seg",
|
||||
".abc": "text/vndabc",
|
||||
".ace": "application/x-ace-compressed",
|
||||
".acgi": "text/html",
|
||||
".afl": "video/animaflex",
|
||||
".ai": "application/postscript",
|
||||
".aif": "audio/aiff",
|
||||
".aifc": "audio/aiff",
|
||||
".aiff": "audio/aiff",
|
||||
".aim": "application/x-aim",
|
||||
".aip": "text/x-audiosoft-intra",
|
||||
".alz": "application/x-alz-compressed",
|
||||
".ani": "application/x-navi-animation",
|
||||
".aos": "application/x-nokia-9000-communicator-add-on-software",
|
||||
".aps": "application/mime",
|
||||
".arc": "application/x-arc-compressed",
|
||||
".arj": "application/arj",
|
||||
".art": "image/x-jg",
|
||||
".asf": "video/x-ms-asf",
|
||||
".asm": "text/x-asm",
|
||||
".asp": "text/asp",
|
||||
".asx": "application/x-mplayer2",
|
||||
".au": "audio/basic",
|
||||
".avi": "video/x-msvideo",
|
||||
".avs": "video/avs-video",
|
||||
".bcpio": "application/x-bcpio",
|
||||
".bin": "application/mac-binary",
|
||||
".bmp": "image/bmp",
|
||||
".boo": "application/book",
|
||||
".book": "application/book",
|
||||
".boz": "application/x-bzip2",
|
||||
".bsh": "application/x-bsh",
|
||||
".bz2": "application/x-bzip2",
|
||||
".bz": "application/x-bzip",
|
||||
".c++": "text/plain",
|
||||
".c": "text/x-c",
|
||||
".cab": "application/vnd.ms-cab-compressed",
|
||||
".cat": "application/vndms-pkiseccat",
|
||||
".cc": "text/x-c",
|
||||
".ccad": "application/clariscad",
|
||||
".cco": "application/x-cocoa",
|
||||
".cdf": "application/cdf",
|
||||
".cer": "application/pkix-cert",
|
||||
".cha": "application/x-chat",
|
||||
".chat": "application/x-chat",
|
||||
".chrt": "application/vnd.kde.kchart",
|
||||
".class": "application/java",
|
||||
".com": "text/plain",
|
||||
".conf": "text/plain",
|
||||
".cpio": "application/x-cpio",
|
||||
".cpp": "text/x-c",
|
||||
".cpt": "application/mac-compactpro",
|
||||
".crl": "application/pkcs-crl",
|
||||
".crt": "application/pkix-cert",
|
||||
".crx": "application/x-chrome-extension",
|
||||
".csh": "text/x-scriptcsh",
|
||||
".css": "text/css",
|
||||
".csv": "text/csv",
|
||||
".cxx": "text/plain",
|
||||
".dar": "application/x-dar",
|
||||
".dcr": "application/x-director",
|
||||
".deb": "application/x-debian-package",
|
||||
".deepv": "application/x-deepv",
|
||||
".def": "text/plain",
|
||||
".der": "application/x-x509-ca-cert",
|
||||
".dif": "video/x-dv",
|
||||
".dir": "application/x-director",
|
||||
".divx": "video/divx",
|
||||
".dl": "video/dl",
|
||||
".dmg": "application/x-apple-diskimage",
|
||||
".doc": "application/msword",
|
||||
".dot": "application/msword",
|
||||
".dp": "application/commonground",
|
||||
".drw": "application/drafting",
|
||||
".dump": "application/octet-stream",
|
||||
".dv": "video/x-dv",
|
||||
".dvi": "application/x-dvi",
|
||||
".dwf": "drawing/x-dwf=(old)",
|
||||
".dwg": "application/acad",
|
||||
".dxf": "application/dxf",
|
||||
".dxr": "application/x-director",
|
||||
".el": "text/x-scriptelisp",
|
||||
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
|
||||
".eml": "message/rfc822",
|
||||
".env": "application/x-envoy",
|
||||
".eps": "application/postscript",
|
||||
".es": "application/x-esrehber",
|
||||
".etx": "text/x-setext",
|
||||
".evy": "application/envoy",
|
||||
".exe": "application/octet-stream",
|
||||
".f77": "text/x-fortran",
|
||||
".f90": "text/x-fortran",
|
||||
".f": "text/x-fortran",
|
||||
".fdf": "application/vndfdf",
|
||||
".fif": "application/fractals",
|
||||
".fli": "video/fli",
|
||||
".flo": "image/florian",
|
||||
".flv": "video/x-flv",
|
||||
".flx": "text/vndfmiflexstor",
|
||||
".fmf": "video/x-atomic3d-feature",
|
||||
".for": "text/x-fortran",
|
||||
".fpx": "image/vndfpx",
|
||||
".frl": "application/freeloader",
|
||||
".funk": "audio/make",
|
||||
".g3": "image/g3fax",
|
||||
".g": "text/plain",
|
||||
".gif": "image/gif",
|
||||
".gl": "video/gl",
|
||||
".gsd": "audio/x-gsm",
|
||||
".gsm": "audio/x-gsm",
|
||||
".gsp": "application/x-gsp",
|
||||
".gss": "application/x-gss",
|
||||
".gtar": "application/x-gtar",
|
||||
".gz": "application/x-compressed",
|
||||
".gzip": "application/x-gzip",
|
||||
".h": "text/x-h",
|
||||
".hdf": "application/x-hdf",
|
||||
".help": "application/x-helpfile",
|
||||
".hgl": "application/vndhp-hpgl",
|
||||
".hh": "text/x-h",
|
||||
".hlb": "text/x-script",
|
||||
".hlp": "application/hlp",
|
||||
".hpg": "application/vndhp-hpgl",
|
||||
".hpgl": "application/vndhp-hpgl",
|
||||
".hqx": "application/binhex",
|
||||
".hta": "application/hta",
|
||||
".htc": "text/x-component",
|
||||
".htm": "text/html",
|
||||
".html": "text/html",
|
||||
".htmls": "text/html",
|
||||
".htt": "text/webviewhtml",
|
||||
".htx": "text/html",
|
||||
".ice": "x-conference/x-cooltalk",
|
||||
".ico": "image/x-icon",
|
||||
".ics": "text/calendar",
|
||||
".icz": "text/calendar",
|
||||
".idc": "text/plain",
|
||||
".ief": "image/ief",
|
||||
".iefs": "image/ief",
|
||||
".iges": "application/iges",
|
||||
".igs": "application/iges",
|
||||
".ima": "application/x-ima",
|
||||
".imap": "application/x-httpd-imap",
|
||||
".inf": "application/inf",
|
||||
".ins": "application/x-internett-signup",
|
||||
".ip": "application/x-ip2",
|
||||
".isu": "video/x-isvideo",
|
||||
".it": "audio/it",
|
||||
".iv": "application/x-inventor",
|
||||
".ivr": "i-world/i-vrml",
|
||||
".ivy": "application/x-livescreen",
|
||||
".jam": "audio/x-jam",
|
||||
".jav": "text/x-java-source",
|
||||
".java": "text/x-java-source",
|
||||
".jcm": "application/x-java-commerce",
|
||||
".jfif-tbnl": "image/jpeg",
|
||||
".jfif": "image/jpeg",
|
||||
".jnlp": "application/x-java-jnlp-file",
|
||||
".jpe": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".jps": "image/x-jps",
|
||||
".js": "application/javascript",
|
||||
".json": "application/json",
|
||||
".jut": "image/jutvision",
|
||||
".kar": "audio/midi",
|
||||
".karbon": "application/vnd.kde.karbon",
|
||||
".kfo": "application/vnd.kde.kformula",
|
||||
".flw": "application/vnd.kde.kivio",
|
||||
".kml": "application/vnd.google-earth.kml+xml",
|
||||
".kmz": "application/vnd.google-earth.kmz",
|
||||
".kon": "application/vnd.kde.kontour",
|
||||
".kpr": "application/vnd.kde.kpresenter",
|
||||
".kpt": "application/vnd.kde.kpresenter",
|
||||
".ksp": "application/vnd.kde.kspread",
|
||||
".kwd": "application/vnd.kde.kword",
|
||||
".kwt": "application/vnd.kde.kword",
|
||||
".ksh": "text/x-scriptksh",
|
||||
".la": "audio/nspaudio",
|
||||
".lam": "audio/x-liveaudio",
|
||||
".latex": "application/x-latex",
|
||||
".lha": "application/lha",
|
||||
".lhx": "application/octet-stream",
|
||||
".list": "text/plain",
|
||||
".lma": "audio/nspaudio",
|
||||
".log": "text/plain",
|
||||
".lsp": "text/x-scriptlisp",
|
||||
".lst": "text/plain",
|
||||
".lsx": "text/x-la-asf",
|
||||
".ltx": "application/x-latex",
|
||||
".lzh": "application/octet-stream",
|
||||
".lzx": "application/lzx",
|
||||
".m1v": "video/mpeg",
|
||||
".m2a": "audio/mpeg",
|
||||
".m2v": "video/mpeg",
|
||||
".m3u": "audio/x-mpegurl",
|
||||
".m": "text/x-m",
|
||||
".man": "application/x-troff-man",
|
||||
".manifest": "text/cache-manifest",
|
||||
".map": "application/x-navimap",
|
||||
".mar": "text/plain",
|
||||
".mbd": "application/mbedlet",
|
||||
".mc$": "application/x-magic-cap-package-10",
|
||||
".mcd": "application/mcad",
|
||||
".mcf": "text/mcf",
|
||||
".mcp": "application/netmc",
|
||||
".me": "application/x-troff-me",
|
||||
".mht": "message/rfc822",
|
||||
".mhtml": "message/rfc822",
|
||||
".mid": "application/x-midi",
|
||||
".midi": "application/x-midi",
|
||||
".mif": "application/x-frame",
|
||||
".mime": "message/rfc822",
|
||||
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
|
||||
".mjpg": "video/x-motion-jpeg",
|
||||
".mm": "application/base64",
|
||||
".mme": "application/base64",
|
||||
".mod": "audio/mod",
|
||||
".moov": "video/quicktime",
|
||||
".mov": "video/quicktime",
|
||||
".movie": "video/x-sgi-movie",
|
||||
".mp2": "audio/mpeg",
|
||||
".mp3": "audio/mpeg3",
|
||||
".mp4": "video/mp4",
|
||||
".mpa": "audio/mpeg",
|
||||
".mpc": "application/x-project",
|
||||
".mpe": "video/mpeg",
|
||||
".mpeg": "video/mpeg",
|
||||
".mpg": "video/mpeg",
|
||||
".mpga": "audio/mpeg",
|
||||
".mpp": "application/vndms-project",
|
||||
".mpt": "application/x-project",
|
||||
".mpv": "application/x-project",
|
||||
".mpx": "application/x-project",
|
||||
".mrc": "application/marc",
|
||||
".ms": "application/x-troff-ms",
|
||||
".mv": "video/x-sgi-movie",
|
||||
".my": "audio/make",
|
||||
".mzz": "application/x-vndaudioexplosionmzz",
|
||||
".nap": "image/naplps",
|
||||
".naplps": "image/naplps",
|
||||
".nc": "application/x-netcdf",
|
||||
".ncm": "application/vndnokiaconfiguration-message",
|
||||
".nif": "image/x-niff",
|
||||
".niff": "image/x-niff",
|
||||
".nix": "application/x-mix-transfer",
|
||||
".nsc": "application/x-conference",
|
||||
".nvd": "application/x-navidoc",
|
||||
".o": "application/octet-stream",
|
||||
".oda": "application/oda",
|
||||
".odb": "application/vnd.oasis.opendocument.database",
|
||||
".odc": "application/vnd.oasis.opendocument.chart",
|
||||
".odf": "application/vnd.oasis.opendocument.formula",
|
||||
".odg": "application/vnd.oasis.opendocument.graphics",
|
||||
".odi": "application/vnd.oasis.opendocument.image",
|
||||
".odm": "application/vnd.oasis.opendocument.text-master",
|
||||
".odp": "application/vnd.oasis.opendocument.presentation",
|
||||
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
".odt": "application/vnd.oasis.opendocument.text",
|
||||
".oga": "audio/ogg",
|
||||
".ogg": "audio/ogg",
|
||||
".ogv": "video/ogg",
|
||||
".omc": "application/x-omc",
|
||||
".omcd": "application/x-omcdatamaker",
|
||||
".omcr": "application/x-omcregerator",
|
||||
".otc": "application/vnd.oasis.opendocument.chart-template",
|
||||
".otf": "application/vnd.oasis.opendocument.formula-template",
|
||||
".otg": "application/vnd.oasis.opendocument.graphics-template",
|
||||
".oth": "application/vnd.oasis.opendocument.text-web",
|
||||
".oti": "application/vnd.oasis.opendocument.image-template",
|
||||
".otm": "application/vnd.oasis.opendocument.text-master",
|
||||
".otp": "application/vnd.oasis.opendocument.presentation-template",
|
||||
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
|
||||
".ott": "application/vnd.oasis.opendocument.text-template",
|
||||
".p10": "application/pkcs10",
|
||||
".p12": "application/pkcs-12",
|
||||
".p7a": "application/x-pkcs7-signature",
|
||||
".p7c": "application/pkcs7-mime",
|
||||
".p7m": "application/pkcs7-mime",
|
||||
".p7r": "application/x-pkcs7-certreqresp",
|
||||
".p7s": "application/pkcs7-signature",
|
||||
".p": "text/x-pascal",
|
||||
".part": "application/pro_eng",
|
||||
".pas": "text/pascal",
|
||||
".pbm": "image/x-portable-bitmap",
|
||||
".pcl": "application/vndhp-pcl",
|
||||
".pct": "image/x-pict",
|
||||
".pcx": "image/x-pcx",
|
||||
".pdb": "chemical/x-pdb",
|
||||
".pdf": "application/pdf",
|
||||
".pfunk": "audio/make",
|
||||
".pgm": "image/x-portable-graymap",
|
||||
".pic": "image/pict",
|
||||
".pict": "image/pict",
|
||||
".pkg": "application/x-newton-compatible-pkg",
|
||||
".pko": "application/vndms-pkipko",
|
||||
".pl": "text/x-scriptperl",
|
||||
".plx": "application/x-pixclscript",
|
||||
".pm4": "application/x-pagemaker",
|
||||
".pm5": "application/x-pagemaker",
|
||||
".pm": "text/x-scriptperl-module",
|
||||
".png": "image/png",
|
||||
".pnm": "application/x-portable-anymap",
|
||||
".pot": "application/mspowerpoint",
|
||||
".pov": "model/x-pov",
|
||||
".ppa": "application/vndms-powerpoint",
|
||||
".ppm": "image/x-portable-pixmap",
|
||||
".pps": "application/mspowerpoint",
|
||||
".ppt": "application/mspowerpoint",
|
||||
".ppz": "application/mspowerpoint",
|
||||
".pre": "application/x-freelance",
|
||||
".prt": "application/pro_eng",
|
||||
".ps": "application/postscript",
|
||||
".psd": "application/octet-stream",
|
||||
".pvu": "paleovu/x-pv",
|
||||
".pwz": "application/vndms-powerpoint",
|
||||
".py": "text/x-scriptphyton",
|
||||
".pyc": "applicaiton/x-bytecodepython",
|
||||
".qcp": "audio/vndqcelp",
|
||||
".qd3": "x-world/x-3dmf",
|
||||
".qd3d": "x-world/x-3dmf",
|
||||
".qif": "image/x-quicktime",
|
||||
".qt": "video/quicktime",
|
||||
".qtc": "video/x-qtc",
|
||||
".qti": "image/x-quicktime",
|
||||
".qtif": "image/x-quicktime",
|
||||
".ra": "audio/x-pn-realaudio",
|
||||
".ram": "audio/x-pn-realaudio",
|
||||
".rar": "application/x-rar-compressed",
|
||||
".ras": "application/x-cmu-raster",
|
||||
".rast": "image/cmu-raster",
|
||||
".rexx": "text/x-scriptrexx",
|
||||
".rf": "image/vndrn-realflash",
|
||||
".rgb": "image/x-rgb",
|
||||
".rm": "application/vndrn-realmedia",
|
||||
".rmi": "audio/mid",
|
||||
".rmm": "audio/x-pn-realaudio",
|
||||
".rmp": "audio/x-pn-realaudio",
|
||||
".rng": "application/ringing-tones",
|
||||
".rnx": "application/vndrn-realplayer",
|
||||
".roff": "application/x-troff",
|
||||
".rp": "image/vndrn-realpix",
|
||||
".rpm": "audio/x-pn-realaudio-plugin",
|
||||
".rt": "text/vndrn-realtext",
|
||||
".rtf": "text/richtext",
|
||||
".rtx": "text/richtext",
|
||||
".rv": "video/vndrn-realvideo",
|
||||
".s": "text/x-asm",
|
||||
".s3m": "audio/s3m",
|
||||
".s7z": "application/x-7z-compressed",
|
||||
".saveme": "application/octet-stream",
|
||||
".sbk": "application/x-tbook",
|
||||
".scm": "text/x-scriptscheme",
|
||||
".sdml": "text/plain",
|
||||
".sdp": "application/sdp",
|
||||
".sdr": "application/sounder",
|
||||
".sea": "application/sea",
|
||||
".set": "application/set",
|
||||
".sgm": "text/x-sgml",
|
||||
".sgml": "text/x-sgml",
|
||||
".sh": "text/x-scriptsh",
|
||||
".shar": "application/x-bsh",
|
||||
".shtml": "text/x-server-parsed-html",
|
||||
".sid": "audio/x-psid",
|
||||
".skd": "application/x-koan",
|
||||
".skm": "application/x-koan",
|
||||
".skp": "application/x-koan",
|
||||
".skt": "application/x-koan",
|
||||
".sit": "application/x-stuffit",
|
||||
".sitx": "application/x-stuffitx",
|
||||
".sl": "application/x-seelogo",
|
||||
".smi": "application/smil",
|
||||
".smil": "application/smil",
|
||||
".snd": "audio/basic",
|
||||
".sol": "application/solids",
|
||||
".spc": "text/x-speech",
|
||||
".spl": "application/futuresplash",
|
||||
".spr": "application/x-sprite",
|
||||
".sprite": "application/x-sprite",
|
||||
".spx": "audio/ogg",
|
||||
".src": "application/x-wais-source",
|
||||
".ssi": "text/x-server-parsed-html",
|
||||
".ssm": "application/streamingmedia",
|
||||
".sst": "application/vndms-pkicertstore",
|
||||
".step": "application/step",
|
||||
".stl": "application/sla",
|
||||
".stp": "application/step",
|
||||
".sv4cpio": "application/x-sv4cpio",
|
||||
".sv4crc": "application/x-sv4crc",
|
||||
".svf": "image/vnddwg",
|
||||
".svg": "image/svg+xml",
|
||||
".svr": "application/x-world",
|
||||
".swf": "application/x-shockwave-flash",
|
||||
".t": "application/x-troff",
|
||||
".talk": "text/x-speech",
|
||||
".tar": "application/x-tar",
|
||||
".tbk": "application/toolbook",
|
||||
".tcl": "text/x-scripttcl",
|
||||
".tcsh": "text/x-scripttcsh",
|
||||
".tex": "application/x-tex",
|
||||
".texi": "application/x-texinfo",
|
||||
".texinfo": "application/x-texinfo",
|
||||
".text": "text/plain",
|
||||
".tgz": "application/gnutar",
|
||||
".tif": "image/tiff",
|
||||
".tiff": "image/tiff",
|
||||
".tr": "application/x-troff",
|
||||
".tsi": "audio/tsp-audio",
|
||||
".tsp": "application/dsptype",
|
||||
".tsv": "text/tab-separated-values",
|
||||
".turbot": "image/florian",
|
||||
".txt": "text/plain",
|
||||
".uil": "text/x-uil",
|
||||
".uni": "text/uri-list",
|
||||
".unis": "text/uri-list",
|
||||
".unv": "application/i-deas",
|
||||
".uri": "text/uri-list",
|
||||
".uris": "text/uri-list",
|
||||
".ustar": "application/x-ustar",
|
||||
".uu": "text/x-uuencode",
|
||||
".uue": "text/x-uuencode",
|
||||
".vcd": "application/x-cdlink",
|
||||
".vcf": "text/x-vcard",
|
||||
".vcard": "text/x-vcard",
|
||||
".vcs": "text/x-vcalendar",
|
||||
".vda": "application/vda",
|
||||
".vdo": "video/vdo",
|
||||
".vew": "application/groupwise",
|
||||
".viv": "video/vivo",
|
||||
".vivo": "video/vivo",
|
||||
".vmd": "application/vocaltec-media-desc",
|
||||
".vmf": "application/vocaltec-media-file",
|
||||
".voc": "audio/voc",
|
||||
".vos": "video/vosaic",
|
||||
".vox": "audio/voxware",
|
||||
".vqe": "audio/x-twinvq-plugin",
|
||||
".vqf": "audio/x-twinvq",
|
||||
".vql": "audio/x-twinvq-plugin",
|
||||
".vrml": "application/x-vrml",
|
||||
".vrt": "x-world/x-vrt",
|
||||
".vsd": "application/x-visio",
|
||||
".vst": "application/x-visio",
|
||||
".vsw": "application/x-visio",
|
||||
".w60": "application/wordperfect60",
|
||||
".w61": "application/wordperfect61",
|
||||
".w6w": "application/msword",
|
||||
".wav": "audio/wav",
|
||||
".wb1": "application/x-qpro",
|
||||
".wbmp": "image/vnd.wap.wbmp",
|
||||
".web": "application/vndxara",
|
||||
".wiz": "application/msword",
|
||||
".wk1": "application/x-123",
|
||||
".wmf": "windows/metafile",
|
||||
".wml": "text/vnd.wap.wml",
|
||||
".wmlc": "application/vnd.wap.wmlc",
|
||||
".wmls": "text/vnd.wap.wmlscript",
|
||||
".wmlsc": "application/vnd.wap.wmlscriptc",
|
||||
".word": "application/msword",
|
||||
".wp5": "application/wordperfect",
|
||||
".wp6": "application/wordperfect",
|
||||
".wp": "application/wordperfect",
|
||||
".wpd": "application/wordperfect",
|
||||
".wq1": "application/x-lotus",
|
||||
".wri": "application/mswrite",
|
||||
".wrl": "application/x-world",
|
||||
".wrz": "model/vrml",
|
||||
".wsc": "text/scriplet",
|
||||
".wsrc": "application/x-wais-source",
|
||||
".wtk": "application/x-wintalk",
|
||||
".x-png": "image/png",
|
||||
".xbm": "image/x-xbitmap",
|
||||
".xdr": "video/x-amt-demorun",
|
||||
".xgz": "xgl/drawing",
|
||||
".xif": "image/vndxiff",
|
||||
".xl": "application/excel",
|
||||
".xla": "application/excel",
|
||||
".xlb": "application/excel",
|
||||
".xlc": "application/excel",
|
||||
".xld": "application/excel",
|
||||
".xlk": "application/excel",
|
||||
".xll": "application/excel",
|
||||
".xlm": "application/excel",
|
||||
".xls": "application/excel",
|
||||
".xlt": "application/excel",
|
||||
".xlv": "application/excel",
|
||||
".xlw": "application/excel",
|
||||
".xm": "audio/xm",
|
||||
".xml": "text/xml",
|
||||
".xmz": "xgl/movie",
|
||||
".xpix": "application/x-vndls-xpix",
|
||||
".xpm": "image/x-xpixmap",
|
||||
".xsr": "video/x-amt-showrun",
|
||||
".xwd": "image/x-xwd",
|
||||
".xyz": "chemical/x-pdb",
|
||||
".z": "application/x-compress",
|
||||
".zip": "application/zip",
|
||||
".zoo": "application/octet-stream",
|
||||
".zsh": "text/x-scriptzsh",
|
||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".docm": "application/vnd.ms-word.document.macroEnabled.12",
|
||||
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
|
||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
|
||||
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
|
||||
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
|
||||
".thmx": "application/vnd.ms-officetheme",
|
||||
".onetoc": "application/onenote",
|
||||
".onetoc2": "application/onenote",
|
||||
".onetmp": "application/onenote",
|
||||
".onepkg": "application/onenote",
|
||||
".key": "application/x-iwork-keynote-sffkey",
|
||||
".kth": "application/x-iwork-keynote-sffkth",
|
||||
".nmbtemplate": "application/x-iwork-numbers-sfftemplate",
|
||||
".numbers": "application/x-iwork-numbers-sffnumbers",
|
||||
".pages": "application/x-iwork-pages-sffpages",
|
||||
".template": "application/x-iwork-pages-sfftemplate",
|
||||
".xpi": "application/x-xpinstall",
|
||||
".oex": "application/x-opera-extension",
|
||||
".mustache": "text/html",
|
||||
}
|
||||
|
||||
func initMime() {
|
||||
for k, v := range mimemaps {
|
||||
mime.AddExtensionType(k, v)
|
||||
}
|
||||
}
|
36
model.go
@ -1,36 +0,0 @@
|
||||
package beego
|
||||
|
||||
type BeeModel struct {
|
||||
driver string
|
||||
}
|
||||
|
||||
func (this *BeeModel) Insert() {
|
||||
|
||||
}
|
||||
|
||||
func (this *BeeModel) MultipleInsert() {
|
||||
|
||||
}
|
||||
|
||||
func (this *BeeModel) Update() {
|
||||
|
||||
}
|
||||
|
||||
func (this *BeeModel) Query() {
|
||||
|
||||
}
|
||||
|
||||
//Deletes from table with clauses where and using.
|
||||
func (this *BeeModel) Delete() {
|
||||
|
||||
}
|
||||
|
||||
//Start a transaction
|
||||
func (this *BeeModel) Transaction() {
|
||||
|
||||
}
|
||||
|
||||
//commit transaction
|
||||
func (this *BeeModel) Commit() {
|
||||
|
||||
}
|
163
orm/README.md
Normal file
@ -0,0 +1,163 @@
|
||||
# beego orm
|
||||
|
||||
[](https://drone.io/github.com/astaxie/beego/latest)
|
||||
|
||||
A powerful orm framework for go.
|
||||
|
||||
It is heavily influenced by Django ORM, SQLAlchemy.
|
||||
|
||||
now, beta, unstable, may be changing some api make your app build failed.
|
||||
|
||||
**Support Database:**
|
||||
|
||||
* MySQL: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
|
||||
* PostgreSQL: [github.com/lib/pq](https://github.com/lib/pq)
|
||||
* Sqlite3: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
|
||||
|
||||
Passed all test, but need more feedback.
|
||||
|
||||
**Features:**
|
||||
|
||||
* full go type support
|
||||
* easy for usage, simple CRUD operation
|
||||
* auto join with relation table
|
||||
* cross DataBase compatible query
|
||||
* Raw SQL query / mapper without orm model
|
||||
* full test keep stable and strong
|
||||
|
||||
more features please read the docs
|
||||
|
||||
**Install:**
|
||||
|
||||
go get github.com/astaxie/beego/orm
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2013-08-19: support table auto create
|
||||
* 2013-08-13: update test for database types
|
||||
* 2013-08-13: go type support, such as int8, uint8, byte, rune
|
||||
* 2013-08-13: date / datetime timezone support very well
|
||||
|
||||
## Quick Start
|
||||
|
||||
#### Simple Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql" // import your used driver
|
||||
)
|
||||
|
||||
// Model Struct
|
||||
type User struct {
|
||||
Id int `orm:"auto"`
|
||||
Name string `orm:"size(100)"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// register model
|
||||
orm.RegisterModel(new(User))
|
||||
|
||||
// set default database
|
||||
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||
}
|
||||
|
||||
func main() {
|
||||
o := orm.NewOrm()
|
||||
|
||||
user := User{Name: "slene"}
|
||||
|
||||
// insert
|
||||
id, err := o.Insert(&user)
|
||||
|
||||
// update
|
||||
user.Name = "astaxie"
|
||||
num, err := o.Update(&user)
|
||||
|
||||
// read one
|
||||
u := User{Id: user.Id}
|
||||
err = o.Read(&u)
|
||||
|
||||
// delete
|
||||
num, err = o.Delete(&u)
|
||||
}
|
||||
```
|
||||
|
||||
#### Next with relation
|
||||
|
||||
```go
|
||||
type Post struct {
|
||||
Id int `orm:"auto"`
|
||||
Title string `orm:"size(100)"`
|
||||
User *User `orm:"rel(fk)"`
|
||||
}
|
||||
|
||||
var posts []*Post
|
||||
qs := o.QueryTable("post")
|
||||
num, err := qs.Filter("User__Name", "slene").All(&posts)
|
||||
```
|
||||
|
||||
#### Use Raw sql
|
||||
|
||||
If you don't like ORM,use Raw SQL to query / mapping without ORM setting
|
||||
|
||||
```go
|
||||
var maps []Params
|
||||
num, err := o.Raw("SELECT id FROM user WHERE name = ?", "slene").Values(&maps)
|
||||
if num > 0 {
|
||||
fmt.Println(maps[0]["id"])
|
||||
}
|
||||
```
|
||||
|
||||
#### Transaction
|
||||
|
||||
```go
|
||||
o.Begin()
|
||||
...
|
||||
user := User{Name: "slene"}
|
||||
id, err := o.Insert(&user)
|
||||
if err != nil {
|
||||
o.Commit()
|
||||
} else {
|
||||
o.Rollback()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Debug Log Queries
|
||||
|
||||
In development env, you can simple use
|
||||
|
||||
```go
|
||||
func main() {
|
||||
orm.Debug = true
|
||||
...
|
||||
```
|
||||
|
||||
enable log queries.
|
||||
|
||||
output include all queries, such as exec / prepare / transaction.
|
||||
|
||||
like this:
|
||||
|
||||
```go
|
||||
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.4ms] - [INSERT INTO `user` (`name`) VALUES (?)] - `slene`
|
||||
...
|
||||
```
|
||||
|
||||
note: not recommend use this in product env.
|
||||
|
||||
## Docs
|
||||
|
||||
more details and examples in docs and test
|
||||
|
||||
* [中文](http://beego.me/docs/Models_Overview?lang=zh)
|
||||
* [English](http://beego.me/docs/Models_Overview?lang=en)
|
||||
|
||||
## TODO
|
||||
- some unrealized api
|
||||
- examples
|
||||
- docs
|
257
orm/cmd.go
Normal file
@ -0,0 +1,257 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type commander interface {
|
||||
Parse([]string)
|
||||
Run() error
|
||||
}
|
||||
|
||||
var (
|
||||
commands = make(map[string]commander)
|
||||
)
|
||||
|
||||
func printHelp(errs ...string) {
|
||||
content := `orm command usage:
|
||||
|
||||
syncdb - auto create tables
|
||||
sqlall - print sql of create tables
|
||||
help - print this help
|
||||
`
|
||||
|
||||
if len(errs) > 0 {
|
||||
fmt.Println(errs[0])
|
||||
}
|
||||
fmt.Println(content)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func RunCommand() {
|
||||
if len(os.Args) < 2 || os.Args[1] != "orm" {
|
||||
return
|
||||
}
|
||||
|
||||
BootStrap()
|
||||
|
||||
args := argString(os.Args[2:])
|
||||
name := args.Get(0)
|
||||
|
||||
if name == "help" {
|
||||
printHelp()
|
||||
}
|
||||
|
||||
if cmd, ok := commands[name]; ok {
|
||||
cmd.Parse(os.Args[3:])
|
||||
cmd.Run()
|
||||
os.Exit(0)
|
||||
} else {
|
||||
if name == "" {
|
||||
printHelp()
|
||||
} else {
|
||||
printHelp(fmt.Sprintf("unknown command %s", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type commandSyncDb struct {
|
||||
al *alias
|
||||
force bool
|
||||
verbose bool
|
||||
noInfo bool
|
||||
rtOnError bool
|
||||
}
|
||||
|
||||
func (d *commandSyncDb) Parse(args []string) {
|
||||
var name string
|
||||
|
||||
flagSet := flag.NewFlagSet("orm command: syncdb", flag.ExitOnError)
|
||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||
flagSet.BoolVar(&d.force, "force", false, "drop tables before create")
|
||||
flagSet.BoolVar(&d.verbose, "v", false, "verbose info")
|
||||
flagSet.Parse(args)
|
||||
|
||||
d.al = getDbAlias(name)
|
||||
}
|
||||
|
||||
func (d *commandSyncDb) Run() error {
|
||||
var drops []string
|
||||
if d.force {
|
||||
drops = getDbDropSql(d.al)
|
||||
}
|
||||
|
||||
db := d.al.DB
|
||||
|
||||
if d.force {
|
||||
for i, mi := range modelCache.allOrdered() {
|
||||
query := drops[i]
|
||||
if !d.noInfo {
|
||||
fmt.Printf("drop table `%s`\n", mi.table)
|
||||
}
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqls, indexes := getDbCreateSql(d.al)
|
||||
|
||||
tables, err := d.al.DbBaser.GetTables(db)
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
|
||||
for i, mi := range modelCache.allOrdered() {
|
||||
if tables[mi.table] {
|
||||
if !d.noInfo {
|
||||
fmt.Printf("table `%s` already exists, skip\n", mi.table)
|
||||
}
|
||||
|
||||
var fields []*fieldInfo
|
||||
columns, err := d.al.DbBaser.GetColumns(db, mi.table)
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
|
||||
for _, fi := range mi.fields.fieldsDB {
|
||||
if _, ok := columns[fi.column]; ok == false {
|
||||
fields = append(fields, fi)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fi := range fields {
|
||||
query := getColumnAddQuery(d.al, fi)
|
||||
|
||||
if !d.noInfo {
|
||||
fmt.Printf("add column `%s` for table `%s`\n", fi.fullName, mi.table)
|
||||
}
|
||||
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for _, idx := range indexes[mi.table] {
|
||||
if d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) == false {
|
||||
if !d.noInfo {
|
||||
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
||||
}
|
||||
|
||||
query := idx.Sql
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
fmt.Printf(" %s\n", query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !d.noInfo {
|
||||
fmt.Printf("create table `%s` \n", mi.table)
|
||||
}
|
||||
|
||||
queries := []string{sqls[i]}
|
||||
for _, idx := range indexes[mi.table] {
|
||||
queries = append(queries, idx.Sql)
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
_, err := db.Exec(query)
|
||||
if d.verbose {
|
||||
query = " " + strings.Join(strings.Split(query, "\n"), "\n ")
|
||||
fmt.Println(query)
|
||||
}
|
||||
if err != nil {
|
||||
if d.rtOnError {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
if d.verbose {
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type commandSqlAll struct {
|
||||
al *alias
|
||||
}
|
||||
|
||||
func (d *commandSqlAll) Parse(args []string) {
|
||||
var name string
|
||||
|
||||
flagSet := flag.NewFlagSet("orm command: sqlall", flag.ExitOnError)
|
||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||
flagSet.Parse(args)
|
||||
|
||||
d.al = getDbAlias(name)
|
||||
}
|
||||
|
||||
func (d *commandSqlAll) Run() error {
|
||||
sqls, indexes := getDbCreateSql(d.al)
|
||||
var all []string
|
||||
for i, mi := range modelCache.allOrdered() {
|
||||
queries := []string{sqls[i]}
|
||||
for _, idx := range indexes[mi.table] {
|
||||
queries = append(queries, idx.Sql)
|
||||
}
|
||||
sql := strings.Join(queries, "\n")
|
||||
all = append(all, sql)
|
||||
}
|
||||
fmt.Println(strings.Join(all, "\n\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
commands["syncdb"] = new(commandSyncDb)
|
||||
commands["sqlall"] = new(commandSqlAll)
|
||||
}
|
||||
|
||||
func RunSyncdb(name string, force bool, verbose bool) error {
|
||||
BootStrap()
|
||||
|
||||
al := getDbAlias(name)
|
||||
cmd := new(commandSyncDb)
|
||||
cmd.al = al
|
||||
cmd.force = force
|
||||
cmd.noInfo = !verbose
|
||||
cmd.verbose = verbose
|
||||
cmd.rtOnError = true
|
||||
return cmd.Run()
|
||||
}
|
219
orm/cmd_utils.go
Normal file
@ -0,0 +1,219 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dbIndex struct {
|
||||
Table string
|
||||
Name string
|
||||
Sql string
|
||||
}
|
||||
|
||||
func getDbDropSql(al *alias) (sqls []string) {
|
||||
if len(modelCache.cache) == 0 {
|
||||
fmt.Println("no Model found, need register your model")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
Q := al.DbBaser.TableQuote()
|
||||
|
||||
for _, mi := range modelCache.allOrdered() {
|
||||
sqls = append(sqls, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q))
|
||||
}
|
||||
return sqls
|
||||
}
|
||||
|
||||
func getColumnTyp(al *alias, fi *fieldInfo) (col string) {
|
||||
T := al.DbBaser.DbTypes()
|
||||
fieldType := fi.fieldType
|
||||
|
||||
checkColumn:
|
||||
switch fieldType {
|
||||
case TypeBooleanField:
|
||||
col = T["bool"]
|
||||
case TypeCharField:
|
||||
col = fmt.Sprintf(T["string"], fi.size)
|
||||
case TypeTextField:
|
||||
col = T["string-text"]
|
||||
case TypeDateField:
|
||||
col = T["time.Time-date"]
|
||||
case TypeDateTimeField:
|
||||
col = T["time.Time"]
|
||||
case TypeBitField:
|
||||
col = T["int8"]
|
||||
case TypeSmallIntegerField:
|
||||
col = T["int16"]
|
||||
case TypeIntegerField:
|
||||
col = T["int32"]
|
||||
case TypeBigIntegerField:
|
||||
if al.Driver == DR_Sqlite {
|
||||
fieldType = TypeIntegerField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["int64"]
|
||||
case TypePositiveBitField:
|
||||
col = T["uint8"]
|
||||
case TypePositiveSmallIntegerField:
|
||||
col = T["uint16"]
|
||||
case TypePositiveIntegerField:
|
||||
col = T["uint32"]
|
||||
case TypePositiveBigIntegerField:
|
||||
col = T["uint64"]
|
||||
case TypeFloatField:
|
||||
col = T["float64"]
|
||||
case TypeDecimalField:
|
||||
s := T["float64-decimal"]
|
||||
if strings.Index(s, "%d") == -1 {
|
||||
col = s
|
||||
} else {
|
||||
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
||||
}
|
||||
case RelForeignKey, RelOneToOne:
|
||||
fieldType = fi.relModelInfo.fields.pk.fieldType
|
||||
goto checkColumn
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getColumnAddQuery(al *alias, fi *fieldInfo) string {
|
||||
Q := al.DbBaser.TableQuote()
|
||||
typ := getColumnTyp(al, fi)
|
||||
|
||||
if fi.null == false {
|
||||
typ += " " + "NOT NULL"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s", Q, fi.mi.table, Q, Q, fi.column, Q, typ)
|
||||
}
|
||||
|
||||
func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex) {
|
||||
if len(modelCache.cache) == 0 {
|
||||
fmt.Println("no Model found, need register your model")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
Q := al.DbBaser.TableQuote()
|
||||
T := al.DbBaser.DbTypes()
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
|
||||
tableIndexes = make(map[string][]dbIndex)
|
||||
|
||||
for _, mi := range modelCache.allOrdered() {
|
||||
sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||
sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName)
|
||||
sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||
|
||||
sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q)
|
||||
|
||||
columns := make([]string, 0, len(mi.fields.fieldsDB))
|
||||
|
||||
sqlIndexes := [][]string{}
|
||||
|
||||
for _, fi := range mi.fields.fieldsDB {
|
||||
|
||||
column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q)
|
||||
col := getColumnTyp(al, fi)
|
||||
|
||||
if fi.auto {
|
||||
switch al.Driver {
|
||||
case DR_Sqlite, DR_Postgres:
|
||||
column += T["auto"]
|
||||
default:
|
||||
column += col + " " + T["auto"]
|
||||
}
|
||||
} else if fi.pk {
|
||||
column += col + " " + T["pk"]
|
||||
} else {
|
||||
column += col
|
||||
|
||||
if fi.null == false {
|
||||
column += " " + "NOT NULL"
|
||||
}
|
||||
|
||||
if fi.unique {
|
||||
column += " " + "UNIQUE"
|
||||
}
|
||||
|
||||
if fi.index {
|
||||
sqlIndexes = append(sqlIndexes, []string{fi.column})
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Index(column, "%COL%") != -1 {
|
||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||
}
|
||||
|
||||
columns = append(columns, column)
|
||||
}
|
||||
|
||||
if mi.model != nil {
|
||||
allnames := getTableUnique(mi.addrField)
|
||||
if !mi.manual && len(mi.uniques) > 0 {
|
||||
allnames = append(allnames, mi.uniques)
|
||||
}
|
||||
for _, names := range allnames {
|
||||
cols := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||
cols = append(cols, fi.column)
|
||||
} else {
|
||||
panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName))
|
||||
}
|
||||
}
|
||||
column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q)
|
||||
columns = append(columns, column)
|
||||
}
|
||||
}
|
||||
|
||||
sql += strings.Join(columns, ",\n")
|
||||
sql += "\n)"
|
||||
|
||||
if al.Driver == DR_MySQL {
|
||||
var engine string
|
||||
if mi.model != nil {
|
||||
engine = getTableEngine(mi.addrField)
|
||||
}
|
||||
if engine == "" {
|
||||
engine = al.Engine
|
||||
}
|
||||
sql += " ENGINE=" + engine
|
||||
}
|
||||
|
||||
sql += ";"
|
||||
sqls = append(sqls, sql)
|
||||
|
||||
if mi.model != nil {
|
||||
for _, names := range getTableIndex(mi.addrField) {
|
||||
cols := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||
cols = append(cols, fi.column)
|
||||
} else {
|
||||
panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName))
|
||||
}
|
||||
}
|
||||
sqlIndexes = append(sqlIndexes, cols)
|
||||
}
|
||||
}
|
||||
|
||||
for _, names := range sqlIndexes {
|
||||
name := mi.table + "_" + strings.Join(names, "_")
|
||||
cols := strings.Join(names, sep)
|
||||
sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q)
|
||||
|
||||
index := dbIndex{}
|
||||
index.Table = mi.table
|
||||
index.Name = name
|
||||
index.Sql = sql
|
||||
|
||||
tableIndexes[mi.table] = append(tableIndexes[mi.table], index)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
228
orm/db_alias.go
Normal file
@ -0,0 +1,228 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DriverType int
|
||||
|
||||
const (
|
||||
_ DriverType = iota
|
||||
DR_MySQL
|
||||
DR_Sqlite
|
||||
DR_Oracle
|
||||
DR_Postgres
|
||||
)
|
||||
|
||||
type driver string
|
||||
|
||||
func (d driver) Type() DriverType {
|
||||
a, _ := dataBaseCache.get(string(d))
|
||||
return a.Driver
|
||||
}
|
||||
|
||||
func (d driver) Name() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
var _ Driver = new(driver)
|
||||
|
||||
var (
|
||||
dataBaseCache = &_dbCache{cache: make(map[string]*alias)}
|
||||
drivers = map[string]DriverType{
|
||||
"mysql": DR_MySQL,
|
||||
"postgres": DR_Postgres,
|
||||
"sqlite3": DR_Sqlite,
|
||||
}
|
||||
dbBasers = map[DriverType]dbBaser{
|
||||
DR_MySQL: newdbBaseMysql(),
|
||||
DR_Sqlite: newdbBaseSqlite(),
|
||||
DR_Oracle: newdbBaseMysql(),
|
||||
DR_Postgres: newdbBasePostgres(),
|
||||
}
|
||||
)
|
||||
|
||||
type _dbCache struct {
|
||||
mux sync.RWMutex
|
||||
cache map[string]*alias
|
||||
}
|
||||
|
||||
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
||||
ac.mux.Lock()
|
||||
defer ac.mux.Unlock()
|
||||
if _, ok := ac.cache[name]; ok == false {
|
||||
ac.cache[name] = al
|
||||
added = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *_dbCache) get(name string) (al *alias, ok bool) {
|
||||
ac.mux.RLock()
|
||||
defer ac.mux.RUnlock()
|
||||
al, ok = ac.cache[name]
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *_dbCache) getDefault() (al *alias) {
|
||||
al, _ = ac.get("default")
|
||||
return
|
||||
}
|
||||
|
||||
type alias struct {
|
||||
Name string
|
||||
Driver DriverType
|
||||
DriverName string
|
||||
DataSource string
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
DB *sql.DB
|
||||
DbBaser dbBaser
|
||||
TZ *time.Location
|
||||
Engine string
|
||||
}
|
||||
|
||||
// Setting the database connect params. Use the database driver self dataSource args.
|
||||
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) {
|
||||
al := new(alias)
|
||||
al.Name = aliasName
|
||||
al.DriverName = driverName
|
||||
al.DataSource = dataSource
|
||||
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
if dr, ok := drivers[driverName]; ok {
|
||||
al.DbBaser = dbBasers[dr]
|
||||
al.Driver = dr
|
||||
} else {
|
||||
err = fmt.Errorf("driver name `%s` have not registered", driverName)
|
||||
goto end
|
||||
}
|
||||
|
||||
if dataBaseCache.add(aliasName, al) == false {
|
||||
err = fmt.Errorf("db name `%s` already registered, cannot reuse", aliasName)
|
||||
goto end
|
||||
}
|
||||
|
||||
al.DB, err = sql.Open(driverName, dataSource)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
||||
goto end
|
||||
}
|
||||
|
||||
// orm timezone system match database
|
||||
// default use Local
|
||||
al.TZ = time.Local
|
||||
|
||||
switch al.Driver {
|
||||
case DR_MySQL:
|
||||
row := al.DB.QueryRow("SELECT @@session.time_zone")
|
||||
var tz string
|
||||
row.Scan(&tz)
|
||||
if tz == "SYSTEM" {
|
||||
tz = ""
|
||||
row = al.DB.QueryRow("SELECT @@system_time_zone")
|
||||
row.Scan(&tz)
|
||||
t, err := time.Parse("MST", tz)
|
||||
if err == nil {
|
||||
al.TZ = t.Location()
|
||||
}
|
||||
} else {
|
||||
t, err := time.Parse("-07:00", tz)
|
||||
if err == nil {
|
||||
al.TZ = t.Location()
|
||||
}
|
||||
}
|
||||
|
||||
// get default engine from current database
|
||||
row = al.DB.QueryRow("SELECT ENGINE, TRANSACTIONS FROM information_schema.engines WHERE SUPPORT = 'DEFAULT'")
|
||||
var engine string
|
||||
var tx bool
|
||||
row.Scan(&engine, &tx)
|
||||
|
||||
if engine != "" {
|
||||
al.Engine = engine
|
||||
} else {
|
||||
engine = "INNODB"
|
||||
}
|
||||
|
||||
case DR_Sqlite:
|
||||
al.TZ = time.UTC
|
||||
|
||||
case DR_Postgres:
|
||||
row := al.DB.QueryRow("SELECT current_setting('TIMEZONE')")
|
||||
var tz string
|
||||
row.Scan(&tz)
|
||||
loc, err := time.LoadLocation(tz)
|
||||
if err == nil {
|
||||
al.TZ = loc
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range params {
|
||||
switch i {
|
||||
case 0:
|
||||
SetMaxIdleConns(al.Name, v)
|
||||
case 1:
|
||||
SetMaxOpenConns(al.Name, v)
|
||||
}
|
||||
}
|
||||
|
||||
err = al.DB.Ping()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
||||
goto end
|
||||
}
|
||||
|
||||
end:
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// Register a database driver use specify driver name, this can be definition the driver is which database type.
|
||||
func RegisterDriver(driverName string, typ DriverType) {
|
||||
if t, ok := drivers[driverName]; ok == false {
|
||||
drivers[driverName] = typ
|
||||
} else {
|
||||
if t != typ {
|
||||
fmt.Sprintf("driverName `%s` db driver already registered and is other type\n", driverName)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change the database default used timezone
|
||||
func SetDataBaseTZ(aliasName string, tz *time.Location) {
|
||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||
al.TZ = tz
|
||||
} else {
|
||||
fmt.Sprintf("DataBase name `%s` not registered\n", aliasName)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// Change the max idle conns for *sql.DB, use specify database alias name
|
||||
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
||||
al := getDbAlias(aliasName)
|
||||
al.MaxIdleConns = maxIdleConns
|
||||
al.DB.SetMaxIdleConns(maxIdleConns)
|
||||
}
|
||||
|
||||
// Change the max open conns for *sql.DB, use specify database alias name
|
||||
func SetMaxOpenConns(aliasName string, maxOpenConns int) {
|
||||
al := getDbAlias(aliasName)
|
||||
al.MaxOpenConns = maxOpenConns
|
||||
// for tip go 1.2
|
||||
if fun := reflect.ValueOf(al.DB).MethodByName("SetMaxOpenConns"); fun.IsValid() {
|
||||
fun.Call([]reflect.Value{reflect.ValueOf(maxOpenConns)})
|
||||
}
|
||||
}
|
79
orm/db_mysql.go
Normal file
@ -0,0 +1,79 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var mysqlOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "LIKE ?",
|
||||
"contains": "LIKE BINARY ?",
|
||||
"icontains": "LIKE ?",
|
||||
// "regex": "REGEXP BINARY ?",
|
||||
// "iregex": "REGEXP ?",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"startswith": "LIKE BINARY ?",
|
||||
"endswith": "LIKE BINARY ?",
|
||||
"istartswith": "LIKE ?",
|
||||
"iendswith": "LIKE ?",
|
||||
}
|
||||
|
||||
var mysqlTypes = map[string]string{
|
||||
"auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-text": "longtext",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "datetime",
|
||||
"int8": "tinyint",
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": "tinyint unsigned",
|
||||
"uint16": "smallint unsigned",
|
||||
"uint32": "integer unsigned",
|
||||
"uint64": "bigint unsigned",
|
||||
"float64": "double precision",
|
||||
"float64-decimal": "numeric(%d, %d)",
|
||||
}
|
||||
|
||||
type dbBaseMysql struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseMysql)
|
||||
|
||||
func (d *dbBaseMysql) OperatorSql(operator string) string {
|
||||
return mysqlOperators[operator]
|
||||
}
|
||||
|
||||
func (d *dbBaseMysql) DbTypes() map[string]string {
|
||||
return mysqlTypes
|
||||
}
|
||||
|
||||
func (d *dbBaseMysql) ShowTablesQuery() string {
|
||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
||||
}
|
||||
|
||||
func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
||||
}
|
||||
|
||||
func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool {
|
||||
row := db.QueryRow("SELECT count(*) FROM information_schema.statistics "+
|
||||
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
func newdbBaseMysql() dbBaser {
|
||||
b := new(dbBaseMysql)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
13
orm/db_oracle.go
Normal file
@ -0,0 +1,13 @@
|
||||
package orm
|
||||
|
||||
type dbBaseOracle struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseOracle)
|
||||
|
||||
func newdbBaseOracle() dbBaser {
|
||||
b := new(dbBaseOracle)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
134
orm/db_postgres.go
Normal file
@ -0,0 +1,134 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var postgresOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "= UPPER(?)",
|
||||
"contains": "LIKE ?",
|
||||
"icontains": "LIKE UPPER(?)",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"startswith": "LIKE ?",
|
||||
"endswith": "LIKE ?",
|
||||
"istartswith": "LIKE UPPER(?)",
|
||||
"iendswith": "LIKE UPPER(?)",
|
||||
}
|
||||
|
||||
var postgresTypes = map[string]string{
|
||||
"auto": "serial NOT NULL PRIMARY KEY",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-text": "text",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "timestamp with time zone",
|
||||
"int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`,
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`,
|
||||
"uint16": `integer CHECK("%COL%" >= 0)`,
|
||||
"uint32": `bigint CHECK("%COL%" >= 0)`,
|
||||
"uint64": `bigint CHECK("%COL%" >= 0)`,
|
||||
"float64": "double precision",
|
||||
"float64-decimal": "numeric(%d, %d)",
|
||||
}
|
||||
|
||||
type dbBasePostgres struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBasePostgres)
|
||||
|
||||
func (d *dbBasePostgres) OperatorSql(operator string) string {
|
||||
return postgresOperators[operator]
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||
switch operator {
|
||||
case "contains", "startswith", "endswith":
|
||||
*leftCol = fmt.Sprintf("%s::text", *leftCol)
|
||||
case "iexact", "icontains", "istartswith", "iendswith":
|
||||
*leftCol = fmt.Sprintf("UPPER(%s::text)", *leftCol)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) SupportUpdateJoin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) MaxLimit() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) TableQuote() string {
|
||||
return `"`
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) ReplaceMarks(query *string) {
|
||||
q := *query
|
||||
num := 0
|
||||
for _, c := range q {
|
||||
if c == '?' {
|
||||
num += 1
|
||||
}
|
||||
}
|
||||
if num == 0 {
|
||||
return
|
||||
}
|
||||
data := make([]byte, 0, len(q)+num)
|
||||
num = 1
|
||||
for i := 0; i < len(q); i++ {
|
||||
c := q[i]
|
||||
if c == '?' {
|
||||
data = append(data, '$')
|
||||
data = append(data, []byte(strconv.Itoa(num))...)
|
||||
num += 1
|
||||
} else {
|
||||
data = append(data, c)
|
||||
}
|
||||
}
|
||||
*query = string(data)
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) (has bool) {
|
||||
if mi.fields.pk.auto {
|
||||
if query != nil {
|
||||
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, mi.fields.pk.column)
|
||||
}
|
||||
has = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) ShowTablesQuery() string {
|
||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')"
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT column_name, data_type, is_nullable FROM information_schema.columns where table_schema NOT IN ('pg_catalog', 'information_schema') and table_name = '%s'", table)
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) DbTypes() map[string]string {
|
||||
return postgresTypes
|
||||
}
|
||||
|
||||
func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bool {
|
||||
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
|
||||
row := db.QueryRow(query)
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
func newdbBasePostgres() dbBaser {
|
||||
b := new(dbBasePostgres)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
120
orm/db_sqlite.go
Normal file
@ -0,0 +1,120 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var sqliteOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"iexact": "LIKE ? ESCAPE '\\'",
|
||||
"contains": "LIKE ? ESCAPE '\\'",
|
||||
"icontains": "LIKE ? ESCAPE '\\'",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"startswith": "LIKE ? ESCAPE '\\'",
|
||||
"endswith": "LIKE ? ESCAPE '\\'",
|
||||
"istartswith": "LIKE ? ESCAPE '\\'",
|
||||
"iendswith": "LIKE ? ESCAPE '\\'",
|
||||
}
|
||||
|
||||
var sqliteTypes = map[string]string{
|
||||
"auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT",
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "varchar(%d)",
|
||||
"string-text": "text",
|
||||
"time.Time-date": "date",
|
||||
"time.Time": "datetime",
|
||||
"int8": "tinyint",
|
||||
"int16": "smallint",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"uint8": "tinyint unsigned",
|
||||
"uint16": "smallint unsigned",
|
||||
"uint32": "integer unsigned",
|
||||
"uint64": "bigint unsigned",
|
||||
"float64": "real",
|
||||
"float64-decimal": "decimal",
|
||||
}
|
||||
|
||||
type dbBaseSqlite struct {
|
||||
dbBase
|
||||
}
|
||||
|
||||
var _ dbBaser = new(dbBaseSqlite)
|
||||
|
||||
func (d *dbBaseSqlite) OperatorSql(operator string) string {
|
||||
return sqliteOperators[operator]
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||
if fi.fieldType == TypeDateField {
|
||||
*leftCol = fmt.Sprintf("DATE(%s)", *leftCol)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) SupportUpdateJoin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) MaxLimit() uint64 {
|
||||
return 9223372036854775807
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) DbTypes() map[string]string {
|
||||
return sqliteTypes
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) ShowTablesQuery() string {
|
||||
return "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) GetColumns(db dbQuerier, table string) (map[string][3]string, error) {
|
||||
query := d.ins.ShowColumnsQuery(table)
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := make(map[string][3]string)
|
||||
for rows.Next() {
|
||||
var tmp, name, typ, null sql.NullString
|
||||
err := rows.Scan(&tmp, &name, &typ, &null, &tmp, &tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns[name.String] = [3]string{name.String, typ.String, null.String}
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("pragma table_info('%s')", table)
|
||||
}
|
||||
|
||||
func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool {
|
||||
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var tmp, index sql.NullString
|
||||
rows.Scan(&tmp, &index, &tmp)
|
||||
if name == index.String {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newdbBaseSqlite() dbBaser {
|
||||
b := new(dbBaseSqlite)
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
390
orm/db_tables.go
Normal file
@ -0,0 +1,390 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type dbTable struct {
|
||||
id int
|
||||
index string
|
||||
name string
|
||||
names []string
|
||||
sel bool
|
||||
inner bool
|
||||
mi *modelInfo
|
||||
fi *fieldInfo
|
||||
jtl *dbTable
|
||||
}
|
||||
|
||||
type dbTables struct {
|
||||
tablesM map[string]*dbTable
|
||||
tables []*dbTable
|
||||
mi *modelInfo
|
||||
base dbBaser
|
||||
}
|
||||
|
||||
func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool) *dbTable {
|
||||
name := strings.Join(names, ExprSep)
|
||||
if j, ok := t.tablesM[name]; ok {
|
||||
j.name = name
|
||||
j.mi = mi
|
||||
j.fi = fi
|
||||
j.inner = inner
|
||||
} else {
|
||||
i := len(t.tables) + 1
|
||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||
t.tablesM[name] = jt
|
||||
t.tables = append(t.tables, jt)
|
||||
}
|
||||
return t.tablesM[name]
|
||||
}
|
||||
|
||||
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
||||
name := strings.Join(names, ExprSep)
|
||||
if _, ok := t.tablesM[name]; ok == false {
|
||||
i := len(t.tables) + 1
|
||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||
t.tablesM[name] = jt
|
||||
t.tables = append(t.tables, jt)
|
||||
return jt, true
|
||||
}
|
||||
return t.tablesM[name], false
|
||||
}
|
||||
|
||||
func (t *dbTables) get(name string) (*dbTable, bool) {
|
||||
j, ok := t.tablesM[name]
|
||||
return j, ok
|
||||
}
|
||||
|
||||
func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []string) []string {
|
||||
if depth < 0 || fi.fieldType == RelManyToMany {
|
||||
return related
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
prefix = fi.name
|
||||
} else {
|
||||
prefix = prefix + ExprSep + fi.name
|
||||
}
|
||||
related = append(related, prefix)
|
||||
|
||||
depth--
|
||||
for _, fi := range fi.relModelInfo.fields.fieldsRel {
|
||||
related = t.loopDepth(depth, prefix, fi, related)
|
||||
}
|
||||
|
||||
return related
|
||||
}
|
||||
|
||||
func (t *dbTables) parseRelated(rels []string, depth int) {
|
||||
|
||||
relsNum := len(rels)
|
||||
related := make([]string, relsNum)
|
||||
copy(related, rels)
|
||||
|
||||
relDepth := depth
|
||||
|
||||
if relsNum != 0 {
|
||||
relDepth = 0
|
||||
}
|
||||
|
||||
relDepth--
|
||||
for _, fi := range t.mi.fields.fieldsRel {
|
||||
related = t.loopDepth(relDepth, "", fi, related)
|
||||
}
|
||||
|
||||
for i, s := range related {
|
||||
var (
|
||||
exs = strings.Split(s, ExprSep)
|
||||
names = make([]string, 0, len(exs))
|
||||
mmi = t.mi
|
||||
cancel = true
|
||||
jtl *dbTable
|
||||
)
|
||||
|
||||
inner := true
|
||||
|
||||
for _, ex := range exs {
|
||||
if fi, ok := mmi.fields.GetByAny(ex); ok && fi.rel && fi.fieldType != RelManyToMany {
|
||||
names = append(names, fi.name)
|
||||
mmi = fi.relModelInfo
|
||||
|
||||
if fi.null {
|
||||
inner = false
|
||||
}
|
||||
|
||||
jt := t.set(names, mmi, fi, inner)
|
||||
jt.jtl = jtl
|
||||
|
||||
if fi.reverse {
|
||||
cancel = false
|
||||
}
|
||||
|
||||
if cancel {
|
||||
jt.sel = depth > 0
|
||||
|
||||
if i < relsNum {
|
||||
jt.sel = true
|
||||
}
|
||||
}
|
||||
|
||||
jtl = jt
|
||||
|
||||
} else {
|
||||
panic(fmt.Errorf("unknown model/table name `%s`", ex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *dbTables) getJoinSql() (join string) {
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
for _, jt := range t.tables {
|
||||
if jt.inner {
|
||||
join += "INNER JOIN "
|
||||
} else {
|
||||
join += "LEFT OUTER JOIN "
|
||||
}
|
||||
var (
|
||||
table string
|
||||
t1, t2 string
|
||||
c1, c2 string
|
||||
)
|
||||
t1 = "T0"
|
||||
if jt.jtl != nil {
|
||||
t1 = jt.jtl.index
|
||||
}
|
||||
t2 = jt.index
|
||||
table = jt.mi.table
|
||||
|
||||
switch {
|
||||
case jt.fi.fieldType == RelManyToMany || jt.fi.reverse && jt.fi.reverseFieldInfo.fieldType == RelManyToMany:
|
||||
c1 = jt.fi.mi.fields.pk.column
|
||||
for _, ffi := range jt.mi.fields.fieldsRel {
|
||||
if jt.fi.mi == ffi.relModelInfo {
|
||||
c2 = ffi.column
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
c1 = jt.fi.column
|
||||
c2 = jt.fi.relModelInfo.fields.pk.column
|
||||
|
||||
if jt.fi.reverse {
|
||||
c1 = jt.mi.fields.pk.column
|
||||
c2 = jt.fi.reverseFieldInfo.column
|
||||
}
|
||||
}
|
||||
|
||||
join += fmt.Sprintf("%s%s%s %s ON %s.%s%s%s = %s.%s%s%s ", Q, table, Q, t2,
|
||||
t2, Q, c2, Q, t1, Q, c1, Q)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string, info *fieldInfo, success bool) {
|
||||
var (
|
||||
jtl *dbTable
|
||||
mmi = mi
|
||||
)
|
||||
|
||||
num := len(exprs) - 1
|
||||
names := make([]string, 0)
|
||||
|
||||
inner := true
|
||||
|
||||
for i, ex := range exprs {
|
||||
|
||||
fi, ok := mmi.fields.GetByAny(ex)
|
||||
|
||||
if ok {
|
||||
|
||||
isRel := fi.rel || fi.reverse
|
||||
|
||||
names = append(names, fi.name)
|
||||
|
||||
switch {
|
||||
case fi.rel:
|
||||
mmi = fi.relModelInfo
|
||||
if fi.fieldType == RelManyToMany {
|
||||
mmi = fi.relThroughModelInfo
|
||||
}
|
||||
case fi.reverse:
|
||||
mmi = fi.reverseFieldInfo.mi
|
||||
}
|
||||
|
||||
if isRel && (fi.mi.isThrough == false || num != i) {
|
||||
if fi.null {
|
||||
inner = false
|
||||
}
|
||||
|
||||
jt, _ := t.add(names, mmi, fi, inner)
|
||||
jt.jtl = jtl
|
||||
jtl = jt
|
||||
}
|
||||
|
||||
if num == i {
|
||||
if i == 0 || jtl == nil {
|
||||
index = "T0"
|
||||
} else {
|
||||
index = jtl.index
|
||||
}
|
||||
|
||||
info = fi
|
||||
|
||||
if jtl == nil {
|
||||
name = fi.name
|
||||
} else {
|
||||
name = jtl.name + ExprSep + fi.name
|
||||
}
|
||||
|
||||
switch {
|
||||
case fi.rel:
|
||||
|
||||
case fi.reverse:
|
||||
switch fi.reverseFieldInfo.fieldType {
|
||||
case RelOneToOne, RelForeignKey:
|
||||
index = jtl.index
|
||||
info = fi.reverseFieldInfo.mi.fields.pk
|
||||
name = info.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
index = ""
|
||||
name = ""
|
||||
info = nil
|
||||
success = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
success = index != "" && info != nil
|
||||
return
|
||||
}
|
||||
|
||||
func (t *dbTables) getCondSql(cond *Condition, sub bool, tz *time.Location) (where string, params []interface{}) {
|
||||
if cond == nil || cond.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
mi := t.mi
|
||||
|
||||
for i, p := range cond.params {
|
||||
if i > 0 {
|
||||
if p.isOr {
|
||||
where += "OR "
|
||||
} else {
|
||||
where += "AND "
|
||||
}
|
||||
}
|
||||
if p.isNot {
|
||||
where += "NOT "
|
||||
}
|
||||
if p.isCond {
|
||||
w, ps := t.getCondSql(p.cond, true, tz)
|
||||
if w != "" {
|
||||
w = fmt.Sprintf("( %s) ", w)
|
||||
}
|
||||
where += w
|
||||
params = append(params, ps...)
|
||||
} else {
|
||||
exprs := p.exprs
|
||||
|
||||
num := len(exprs) - 1
|
||||
operator := ""
|
||||
if operators[exprs[num]] {
|
||||
operator = exprs[num]
|
||||
exprs = exprs[:num]
|
||||
}
|
||||
|
||||
index, _, fi, suc := t.parseExprs(mi, exprs)
|
||||
if suc == false {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
||||
}
|
||||
|
||||
if operator == "" {
|
||||
operator = "exact"
|
||||
}
|
||||
|
||||
operSql, args := t.base.GenerateOperatorSql(mi, fi, operator, p.args, tz)
|
||||
|
||||
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
|
||||
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
|
||||
|
||||
where += fmt.Sprintf("%s %s ", leftCol, operSql)
|
||||
params = append(params, args...)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if sub == false && where != "" {
|
||||
where = "WHERE " + where
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *dbTables) getOrderSql(orders []string) (orderSql string) {
|
||||
if len(orders) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
Q := t.base.TableQuote()
|
||||
|
||||
orderSqls := make([]string, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
asc := "ASC"
|
||||
if order[0] == '-' {
|
||||
asc = "DESC"
|
||||
order = order[1:]
|
||||
}
|
||||
exprs := strings.Split(order, ExprSep)
|
||||
|
||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
||||
if suc == false {
|
||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
||||
}
|
||||
|
||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, asc))
|
||||
}
|
||||
|
||||
orderSql = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *dbTables) getLimitSql(mi *modelInfo, offset int64, limit int64) (limits string) {
|
||||
if limit == 0 {
|
||||
limit = int64(DefaultRowsLimit)
|
||||
}
|
||||
if limit < 0 {
|
||||
// no limit
|
||||
if offset > 0 {
|
||||
maxLimit := t.base.MaxLimit()
|
||||
if maxLimit == 0 {
|
||||
limits = fmt.Sprintf("OFFSET %d", offset)
|
||||
} else {
|
||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", maxLimit, offset)
|
||||
}
|
||||
}
|
||||
} else if offset <= 0 {
|
||||
limits = fmt.Sprintf("LIMIT %d", limit)
|
||||
} else {
|
||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newDbTables(mi *modelInfo, base dbBaser) *dbTables {
|
||||
tables := &dbTables{}
|
||||
tables.tablesM = make(map[string]*dbTable)
|
||||
tables.mi = mi
|
||||
tables.base = base
|
||||
return tables
|
||||
}
|
136
orm/db_utils.go
Normal file
@ -0,0 +1,136 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getDbAlias(name string) *alias {
|
||||
if al, ok := dataBaseCache.get(name); ok {
|
||||
return al
|
||||
} else {
|
||||
panic(fmt.Errorf("unknown DataBase alias name %s", name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
|
||||
fi := mi.fields.pk
|
||||
|
||||
v := ind.Field(fi.fieldIndex)
|
||||
if fi.fieldType&IsPostiveIntegerField > 0 {
|
||||
vu := v.Uint()
|
||||
exist = vu > 0
|
||||
value = vu
|
||||
} else if fi.fieldType&IsIntegerField > 0 {
|
||||
vu := v.Int()
|
||||
exist = vu > 0
|
||||
value = vu
|
||||
} else {
|
||||
vu := v.String()
|
||||
exist = vu != ""
|
||||
value = vu
|
||||
}
|
||||
|
||||
column = fi.column
|
||||
return
|
||||
}
|
||||
|
||||
func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
|
||||
|
||||
outFor:
|
||||
for _, arg := range args {
|
||||
val := reflect.ValueOf(arg)
|
||||
|
||||
if arg == nil {
|
||||
params = append(params, arg)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case []byte:
|
||||
case string:
|
||||
if fi != nil {
|
||||
if fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
||||
var t time.Time
|
||||
var err error
|
||||
if len(v) >= 19 {
|
||||
s := v[:19]
|
||||
t, err = time.ParseInLocation(format_DateTime, s, DefaultTimeLoc)
|
||||
} else {
|
||||
s := v
|
||||
if len(v) > 10 {
|
||||
s = v[:10]
|
||||
}
|
||||
t, err = time.ParseInLocation(format_Date, s, tz)
|
||||
}
|
||||
if err == nil {
|
||||
if fi.fieldType == TypeDateField {
|
||||
v = t.In(tz).Format(format_Date)
|
||||
} else {
|
||||
v = t.In(tz).Format(format_DateTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
arg = v
|
||||
case time.Time:
|
||||
if fi != nil && fi.fieldType == TypeDateField {
|
||||
arg = v.In(tz).Format(format_Date)
|
||||
} else {
|
||||
arg = v.In(tz).Format(format_DateTime)
|
||||
}
|
||||
default:
|
||||
kind := val.Kind()
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
|
||||
var args []interface{}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
v := val.Index(i)
|
||||
|
||||
var vu interface{}
|
||||
if v.CanInterface() {
|
||||
vu = v.Interface()
|
||||
}
|
||||
|
||||
if vu == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
args = append(args, vu)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
p := getFlatParams(fi, args, tz)
|
||||
params = append(params, p...)
|
||||
}
|
||||
continue outFor
|
||||
|
||||
case reflect.Ptr, reflect.Struct:
|
||||
ind := reflect.Indirect(val)
|
||||
|
||||
if ind.Kind() == reflect.Struct {
|
||||
typ := ind.Type()
|
||||
name := getFullName(typ)
|
||||
var value interface{}
|
||||
if mmi, ok := modelCache.getByFN(name); ok {
|
||||
if _, vu, exist := getExistPk(mmi, ind); exist {
|
||||
value = vu
|
||||
}
|
||||
}
|
||||
arg = value
|
||||
|
||||
if arg == nil {
|
||||
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
|
||||
}
|
||||
} else {
|
||||
arg = ind.Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
params = append(params, arg)
|
||||
}
|
||||
return
|
||||
}
|
43
orm/docs/zh/Cmd.md
Normal file
@ -0,0 +1,43 @@
|
||||
## 命令模式
|
||||
|
||||
注册模型与数据库以后,调用 RunCommand 执行 orm 命令
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// orm.RegisterModel...
|
||||
// orm.RegisterDataBase...
|
||||
...
|
||||
orm.RunCommand()
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
go build main.go
|
||||
./main orm
|
||||
# 直接执行可以显示帮助
|
||||
# 如果你的程序可以支持的话,直接运行 go run main.go orm 也是一样的效果
|
||||
```
|
||||
|
||||
## 自动建表
|
||||
|
||||
```bash
|
||||
./main orm syncdb -h
|
||||
Usage of orm command: syncdb:
|
||||
-db="default": DataBase alias name
|
||||
-force=false: drop tables before create
|
||||
-v=false: verbose info
|
||||
```
|
||||
|
||||
使用 `-force=1` 可以 drop table 后再建表
|
||||
|
||||
使用 `-v` 可以查看执行的 sql 语句
|
||||
|
||||
## 打印建表SQL
|
||||
|
||||
```bash
|
||||
./main orm sqlall -h
|
||||
Usage of orm command: syncdb:
|
||||
-db="default": DataBase alias name
|
||||
```
|
||||
|
||||
默认使用别名为 default 的数据库
|
38
orm/docs/zh/CustomFields.md
Normal file
@ -0,0 +1,38 @@
|
||||
## Custom Fields
|
||||
|
||||
TypeBooleanField = 1 << iota
|
||||
|
||||
// string
|
||||
TypeCharField
|
||||
|
||||
// string
|
||||
TypeTextField
|
||||
|
||||
// time.Time
|
||||
TypeDateField
|
||||
// time.Time
|
||||
TypeDateTimeField
|
||||
|
||||
// int16
|
||||
TypeSmallIntegerField
|
||||
// int32
|
||||
TypeIntegerField
|
||||
// int64
|
||||
TypeBigIntegerField
|
||||
// uint16
|
||||
TypePositiveSmallIntegerField
|
||||
// uint32
|
||||
TypePositiveIntegerField
|
||||
// uint64
|
||||
TypePositiveBigIntegerField
|
||||
|
||||
// float64
|
||||
TypeFloatField
|
||||
// float64
|
||||
TypeDecimalField
|
||||
|
||||
RelForeignKey
|
||||
RelOneToOne
|
||||
RelManyToMany
|
||||
RelReverseOne
|
||||
RelReverseMany
|
288
orm/docs/zh/Models.md
Normal file
@ -0,0 +1,288 @@
|
||||
## 模型定义
|
||||
|
||||
复杂的模型定义不是必须的,此功能用作数据库数据转换和[自动建表](Cmd.md#自动建表)
|
||||
|
||||
默认的表名使用驼峰转蛇形,比如 AuthUser -> auth_user
|
||||
|
||||
**自定义表名**
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (u *User) TableName() string {
|
||||
return "auth_user"
|
||||
}
|
||||
```
|
||||
|
||||
如果[前缀设置](Orm.md#registermodelwithprefix)为`prefix_`那么表名为:prefix_auth_user
|
||||
|
||||
## Struct Tag 设置参数
|
||||
```go
|
||||
orm:"null;rel(fk)"
|
||||
```
|
||||
|
||||
多个设置间使用 `;` 分隔,设置的值如果是多个,使用 `,` 分隔。
|
||||
|
||||
#### 忽略字段
|
||||
|
||||
设置 `-` 即可忽略 struct 中的字段
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
...
|
||||
AnyField string `orm:"-"`
|
||||
...
|
||||
```
|
||||
|
||||
#### auto
|
||||
|
||||
当 Field 类型为 int, int32, int64 时,可以设置字段为自增健
|
||||
|
||||
当模型定义里没有主键时,符合上述类型且名称为 `Id` 的 Field 将被视为自增健。
|
||||
|
||||
#### pk
|
||||
|
||||
设置为主键,适用于自定义其他类型为主键
|
||||
|
||||
#### null
|
||||
|
||||
数据库表默认为 `NOT NULL`,设置 null 代表 `ALLOW NULL`
|
||||
|
||||
#### blank
|
||||
|
||||
设置 string 类型的字段允许为空,否则 clean 会返回错误
|
||||
|
||||
#### index
|
||||
|
||||
为字段增加索引
|
||||
|
||||
#### unique
|
||||
|
||||
为字段增加 unique 键
|
||||
|
||||
#### column
|
||||
|
||||
为字段设置 db 字段的名称
|
||||
```go
|
||||
Name `orm:"column(user_name)"`
|
||||
```
|
||||
#### default
|
||||
|
||||
为字段设置默认值,类型必须符合
|
||||
```go
|
||||
type User struct {
|
||||
...
|
||||
Status int `orm:"default(1)"`
|
||||
```
|
||||
#### size
|
||||
|
||||
string 类型字段默认为 varchar(255)
|
||||
|
||||
设置 size 以后,db type 将使用 varchar(size)
|
||||
|
||||
```go
|
||||
Title string `orm:"size(60)"`
|
||||
```
|
||||
#### digits / decimals
|
||||
|
||||
设置 float32, float64 类型的浮点精度
|
||||
```go
|
||||
Money float64 `orm:"digits(12);decimals(4)"`
|
||||
```
|
||||
总长度 12 小数点后 4 位 eg: `99999999.9999`
|
||||
|
||||
#### auto_now / auto_now_add
|
||||
```go
|
||||
Created time.Time `auto_now_add`
|
||||
Updated time.Time `auto_now`
|
||||
```
|
||||
* auto_now 每次 model 保存时都会对时间自动更新
|
||||
* auto_now_add 第一次保存时才设置时间
|
||||
|
||||
对于批量的 update 此设置是不生效的
|
||||
|
||||
#### type
|
||||
|
||||
设置为 date 时,time.Time 字段的对应 db 类型使用 date
|
||||
|
||||
```go
|
||||
Created time.Time `orm:"auto_now_add;type(date)"`
|
||||
```
|
||||
|
||||
设置为 text 时,string 字段对应的 db 类型使用 text
|
||||
|
||||
```go
|
||||
Content string `orm:"type(text)"`
|
||||
```
|
||||
|
||||
## 表关系设置
|
||||
|
||||
#### rel / reverse
|
||||
|
||||
**RelOneToOne**:
|
||||
```go
|
||||
type User struct {
|
||||
...
|
||||
Profile *Profile `orm:"null;rel(one);on_delete(set_null)"`
|
||||
```
|
||||
对应的反向关系 **RelReverseOne**:
|
||||
```go
|
||||
type Profile struct {
|
||||
...
|
||||
User *User `orm:"reverse(one)" json:"-"`
|
||||
```
|
||||
**RelForeignKey**:
|
||||
```go
|
||||
type Post struct {
|
||||
...
|
||||
User*User `orm:"rel(fk)"` // RelForeignKey relation
|
||||
```
|
||||
对应的反向关系 **RelReverseMany**:
|
||||
```go
|
||||
type User struct {
|
||||
...
|
||||
Posts []*Post `orm:"reverse(many)" json:"-"` // fk 的反向关系
|
||||
```
|
||||
**RelManyToMany**:
|
||||
```go
|
||||
type Post struct {
|
||||
...
|
||||
Tags []*Tag `orm:"rel(m2m)"` // ManyToMany relation
|
||||
```
|
||||
对应的反向关系 **RelReverseMany**:
|
||||
```go
|
||||
type Tag struct {
|
||||
...
|
||||
Posts []*Post `orm:"reverse(many)" json:"-"`
|
||||
```
|
||||
#### rel_table / rel_through
|
||||
|
||||
此设置针对 `orm:"rel(m2m)"` 的关系字段
|
||||
|
||||
rel_table 设置自动生成的 m2m 关系表的名称
|
||||
rel_through 如果要在 m2m 关系中使用自定义的 m2m 关系表
|
||||
通过这个设置其名称,格式为 pkg.path.ModelName
|
||||
eg: app.models.PostTagRel
|
||||
PostTagRel 表需要有到 Post 和 Tag 的关系
|
||||
|
||||
当设置 rel_table 时会忽略 rel_through
|
||||
|
||||
#### on_delete
|
||||
|
||||
设置对应的 rel 关系删除时,如何处理关系字段。
|
||||
|
||||
cascade 级联删除(默认值)
|
||||
set_null 设置为 NULL,需要设置 null = true
|
||||
set_default 设置为默认值,需要设置 default 值
|
||||
do_nothing 什么也不做,忽略
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
...
|
||||
Profile *Profile `orm:"null;rel(one);on_delete(set_null)"`
|
||||
...
|
||||
type Profile struct {
|
||||
...
|
||||
User *User `orm:"reverse(one)" json:"-"`
|
||||
|
||||
// 删除 Profile 时将设置 User.Profile 的数据库字段为 NULL
|
||||
```
|
||||
|
||||
|
||||
## 模型字段与数据库类型的对应
|
||||
|
||||
在此列出 orm 推荐的对应数据库类型,自动建表功能也会以此为标准。
|
||||
|
||||
默认所有的字段都是 **NOT NULL**
|
||||
|
||||
#### MySQL
|
||||
|
||||
| go |mysql
|
||||
| :--- | :---
|
||||
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | integer AUTO_INCREMENT
|
||||
| bool | bool
|
||||
| string - 默认为 size 255 | varchar(size)
|
||||
| string - 设置 type(text) 时 | longtext
|
||||
| time.Time - 设置 type 为 date 时 | date
|
||||
| time.TIme | datetime
|
||||
| byte | tinyint unsigned
|
||||
| rune | integer
|
||||
| int | integer
|
||||
| int8 | tinyint
|
||||
| int16 | smallint
|
||||
| int32 | integer
|
||||
| int64 | bigint
|
||||
| uint | integer unsigned
|
||||
| uint8 | tinyint unsigned
|
||||
| uint16 | smallint unsigned
|
||||
| uint32 | integer unsigned
|
||||
| uint64 | bigint unsigned
|
||||
| float32 | double precision
|
||||
| float64 | double precision
|
||||
| float64 - 设置 digits, decimals 时 | numeric(digits, decimals)
|
||||
|
||||
#### Sqlite3
|
||||
|
||||
| go | sqlite3
|
||||
| :--- | :---
|
||||
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | integer AUTOINCREMENT
|
||||
| bool | bool
|
||||
| string - 默认为 size 255 | varchar(size)
|
||||
| string - 设置 type(text) 时 | text
|
||||
| time.Time - 设置 type 为 date 时 | date
|
||||
| time.TIme | datetime
|
||||
| byte | tinyint unsigned
|
||||
| rune | integer
|
||||
| int | integer
|
||||
| int8 | tinyint
|
||||
| int16 | smallint
|
||||
| int32 | integer
|
||||
| int64 | bigint
|
||||
| uint | integer unsigned
|
||||
| uint8 | tinyint unsigned
|
||||
| uint16 | smallint unsigned
|
||||
| uint32 | integer unsigned
|
||||
| uint64 | bigint unsigned
|
||||
| float32 | real
|
||||
| float64 | real
|
||||
| float64 - 设置 digits, decimals 时 | decimal
|
||||
|
||||
#### PostgreSQL
|
||||
|
||||
| go | postgres
|
||||
| :--- | :---
|
||||
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | serial
|
||||
| bool | bool
|
||||
| string - 默认为 size 255 | varchar(size)
|
||||
| string - 设置 type(text) 时 | text
|
||||
| time.Time - 设置 type 为 date 时 | date
|
||||
| time.TIme | timestamp with time zone
|
||||
| byte | smallint CHECK("column" >= 0 AND "column" <= 255)
|
||||
| rune | integer
|
||||
| int | integer
|
||||
| int8 | smallint CHECK("column" >= -127 AND "column" <= 128)
|
||||
| int16 | smallint
|
||||
| int32 | integer
|
||||
| int64 | bigint
|
||||
| uint | bigint CHECK("column" >= 0)
|
||||
| uint8 | smallint CHECK("column" >= 0 AND "column" <= 255)
|
||||
| uint16 | integer CHECK("column" >= 0)
|
||||
| uint32 | bigint CHECK("column" >= 0)
|
||||
| uint64 | bigint CHECK("column" >= 0)
|
||||
| float32 | double precision
|
||||
| float64 | double precision
|
||||
| float64 - 设置 digits, decimals 时 | numeric(digits, decimals)
|
||||
|
||||
|
||||
## 关系型字段
|
||||
|
||||
其字段类型取决于对应的主键。
|
||||
|
||||
* RelForeignKey
|
||||
* RelOneToOne
|
||||
* RelManyToMany
|
||||
* RelReverseOne
|
||||
* RelReverseMany
|
83
orm/docs/zh/Models.sql
Normal file
@ -0,0 +1,83 @@
|
||||
SET NAMES utf8;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `comment`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `comment`;
|
||||
CREATE TABLE `comment` (
|
||||
`id` int(11) NOT NULL,
|
||||
`post_id` bigint(200) NOT NULL,
|
||||
`content` longtext NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`status` smallint(4) NOT NULL,
|
||||
`created` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `post`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `post`;
|
||||
CREATE TABLE `post` (
|
||||
`id` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`title` varchar(60) NOT NULL,
|
||||
`content` longtext NOT NULL,
|
||||
`created` datetime NOT NULL,
|
||||
`updated` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `post_tag_rel`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `post_tag_rel`;
|
||||
CREATE TABLE `post_tag_rel` (
|
||||
`id` int(11) NOT NULL,
|
||||
`post_id` int(11) NOT NULL,
|
||||
`tag_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `tag`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `tag`;
|
||||
CREATE TABLE `tag` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(30) NOT NULL,
|
||||
`status` smallint(4) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `user`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_name` varchar(30) NOT NULL,
|
||||
`email` varchar(100) NOT NULL,
|
||||
`password` varchar(30) NOT NULL,
|
||||
`status` smallint(4) NOT NULL,
|
||||
`is_staff` tinyint(1) NOT NULL,
|
||||
`is_active` tinyint(1) NOT NULL,
|
||||
`created` date NOT NULL,
|
||||
`updated` datetime NOT NULL,
|
||||
`profile_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for `profile`
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `profile`;
|
||||
CREATE TABLE `profile` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`age` smallint(4) NOT NULL,
|
||||
`money` double NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
59
orm/docs/zh/Object.md
Normal file
@ -0,0 +1,59 @@
|
||||
## 对象的CRUD操作
|
||||
|
||||
对 object 操作简单的三个方法 Read / Insert / Update / Delete
|
||||
```go
|
||||
o := orm.NewOrm()
|
||||
user := new(User)
|
||||
user.Name = "slene"
|
||||
|
||||
fmt.Println(o.Insert(user))
|
||||
|
||||
user.Name = "Your"
|
||||
fmt.Println(o.Update(user))
|
||||
fmt.Println(o.Read(user))
|
||||
fmt.Println(o.Delete(user))
|
||||
```
|
||||
### Read
|
||||
```go
|
||||
o := orm.NewOrm()
|
||||
user := User{Id: 1}
|
||||
|
||||
err = o.Read(&user)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
fmt.Println("查询不到")
|
||||
} else if err == orm.ErrMissPK {
|
||||
fmt.Println("找不到主键")
|
||||
} else {
|
||||
fmt.Println(user.Id, user.Name)
|
||||
}
|
||||
```
|
||||
### Insert
|
||||
```go
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
user.Name = "slene"
|
||||
user.IsActive = true
|
||||
|
||||
fmt.Println(o.Insert(&user))
|
||||
fmt.Println(user.Id)
|
||||
```
|
||||
创建后会自动对 auto 的 field 赋值
|
||||
|
||||
### Update
|
||||
```go
|
||||
o := orm.NewOrm()
|
||||
user := User{Id: 1}
|
||||
if o.Read(&user) == nil {
|
||||
user.Name = "MyName"
|
||||
o.Update(&user)
|
||||
}
|
||||
```
|
||||
### Delete
|
||||
```go
|
||||
o := orm.NewOrm()
|
||||
o.Delete(&User{Id: 1})
|
||||
```
|
||||
Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 User 的外键。删除 User 的时候。如果 on_delete 设置为默认的级联操作,将删除对应的 Post
|
||||
|
||||
删除以后会清除 auto field 的值
|