pu369com

用golang写了个统计各单位报送的信息数量的微服务

代码很乱,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公司", "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~2\\Google\\Chrome\\Application\\chrome.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>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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, `">`, v, `</option>`)
    }
    //fmt.Println(options.String())

    //统计
    year, month, _ := time.Now().Date()
    //统计各部门稿件数量的map
    //本月
    thisMonthFirstDay := time.Date(year, month, 1, 0, 0, 0, 0, time.Local) //本月第一天
    nextMonthFirstDay := thisMonthFirstDay.AddDate(0, 1, 0)                //下月第一天
    _ThisMonth := periodInfos(thisMonthFirstDay, nextMonthFirstDay)

    //上月
    lastMonthFirstDay := thisMonthFirstDay.AddDate(0, -1, 0) //上月第一天
    _LastMonth := periodInfos(lastMonthFirstDay, thisMonthFirstDay)

    //本年
    thisYearFirstDay := time.Date(year, 1, 1, 0, 0, 0, 0, time.Local) //本年第一天
    nextYearLastDay := thisYearFirstDay.AddDate(1, 0, 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>`, v.Key, v.Value, `篇`, `</li>`)
    }
    //本年ol
    fmt.Fprintln(&sortlist, `</ol></td><td><ol>`)
    for _, v := range s_thisYear {
        fmt.Fprintln(&sortlist, `<li>`, v.Key, v.Value, `篇`, `</li>`)
    }
    fmt.Fprintln(&sortlist, `</ol></td></tr></table>`)

    //替换模板
    tpl = strings.Replace(tpl, `{{OPTIONS}}`, options.String(), -1)
    tpl = strings.Replace(tpl, `{{SORTLIST}}`, sortlist.String(), -1)
    tpl = strings.Replace(tpl, `{{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, `<li>`, `ID:`, i, `</li>`)
        fmt.Fprintln(&sb, `<li>`, time1, `</li>`)
        fmt.Fprintln(&sb, `<li>`, info.Title, `</li>`)
        fmt.Fprintln(&sb, `<li class="ctx">`, info.Content, `</li>`)
        fmt.Fprintln(&sb, `<li>`, info.Department, `</li>`)
        fmt.Fprintln(&sb, `<li>`, info.Who, `</li>`)
        fmt.Fprintln(&sb, `<li>`, info.Tel, `</li>`)
        fmt.Fprintln(&sb, `<li>`, info.Ip, `</li>`)
        fmt.Fprintln(&sb, `</ul>`)
    }

    //替换模板
    tpl = strings.Replace(tpl, `{{TB}}`, sb.String(), -1)
    tpl = strings.Replace(tpl, `{{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, os.O_RDWR|os.O_CREATE, 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, `"`, `”`, -1)
    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, os.O_RDWR|os.O_CREATE, 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, ":")]
    allowip := GlobalConf["allowip"]
    if !strings.Contains(allowip, ip) {
        fmt.Fprintln(w, "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, `{{Manuscripts}}`, GlobalManuscripts, -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, r, "Add", http.StatusFound)
        return
    } else {
        fmt.Fprintln(w, "what are you 弄啥哩!")
    }

}

 

posted on 2019-03-29 13:35  pu369com  阅读(433)  评论(0编辑  收藏  举报

导航