用golang寫了個統計各單位報送的信息數量的微服務

代碼很亂,bug不少,將就着看吧。參考了不少網上代碼,只能說聲感謝了。javascript

//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 弄啥哩!") } }
相關文章
相關標籤/搜索