├── .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 | 
 30 | 
 31 | 希望账号管理、权限认证系统是一个统一的,独立的后台账号管理服务,目前开源出去的是把这一步省掉了,大家可以根据自己的情况自行添加处理代码(filter/auth.go)
 32 | 
 33 | 2. 首页,如下图:
 34 | 
 35 | 
 36 | 
 37 | 列表里展示的是已配置的可用服务
 38 | 
 39 | 3. 点击新建,进入服务配置页面,如下图:
 40 | 
 41 | 
 42 | 
 43 | 
 44 | 4. 查看某一个配置项
 45 | 
 46 | 
 47 | 
 48 | 
 49 | 5. 进入某一项(默认进入类型是list的action,如果没有配,就会进入类型是customer的列表)
 50 | 
 51 | 
 52 | 
 53 | 
 54 | 如上,是因为配置了cid这个必填字段,limit和offset是可选字段,其中limit配置的默认值是20,执行后,显示如下:
 55 | 
 56 | 
 57 | 
 58 | 
 59 | 以上是列表信息,一共返回1条数据,继续执行会把cid, limit, offset, total字段发给服务端作为输入参数
 60 | 
 61 | 
 62 | 
 63 | 
 64 | 以上是没有配置list类型的action,进入某一项会返回类型是customer的列表(或者点击list类型的页面的“其它”进入)
 65 | 
 66 | 
 67 | 
 68 | 
 69 | 以上是点击“更新”或“删除”进入的页面,每一项数据都可以修改,点击执行后会把以上数据post给服务端(具体执行接口是来自于update或delete类型的action配置)
 70 | 
 71 | 
 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 | 
 91 | 
 92 | 
 93 | 
 94 | 
 95 | 
 96 | 这是配置项的进入页(列表页):
 97 | 
 98 | 
 99 | 
100 | 
101 | 
102 | 
103 | 
104 | 这是点击列表页的更新/删除:
105 | 
106 | 
107 | 
108 | 
109 | 2. 服务上线
110 | 
111 | 这是一个php的上线功能,以下是配置项:
112 | 
113 | 
114 | 
115 | 
116 | 这是配置项的进入页:
117 | 
118 | 
119 | 
120 | 
121 | 点击/online/trans_cmd:
122 | 
123 | 
124 | 
125 | 
126 | 点击/online/trans_file:
127 | 
128 | 
129 | 
130 | 
131 | 这是一个go服务的上线功能,以下是配置项:
132 | 
133 | 
134 | 
135 | 
136 | 这是配置项的进入页:
137 | 
138 | 
139 | 
140 | 
141 | 点击/online/trans_cmd:
142 | 
143 | 
144 | 
145 | 
146 | 点击/online/trans_file:
147 | 
148 | 
149 | 
150 | 
151 | 注:上线服务依赖:github.com/simplejia/online
152 | online项目用于提供远程文件上传及远程执行命令功能,类似运维工具:ansible
153 | 
154 | 3. 下载数据
155 | 
156 | 这是一个提供数据下载的配置项:
157 | 
158 | 
159 | 
160 | 
161 | 注意下类型:transparent,这个类型表示后端接口返回什么数据,页面上直接展示,不做任何处理
162 | 
163 | 以下是配置项的进入页,点击执行后的效果:(提示有文件正在下载)
164 | 
165 | 
166 | 
167 | 
168 | 4. 复杂功能演示
169 | 
170 | 这是一个提供视频处理功能的配置项:(提供按条件过滤的功能)
171 | 
172 | 
173 | 
174 | 
175 | 这是配置项的进入页:
176 | 
177 | 
178 | 
179 | 
180 | 注意“remote_ip”这个字段,配置“数据源”是“从URL”,此ip列表是调用配置的url接口返回的结果
181 | 
182 | 点击“执行”后的运行结果:(部分结果如下)
183 | 
184 | 
185 | 
186 | 
187 | 
188 | 这是一个提供视频处理功能的配置项:(提供更好的报表展示功能)
189 | 
190 | 
191 | 
192 | 
193 | 这是配置项的进入页:
194 | 
195 | 
196 | 
197 | 
198 | 点击“执行”后的运行结果:(部分结果如下)
199 | 
200 | 
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 |     
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 |     
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 | 	
 63 | 	
 64 | 	
 65 | 	
 66 | 		| ID | 名字 | 名字(次要) | 描述 | 地址 | 操作67 | | 
 68 | 	
 69 | 	
 70 | 	{{range .list}}
 71 | 	
 72 | 	
 80 | 	
 81 | 	{{end}}
 82 | 	
 83 | 	
 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 | 		| PATH | 描述75 | 	
 76 | 	
 77 | 	{{range .}}
 78 | | 
 79 | 		| 80 | | {{.Desc}}81 | | 
 82 | 	{{end}}
 83 | 	
 84 | 	
 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 | 	
170 | 	{{with .history_details}}
171 | 	
172 | 	
173 | 	| 参数 | 时间 | 操作174 | | 
175 | 	{{range $pos, $elem := .}}
176 | 	
177 | 		
191 | 	
192 | 	{{end}}
193 | 	
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 | 	
 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 | 	
115 | 	{{with .list}}
116 | 	
117 | 	
118 | 	
119 | 		| 序号120 | | 内容121 | | 操作122 | | 
123 | 	
124 | 	
125 | 	{{range $pos, $elem := .}}
126 | 	
127 | 	
139 | 	
140 | 	{{end}}
141 | 	
142 | 	
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 | 		
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 | 
--------------------------------------------------------------------------------