用golang写了个统计各单位报送的信息数量的微服务
发布时间:2020-12-16 09:29:19 所属栏目:大数据 来源:网络整理
导读:代码很乱,bug很多,将就着看吧。参考了很多网上代码,只能说声感谢了。 // cjl.ZongHeInfo.1.0 // 目的:对各部门报上来的信息数量进行排名 // 思路:预计一年信息量不超过100M,全部存入全局变量GlobalInfoDoc中,以方便排序,统计 // 在协程中每5分钟将Gl
代码很乱,bug很多,将就着看吧。参考了很多网上代码,只能说声感谢了。 //cjl.ZongHeInfo.1.0 //目的:对各部门报上来的信息数量进行排名 //思路:预计一年信息量不超过100M,全部存入全局变量GlobalInfoDoc中,以方便排序,统计 //在协程中每5分钟将GlobalInfoDoc用json编码后存入文件中。因此,退出程序前应先手动保存(一定程度上可考虑用signal),避免5分钟内的数据丢失 //重要:生成的json备份文件不能用notepad编辑,要保存为UTF-8 NO BOM package main import ( "io/ioutil" "math" "net/url" "strconv" //"encoding/base64" "encoding/json" "fmt" "log" "sort" "strings" "sync" "net/http" "os" "os/exec" "time" ) var ( DEPA = []string{"XX部","XXX部","X1部","X2部","XX公司","XX厂","XXX厂","XX中心"} GlobalInfoDoc TInfoDoc //保存了所有报上来的信息,其实就相当于一个文本文件 GlobalConf = make(map[string]string) //配置文件 GlobalManuscripts = "" //约稿要求,直接放投稿页面的placeholder中了 ) type TInfoDoc struct { Infos []TInfo //属性名一定要大写,血的教训 sync.RWMutex //http是多线程的,加上锁 } type TInfo struct { Time int64 `json:"name,omitempty"` //`时间` Title string `json:"title,omitempty"` //`标题` Content string `json:"Content,omitempty"` //`内容` Department string `json:"Department,omitempty"` //`单位` Who string `json:"Who,omitempty"` //`报送人` Tel string `json:"Tel,omitempty"` //`电话` Ip string `json:"IP,omitempty"` //IP } //原计划利用cookie保存,后来认为用不上,把相关代码注释掉了 type TUser struct { Name string Tel string } //用于对所报的信息按单位名称排序 type TInfoRanks []TInfoRank type TInfoRank struct { Key string Value int } func main() { //打开文件,若文件不存在则生成 year,_,_ := time.Now().Date() fname := "info" + strconv.Itoa(year) f,_ := os.OpenFile(fname,os.O_RDWR|os.O_CREATE,0755) //将文件内容反序列化到全局变量infoDoc out,_ := ioutil.ReadAll(f) //fmt.Println(string(out)) json.Unmarshal(out,&GlobalInfoDoc) //这里不能用defer f.Close(),因为main函数不会结束 f.Close() //fmt.Println(infoDoc) http.HandleFunc("/Add",Add) http.HandleFunc("/Add2",Add2) http.HandleFunc("/List",List) http.HandleFunc("/save",save) http.HandleFunc("/conf",conf) http.HandleFunc("/yg",Manuscripts) http.HandleFunc("/",Index) exec.Command("cmd","/c","C:Progra~2GoogleChromeApplicationchrome.exe","http://localhost:8000/Add").Run() go savefile() fmt.Println("因5分钟才保存一次文件,所以退出程序前请访问/save以防止最近5分钟提交的信息丢失") fmt.Println("请访问/conf更新配置文件的allowip") http.ListenAndServe(":8000",nil) } func checkErr(err error) { if err != nil { log.Println(err) } } func Index(w http.ResponseWriter,r *http.Request) { s := ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style type="text/css"> a:link,a:visited{ text-decoration:none; /*超链接无下划线*/ color:red; } a:hover{ text-decoration:underline; /*鼠标放上去有下划线*/ } </style> </head> <body> <a href="http://192.168.0.239:8000/Add" target="_blank">信息报送</a> </body> </html> ` fmt.Fprintf(w,s) } func Add(w http.ResponseWriter,r *http.Request) { //模板 tpl := `<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style> input{ width:500px; } textarea{ width:500px; } .labelspan{ display:inline-block; width:80px; } .input2{ width:410px; } </style> </head> <body onload="javascript:init()"> <form action="Add2" method="post" onsubmit="return check()"> <fieldset> <legend><a href="yg">_</a>信息汇报<a href="List">_</a></legend> 标题: <input id="Title" name="Title" type="text" size="30" required="required"><br/> 内容: <textarea id="Content" name="Content" rows="15" placeholder="{{Manuscripts}}" required="required"></textarea><br/> 正文限制(100-1000字):<span id="txtNum">0</span><br/> 部门(单位): <select id="Department" name="Department" required="required"> <option value="">请选择</option> {{OPTIONS}} </select><br> <span class="labelspan">报 送 人:</span> <input id="Who" name="Who" class="input2" value="" type="text" size="30" required="required"><br/> <span class="labelspan">联系电话:</span> <input id="Tel" name="Tel" class="input2" value="" type="text" size="30" required="required"><br/> <input type="submit" value="提交"> </fieldset> </form> {{SORTLIST}} <script> var txt = document.getElementById("Content"); var txtNum = document.getElementById("txtNum"); txt.addEventListener("keyup",function(){ txtNum.textContent = txt.value.length; }) </script> <script> function check(){ var title = document.getElementById("Title").value; var Content = document.getElementById("Content").value; var Department = document.getElementById("Department").selectedIndex; var Who = document.getElementById("Who").value; var Tel = document.getElementById("Tel").value; if(title == null || title == ‘‘ ||title.length <6){ alert("请完善标题!"); return false; }else if(Content.length <100||Content.length >1000){ alert("正文限制100至1000字!"); return false; }else if(Department == 0){ alert("请选择部门!"); return false; }else if(Who.length <2){ alert("请填写正确的姓名!"); return false; }else if(Tel.length <7){ alert("请填写联系电话!"); return false; } return true; } </script> <script> function init(){ self.moveTo(0,0); self.resizeTo(screen.width,screen.height);} </script> </body> </html>` //读cookie 注意:直接读来自用户的数据不安全 /* var User TUser u,err := r.Cookie("u") if err == nil { uu,_ := base64.StdEncoding.DecodeString(u.Value) json.Unmarshal(uu,&User) } //fmt.Println(User) tpl = strings.Replace(tpl,`{{WHO}}`,string(User.Name),-1) tpl = strings.Replace(tpl,`{{TEL}}`,string(User.Tel),-1) */ //选项 var options strings.Builder for _,v := range DEPA { fmt.Fprintln(&options,`<option value="`,v,`">`,`</option>`) } //fmt.Println(options.String()) //统计 year,month,_ := time.Now().Date() //统计各部门稿件数量的map //本月 thisMonthFirstDay := time.Date(year,1,0,time.Local) //本月第一天 nextMonthFirstDay := thisMonthFirstDay.AddDate(0,0) //下月第一天 _ThisMonth := periodInfos(thisMonthFirstDay,nextMonthFirstDay) //上月 lastMonthFirstDay := thisMonthFirstDay.AddDate(0,-1,0) //上月第一天 _LastMonth := periodInfos(lastMonthFirstDay,thisMonthFirstDay) //本年 thisYearFirstDay := time.Date(year,time.Local) //本年第一天 nextYearLastDay := thisYearFirstDay.AddDate(1,0) //明年第一天 _ThisYear := periodInfos(thisYearFirstDay,nextYearLastDay) //将map转切片,用sort.Slice排序后输出 var sortlist strings.Builder s_ThisMonth := sortByValue(_ThisMonth) s_LastMonth := sortByValue(_LastMonth) s_thisYear := sortByValue(_ThisYear) //本月ol fmt.Fprintln(&sortlist,`<table><tr><th>本月排序</th><th>上月排序</th><th>本年排序</th><tr><td>`) fmt.Fprintln(&sortlist,`<ol>`) for _,v := range s_ThisMonth { fmt.Fprintln(&sortlist,`<li>`,v.Key,v.Value,`篇`,`</li>`) } //上月ol fmt.Fprintln(&sortlist,`</ol></td><td><ol>`) for _,v := range s_LastMonth { fmt.Fprintln(&sortlist,`</li>`) } //本年ol fmt.Fprintln(&sortlist,v := range s_thisYear { fmt.Fprintln(&sortlist,`</li>`) } fmt.Fprintln(&sortlist,`</ol></td></tr></table>`) //替换模板 tpl = strings.Replace(tpl,`{{OPTIONS}}`,options.String(),-1) tpl = strings.Replace(tpl,`{{SORTLIST}}`,sortlist.String(),`{{Manuscripts}}`,GlobalManuscripts,-1) //模板输出 fmt.Fprintln(w,tpl) } //对报送的稿件数量排序 func sortByValue(m map[string]int) TInfoRanks { pl := make(TInfoRanks,len(m)) i := 0 for k,v := range m { pl[i] = TInfoRank{k,v} i++ } sort.Slice(pl,func(i,j int) bool { flag := false if pl[i].Value > pl[j].Value { flag = true } else if pl[i].Value == pl[j].Value { if pl[i].Key < pl[j].Key { flag = true } } return flag }) return pl } func Add2(w http.ResponseWriter,r *http.Request) { //r.ParseForm() if r.Method == "POST" { info := TInfo{} info.Time = time.Now().Unix() info.Title = safeFilter(r.PostFormValue("Title")) info.Content = safeFilter(r.PostFormValue("Content")) info.Department = safeFilter(r.PostFormValue("Department")) info.Who = safeFilter(r.PostFormValue("Who")) info.Tel = safeFilter(r.PostFormValue("Tel")) ip := r.RemoteAddr info.Ip = ip[0:strings.LastIndex(ip,":")] //在全局InfoDoc的Info切片后追加info GlobalInfoDoc.Lock() GlobalInfoDoc.Infos = append(GlobalInfoDoc.Infos,info) GlobalInfoDoc.Unlock() //设置cookie //base64 /* u := TUser{} u.Name = info.Who u.Tel = info.Tel us,_ := json.Marshal(u) uu := base64.StdEncoding.EncodeToString(us) cookieu := http.Cookie{Name: "u",Value: uu,Path: "/",MaxAge: 86400 * 180} http.SetCookie(w,&cookieu) */ //重定向,避免用户重复提交 http.Redirect(w,r,"Add",http.StatusFound) return } //不允许GET访问 fmt.Fprintln(w,"what are you 弄啥哩!") } func List(w http.ResponseWriter,r *http.Request) { ip := r.RemoteAddr ip = ip[0:strings.LastIndex(ip,":")] allowip := GlobalConf["allowip"] if !strings.Contains(allowip,ip) { fmt.Fprintln(w,"not allow") return } tpl := ` <html><head> <meta charset="UTF-8"> <title></title> <style type="text/css"> body{background-color:#f0f0f0;} table{ width:1000px; table-layout:fixed;/* 只有定义了表格的布局算法为fixed,下面td的定义才能起作用。 */ } td{ width:100%; min-width:300px; word-break:keep-all;/* 不换行 */ white-space:nowrap;/* 不换行 */ overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */ text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用*/ } ul{ border:1px solid #000; } li{ word-break:break-all; } .ctx{background-color:#f0f0f9;} </style> </head> <body> {{TB}} {{PAGENAV}} </body></html>` //获取用户输入url参数?p=1中的页码1 uri,_ := url.Parse(r.RequestURI) urlParam := uri.RawQuery uri_m,_ := url.ParseQuery(urlParam) curpage := 1 //设当前页为1 p_uri_int := 1 //用户提供的页码,默认为1 uri_m_p,ok := uri_m["p"] if ok { p_uri_int,_ = strconv.Atoi(uri_m_p[0]) } perpage := 1000 //每页1000条 numall := len(GlobalInfoDoc.Infos) if numall <= 0 { fmt.Fprintln(w,"无数据") return } //总信息数 maxpage := int(math.Ceil(float64(numall) / float64(perpage))) //末页 if p_uri_int <= 0 { curpage = 1 } else if p_uri_int > maxpage { curpage = maxpage } else { curpage = p_uri_int } pagenav := `<a href=List>首页</a> <a href=List?p=` + strconv.Itoa(curpage-1) + `>上一页</a> <a href=List?p=` + strconv.Itoa(curpage+1) + `>下一页</a> <a href=List?p=` + strconv.Itoa(maxpage) + `>尾页</a>` //以ul>li方式输出表格 var sb strings.Builder //逆序输出1000条,最新的稿件显示在最上面 start1000 := numall - (curpage-1)*1000 end1000 := numall - curpage*1000 + 1 //如:从1001到2000,而不是1000到2000,所以加1 if end1000 < 1 { end1000 = 1 //逆序输出,最后一条也就是第一条 } for i := start1000; i >= end1000; i-- { info := GlobalInfoDoc.Infos[i-1] //第一条对应的索引为0,所以减1 time1 := time.Unix(int64(1553254972),0).Format("2006-01-02 15:04:05") fmt.Fprintln(&sb,`<ul>`) fmt.Fprintln(&sb,`ID:`,i,`</li>`) fmt.Fprintln(&sb,time1,info.Title,`<li class="ctx">`,info.Content,info.Department,info.Who,info.Tel,info.Ip,`</ul>`) } //替换模板 tpl = strings.Replace(tpl,`{{TB}}`,sb.String(),`{{PAGENAV}}`,pagenav,-1) fmt.Fprintln(w,tpl) } func save(w http.ResponseWriter,r *http.Request) { savefile() } //保存GlobalInfoDoc,每5分钟保存一次 func savefile() { t1 := time.Tick(300 * time.Second) for { select { case <-t1: //fmt.Println("t1定时器") year,_ := time.Now().Date() fname := "info" + strconv.Itoa(year) //文件不存在则创建 _,_ = os.OpenFile(fname,0755) out,_ := json.Marshal(GlobalInfoDoc) ioutil.WriteFile(fname,out,os.ModeExclusive) } } } //安全过滤字符串 func safeFilter(s string) string { s = strings.Replace(s,`=`,`=`,-1) s = strings.Replace(s,`‘`,`’`,-1) s = strings.Replace(s,`"`,`”`,`<`,`〈`,`>`,`〉`,string(byte(10)),`<br>`,-1) return s } //统计一段时间内的稿件数量排名 func periodInfos(t1,t2 time.Time) map[string]int { m := make(map[string]int,len(DEPA)) for _,v := range DEPA { m[v] = 0 } //更新投稿数量 for _,info := range GlobalInfoDoc.Infos { dp := strings.TrimSpace(info.Department) //这里将字符串转[]byte后发现前后有空格,asii为32 if _,ok := m[dp]; ok { if info.Time >= t1.Unix() && info.Time < t2.Unix() { m[dp]++ } } } return m } //读配置文件 func conf(w http.ResponseWriter,r *http.Request) { //配置文件中每行的格式类似:a=1 //将a=1解析到map中,k为等号左边的a,v为等号右边的1 var m = make(map[string]string,0) fname := "conf.txt" //文件不存在则创建 f,_ := os.OpenFile(fname,0755) defer f.Close() if si,_ := f.Stat(); si.Size() == 0 { f.WriteString("allowip=[::1],192.168.3.4,192.168.2.4") } b,_ := ioutil.ReadFile(fname) c := strings.Split(string(b),"n") for _,v := range c { if v != "" { d := strings.Split(v,"=") m[d[0]] = d[1] } } var l *sync.Mutex l = new(sync.Mutex) l.Lock() defer l.Unlock() GlobalConf = m } //设置约稿内容 func Manuscripts(w http.ResponseWriter,r *http.Request) { ip := r.RemoteAddr ip = ip[0:strings.LastIndex(ip,"not allow") return } //输入录入页面 if r.Method == "GET" { tpl := ` <html> <head> <meta charset="utf-8"> <title></title></head> <body> <form action="" method="POST"> <textarea id="yg" name="yg" rows=15 cols=50 value="" >{{Manuscripts}}</textarea> <input type="submit" value="提交"> </form> </body> </html> ` //替换模板 tpl = strings.Replace(tpl,-1) fmt.Fprintln(w,tpl) return } else if r.Method == "POST" { s := r.PostFormValue("yg") var l *sync.Mutex l = new(sync.Mutex) l.Lock() defer l.Unlock() GlobalManuscripts = s //同时将约稿内容保存为yg.txt 如果用ioutil.WriteFile,则os.ModeAppend是无效的 //outil.WriteFile("yg.txt",[]byte(s),os.ModeAppend) fl,err := os.OpenFile("yg.txt",os.O_APPEND|os.O_CREATE,0644) if err != nil { return } defer fl.Close() fl.Write([]byte(s)) //重定向,避免用户重复提交 http.Redirect(w,http.StatusFound) return } else { fmt.Fprintln(w,"what are you 弄啥哩!") } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |