├── .gitignore ├── COPYING ├── README.md ├── WSP.go ├── conf ├── conf.go └── conf.json ├── controller ├── controller.go ├── history │ ├── history.go │ └── remove.go └── srv │ ├── add.go │ ├── delete.go │ ├── get.go │ ├── list.go │ ├── srv.go │ ├── srv_customer_list.go │ ├── srv_customer_proc.go │ ├── srv_delete.go │ ├── srv_get.go │ ├── srv_list.go │ ├── srv_update.go │ └── update.go ├── filter ├── auth.go ├── boss.go └── doc.go ├── main.go ├── model ├── history │ ├── add.go │ ├── get.go │ ├── history.go │ └── update.go ├── model.go └── srv │ ├── add.go │ ├── count.go │ ├── delete.go │ ├── get.go │ ├── list.go │ ├── srv.go │ └── update.go ├── mongo ├── mongo.go └── op.json └── vendor └── lib ├── base.go ├── code.go ├── def.go ├── doc.go ├── stru.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | /op -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020, 353474225@qq.com 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 通用op(后台运营)系统 2 | 3 | 通用op(后台运营)系统,后台开发工程师打造的针对后台服务的通用配置管理系统,带web操作界面 4 | 5 | 做这套系统的缘由: 6 | > 由于专业web前端开发人员支持不足,后台服务,没有web管理界面可用,管理后台服务很不方便,尤其是微服务化后,后台大大小小服务特别多,急需一个简单可视化的界面系统统一管理和利用这些服务 7 | 8 | 这套系统的最初设想: 9 | 1. 界面足够简单,html,js,css足够简单,因为后续维护开发都是由后台开发人员执行(非专业web前端) 10 | 2. 界面简单,但功能完善,支持列表,支持翻页,支持数据更新,删除等操作,支持外部接口调用,支持输入参数校验,支持输入参数按单选或者复选的方式辅助输入,支持按输入参数过滤数据等 11 | 3. 支持基本的后台服务上线,回滚,重启等操作,支持文件上传、下载 12 | 4. 集中式管理,支持区分账号的数据保存,比如支持操作历史保存和复用,支持基于账号的权限控制等 13 | 14 | 这套系统目前具有的功能: 15 | > 简单说,我对这套系统最初的设想,目前已全部实现,早在几年前,我的心里就萌生了要做成这套系统的想法,我做后台服务开发至今有10多年了,一直就想有这样的一套系统来管理项目里的各种后台服务,这个服务一定要满足:通用,实用,简单,现在这个最初的想法终于实现了。 16 | 17 | TODO: 18 | 1. 支持更细粒度的权限控制,比如某些配置项只能由创建者和管理员操作等,理想中这一功能需要一个通用的后台账号服务支持 19 | 2. 接口类型是list的接口支持对返回的参数做定制化配置,比如返回错误码定义,返回列表字段名等,也就是格式支持自定义,而不仅仅是目前内定好的格式,目前格式支持如下:json格式,需满足:{"ret": 1, "data": {"list": [...]}},ret存储返回码,1代表成功,其它为失败,data.list存储数组结构列表数据 20 | 3. 调用后端服务不仅仅限post方式,也不仅仅限json格式的body,比如支持form表单,get,put等 21 | 4. 支持针对输入输出字段配置描述信息 22 | 5. 支持接口类型是update的接口的操作历史保存 23 | 6. 为方便审计,支持操作历史记录永久存储备份,而不像现在针对每个人,每个接口,最多只保存一定数目的记录 24 | 7. 支持以现有配置为模板复制生成新的配置项 25 | 26 | 这套系统现在长什么样? 27 | 1. 有账号认证,如下图: 28 | 29 | ![1](https://github.com/simplejia/nothing/raw/master/1.png) 30 | 31 | 希望账号管理、权限认证系统是一个统一的,独立的后台账号管理服务,目前开源出去的是把这一步省掉了,大家可以根据自己的情况自行添加处理代码(filter/auth.go) 32 | 33 | 2. 首页,如下图: 34 | 35 | ![2](https://github.com/simplejia/nothing/raw/master/2.png) 36 | 37 | 列表里展示的是已配置的可用服务 38 | 39 | 3. 点击新建,进入服务配置页面,如下图: 40 | 41 | ![3](https://github.com/simplejia/nothing/raw/master/3.png) 42 |  43 | 44 | 4. 查看某一个配置项 45 | 46 | ![4](https://github.com/simplejia/nothing/raw/master/4.png) 47 |  48 | 49 | 5. 进入某一项(默认进入类型是list的action,如果没有配,就会进入类型是customer的列表) 50 | 51 | ![5](https://github.com/simplejia/nothing/raw/master/5.png) 52 |  53 | 54 | 如上,是因为配置了cid这个必填字段,limit和offset是可选字段,其中limit配置的默认值是20,执行后,显示如下: 55 | 56 | ![6](https://github.com/simplejia/nothing/raw/master/6.png) 57 | 58 |  59 | 以上是列表信息,一共返回1条数据,继续执行会把cid, limit, offset, total字段发给服务端作为输入参数 60 | 61 | ![7](https://github.com/simplejia/nothing/raw/master/7.png) 62 | 63 |  64 | 以上是没有配置list类型的action,进入某一项会返回类型是customer的列表(或者点击list类型的页面的“其它”进入) 65 | 66 | ![8](https://github.com/simplejia/nothing/raw/master/8.png) 67 | 68 |  69 | 以上是点击“更新”或“删除”进入的页面,每一项数据都可以修改,点击执行后会把以上数据post给服务端(具体执行接口是来自于update或delete类型的action配置) 70 | 71 | ![9](https://github.com/simplejia/nothing/raw/master/9.png) 72 | 73 |  74 | 以上是点击某一customer类型的action进入的页面,上面一部分是传给后端的输入参数,可以删除一些字段,不用传,下面一部分是执行过的历史,可以恢复记录重新执行。 75 | 76 | 安装使用: 77 | 1. 下载源代码 78 | > go get github.com/simplejia/op 79 | 2. 配置数据库 80 | > 目前的配置信息存储在mongo db,需要修改配置文件:mongo/op.json 81 | 3. 使用 82 | > 进入op目录,启动编译好的op程序,比如:./op -env dev,打开浏览器,输入网址,如果是本地测试运行,请输入:127.0.0.1:8336 83 | 84 | 85 | 最佳实践: 86 | 1. 基本功能演示(增删改查) 87 | 88 | 这是一个推送服务,以下是配置项: 89 | 90 | ![10](https://github.com/simplejia/nothing/raw/master/10.png) 91 | 92 | ![11](https://github.com/simplejia/nothing/raw/master/11.png) 93 |  94 |  95 | 96 | 这是配置项的进入页(列表页): 97 | 98 | ![12](https://github.com/simplejia/nothing/raw/master/12.png) 99 | 100 | ![13](https://github.com/simplejia/nothing/raw/master/13.png) 101 |  102 | 103 |  104 | 这是点击列表页的更新/删除: 105 | 106 | ![14](https://github.com/simplejia/nothing/raw/master/14.png) 107 |  108 | 109 | 2. 服务上线 110 | 111 | 这是一个php的上线功能,以下是配置项: 112 | 113 | ![15](https://github.com/simplejia/nothing/raw/master/15.png) 114 |  115 | 116 | 这是配置项的进入页: 117 | 118 | ![16](https://github.com/simplejia/nothing/raw/master/16.png) 119 |  120 | 121 | 点击/online/trans_cmd: 122 | 123 | ![17](https://github.com/simplejia/nothing/raw/master/17.png) 124 |  125 | 126 | 点击/online/trans_file: 127 | 128 | ![18](https://github.com/simplejia/nothing/raw/master/18.png) 129 |  130 | 131 | 这是一个go服务的上线功能,以下是配置项: 132 | 133 | ![19](https://github.com/simplejia/nothing/raw/master/19.png) 134 |  135 | 136 | 这是配置项的进入页: 137 | 138 | ![20](https://github.com/simplejia/nothing/raw/master/20.png) 139 | 140 |  141 | 点击/online/trans_cmd: 142 | 143 | ![21](https://github.com/simplejia/nothing/raw/master/21.png) 144 | 145 |  146 | 点击/online/trans_file: 147 | 148 | ![22](https://github.com/simplejia/nothing/raw/master/22.png) 149 |  150 | 151 | 注:上线服务依赖:github.com/simplejia/online 152 | online项目用于提供远程文件上传及远程执行命令功能,类似运维工具:ansible 153 | 154 | 3. 下载数据 155 | 156 | 这是一个提供数据下载的配置项: 157 | 158 | ![23](https://github.com/simplejia/nothing/raw/master/23.png) 159 | 160 |  161 | 注意下类型:transparent,这个类型表示后端接口返回什么数据,页面上直接展示,不做任何处理 162 | 163 | 以下是配置项的进入页,点击执行后的效果:(提示有文件正在下载) 164 | 165 | ![24](https://github.com/simplejia/nothing/raw/master/24.png) 166 |  167 | 168 | 4. 复杂功能演示 169 | 170 | 这是一个提供视频处理功能的配置项:(提供按条件过滤的功能) 171 | 172 | ![25](https://github.com/simplejia/nothing/raw/master/25.png) 173 |  174 | 175 | 这是配置项的进入页: 176 | 177 | ![27](https://github.com/simplejia/nothing/raw/master/27.png) 178 |  179 | 180 | 注意“remote_ip”这个字段,配置“数据源”是“从URL”,此ip列表是调用配置的url接口返回的结果 181 | 182 | 点击“执行”后的运行结果:(部分结果如下) 183 | 184 | ![28](https://github.com/simplejia/nothing/raw/master/28.png) 185 |  186 | 187 | 188 | 这是一个提供视频处理功能的配置项:(提供更好的报表展示功能) 189 | 190 | ![26](https://github.com/simplejia/nothing/raw/master/26.png) 191 |  192 | 193 | 这是配置项的进入页: 194 | 195 | ![29](https://github.com/simplejia/nothing/raw/master/29.png) 196 |  197 | 198 | 点击“执行”后的运行结果:(部分结果如下) 199 | 200 | ![30](https://github.com/simplejia/nothing/raw/master/30.png) 201 |  202 | 203 | 注:此表格是调用的后端接口直接吐出来的html代码 204 | 205 | 206 | ## 依赖 207 | wsp: github.com/simplejia/wsp 208 | clog: github.com/simplejia/clog 209 | utils: github.com/simplejia/utils 210 | namecli: github.com/simplejia/namecli 211 | mongo: github.com/globalsign/mgo 212 | 213 | ## 注意 214 | > 如果在controller里修改了路由,编译前需执行go generate,实际是运行了wsp这个工具,所以需要提前go get github.com/simplejia/wsp 215 | -------------------------------------------------------------------------------- /WSP.go: -------------------------------------------------------------------------------- 1 | // generated by wsp, DO NOT EDIT. 2 | 3 | package main 4 | 5 | import "net/http" 6 | import "time" 7 | import "github.com/simplejia/op/controller/history" 8 | import "github.com/simplejia/op/controller/srv" 9 | import "github.com/simplejia/op/filter" 10 | 11 | func init() { 12 | http.HandleFunc("/history/remove", func(w http.ResponseWriter, r *http.Request) { 13 | t := time.Now() 14 | _ = t 15 | var e interface{} 16 | c := new(history.History) 17 | defer func() { 18 | e = recover() 19 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/history/remove"}); !ok { 20 | return 21 | } 22 | }() 23 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/history/remove"}); !ok { 24 | return 25 | } 26 | c.Remove(w, r) 27 | }) 28 | 29 | http.HandleFunc("/srv/add", func(w http.ResponseWriter, r *http.Request) { 30 | t := time.Now() 31 | _ = t 32 | var e interface{} 33 | c := new(srv.Srv) 34 | defer func() { 35 | e = recover() 36 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/add"}); !ok { 37 | return 38 | } 39 | }() 40 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/add"}); !ok { 41 | return 42 | } 43 | c.Add(w, r) 44 | }) 45 | 46 | http.HandleFunc("/srv/delete", func(w http.ResponseWriter, r *http.Request) { 47 | t := time.Now() 48 | _ = t 49 | var e interface{} 50 | c := new(srv.Srv) 51 | defer func() { 52 | e = recover() 53 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/delete"}); !ok { 54 | return 55 | } 56 | }() 57 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/delete"}); !ok { 58 | return 59 | } 60 | c.Delete(w, r) 61 | }) 62 | 63 | http.HandleFunc("/srv/get", func(w http.ResponseWriter, r *http.Request) { 64 | t := time.Now() 65 | _ = t 66 | var e interface{} 67 | c := new(srv.Srv) 68 | defer func() { 69 | e = recover() 70 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/get"}); !ok { 71 | return 72 | } 73 | }() 74 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/get"}); !ok { 75 | return 76 | } 77 | c.Get(w, r) 78 | }) 79 | 80 | http.HandleFunc("/srv/list", func(w http.ResponseWriter, r *http.Request) { 81 | t := time.Now() 82 | _ = t 83 | var e interface{} 84 | c := new(srv.Srv) 85 | defer func() { 86 | e = recover() 87 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/list"}); !ok { 88 | return 89 | } 90 | }() 91 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/list"}); !ok { 92 | return 93 | } 94 | c.List(w, r) 95 | }) 96 | 97 | http.HandleFunc("/srv/srv_customer_list", func(w http.ResponseWriter, r *http.Request) { 98 | t := time.Now() 99 | _ = t 100 | var e interface{} 101 | c := new(srv.Srv) 102 | defer func() { 103 | e = recover() 104 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_customer_list"}); !ok { 105 | return 106 | } 107 | }() 108 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_customer_list"}); !ok { 109 | return 110 | } 111 | c.SrvCustomerList(w, r) 112 | }) 113 | 114 | http.HandleFunc("/srv/srv_customer_proc", func(w http.ResponseWriter, r *http.Request) { 115 | t := time.Now() 116 | _ = t 117 | var e interface{} 118 | c := new(srv.Srv) 119 | defer func() { 120 | e = recover() 121 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_customer_proc"}); !ok { 122 | return 123 | } 124 | }() 125 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_customer_proc"}); !ok { 126 | return 127 | } 128 | c.SrvCustomerProc(w, r) 129 | }) 130 | 131 | http.HandleFunc("/srv/srv_delete", func(w http.ResponseWriter, r *http.Request) { 132 | t := time.Now() 133 | _ = t 134 | var e interface{} 135 | c := new(srv.Srv) 136 | defer func() { 137 | e = recover() 138 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_delete"}); !ok { 139 | return 140 | } 141 | }() 142 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_delete"}); !ok { 143 | return 144 | } 145 | c.SrvDelete(w, r) 146 | }) 147 | 148 | http.HandleFunc("/srv/srv_get", func(w http.ResponseWriter, r *http.Request) { 149 | t := time.Now() 150 | _ = t 151 | var e interface{} 152 | c := new(srv.Srv) 153 | defer func() { 154 | e = recover() 155 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_get"}); !ok { 156 | return 157 | } 158 | }() 159 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_get"}); !ok { 160 | return 161 | } 162 | c.SrvGet(w, r) 163 | }) 164 | 165 | http.HandleFunc("/srv/srv_list", func(w http.ResponseWriter, r *http.Request) { 166 | t := time.Now() 167 | _ = t 168 | var e interface{} 169 | c := new(srv.Srv) 170 | defer func() { 171 | e = recover() 172 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_list"}); !ok { 173 | return 174 | } 175 | }() 176 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_list"}); !ok { 177 | return 178 | } 179 | c.SrvList(w, r) 180 | }) 181 | 182 | http.HandleFunc("/srv/srv_update", func(w http.ResponseWriter, r *http.Request) { 183 | t := time.Now() 184 | _ = t 185 | var e interface{} 186 | c := new(srv.Srv) 187 | defer func() { 188 | e = recover() 189 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_update"}); !ok { 190 | return 191 | } 192 | }() 193 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/srv_update"}); !ok { 194 | return 195 | } 196 | c.SrvUpdate(w, r) 197 | }) 198 | 199 | http.HandleFunc("/srv/update", func(w http.ResponseWriter, r *http.Request) { 200 | t := time.Now() 201 | _ = t 202 | var e interface{} 203 | c := new(srv.Srv) 204 | defer func() { 205 | e = recover() 206 | if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/update"}); !ok { 207 | return 208 | } 209 | }() 210 | if ok := filter.Auth(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e, "__P__": "/srv/update"}); !ok { 211 | return 212 | } 213 | c.Update(w, r) 214 | }) 215 | 216 | } -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package conf 用于项目基本配置。 3 | */ 4 | package conf 5 | 6 | import ( 7 | "encoding/json" 8 | "flag" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/simplejia/utils" 15 | ) 16 | 17 | // Conf 定义配置参数 18 | type Conf struct { 19 | // 基本配置 20 | App *struct { 21 | Name string 22 | Port int 23 | } 24 | // clog日志输出配置 25 | Clog *struct { 26 | Name string 27 | Mode int 28 | Level int 29 | } 30 | // 各种名字或addr配置 31 | Addrs *struct { 32 | Clog string 33 | } 34 | } 35 | 36 | var ( 37 | // Env 代表当前运行环境 38 | Env string 39 | // C 代表当前运行配置对象 40 | C *Conf 41 | // TestCase 运行单元测试时,会设为true 42 | TestCase bool 43 | ) 44 | 45 | func init() { 46 | var env string 47 | flag.StringVar(&env, "env", "prod", "set env") 48 | var test bool 49 | flag.BoolVar(&test, "test", false, "set test case flag") 50 | flag.Parse() 51 | 52 | Env = env 53 | TestCase = test 54 | 55 | dir := "conf" 56 | for i := 0; i < 3; i++ { 57 | if info, err := os.Stat(dir); err == nil && info.IsDir() { 58 | break 59 | } 60 | dir = filepath.Join("..", dir) 61 | } 62 | fcontent, err := ioutil.ReadFile(filepath.Join(dir, "conf.json")) 63 | if err != nil { 64 | log.Printf("get conf file contents error: %v\n", err) 65 | os.Exit(-1) 66 | } 67 | 68 | fcontent = utils.RemoveAnnotation(fcontent) 69 | var envs map[string]*Conf 70 | if err := json.Unmarshal(fcontent, &envs); err != nil { 71 | log.Printf("conf.json wrong format: %v\n", err) 72 | os.Exit(-1) 73 | } 74 | 75 | C = envs[env] 76 | if C == nil { 77 | log.Printf("env not right: %s\n", env) 78 | os.Exit(-1) 79 | } 80 | 81 | log.Printf("env: %s\nconf: %s\n", env, utils.Iprint(C)) 82 | } 83 | -------------------------------------------------------------------------------- /conf/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "prod": { // 运行环境,启动时通过-env参数指定,如:$prog -env prod 3 | "app": { 4 | "name": "op", 5 | "port": 8336 // 服务监听端口 6 | }, 7 | "clog": { 8 | "name":"op", 9 | "mode": 2, // 0: none, 1: localfile, 2: clog (数字代表bit位) 10 | "level": 14 // 0: none, 1: debug, 2: warn 4: error 8: info (数字代表bit位) 11 | }, 12 | "addrs": { // 定义各种名字服务 13 | "clog": "clog.srv.ns" 14 | } 15 | }, 16 | "dev": { // 运行环境,启动时通过-env参数指定,如:$prog -env dev 17 | "app": { 18 | "name": "op", 19 | "port": 8336 // 服务监听端口 20 | }, 21 | "clog": { 22 | "name":"op", 23 | "mode": 3, // 0: none, 1: localfile, 2: clog (数字代表bit位) 24 | "level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (数字代表bit位) 25 | }, 26 | "addrs": { // 定义各种名字服务 27 | "clog": "127.0.0.1:8313" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package controller 用于定义控制层代码。 3 | 只允许在这里添加对外暴露的接口 4 | */ 5 | package controller 6 | -------------------------------------------------------------------------------- /controller/history/history.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "lib" 5 | ) 6 | 7 | type History struct { 8 | lib.Base 9 | } 10 | -------------------------------------------------------------------------------- /controller/history/remove.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "lib" 9 | 10 | "github.com/simplejia/clog/api" 11 | "github.com/simplejia/op/model" 12 | "github.com/simplejia/utils" 13 | ) 14 | 15 | // @prefilter("Auth") 16 | // @postfilter("Boss") 17 | func (history *History) Remove(w http.ResponseWriter, r *http.Request) { 18 | fun := "history.History.Update" 19 | 20 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 21 | actionPath := r.URL.Query().Get("action_path") 22 | pos, _ := strconv.Atoi(r.URL.Query().Get("pos")) 23 | 24 | historyModel := model.NewHistory() 25 | historyModel.SrvID = id 26 | historyModel.SrvActionPath = actionPath 27 | headerVal, _ := history.GetParam(lib.KeyHeader) 28 | header := headerVal.(*lib.Header) 29 | historyModel.Uid = header.ID 30 | historyModel, err := historyModel.GetByUidAndSrv() 31 | if err != nil { 32 | detail := fmt.Sprintf("%s history.GetByUidAndSrv err: %v, req: %v", fun, err, historyModel) 33 | clog.Error(detail) 34 | history.ReplyFailWithDetail(w, lib.CodeSrv, detail) 35 | return 36 | } 37 | 38 | if historyModel == nil || pos >= len(historyModel.Details) { 39 | detail := fmt.Sprintf("%s history invalid", fun) 40 | clog.Error(detail) 41 | history.ReplyFailWithDetail(w, lib.CodeSrv, detail) 42 | return 43 | } 44 | 45 | historyModel.Details = append(historyModel.Details[:pos], historyModel.Details[pos+1:]...) 46 | 47 | if err := historyModel.Update(); err != nil { 48 | detail := fmt.Sprintf("%s history.Update err: %v, req: %v", fun, err, utils.Iprint(historyModel)) 49 | clog.Error(detail) 50 | history.ReplyFailWithDetail(w, lib.CodeSrv, detail) 51 | return 52 | } 53 | 54 | http.Redirect(w, r, r.Referer(), http.StatusFound) 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /controller/srv/add.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/simplejia/utils" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | ) 15 | 16 | var AddTpl = ` 17 | 18 | 19 | 20 | 54 | 55 | 152 | 153 | 154 | 返回首页 155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 |
name
name(次要)
desc
addr
163 |

164 | 165 | 166 |

167 |

168 |

169 |
170 | 171 | 172 | ` 173 | 174 | // @prefilter("Auth") 175 | // @postfilter("Boss") 176 | func (srv *Srv) Add(w http.ResponseWriter, r *http.Request) { 177 | fun := "srv.Srv.Add" 178 | 179 | if r.PostFormValue("_") == "" { 180 | w.Write([]byte(AddTpl)) 181 | return 182 | } 183 | 184 | srvModel := model.NewSrv() 185 | if err := srvModel.ParseFromRequest(r); err != nil { 186 | detail := fmt.Sprintf("%s param err: %v", fun, err) 187 | clog.Error(detail) 188 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 189 | return 190 | } 191 | 192 | srvModelExist, err := srvModel.GetByName() 193 | if err != nil { 194 | detail := fmt.Sprintf("%s srv.GetByName err: %v, req: %v", fun, err, srvModel) 195 | clog.Error(detail) 196 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 197 | return 198 | } 199 | 200 | if srvModelExist != nil { 201 | detail := fmt.Sprintf("%s name has exist, req: %v", fun, srvModelExist) 202 | clog.Error(detail) 203 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 204 | return 205 | } 206 | 207 | if err := srvModel.Add(); err != nil { 208 | detail := fmt.Sprintf("%s srv.Add err: %v, req: %v", fun, err, utils.Iprint(srvModel)) 209 | clog.Error(detail) 210 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 211 | return 212 | } 213 | 214 | srv.WriteJson(w, nil) 215 | } 216 | -------------------------------------------------------------------------------- /controller/srv/delete.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/simplejia/clog/api" 9 | 10 | "lib" 11 | 12 | "github.com/simplejia/op/model" 13 | ) 14 | 15 | // @prefilter("Auth") 16 | // @postfilter("Boss") 17 | func (srv *Srv) Delete(w http.ResponseWriter, r *http.Request) { 18 | fun := "srv.Srv.Delete" 19 | 20 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 21 | 22 | srvModel := model.NewSrv() 23 | srvModel.ID = id 24 | if err := srvModel.Delete(); err != nil { 25 | detail := fmt.Sprintf("%s srv.Delete err: %v, req: %v", fun, err, id) 26 | clog.Error(detail) 27 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 28 | return 29 | } 30 | 31 | srv.WriteJson(w, nil) 32 | } 33 | -------------------------------------------------------------------------------- /controller/srv/get.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | ) 15 | 16 | var GetTpl = ` 17 | 18 | 19 | 20 | 57 | 58 | 189 | 190 | 191 | 返回首页 192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |
ID
name
name(次要)
desc
addr
201 |

202 | 203 | 204 |

205 |

206 |

207 |

208 |

209 |
210 | 211 | 212 | ` 213 | 214 | // @prefilter("Auth") 215 | // @postfilter("Boss") 216 | func (srv *Srv) Get(w http.ResponseWriter, r *http.Request) { 217 | fun := "srv.Srv.Get" 218 | 219 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 220 | 221 | srvModel := model.NewSrv() 222 | srvModel.ID = id 223 | srvModel, err := srvModel.Get() 224 | if err != nil || srvModel == nil { 225 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 226 | clog.Error(detail) 227 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 228 | return 229 | } 230 | 231 | data := srvModel 232 | 233 | tpl := template.Must(template.New("get").Parse(GetTpl)) 234 | if err := tpl.Execute(w, data); err != nil { 235 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 236 | clog.Error(detail) 237 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 238 | return 239 | } 240 | 241 | return 242 | } 243 | -------------------------------------------------------------------------------- /controller/srv/list.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | ) 15 | 16 | var ListTpl = ` 17 | 18 | 19 | 20 | 55 | 57 | 58 | 59 | 返回首页 60 |
61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {{range .list}} 71 | 72 | 73 | 74 | 79 | 80 | 81 | {{end}} 82 | 83 |
ID名字名字(次要)描述地址操作
{{.ID}}{{.MName}}{{.SName}}{{.Desc}}{{.Addr}} 75 | 76 | 77 | 78 |
84 | 85 | 86 | 87 | 88 | 89 | ` 90 | 91 | // @prefilter("Auth") 92 | // @postfilter("Boss") 93 | func (srv *Srv) List(w http.ResponseWriter, r *http.Request) { 94 | fun := "srv.Srv.List" 95 | 96 | offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) 97 | limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) 98 | 99 | if limit <= 0 { 100 | limit = 50 101 | } 102 | 103 | total, err := model.NewSrv().Count() 104 | if err != nil { 105 | detail := fmt.Sprintf("%s srv.Count err: %v", fun, err) 106 | clog.Error(detail) 107 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 108 | return 109 | } 110 | 111 | srvs, err := model.NewSrv().List(offset, limit) 112 | if err != nil { 113 | detail := fmt.Sprintf("%s srv.List err: %v", fun, err) 114 | clog.Error(detail) 115 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 116 | return 117 | } 118 | 119 | data := map[string]interface{}{ 120 | "list": srvs, 121 | "offset": offset + len(srvs), 122 | "limit": limit, 123 | "total": total, 124 | } 125 | 126 | tpl := template.Must(template.New("list").Parse(ListTpl)) 127 | if err := tpl.Execute(w, data); err != nil { 128 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 129 | clog.Error(detail) 130 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 131 | return 132 | } 133 | 134 | return 135 | } 136 | -------------------------------------------------------------------------------- /controller/srv/srv.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "html" 8 | "io" 9 | "net/http" 10 | "strconv" 11 | "strings" 12 | 13 | "lib" 14 | 15 | srv_model "github.com/simplejia/op/model/srv" 16 | ) 17 | 18 | type Srv struct { 19 | lib.Base 20 | } 21 | 22 | func (srv *Srv) FieldValue(field *srv_model.SrvField, value string) (ret interface{}, err error) { 23 | switch field.Kind { 24 | case srv_model.FieldKindInteger: 25 | if value == "" { 26 | ret = 0 27 | } else { 28 | var v uint64 29 | v, err := strconv.ParseUint(value, 10, 64) 30 | if err != nil { 31 | return nil, err 32 | } 33 | ret = v 34 | } 35 | case srv_model.FieldKindString: 36 | if value == "" { 37 | ret = "" 38 | } else { 39 | if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { 40 | v, err := strconv.Unquote(value) 41 | if err != nil { 42 | return nil, err 43 | } 44 | ret = v 45 | } else { 46 | ret = value 47 | } 48 | } 49 | case srv_model.FieldKindFloat: 50 | if value == "" { 51 | ret = 0.0 52 | } else { 53 | var v float64 54 | v, err := strconv.ParseFloat(value, 64) 55 | if err != nil { 56 | return nil, err 57 | } 58 | ret = v 59 | } 60 | case srv_model.FieldKindBool: 61 | if value == "" { 62 | ret = false 63 | } else { 64 | var v bool 65 | v, err := strconv.ParseBool(value) 66 | if err != nil { 67 | return nil, err 68 | } 69 | ret = v 70 | } 71 | case srv_model.FieldKindMap: 72 | if value == "" { 73 | ret = nil 74 | } else { 75 | var v map[string]json.RawMessage 76 | err := json.Unmarshal([]byte(value), &v) 77 | if err != nil { 78 | return nil, err 79 | } 80 | ret = v 81 | } 82 | case srv_model.FieldKindArray: 83 | if value == "" { 84 | ret = nil 85 | } else { 86 | var v []json.RawMessage 87 | err := json.Unmarshal([]byte(value), &v) 88 | if err != nil { 89 | return nil, err 90 | } 91 | ret = v 92 | } 93 | case srv_model.FieldKindNone: 94 | if value == "" { 95 | ret = "" 96 | } else { 97 | var v json.RawMessage 98 | if err := json.Unmarshal([]byte(value), &v); err != nil { 99 | return nil, err 100 | } 101 | ret = v 102 | } 103 | case srv_model.FieldKindFile: 104 | ret = value 105 | } 106 | 107 | return 108 | } 109 | 110 | func (srv *Srv) FormToMap(r *http.Request, srvActionField *srv_model.SrvActionField) (result map[string]interface{}, err error) { 111 | result = map[string]interface{}{} 112 | 113 | r.ParseForm() 114 | 115 | fields := map[string]*srv_model.SrvField{} 116 | for _, field := range srvActionField.Fields { 117 | fields[field.Name] = field 118 | } 119 | 120 | for name, vs := range r.PostForm { 121 | if name == "_" { 122 | continue 123 | } 124 | 125 | field := fields[name] 126 | if field == nil { 127 | value := vs[0] 128 | if value == "" { 129 | result[name] = "" 130 | } else { 131 | var v json.RawMessage 132 | if err := json.Unmarshal([]byte(value), &v); err != nil { 133 | return nil, err 134 | } 135 | result[name] = v 136 | } 137 | continue 138 | } 139 | 140 | if field.Kind == srv_model.FieldKindArray && 141 | (field.Source == srv_model.FieldSourceUrl || field.Source == srv_model.FieldSourceArray) { 142 | var r []json.RawMessage 143 | for _, value := range vs { 144 | var v json.RawMessage 145 | if err := json.Unmarshal([]byte(value), &v); err != nil { 146 | return nil, err 147 | } 148 | r = append(r, v) 149 | } 150 | result[name] = r 151 | continue 152 | } 153 | 154 | value := vs[0] 155 | ret, err := srv.FieldValue(field, value) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | result[name] = ret 161 | } 162 | 163 | for _, field := range srvActionField.Fields { 164 | name := field.Name 165 | if r.PostFormValue(name) != "" { 166 | continue 167 | } 168 | 169 | param := field.Param 170 | if field.Required && 171 | (field.Source != srv_model.FieldSourceUser || param == "") { 172 | err = fmt.Errorf("%s must supply", name) 173 | return nil, err 174 | } 175 | 176 | if field.Source != srv_model.FieldSourceUser { 177 | continue 178 | } 179 | 180 | v, err := srv.FieldValue(field, param) 181 | if err != nil { 182 | return nil, err 183 | } 184 | result[name] = v 185 | } 186 | 187 | return 188 | } 189 | 190 | func (srv *Srv) WriteJson(w http.ResponseWriter, body []byte) { 191 | t := ` 192 | 193 | 返回首页 194 | %s 195 | ` 196 | 197 | if len(body) > 0 { 198 | t = fmt.Sprintf(t, fmt.Sprintf(` 199 | 217 | `, strconv.Quote(html.EscapeString(string(body))))) 218 | } else { 219 | t = fmt.Sprintf(t, "") 220 | } 221 | 222 | io.WriteString(w, t) 223 | 224 | return 225 | } 226 | 227 | func IsAInB(a string, b string) (ok bool) { 228 | var vs []json.RawMessage 229 | if err := json.Unmarshal([]byte(b), &vs); err != nil { 230 | return 231 | } 232 | 233 | for _, v := range vs { 234 | if a == string(v) { 235 | return true 236 | } 237 | } 238 | 239 | return 240 | } 241 | 242 | type FieldValueDesc struct { 243 | Value string 244 | Desc string 245 | } 246 | 247 | func ParseFieldParam(param string) (valueDescs []*FieldValueDesc, err error) { 248 | var vs1 []map[string]json.RawMessage 249 | if err = json.Unmarshal([]byte(param), &vs1); err == nil && len(vs1) > 0 { 250 | for _, v := range vs1 { 251 | var desc string 252 | var value json.RawMessage 253 | for desc, value = range v { 254 | break 255 | } 256 | 257 | if len(value) == 0 || desc == "" { 258 | break 259 | } 260 | 261 | valueDescs = append(valueDescs, &FieldValueDesc{ 262 | Value: string(value), 263 | Desc: desc, 264 | }) 265 | } 266 | 267 | if len(vs1) == len(valueDescs) { 268 | return 269 | } 270 | 271 | valueDescs = nil 272 | } 273 | 274 | var vs2 []json.RawMessage 275 | if err = json.Unmarshal([]byte(param), &vs2); err == nil { 276 | for _, v := range vs2 { 277 | valueDescs = append(valueDescs, &FieldValueDesc{ 278 | Value: string(v), 279 | Desc: string(v), 280 | }) 281 | } 282 | return 283 | } 284 | 285 | err = errors.New("parse field param err") 286 | return 287 | } 288 | -------------------------------------------------------------------------------- /controller/srv/srv_customer_list.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | srv_model "github.com/simplejia/op/model/srv" 15 | ) 16 | 17 | var SrvCustomerListTpl = ` 18 | 19 | 20 | 21 | 64 | 65 | 67 | 68 | 69 | {{$id := .id}} 70 | 返回首页 71 | {{with .actions}} 72 | 73 | 74 | 75 | 76 | 77 | {{range .}} 78 | 79 | 80 | 81 | 82 | {{end}} 83 | 84 |
PATH描述
{{.Desc}}
85 | {{end}} 86 | 87 | 88 | ` 89 | 90 | // @prefilter("Auth") 91 | // @postfilter("Boss") 92 | func (srv *Srv) SrvCustomerList(w http.ResponseWriter, r *http.Request) { 93 | fun := "srv.Srv.SrvCustomerList" 94 | 95 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 96 | 97 | srvModel := model.NewSrv() 98 | srvModel.ID = id 99 | srvModel, err := srvModel.Get() 100 | if err != nil || srvModel == nil { 101 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 102 | clog.Error(detail) 103 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 104 | return 105 | } 106 | 107 | var actions []*srv_model.SrvAction 108 | for _, actionField := range srvModel.ActionFields { 109 | if actionField.Action.Kind != srv_model.ActionKindCustomer && actionField.Action.Kind != srv_model.ActionKindTransparent { 110 | continue 111 | } 112 | 113 | actions = append(actions, actionField.Action) 114 | } 115 | 116 | data := map[string]interface{}{ 117 | "id": id, 118 | "actions": actions, 119 | } 120 | 121 | tpl := template.Must(template.New("srv_customer_list").Parse(SrvCustomerListTpl)) 122 | if err := tpl.Execute(w, data); err != nil { 123 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 124 | clog.Error(detail) 125 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 126 | return 127 | } 128 | 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /controller/srv/srv_customer_proc.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/simplejia/clog/api" 12 | 13 | "lib" 14 | 15 | "github.com/simplejia/op/model" 16 | history_model "github.com/simplejia/op/model/history" 17 | srv_model "github.com/simplejia/op/model/srv" 18 | ) 19 | 20 | var SrvCustomerProcTpl = ` 21 | 22 | 23 | 24 | 75 | 76 | 118 | 119 | 120 | {{$id := .id}} 121 | {{$action_path := .action_path}} 122 | {{$file_fields := .file_fields}} 123 | {{$select_field_params := .select_field_params}} 124 | {{$multi_select_field_params := .multi_select_field_params}} 125 | 返回首页 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | {{range $k, $v := .fields}} 134 | 135 | 136 | {{if eq (index $file_fields $k) true}} 137 | 141 | {{else if ne ((index $select_field_params $k)|len) 0}} 142 | 147 | {{else if ne ((index $multi_select_field_params $k)|len) 0}} 148 | 156 | {{else}} 157 | 160 | {{end}} 161 | 164 | 165 | {{end}} 166 | 167 |
{{$k}} 138 | 139 | 140 | 143 | {{range index $select_field_params $k}} 144 | {{.Desc}} 145 | {{end}} 146 | 149 |

150 | * 151 |

152 | {{range index $multi_select_field_params $k}} 153 | {{.Desc}} 154 | {{end}} 155 |
158 | 159 | 162 | 163 |
168 | 169 |
170 | {{with .history_details}} 171 | 172 | 173 | 174 | 175 | {{range $pos, $elem := .}} 176 | 177 | 178 | 183 | 184 | 190 | 191 | 192 | {{end}} 193 |
参数时间操作
179 | {{range $k, $v := $elem.M}} 180 | {{$k}}: {{truncate $v 512 "..."}} 181 | {{end}} 182 | {{.Ct}} 185 |

186 | 187 | 188 |

189 |
194 | {{end}} 195 | 196 | 197 | ` 198 | 199 | type KeyValue struct { 200 | Key string `json:"key"` 201 | Value string `json:"value"` 202 | } 203 | 204 | // @prefilter("Auth") 205 | // @postfilter("Boss") 206 | func (srv *Srv) SrvCustomerProc(w http.ResponseWriter, r *http.Request) { 207 | fun := "srv.Srv.SrvCustomerProc" 208 | 209 | r.Body = http.MaxBytesReader(w, r.Body, 1e9) 210 | 211 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 212 | actionPath := r.URL.Query().Get("action_path") 213 | 214 | srvModel := model.NewSrv() 215 | srvModel.ID = id 216 | srvModel, err := srvModel.Get() 217 | if err != nil || srvModel == nil { 218 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 219 | clog.Error(detail) 220 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 221 | return 222 | } 223 | 224 | var srvActionField *srv_model.SrvActionField 225 | for _, actionField := range srvModel.ActionFields { 226 | if actionField.Action.Path == actionPath { 227 | srvActionField = actionField 228 | break 229 | } 230 | } 231 | 232 | if srvActionField == nil { 233 | detail := fmt.Sprintf("%s actionField empty", fun) 234 | clog.Error(detail) 235 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 236 | return 237 | } 238 | 239 | fileFields := map[string]bool{} 240 | selectFieldParams := make(map[string][]*FieldValueDesc) 241 | multiSelectFieldParams := make(map[string][]*FieldValueDesc) 242 | for _, field := range srvActionField.Fields { 243 | if field.Kind == srv_model.FieldKindFile { 244 | fileFields[field.Name] = true 245 | } else if field.Source == srv_model.FieldSourceArray { 246 | options, err := ParseFieldParam(field.Param) 247 | if err != nil { 248 | detail := fmt.Sprintf("%s field err: %v, req: %v", fun, err, field.Param) 249 | clog.Error(detail) 250 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 251 | return 252 | } 253 | if field.Kind == srv_model.FieldKindArray { 254 | multiSelectFieldParams[field.Name] = options 255 | } else { 256 | selectFieldParams[field.Name] = options 257 | } 258 | } else if field.Source == srv_model.FieldSourceUrl { 259 | path := field.Param 260 | body, err := lib.PostProxy(srvModel.Addr, path, nil) 261 | if err != nil { 262 | detail := fmt.Sprintf("%s get field param post err: %v, req: %v", fun, err, path) 263 | clog.Error(detail) 264 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 265 | return 266 | } 267 | 268 | resp := &struct { 269 | lib.Resp 270 | Data struct { 271 | List json.RawMessage 272 | } `json:"data"` 273 | }{} 274 | if err := json.Unmarshal([]byte(body), &resp); err != nil || resp.Ret != lib.CodeOk { 275 | detail := fmt.Sprintf("%s get field post response err: %v, req: %v, resp: %s", fun, err, path, body) 276 | clog.Error(detail) 277 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 278 | return 279 | } 280 | 281 | options, err := ParseFieldParam(string(resp.Data.List)) 282 | if err != nil { 283 | detail := fmt.Sprintf("%s field err: %v, req: %v", fun, err, field.Param) 284 | clog.Error(detail) 285 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 286 | return 287 | } 288 | 289 | if field.Kind == srv_model.FieldKindArray { 290 | multiSelectFieldParams[field.Name] = options 291 | } else { 292 | selectFieldParams[field.Name] = options 293 | } 294 | } 295 | } 296 | 297 | if r.FormValue("_") == "" { 298 | fields := map[string]string{} 299 | for _, field := range srvActionField.Fields { 300 | if field.Source == srv_model.FieldSourceUser { 301 | fields[field.Name] = field.Param 302 | } else { 303 | fields[field.Name] = "" 304 | } 305 | } 306 | 307 | r.ParseForm() 308 | for k, vs := range r.PostForm { 309 | fields[k] = vs[0] 310 | } 311 | 312 | historyModel := model.NewHistory() 313 | historyModel.SrvID = id 314 | historyModel.SrvActionPath = actionPath 315 | headerVal, _ := srv.GetParam(lib.KeyHeader) 316 | header := headerVal.(*lib.Header) 317 | historyModel.Uid = header.ID 318 | historyModel, err := historyModel.GetByUidAndSrv() 319 | if err != nil { 320 | detail := fmt.Sprintf("%s history.GetByUidAndSrv err: %v, req: %v", fun, err, historyModel) 321 | clog.Error(detail) 322 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 323 | return 324 | } 325 | 326 | var historyDetails []*history_model.HistoryDetail 327 | if historyModel != nil { 328 | historyDetails = historyModel.Details 329 | } 330 | 331 | data := map[string]interface{}{ 332 | "id": id, 333 | "action_path": actionPath, 334 | "fields": fields, 335 | "file_fields": fileFields, 336 | "history_details": historyDetails, 337 | "select_field_params": selectFieldParams, 338 | "multi_select_field_params": multiSelectFieldParams, 339 | } 340 | 341 | funcMap := template.FuncMap{ 342 | "truncate": lib.TruncateWithSuffix, 343 | "is_a_in_b": IsAInB, 344 | } 345 | tpl := template.Must(template.New("srv_customer_proc").Funcs(funcMap).Parse(SrvCustomerProcTpl)) 346 | if err := tpl.Execute(w, data); err != nil { 347 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 348 | clog.Error(detail) 349 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 350 | return 351 | } 352 | return 353 | } 354 | 355 | result, err := srv.FormToMap(r, srvActionField) 356 | if err != nil { 357 | detail := fmt.Sprintf("%s field err: %v", fun, err) 358 | clog.Error(detail) 359 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 360 | return 361 | } 362 | 363 | var body []byte 364 | if srvActionField.Action.Kind == srv_model.ActionKindTransparent { 365 | req, _ := json.Marshal(result) 366 | _body, header, err := lib.PostProxyReturnHeader(srvModel.Addr, srvActionField.Action.Path, req) 367 | if err != nil { 368 | detail := fmt.Sprintf("%s post err: %v", fun, err) 369 | clog.Error(detail) 370 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 371 | return 372 | } 373 | for k, vs := range header { 374 | for _, v := range vs { 375 | w.Header().Add(k, v) 376 | } 377 | } 378 | body = _body 379 | } else { 380 | req, _ := json.Marshal(result) 381 | _body, err := lib.PostProxy(srvModel.Addr, srvActionField.Action.Path, req) 382 | if err != nil { 383 | detail := fmt.Sprintf("%s post err: %v", fun, err) 384 | clog.Error(detail) 385 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 386 | return 387 | } 388 | body = _body 389 | } 390 | 391 | if srvActionField.Action.Kind == srv_model.ActionKindTransparent { 392 | w.Write(body) 393 | } else { 394 | srv.WriteJson(w, body) 395 | } 396 | 397 | fields := map[string]string{} 398 | 399 | for name, v := range result { 400 | if fileFields[name] { 401 | continue 402 | } 403 | 404 | s, _ := json.Marshal(v) 405 | fields[name] = string(s) 406 | } 407 | 408 | historyDetail := &history_model.HistoryDetail{ 409 | M: fields, 410 | Ct: time.Now(), 411 | } 412 | historyModel := model.NewHistory() 413 | historyModel.SrvID = id 414 | historyModel.SrvActionPath = actionPath 415 | historyModel.Details = append(historyModel.Details, historyDetail) 416 | headerVal, _ := srv.GetParam(lib.KeyHeader) 417 | header := headerVal.(*lib.Header) 418 | historyModel.Uid = header.ID 419 | 420 | if err := historyModel.Add(); err != nil { 421 | detail := fmt.Sprintf("%s history.Add err: %v, req: %v", fun, err, historyModel) 422 | clog.Warn(detail) 423 | } 424 | 425 | return 426 | } 427 | -------------------------------------------------------------------------------- /controller/srv/srv_delete.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | srv_model "github.com/simplejia/op/model/srv" 15 | ) 16 | 17 | // @prefilter("Auth") 18 | // @postfilter("Boss") 19 | func (srv *Srv) SrvDelete(w http.ResponseWriter, r *http.Request) { 20 | fun := "srv.Srv.SrvDelete" 21 | 22 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 23 | 24 | srvModel := model.NewSrv() 25 | srvModel.ID = id 26 | srvModel, err := srvModel.Get() 27 | if err != nil || srvModel == nil { 28 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 29 | clog.Error(detail) 30 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 31 | return 32 | } 33 | 34 | var srvActionField *srv_model.SrvActionField 35 | for _, actionField := range srvModel.ActionFields { 36 | if actionField.Action.Kind == srv_model.ActionKindDelete { 37 | srvActionField = actionField 38 | break 39 | } 40 | } 41 | 42 | if srvActionField == nil { 43 | detail := fmt.Sprintf("%s must set delete action", fun) 44 | clog.Error(detail) 45 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 46 | return 47 | } 48 | 49 | result, err := srv.FormToMap(r, srvActionField) 50 | if err != nil { 51 | detail := fmt.Sprintf("%s field err: %v", fun, err) 52 | clog.Error(detail) 53 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 54 | return 55 | } 56 | 57 | req, _ := json.Marshal(result) 58 | body, err := lib.PostProxy(srvModel.Addr, srvActionField.Action.Path, req) 59 | if err != nil { 60 | detail := fmt.Sprintf("%s post err: %v", fun, err) 61 | clog.Error(detail) 62 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 63 | return 64 | } 65 | 66 | srv.WriteJson(w, body) 67 | } 68 | -------------------------------------------------------------------------------- /controller/srv/srv_get.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | srv_model "github.com/simplejia/op/model/srv" 15 | ) 16 | 17 | var SrvGetTpl = ` 18 | 19 | 20 | 21 | 59 | 60 | 65 | 66 | 67 | 返回首页 68 |
69 | 70 | 71 | 72 | 73 | 74 | {{range $k, $v := .fields}} 75 | 76 | 77 | 78 | 79 | 80 | {{end}} 81 | 82 |
{{$k}}
83 | {{if eq .crud "u"}} 84 | 85 | {{else}} 86 | 87 | {{end}} 88 |
89 | 90 | 91 | ` 92 | 93 | // @prefilter("Auth") 94 | // @postfilter("Boss") 95 | func (srv *Srv) SrvGet(w http.ResponseWriter, r *http.Request) { 96 | fun := "srv.Srv.SrvGet" 97 | 98 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 99 | crud := r.URL.Query().Get("crud") 100 | 101 | srvModel := model.NewSrv() 102 | srvModel.ID = id 103 | srvModel, err := srvModel.Get() 104 | if err != nil { 105 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 106 | clog.Error(detail) 107 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 108 | return 109 | } 110 | 111 | var srvActionField *srv_model.SrvActionField 112 | for _, actionField := range srvModel.ActionFields { 113 | if crud == "u" { 114 | if actionField.Action.Kind == srv_model.ActionKindUpdate { 115 | srvActionField = actionField 116 | break 117 | } 118 | } else { 119 | if actionField.Action.Kind == srv_model.ActionKindDelete { 120 | srvActionField = actionField 121 | break 122 | } 123 | } 124 | } 125 | 126 | if srvActionField == nil { 127 | detail := fmt.Sprintf("%s must set proper action", fun) 128 | clog.Error(detail) 129 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 130 | return 131 | } 132 | 133 | fields := map[string]string{} 134 | for _, field := range srvActionField.Fields { 135 | fields[field.Name] = field.Param 136 | } 137 | 138 | r.ParseForm() 139 | for k, v := range r.PostForm { 140 | fields[k] = v[0] 141 | } 142 | 143 | data := map[string]interface{}{ 144 | "id": id, 145 | "fields": fields, 146 | "crud": crud, 147 | } 148 | 149 | tpl := template.Must(template.New("srv_get").Parse(SrvGetTpl)) 150 | if err := tpl.Execute(w, data); err != nil { 151 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 152 | clog.Error(detail) 153 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 154 | return 155 | } 156 | 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /controller/srv/srv_list.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/simplejia/clog/api" 11 | 12 | "lib" 13 | 14 | "github.com/simplejia/op/model" 15 | srv_model "github.com/simplejia/op/model/srv" 16 | ) 17 | 18 | var SrvListTpl = ` 19 | 20 | 21 | 22 | 65 | 66 | 76 | 77 | 78 | {{$id := .id}} 79 | {{$select_field_params := .select_field_params}} 80 | {{$multi_select_field_params := .multi_select_field_params}} 81 | 返回首页 82 | 其它 83 |
84 | 85 | 86 | {{range $k, $v := .fields}} 87 | 88 | 89 | {{if ne ((index $select_field_params $k)|len) 0}} 90 | 95 | {{else if ne ((index $multi_select_field_params $k)|len) 0}} 96 | 104 | {{else}} 105 | 108 | {{end}} 109 | 110 | 111 | {{end}} 112 |
{{$k}} 91 | {{range index $select_field_params $k}} 92 | {{.Desc}} 93 | {{end}} 94 | 97 |

98 | * 99 |

100 | {{range index $multi_select_field_params $k}} 101 | {{.Desc}} 102 | {{end}} 103 |
106 | 107 |
113 | 114 |
115 | {{with .list}} 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | {{range $pos, $elem := .}} 126 | 127 | 128 | 129 | 134 | 138 | 139 | 140 | {{end}} 141 | 142 |
序号内容操作
{{$pos}} 130 | {{range $k, $v := $elem}} 131 | {{$k}}: {{truncate $v 512 "..."}} 132 | {{end}} 133 | 135 | 136 | 137 |
143 | {{end}} 144 | 145 | 146 | ` 147 | 148 | // @prefilter("Auth") 149 | // @postfilter("Boss") 150 | func (srv *Srv) SrvList(w http.ResponseWriter, r *http.Request) { 151 | fun := "srv.Srv.SrvList" 152 | 153 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 154 | 155 | srvModel := model.NewSrv() 156 | srvModel.ID = id 157 | srvModel, err := srvModel.Get() 158 | if err != nil || srvModel == nil { 159 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 160 | clog.Error(detail) 161 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 162 | return 163 | } 164 | 165 | var srvActionField *srv_model.SrvActionField 166 | for _, actionField := range srvModel.ActionFields { 167 | if actionField.Action.Kind == srv_model.ActionKindList { 168 | srvActionField = actionField 169 | break 170 | } 171 | } 172 | 173 | if srvActionField == nil { 174 | http.Redirect(w, r, fmt.Sprintf("/srv/srv_customer_list?id=%d", id), http.StatusFound) 175 | return 176 | } 177 | 178 | selectFieldParams := make(map[string][]*FieldValueDesc) 179 | multiSelectFieldParams := make(map[string][]*FieldValueDesc) 180 | for _, field := range srvActionField.Fields { 181 | if field.Source == srv_model.FieldSourceArray { 182 | options, err := ParseFieldParam(field.Param) 183 | if err != nil { 184 | detail := fmt.Sprintf("%s field err: %v, req: %v", fun, err, field.Param) 185 | clog.Error(detail) 186 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 187 | return 188 | } 189 | if field.Kind == srv_model.FieldKindArray { 190 | multiSelectFieldParams[field.Name] = options 191 | } else { 192 | selectFieldParams[field.Name] = options 193 | } 194 | } else if field.Source == srv_model.FieldSourceUrl { 195 | path := field.Param 196 | body, err := lib.PostProxy(srvModel.Addr, path, nil) 197 | if err != nil { 198 | detail := fmt.Sprintf("%s get field param post err: %v, req: %v", fun, err, path) 199 | clog.Error(detail) 200 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 201 | return 202 | } 203 | 204 | resp := &struct { 205 | lib.Resp 206 | Data struct { 207 | List json.RawMessage 208 | } `json:"data"` 209 | }{} 210 | if err := json.Unmarshal([]byte(body), &resp); err != nil || resp.Ret != lib.CodeOk { 211 | detail := fmt.Sprintf("%s get field post response err: %v, req: %v, resp: %s", fun, err, path, body) 212 | clog.Error(detail) 213 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 214 | return 215 | } 216 | 217 | options, err := ParseFieldParam(string(resp.Data.List)) 218 | if err != nil { 219 | detail := fmt.Sprintf("%s field err: %v, req: %v", fun, err, field.Param) 220 | clog.Error(detail) 221 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 222 | return 223 | } 224 | 225 | if field.Kind == srv_model.FieldKindArray { 226 | multiSelectFieldParams[field.Name] = options 227 | } else { 228 | selectFieldParams[field.Name] = options 229 | } 230 | } 231 | } 232 | 233 | fields := map[string]string{} 234 | for _, field := range srvActionField.Fields { 235 | if field.Source == srv_model.FieldSourceUser { 236 | fields[field.Name] = field.Param 237 | } else { 238 | fields[field.Name] = "" 239 | } 240 | } 241 | 242 | if r.PostFormValue("_") == "" { 243 | needSupply := false 244 | for _, field := range srvActionField.Fields { 245 | if field.Required && 246 | (field.Source != srv_model.FieldSourceUser || field.Param == "") { 247 | needSupply = true 248 | break 249 | } 250 | } 251 | 252 | if needSupply { 253 | data := map[string]interface{}{ 254 | "id": id, 255 | "fields": fields, 256 | "select_field_params": selectFieldParams, 257 | "multi_select_field_params": multiSelectFieldParams, 258 | } 259 | 260 | funcMap := template.FuncMap{ 261 | "truncate": lib.TruncateWithSuffix, 262 | "is_a_in_b": IsAInB, 263 | } 264 | tpl := template.Must(template.New("srv_list").Funcs(funcMap).Parse(SrvListTpl)) 265 | if err := tpl.Execute(w, data); err != nil { 266 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 267 | clog.Error(detail) 268 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 269 | return 270 | } 271 | return 272 | } 273 | } 274 | 275 | result, err := srv.FormToMap(r, srvActionField) 276 | if err != nil { 277 | detail := fmt.Sprintf("%s field err: %v", fun, err) 278 | clog.Error(detail) 279 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 280 | return 281 | } 282 | 283 | req, _ := json.Marshal(result) 284 | body, err := lib.PostProxy(srvModel.Addr, srvActionField.Action.Path, req) 285 | if err != nil { 286 | detail := fmt.Sprintf("%s post err: %v", fun, err) 287 | clog.Error(detail) 288 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 289 | return 290 | } 291 | 292 | resp := &struct { 293 | lib.Resp 294 | Data map[string]json.RawMessage `json:"data"` 295 | }{} 296 | 297 | if err := json.Unmarshal(body, resp); err != nil { 298 | detail := fmt.Sprintf("%s decode err: %v, req: %s", fun, err, body) 299 | clog.Error(detail) 300 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 301 | return 302 | } 303 | 304 | if resp.Ret != lib.CodeOk { 305 | srv.ReplyOk(w, resp) 306 | return 307 | } 308 | 309 | list := []map[string]interface{}{} 310 | 311 | if l := resp.Data["list"]; len(l) > 0 { 312 | respDataList := []map[string]json.RawMessage{} 313 | if err := json.Unmarshal(l, &respDataList); err != nil { 314 | detail := fmt.Sprintf("%s list element not found in resp data, err: %v, req: %s", fun, err, body) 315 | clog.Error(detail) 316 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 317 | return 318 | } 319 | 320 | for _, srcElem := range respDataList { 321 | dstElem := map[string]interface{}{} 322 | for k, srcV := range srcElem { 323 | dstElem[k] = string(srcV) 324 | } 325 | 326 | list = append(list, dstElem) 327 | } 328 | } 329 | 330 | for name, v := range result { 331 | s, _ := json.Marshal(v) 332 | fields[name] = string(s) 333 | } 334 | 335 | for k, v := range resp.Data { 336 | if k == "list" { 337 | continue 338 | } 339 | 340 | fields[k] = string(v) 341 | } 342 | 343 | data := map[string]interface{}{ 344 | "id": id, 345 | "list": list, 346 | "fields": fields, 347 | "select_field_params": selectFieldParams, 348 | "multi_select_field_params": multiSelectFieldParams, 349 | } 350 | 351 | funcMap := template.FuncMap{ 352 | "truncate": lib.TruncateWithSuffix, 353 | "is_a_in_b": IsAInB, 354 | } 355 | tpl := template.Must(template.New("srv_list").Funcs(funcMap).Parse(SrvListTpl)) 356 | if err := tpl.Execute(w, data); err != nil { 357 | detail := fmt.Sprintf("%s tpl err: %v", fun, err) 358 | clog.Error(detail) 359 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 360 | return 361 | } 362 | 363 | return 364 | } 365 | -------------------------------------------------------------------------------- /controller/srv/srv_update.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | srv_model "github.com/simplejia/op/model/srv" 15 | ) 16 | 17 | // @prefilter("Auth") 18 | // @postfilter("Boss") 19 | func (srv *Srv) SrvUpdate(w http.ResponseWriter, r *http.Request) { 20 | fun := "srv.Srv.SrvUpdate" 21 | 22 | id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) 23 | 24 | srvModel := model.NewSrv() 25 | srvModel.ID = id 26 | srvModel, err := srvModel.Get() 27 | if err != nil || srvModel == nil { 28 | detail := fmt.Sprintf("%s srv.Get err: %v, req: %v", fun, err, id) 29 | clog.Error(detail) 30 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 31 | return 32 | } 33 | 34 | var srvActionField *srv_model.SrvActionField 35 | for _, actionField := range srvModel.ActionFields { 36 | if actionField.Action.Kind == srv_model.ActionKindUpdate { 37 | srvActionField = actionField 38 | break 39 | } 40 | } 41 | 42 | if srvActionField == nil { 43 | detail := fmt.Sprintf("%s must set update action", fun) 44 | clog.Error(detail) 45 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 46 | return 47 | } 48 | 49 | result, err := srv.FormToMap(r, srvActionField) 50 | if err != nil { 51 | detail := fmt.Sprintf("%s field err: %v", fun, err) 52 | clog.Error(detail) 53 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 54 | return 55 | } 56 | 57 | req, _ := json.Marshal(result) 58 | body, err := lib.PostProxy(srvModel.Addr, srvActionField.Action.Path, req) 59 | if err != nil { 60 | detail := fmt.Sprintf("%s post err: %v", fun, err) 61 | clog.Error(detail) 62 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 63 | return 64 | } 65 | 66 | srv.WriteJson(w, body) 67 | } 68 | -------------------------------------------------------------------------------- /controller/srv/update.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/simplejia/utils" 8 | 9 | "github.com/simplejia/clog/api" 10 | 11 | "lib" 12 | 13 | "github.com/simplejia/op/model" 14 | ) 15 | 16 | // @prefilter("Auth") 17 | // @postfilter("Boss") 18 | func (srv *Srv) Update(w http.ResponseWriter, r *http.Request) { 19 | fun := "srv.Srv.Update" 20 | 21 | srvModel := model.NewSrv() 22 | if err := srvModel.ParseFromRequest(r); err != nil { 23 | detail := fmt.Sprintf("%s param err: %v", fun, err) 24 | clog.Error(detail) 25 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 26 | return 27 | } 28 | 29 | srvModelExist, err := srvModel.GetByName() 30 | if err != nil { 31 | detail := fmt.Sprintf("%s srv.GetByName err: %v, req: %v", fun, err, srvModel) 32 | clog.Error(detail) 33 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 34 | return 35 | } 36 | 37 | if srvModelExist != nil && srvModelExist.ID != srvModel.ID { 38 | detail := fmt.Sprintf("%s name has exist, req: %v", fun, srvModelExist) 39 | clog.Error(detail) 40 | srv.ReplyFailWithDetail(w, lib.CodePara, detail) 41 | return 42 | } 43 | 44 | if err := srvModel.Update(); err != nil { 45 | detail := fmt.Sprintf("%s srv.Update err: %v, req: %v", fun, err, utils.Iprint(srvModel)) 46 | clog.Error(detail) 47 | srv.ReplyFailWithDetail(w, lib.CodeSrv, detail) 48 | return 49 | } 50 | 51 | srv.WriteJson(w, nil) 52 | } 53 | -------------------------------------------------------------------------------- /filter/auth.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "strconv" 7 | 8 | "lib" 9 | 10 | "github.com/simplejia/op/conf" 11 | ) 12 | 13 | // Auth 前置过滤器,用于登陆态校验,权限校验 14 | func Auth(w http.ResponseWriter, r *http.Request, m map[string]interface{}) (ok bool) { 15 | if conf.Env == lib.DEV { 16 | c := m["__C__"].(lib.IBase) 17 | header := &lib.Header{ 18 | ID: 1, 19 | Token: "", 20 | } 21 | c.SetParam(lib.KeyHeader, header) 22 | return true 23 | } 24 | 25 | defer func() { 26 | if ok { 27 | return 28 | } 29 | 30 | io.WriteString(w, ` 31 | 32 | 返回首页 33 |
34 | 35 | 36 | 37 |
用户:
密码:
38 |

39 |
40 | `) 41 | }() 42 | 43 | cookieID, _ := r.Cookie("h_op_id") 44 | if cookieID == nil || cookieID.Value == "" { 45 | return false 46 | } 47 | id, _ := strconv.ParseInt(cookieID.Value, 10, 64) 48 | 49 | cookieToken, _ := r.Cookie("h_op_token") 50 | if cookieToken == nil || cookieToken.Value == "" { 51 | return false 52 | } 53 | token := cookieToken.Value 54 | 55 | // TODO: check id & token 56 | 57 | c := m["__C__"].(lib.IBase) 58 | header := &lib.Header{ 59 | ID: id, 60 | Token: token, 61 | } 62 | c.SetParam(lib.KeyHeader, header) 63 | 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /filter/boss.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | "time" 7 | 8 | "lib" 9 | 10 | "github.com/simplejia/clog/api" 11 | ) 12 | 13 | // Boss 后置过滤器,用于数据上报,比如调用延时,出错等 14 | func Boss(w http.ResponseWriter, r *http.Request, m map[string]interface{}) bool { 15 | err := m["__E__"] 16 | path := m["__P__"] 17 | bt := m["__T__"].(time.Time) 18 | 19 | if r.Form == nil { 20 | r.ParseForm() 21 | } 22 | 23 | for _, vs := range r.Form { 24 | for pos, v := range vs { 25 | if maxlen := 512; len(v) > maxlen { 26 | vs[pos] = lib.TruncateWithSuffix(v, maxlen, "...") 27 | } 28 | } 29 | } 30 | 31 | if err != nil { 32 | clog.Error("Boss() path: %v, body: %v, err: %v, stack: %s", path, r.Form, err, debug.Stack()) 33 | w.WriteHeader(http.StatusInternalServerError) 34 | } else { 35 | clog.Info("Boss() path: %v, body: %v, elapse: %s", path, r.Form, time.Since(bt)) 36 | } 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /filter/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package filter 定义各种过滤器组件,包括前置过滤器,后置过滤器。 3 | */ 4 | package filter 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 通用op(后台运营)系统 2 | // author: simplejia 3 | // date: 2017/12/8 4 | 5 | //go:generate wsp -s -d 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "lib" 12 | "net/http" 13 | 14 | "github.com/simplejia/clog/api" 15 | "github.com/simplejia/utils" 16 | 17 | "github.com/simplejia/op/conf" 18 | ) 19 | 20 | func init() { 21 | clog.AddrFunc = func() (string, error) { 22 | return lib.NameWrap(conf.C.Addrs.Clog) 23 | } 24 | clog.Init(conf.C.Clog.Name, "", conf.C.Clog.Level, conf.C.Clog.Mode) 25 | 26 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 27 | if r.URL.Path == "/favicon.ico" { 28 | return 29 | } 30 | http.Redirect(w, r, "/srv/list", http.StatusFound) 31 | }) 32 | } 33 | 34 | func main() { 35 | fun := "main" 36 | clog.Info(fun) 37 | 38 | addr := fmt.Sprintf("%s:%d", "0.0.0.0", conf.C.App.Port) 39 | err := utils.ListenAndServe(addr, nil) 40 | if err != nil { 41 | clog.Error("%s err: %v, addr: %v", fun, err, addr) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /model/history/add.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/globalsign/mgo/bson" 7 | ) 8 | 9 | func (history *History) Add() (err error) { 10 | c := history.GetC() 11 | defer c.Database.Session.Close() 12 | 13 | up := bson.M{ 14 | "$push": bson.M{ 15 | "details": bson.M{ 16 | "$each": history.Details, 17 | "$slice": 60, 18 | "$position": 0, 19 | }, 20 | }, 21 | "$set": bson.M{ 22 | "ut": time.Now(), 23 | }, 24 | } 25 | 26 | q := bson.M{ 27 | "uid": history.Uid, 28 | "srv_id": history.SrvID, 29 | "srv_action_path": history.SrvActionPath, 30 | } 31 | _, err = c.Upsert(q, up) 32 | if err != nil { 33 | return 34 | } 35 | 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /model/history/get.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | mgo "github.com/globalsign/mgo" 5 | "github.com/globalsign/mgo/bson" 6 | ) 7 | 8 | func (history *History) GetByUidAndSrv() (historyRet *History, err error) { 9 | c := history.GetC() 10 | defer c.Database.Session.Close() 11 | 12 | q := bson.M{ 13 | "uid": history.Uid, 14 | "srv_id": history.SrvID, 15 | "srv_action_path": history.SrvActionPath, 16 | } 17 | err = c.Find(q).One(&historyRet) 18 | if err != nil { 19 | if err != mgo.ErrNotFound { 20 | return 21 | } 22 | 23 | err = nil 24 | return 25 | } 26 | 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /model/history/history.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "time" 5 | 6 | mgo "github.com/globalsign/mgo" 7 | "github.com/globalsign/mgo/bson" 8 | "github.com/simplejia/op/mongo" 9 | ) 10 | 11 | type HistoryDetail struct { 12 | M map[string]string 13 | Ct time.Time 14 | } 15 | 16 | type History struct { 17 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 18 | Uid int64 `json:"uid" bson:"uid"` 19 | SrvID int64 `json:"srv_id,omitempty" bson:"srv_id,omitempty"` 20 | SrvActionPath string `json:"srv_action_path,omitempty" bson:"srv_action_path,omitempty"` 21 | Details []*HistoryDetail `json:"details,omitempty" bson:"details,omitempty"` 22 | Ut time.Time `json:"ut" bson:"ut"` 23 | } 24 | 25 | func NewHistory() *History { 26 | return &History{} 27 | } 28 | 29 | func (history *History) Db() (db string) { 30 | return "op" 31 | } 32 | 33 | func (history *History) Table() (table string) { 34 | return "history" 35 | } 36 | 37 | func (history *History) GetC() (c *mgo.Collection) { 38 | db, table := history.Db(), history.Table() 39 | session := mongo.DBS[db] 40 | sessionCopy := session.Copy() 41 | c = sessionCopy.DB(db).C(table) 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /model/history/update.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | mgo "github.com/globalsign/mgo" 5 | "github.com/globalsign/mgo/bson" 6 | ) 7 | 8 | func (history *History) Update() (err error) { 9 | c := history.GetC() 10 | defer c.Database.Session.Close() 11 | 12 | q := bson.M{ 13 | "uid": history.Uid, 14 | "srv_id": history.SrvID, 15 | "srv_action_path": history.SrvActionPath, 16 | } 17 | err = c.Update(q, history) 18 | if err != nil { 19 | if err != mgo.ErrNotFound { 20 | return 21 | } 22 | 23 | err = nil 24 | return 25 | } 26 | 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /model/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package model 用于模型层定义,所有db及cache对象封装均定义在这里。 3 | 只允许在这里添加对外暴露的接口 4 | */ 5 | package model 6 | 7 | import ( 8 | "github.com/simplejia/op/model/history" 9 | "github.com/simplejia/op/model/srv" 10 | ) 11 | 12 | func NewSrv() *srv.Srv { 13 | return srv.NewSrv() 14 | } 15 | 16 | func NewHistory() *history.History { 17 | return history.NewHistory() 18 | } 19 | -------------------------------------------------------------------------------- /model/srv/add.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "time" 5 | 6 | mgo "github.com/globalsign/mgo" 7 | ) 8 | 9 | func (srv *Srv) Add() (err error) { 10 | c := srv.GetC() 11 | defer c.Database.Session.Close() 12 | 13 | if srv.ID <= 0 { 14 | var _srv *Srv 15 | err = c.Find(nil).Sort("-_id").Limit(1).One(&_srv) 16 | if err != nil { 17 | if err != mgo.ErrNotFound { 18 | return 19 | } 20 | srv.ID = 100000 21 | err = nil 22 | } else { 23 | srv.ID = _srv.ID + 1 24 | } 25 | } 26 | 27 | srv.Ct = time.Now() 28 | err = c.Insert(srv) 29 | if err != nil { 30 | return 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /model/srv/count.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | func (srv *Srv) Count() (total int, err error) { 4 | c := srv.GetC() 5 | defer c.Database.Session.Close() 6 | 7 | total, err = c.Find(nil).Count() 8 | if err != nil { 9 | return 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /model/srv/delete.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | func (srv *Srv) Delete() (err error) { 4 | c := srv.GetC() 5 | defer c.Database.Session.Close() 6 | 7 | err = c.RemoveId(srv.ID) 8 | if err != nil { 9 | return 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /model/srv/get.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | mgo "github.com/globalsign/mgo" 5 | "github.com/globalsign/mgo/bson" 6 | ) 7 | 8 | func (srv *Srv) Get() (srvRet *Srv, err error) { 9 | c := srv.GetC() 10 | defer c.Database.Session.Close() 11 | 12 | err = c.FindId(srv.ID).One(&srvRet) 13 | if err != nil { 14 | if err != mgo.ErrNotFound { 15 | return 16 | } 17 | err = nil 18 | return 19 | } 20 | 21 | return 22 | } 23 | 24 | func (srv *Srv) GetByName() (srvRet *Srv, err error) { 25 | c := srv.GetC() 26 | defer c.Database.Session.Close() 27 | 28 | q := bson.M{ 29 | "m_name": srv.MName, 30 | "s_name": srv.SName, 31 | } 32 | err = c.Find(q).One(&srvRet) 33 | if err != nil { 34 | if err != mgo.ErrNotFound { 35 | return 36 | } 37 | err = nil 38 | return 39 | } 40 | 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /model/srv/list.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | func (srv *Srv) List(offset, limit int) (srvs []*Srv, err error) { 4 | c := srv.GetC() 5 | defer c.Database.Session.Close() 6 | 7 | err = c.Find(nil).Sort("_id").Skip(offset).Limit(limit).All(&srvs) 8 | if err != nil { 9 | return 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /model/srv/srv.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | mgo "github.com/globalsign/mgo" 11 | "github.com/simplejia/op/mongo" 12 | ) 13 | 14 | type FieldKind int 15 | 16 | const ( 17 | FieldKindNone FieldKind = iota 18 | FieldKindString 19 | FieldKindInteger 20 | FieldKindFloat 21 | FieldKindBool 22 | FieldKindMap 23 | FieldKindArray 24 | FieldKindFile 25 | ) 26 | 27 | type ActionKind int 28 | 29 | const ( 30 | ActionKindNone ActionKind = iota 31 | ActionKindList 32 | ActionKindUpdate 33 | ActionKindDelete 34 | ActionKindCustomer 35 | ActionKindTransparent 36 | ) 37 | 38 | type FieldSource int 39 | 40 | const ( 41 | FieldSourceNone FieldSource = iota 42 | FieldSourceUser 43 | FieldSourceArray 44 | FieldSourceUrl 45 | ) 46 | 47 | type SrvAction struct { 48 | Path string `json:"path" bson:"path"` 49 | Desc string `json:"desc" bson:"desc"` 50 | Kind ActionKind `json:"kind" bson:"kind"` 51 | } 52 | 53 | func (srvAction *SrvAction) Regular() (ok bool) { 54 | if srvAction == nil { 55 | return 56 | } 57 | 58 | srvAction.Path = strings.TrimSpace(srvAction.Path) 59 | if srvAction.Path == "" { 60 | return 61 | } 62 | 63 | if srvAction.Kind == ActionKindNone { 64 | return 65 | } 66 | 67 | ok = true 68 | return 69 | } 70 | 71 | type SrvField struct { 72 | Name string `json:"name" bson:"name"` 73 | Required bool `json:"required" bson:"required"` 74 | Kind FieldKind `json:"kind" bson:"kind"` 75 | Source FieldSource `json:"source" bson:"source"` 76 | Param string `json:"param,omitempty" bson:"param,omitempty"` 77 | } 78 | 79 | func (srvField *SrvField) Regular() (ok bool) { 80 | if srvField == nil { 81 | return 82 | } 83 | 84 | srvField.Name = strings.TrimSpace(srvField.Name) 85 | if srvField.Name == "" { 86 | return 87 | } 88 | 89 | if srvField.Source == FieldSourceNone { 90 | return 91 | } 92 | 93 | ok = true 94 | return 95 | } 96 | 97 | type SrvActionField struct { 98 | Action *SrvAction `json:"action" bson:"action"` 99 | Fields []*SrvField `json:"fields" bson:"fields"` 100 | } 101 | 102 | func (srvActionField *SrvActionField) Regular() (ok bool) { 103 | if srvActionField == nil { 104 | return 105 | } 106 | 107 | if !srvActionField.Action.Regular() { 108 | return 109 | } 110 | 111 | for _, field := range srvActionField.Fields { 112 | if !field.Regular() { 113 | return 114 | } 115 | } 116 | 117 | ok = true 118 | return 119 | } 120 | 121 | type Srv struct { 122 | ID int64 `json:"id" bson:"_id"` 123 | MName string `json:"m_name" bson:"m_name"` 124 | SName string `json:"s_name" bson:"s_name"` 125 | Desc string `json:"desc" bisn:"desc"` 126 | Addr string `json:"addr" bson:"addr"` 127 | ActionFields []*SrvActionField `json:"action_fields" bson:"action_fields"` 128 | Ct time.Time `json:"ct" bson:"ct"` 129 | Ut time.Time `json:"ut" bson:"ut"` 130 | } 131 | 132 | func NewSrv() *Srv { 133 | return &Srv{} 134 | } 135 | 136 | func (srv *Srv) Db() (db string) { 137 | return "op" 138 | } 139 | 140 | func (srv *Srv) Table() (table string) { 141 | return "srv" 142 | } 143 | 144 | func (srv *Srv) GetC() (c *mgo.Collection) { 145 | db, table := srv.Db(), srv.Table() 146 | session := mongo.DBS[db] 147 | sessionCopy := session.Copy() 148 | c = sessionCopy.DB(db).C(table) 149 | return 150 | } 151 | 152 | func (srv *Srv) Regular() (ok bool) { 153 | if srv == nil { 154 | return 155 | } 156 | 157 | srv.MName = strings.TrimSpace(srv.MName) 158 | if srv.MName == "" { 159 | return 160 | } 161 | 162 | srv.SName = strings.TrimSpace(srv.SName) 163 | if srv.SName == "" { 164 | return 165 | } 166 | 167 | srv.Addr = strings.TrimSpace(srv.Addr) 168 | if srv.Addr == "" { 169 | return 170 | } 171 | 172 | for _, actionField := range srv.ActionFields { 173 | if !actionField.Regular() { 174 | return 175 | } 176 | } 177 | 178 | ok = true 179 | return 180 | } 181 | 182 | func (srv *Srv) ParseFromRequest(r *http.Request) (err error) { 183 | if srv == nil { 184 | err = errors.New("srv empty") 185 | return 186 | } 187 | 188 | srv.ID, _ = strconv.ParseInt(r.PostFormValue("id"), 10, 64) 189 | srv.MName = r.PostFormValue("m_name") 190 | srv.SName = r.PostFormValue("s_name") 191 | srv.Desc = r.PostFormValue("desc") 192 | srv.Addr = r.PostFormValue("addr") 193 | 194 | actionKinds := map[ActionKind]int{} 195 | actionPaths := map[string]int{} 196 | 197 | maxActionNum := 50 198 | 199 | for actionPos := 0; actionPos < maxActionNum; actionPos++ { 200 | actionPosStr := strconv.Itoa(actionPos) 201 | 202 | actionPath := r.PostFormValue("action_path_" + actionPosStr) 203 | if actionPath == "" { 204 | continue 205 | } 206 | 207 | if actionPaths[actionPath]++; actionPaths[actionPath] > 1 { 208 | err = errors.New("action path err, multi exist") 209 | return 210 | } 211 | 212 | actionKind, _ := strconv.Atoi(r.PostFormValue("action_kind_" + actionPosStr)) 213 | _actionKind := ActionKind(actionKind) 214 | 215 | if _actionKind != ActionKindCustomer && _actionKind != ActionKindTransparent { 216 | if actionKinds[_actionKind]++; actionKinds[_actionKind] > 1 { 217 | err = errors.New("action kind err, multi exist, but customer or transparent kind is unlimit") 218 | return 219 | } 220 | } 221 | 222 | srvActionField := &SrvActionField{} 223 | actionDesc := r.PostFormValue("action_desc_" + actionPosStr) 224 | srvActionField.Action = &SrvAction{ 225 | Path: actionPath, 226 | Desc: actionDesc, 227 | Kind: _actionKind, 228 | } 229 | 230 | fieldNames := r.PostForm["field_name_"+actionPosStr] 231 | fieldKinds := r.PostForm["field_kind_"+actionPosStr] 232 | fieldRequires := r.PostForm["field_require_"+actionPosStr] 233 | fieldSources := r.PostForm["field_source_"+actionPosStr] 234 | fieldParams := r.PostForm["field_param_"+actionPosStr] 235 | for fieldPos, fieldName := range fieldNames { 236 | if fieldName == "" { 237 | continue 238 | } 239 | 240 | fieldRequire, _ := strconv.ParseBool(fieldRequires[fieldPos]) 241 | fieldKind, _ := strconv.Atoi(fieldKinds[fieldPos]) 242 | fieldSource, _ := strconv.Atoi(fieldSources[fieldPos]) 243 | fieldParam := fieldParams[fieldPos] 244 | srvActionField.Fields = append(srvActionField.Fields, &SrvField{ 245 | Name: fieldName, 246 | Required: fieldRequire, 247 | Kind: FieldKind(fieldKind), 248 | Source: FieldSource(fieldSource), 249 | Param: fieldParam, 250 | }) 251 | } 252 | 253 | srv.ActionFields = append(srv.ActionFields, srvActionField) 254 | } 255 | 256 | if !srv.Regular() { 257 | err = errors.New("input invalid") 258 | return 259 | } 260 | 261 | return 262 | } 263 | -------------------------------------------------------------------------------- /model/srv/update.go: -------------------------------------------------------------------------------- 1 | package srv 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func (srv *Srv) Update() (err error) { 8 | c := srv.GetC() 9 | defer c.Database.Session.Close() 10 | 11 | srv.Ut = time.Now() 12 | err = c.UpdateId(srv.ID, srv) 13 | if err != nil { 14 | return 15 | } 16 | 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /mongo/mongo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package mongo 用于mongo连接定义,所有mongo db配置需在此目录定义,一个db对应一个文件。 3 | */ 4 | package mongo 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/globalsign/mgo" 16 | 17 | "github.com/simplejia/op/conf" 18 | "github.com/simplejia/utils" 19 | ) 20 | 21 | // Conf 用于mongo连接配置 22 | type Conf struct { 23 | Dsn string // 连接串: mongo://127.0.0.1:27017... 24 | } 25 | 26 | var ( 27 | // DBS 表示mongo连接,key是db名,value是db连接 28 | DBS map[string]*mgo.Session = map[string]*mgo.Session{} 29 | ) 30 | 31 | func init() { 32 | dir := "mongo" 33 | for i := 0; i < 3; i++ { 34 | if info, err := os.Stat(dir); err == nil && info.IsDir() { 35 | break 36 | } 37 | dir = filepath.Join("..", dir) 38 | } 39 | err := filepath.Walk( 40 | dir, 41 | func(path string, info os.FileInfo, err error) (reterr error) { 42 | if err != nil { 43 | reterr = err 44 | return 45 | } 46 | if info.IsDir() { 47 | return 48 | } 49 | if strings.HasPrefix(path, ".") { 50 | return 51 | } 52 | if filepath.Ext(path) != ".json" { 53 | return 54 | } 55 | 56 | fcontent, err := ioutil.ReadFile(path) 57 | if err != nil { 58 | reterr = err 59 | return 60 | } 61 | fcontent = utils.RemoveAnnotation(fcontent) 62 | var envs map[string]*Conf 63 | if err := json.Unmarshal(fcontent, &envs); err != nil { 64 | reterr = err 65 | return 66 | } 67 | 68 | c := envs[conf.Env] 69 | if c == nil { 70 | reterr = fmt.Errorf("env not right: %s", conf.Env) 71 | return 72 | } 73 | 74 | session, err := mgo.Dial(c.Dsn) 75 | if err != nil { 76 | reterr = err 77 | return 78 | } 79 | // 在跑单元测试时,避免db主从同步延迟导致读不到最新数据的问题 80 | if conf.TestCase { 81 | session.SetMode(mgo.PrimaryPreferred, true) 82 | } else { 83 | session.SetMode(mgo.SecondaryPreferred, true) 84 | } 85 | 86 | key := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) 87 | DBS[key] = session 88 | return 89 | }, 90 | ) 91 | if err != nil { 92 | log.Printf("conf(mongo) not right: %v\n", err) 93 | os.Exit(-1) 94 | } 95 | 96 | log.Printf("conf(mongo): %v\n", DBS) 97 | } 98 | -------------------------------------------------------------------------------- /mongo/op.json: -------------------------------------------------------------------------------- 1 | { 2 | "prod": { 3 | "dsn": "mongodb://127.0.0.1:27017" 4 | }, 5 | "dev": { 6 | "dsn": "mongodb://127.0.0.1:27017" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vendor/lib/base.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/simplejia/clog/api" 9 | ) 10 | 11 | const ( 12 | // KeyBody request body内容会存在此key对应params参数里 13 | KeyBody = "_body_" 14 | // KeyHeader 公共头内容会存在此key对应params参数里 15 | KeyHeader = "_header_" 16 | // KeyResp resp body内容会存在此key对应params参数里 17 | KeyResp = "_resp_" 18 | ) 19 | 20 | // IBase 所有Controller必须实现此接口 21 | type IBase interface { 22 | SetParam(string, interface{}) 23 | GetParam(string) (interface{}, bool) 24 | ReadBody(*http.Request) []byte 25 | } 26 | 27 | type Base struct { 28 | params map[string]interface{} 29 | } 30 | 31 | func (base *Base) SetParam(key string, value interface{}) { 32 | if base.params == nil { 33 | base.params = make(map[string]interface{}) 34 | } 35 | base.params[key] = value 36 | } 37 | 38 | func (base *Base) GetParam(key string) (value interface{}, ok bool) { 39 | value, ok = base.params[key] 40 | return 41 | } 42 | 43 | func (base *Base) ReadBody(r *http.Request) (body []byte) { 44 | key := KeyBody 45 | value, ok := base.GetParam(key) 46 | if ok { 47 | body = value.([]byte) 48 | return 49 | } 50 | 51 | body, err := ioutil.ReadAll(r.Body) 52 | if err != nil { 53 | clog.Error("lib.Base.ReadBody err: %v", err) 54 | return 55 | } 56 | 57 | base.SetParam(key, body) 58 | return 59 | } 60 | 61 | func (base *Base) Reply(w http.ResponseWriter, data interface{}) { 62 | d, err := json.Marshal(data) 63 | if err != nil { 64 | clog.Error("lib.Base.Reply json err: %v", err) 65 | return 66 | } 67 | 68 | if _, err := w.Write(d); err != nil { 69 | clog.Error("lib.Base.Reply write err: %v", err) 70 | return 71 | } 72 | 73 | base.SetParam(KeyResp, d) 74 | return 75 | } 76 | 77 | func (base *Base) ReplyOk(w http.ResponseWriter, data interface{}) { 78 | base.Reply(w, &Resp{ 79 | Ret: CodeOk, 80 | Data: data, 81 | }) 82 | return 83 | } 84 | 85 | func (base *Base) ReplyFail(w http.ResponseWriter, code Code) { 86 | base.Reply(w, &Resp{ 87 | Ret: code, 88 | Msg: CodeMap[Code(code)], 89 | }) 90 | return 91 | } 92 | 93 | func (base *Base) ReplyFailWithDetail(w http.ResponseWriter, code Code, detail string) { 94 | base.Reply(w, &Resp{ 95 | Ret: code, 96 | Detail: detail, 97 | Msg: CodeMap[Code(code)], 98 | }) 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /vendor/lib/code.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | // Code 用于定义返回码 4 | type Code int 5 | 6 | // 定义各种返回码 7 | const ( 8 | CodeOk Code = 1 9 | CodeSrv = 2 10 | CodePara = 3 11 | ) 12 | 13 | // CodeMap 定义返回码对应的描述 14 | var CodeMap = map[Code]string{ 15 | CodeSrv: "服务错误", 16 | CodePara: "参数错误", 17 | } 18 | -------------------------------------------------------------------------------- /vendor/lib/def.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | const ( 4 | // PROD 正式环境 5 | PROD = "prod" 6 | // DEV 开发环境,一般用于本机测试 7 | DEV = "dev" 8 | // TEST 测试环境 9 | TEST = "test" 10 | ) 11 | -------------------------------------------------------------------------------- /vendor/lib/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package lib 定义公共组件 3 | */ 4 | package lib 5 | -------------------------------------------------------------------------------- /vendor/lib/stru.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | // Resp 用于定义返回数据格式(json) 4 | type Resp struct { 5 | Ret Code `json:"ret"` 6 | Msg string `json:"msg,omitempty"` 7 | Detail string `json:"detail,omitempty"` 8 | Data interface{} `json:"data,omitempty"` 9 | } 10 | 11 | // Header 用于定义通用头 12 | type Header struct { 13 | ID int64 `json:"h_id"` 14 | Token string `json:"h_token"` 15 | } 16 | -------------------------------------------------------------------------------- /vendor/lib/utils.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/httptest" 11 | "net/url" 12 | "regexp" 13 | "sort" 14 | "strings" 15 | 16 | "github.com/simplejia/namecli/api" 17 | "github.com/simplejia/utils" 18 | ) 19 | 20 | func TestPost(h http.HandlerFunc, params interface{}) (body []byte, err error) { 21 | v, err := json.Marshal(params) 22 | if err != nil { 23 | return 24 | } 25 | r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(v)) 26 | if err != nil { 27 | return 28 | } 29 | w := httptest.NewRecorder() 30 | h(w, r) 31 | body = w.Body.Bytes() 32 | if g, e := w.Code, http.StatusOK; g != e { 33 | err = fmt.Errorf("http resp status not ok: %s", http.StatusText(g)) 34 | return 35 | } 36 | return 37 | } 38 | 39 | func DeduplicateInt64s(a []int64) (result []int64) { 40 | if len(a) == 0 { 41 | return 42 | } 43 | 44 | exists := map[int64]bool{} 45 | 46 | result = a[:0] 47 | for _, e := range a { 48 | if exists[e] { 49 | continue 50 | } 51 | exists[e] = true 52 | 53 | result = append(result, e) 54 | } 55 | 56 | return 57 | } 58 | 59 | func SearchInt64s(a []int64, x int64) int { 60 | return sort.Search(len(a), func(i int) bool { return a[i] >= x }) 61 | } 62 | 63 | func BingoDisorderInt64s(a []int64, x int64) bool { 64 | for _, e := range a { 65 | if e == x { 66 | return true 67 | } 68 | } 69 | 70 | return false 71 | } 72 | 73 | func Int64s(a []int64) { 74 | sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) 75 | } 76 | 77 | func ZipInt64s(a []int64) (result []byte, err error) { 78 | if len(a) == 0 { 79 | return 80 | } 81 | 82 | var b bytes.Buffer 83 | zw, err := flate.NewWriter(&b, flate.BestCompression) 84 | if err != nil { 85 | return 86 | } 87 | 88 | err = json.NewEncoder(zw).Encode(a) 89 | if err != nil { 90 | return 91 | } 92 | zw.Close() 93 | 94 | result = b.Bytes() 95 | return 96 | } 97 | 98 | func UnzipInt64s(a []byte) (result []int64, err error) { 99 | if len(a) == 0 { 100 | return 101 | } 102 | 103 | zr := flate.NewReader(bytes.NewReader(a)) 104 | err = json.NewDecoder(zr).Decode(&result) 105 | if err != nil { 106 | return 107 | } 108 | return 109 | } 110 | 111 | func ZipBytes(a []byte) (result []byte, err error) { 112 | if len(a) == 0 { 113 | return 114 | } 115 | 116 | var b bytes.Buffer 117 | zw, err := flate.NewWriter(&b, flate.BestCompression) 118 | if err != nil { 119 | return 120 | } 121 | 122 | zw.Write(a) 123 | zw.Close() 124 | 125 | result = b.Bytes() 126 | return 127 | } 128 | 129 | func UnzipBytes(a []byte) (result []byte, err error) { 130 | if len(a) == 0 { 131 | return 132 | } 133 | 134 | zr := flate.NewReader(bytes.NewReader(a)) 135 | bs, err := ioutil.ReadAll(zr) 136 | if err != nil { 137 | return 138 | } 139 | 140 | result = bs 141 | return 142 | } 143 | 144 | func NameWrap(name string) (addr string, err error) { 145 | if strings.HasSuffix(name, ".ns") { 146 | return api.Name(name) 147 | } 148 | 149 | return name, nil 150 | } 151 | 152 | func PostProxy(name, path string, req []byte) (rsp []byte, err error) { 153 | addr, err := NameWrap(name) 154 | if err != nil { 155 | return 156 | } 157 | url := fmt.Sprintf("http://%s/%s", addr, strings.TrimPrefix(path, "/")) 158 | 159 | gpp := &utils.GPP{ 160 | Uri: url, 161 | Params: req, 162 | } 163 | rsp, err = utils.Post(gpp) 164 | if err != nil { 165 | return 166 | } 167 | 168 | return 169 | } 170 | 171 | func PostProxyReturnHeader(name, path string, req []byte) (rsp []byte, header http.Header, err error) { 172 | addr, err := NameWrap(name) 173 | if err != nil { 174 | return 175 | } 176 | url := fmt.Sprintf("http://%s/%s", addr, strings.TrimPrefix(path, "/")) 177 | 178 | reader := bytes.NewReader(req) 179 | r, err := http.Post(url, "application/json", reader) 180 | if r != nil { 181 | defer r.Body.Close() 182 | } 183 | if err != nil { 184 | return 185 | } 186 | rsp, err = ioutil.ReadAll(r.Body) 187 | if err != nil { 188 | return 189 | } 190 | if g, e := r.StatusCode, http.StatusOK; g != e { 191 | err = fmt.Errorf("http resp code: %d", g) 192 | return 193 | } 194 | 195 | header = r.Header 196 | return 197 | } 198 | 199 | func PostProxyWithHeader(name, path string, req []byte, headers map[string]string) (rsp []byte, err error) { 200 | addr, err := NameWrap(name) 201 | if err != nil { 202 | return 203 | } 204 | url := fmt.Sprintf("http://%s/%s", addr, strings.TrimPrefix(path, "/")) 205 | 206 | gpp := &utils.GPP{ 207 | Uri: url, 208 | Params: req, 209 | Headers: headers, 210 | } 211 | rsp, err = utils.Post(gpp) 212 | if err != nil { 213 | return 214 | } 215 | 216 | return 217 | } 218 | 219 | func ClientWithProxy(name string) (client *http.Client, err error) { 220 | if name == "" { 221 | client = &http.Client{} 222 | return 223 | } 224 | 225 | addr, err := NameWrap(name) 226 | if err != nil { 227 | return 228 | } 229 | 230 | client = &http.Client{ 231 | Transport: &http.Transport{ 232 | Proxy: func(*http.Request) (*url.URL, error) { 233 | return url.Parse(fmt.Sprintf("http://%s", addr)) 234 | }, 235 | }, 236 | } 237 | return 238 | } 239 | 240 | func TrimDataURL(data string) (ret string) { 241 | if data == "" { 242 | return 243 | } 244 | 245 | return regexp.MustCompile(`^data:[0-9a-zA-Z/]+?;base64,`).ReplaceAllString(data, "") 246 | } 247 | 248 | func TruncateWithSuffix(data string, length int, suffix string) (ret string) { 249 | rdata := []rune(data) 250 | if len(rdata) > length { 251 | ret = string(rdata[:length]) + suffix 252 | } else { 253 | ret = data 254 | } 255 | 256 | return 257 | } 258 | --------------------------------------------------------------------------------