package main
//使用する汎用パッケージ
import (
fmt "fmt";
util "io/ioutil"
strings "strings"
os "os"
template "template"
strconv "strconv"
time "time"
)
//テンプレートに引き渡す情報
type Record struct {
today string
yesterday string
total string
}
//ファイルレコード分析用
const (
Today = iota;
Yesterday;
Total;
Day;
)
//データファイル名(実行exeと同じフォルダにあること)
const COUNTER_FILE = "cnt.dat"
const VISITED_FILE = "chk.dat"
//出力テンプレート
const tpl = `
<div class="counter">
<div class="ctToday">
<div class="hToday">Today:</div>
<div class="cToday">{today}</div>
</div>
<div class="ctYesterday">
<div class="hYesterday">YesterDay:</div>
<div class="cYesterday">{yesterday}</div>
</div>
<div class="ctTotal">
<div class="hTotal">Total:</div>
<div class="cTotal">{total}</div>
</div>
</div>
`
//エラー処理
func IfErrorExit(err os.Error) {
if err == nil {
return
}
fmt.Printf("Content-Type: text/html;charset=UTF-8\n\n")
fmt.Printf("%s\n",err.String())
os.Exit(0)
}
//重複アクセスチェック
func checkVisited(cbuf string, nowday int, inrm string) (bool,string) {
//先頭1つめの記録日付を取り出して、チェック
rmInfos := strings.Split(cbuf , "," ,-1)
v,er := strconv.Atoi(rmInfos[0])
IfErrorExit(er)
//今日の最初のサイト訪問者なら無条件にカウントアップ
if v != nowday {
return false,fmt.Sprintf("%d,%s,",nowday,inrm)
}
//今日はじめての訪問なら無条件にカウントアップ
if strings.Index(cbuf, fmt.Sprintf(",%s,",inrm)) < 0 {
return false,fmt.Sprintf("%s,%s,",cbuf,inrm)
}
return true,cbuf
}
//ファイル名を取得
func getReadFileName() (string,string ){
//IISなので、SSIで動かす時はルート相対パス指定
//IISからみたURLのルートパス(URLのルート物理パス)
myrpath := strings.Split(os.Getenv("PATH_TRANSLATED"),"\\",-1)
//IISからみた実行スクリプトパス
myspath := strings.Split(os.Getenv("SCRIPT_NAME"),"/",-1)
//配列の一番後ろ=スクリプトexeファイル名なので消去
myspath[len(myspath)-1] = "";
//ルートパス+スクリプトパス+データファイル名の物理パスを返す
ppath := fmt.Sprintf("%s%s",strings.Join(myrpath,"\\"),strings.Join(myspath,"\\"))
return fmt.Sprintf("%s%s",ppath,COUNTER_FILE),fmt.Sprintf("%s%s",ppath,VISITED_FILE)
}
//メイン処理
func main() {
//読込データファイル(exeと同フォルダ内の固定ファイル)を読む
ctFile,ckFile := getReadFileName()
buf,er := util.ReadFile(ctFile)
IfErrorExit(er)
checkbuf,er := util.ReadFile(ckFile)
IfErrorExit(er)
//データレコードを分割して、順番に意味づけ
myCounter := strings.Split(fmt.Sprintf("%s",buf),",",-1)
wtotal,er := strconv.Atoi(myCounter[Total])
IfErrorExit(er)
wtoday,er := strconv.Atoi(myCounter[Today])
IfErrorExit(er)
wyesterday,er := strconv.Atoi(myCounter[Yesterday])
IfErrorExit(er)
wday,er := strconv.Atoi(myCounter[Day])
IfErrorExit(er)
//チェックのための日付準備
now := time.LocalTime()
visitInfo := os.Getenv("REMOTE_ADDR")
//サイト再訪問チェック
retbln,retstr := checkVisited(fmt.Sprintf("%s",checkbuf), now.Day ,visitInfo)
//今日はじめての訪問だったら、カウントアップしてログ記録
if retbln == false {
//前回データ保持日付が記録されているので、比較して日付更新処理
if now.Day != wday {
wyesterday = wtoday
wtoday = 1
wday = now.Day
}else{
wtoday++;
}
//トータルカウントアップ処理
wtotal++;
//データの書き込み
er = util.WriteFile(ctFile, []byte(fmt.Sprintf("%d,%d,%d,%d",wtoday,wyesterday,wtotal,wday)),777)
IfErrorExit(er)
er = util.WriteFile(ckFile, []byte(retstr),777)
IfErrorExit(er)
}
//テンプレの準備
var nowCounter Record;
nowCounter.today = fmt.Sprintf("%05d",wtoday);
nowCounter.yesterday = fmt.Sprintf("%05d",wyesterday);
nowCounter.total = fmt.Sprintf("%05d",wtotal);
//テンプレ書き出し
fmt.Printf("Content-Type: text/html;charset=UTF-8\n\n")
t := template.MustParse(tpl, nil);
t.Execute(nowCounter, os.Stdout);
os.Exit(0)
}
SSIなので、CookieヘッダーはWEBサーバー側で握りつぶされるので使おうと思っても使えないから、サーバー側で対処ね。ここ、意外とひっかかるので注意だったり。
今回のソースは、Go言語らしく、1関数で複数の戻り値を使って書いてるから、実行する効率はPerlやCで書く時よりも良いはず。Go言語の作者さんは「ここがすごいところなんだぜ?」とイチオチの機能だし。