package main import ( "encoding/json" "io/ioutil" "log" "os" "path/filepath" "time" ) //openSession is the biggest description for a user's openID // | it contains a name called procedure, whose data is stored elsewhere // | // +-- a Procedure name is a alphanumerical string lessthan 128 bytes. // | // +--- state is part of procedure, a procedure consists of sequence of states //where to find session data var sessionDir = "sessions" //where to find the procedure state information var procedureDir = "procedure" //openIDSessionData openID user's session type openIDSessionData struct { OpenID string `json:"OpenID"` //who's session this is belongs to Procedure string `json:"Procedure"` //name of the current current process, alphanumerical CreateAt int32 `json:"CreateAt"` //when is this session created UpdateAt int32 `json:"UpdateAt"` //when is this session updated Expire int32 `json:"Expire"` //unix timestamp of when this Procedure expires KvPair map[string]string `json:"KvPair"` //key value pair persistant for this session // // //current state for the prcedure state chatState } func writeSession(ss openIDSessionData) (err error) { openID := ss.OpenID path := getSessionPath(openID) r, err := json.Marshal(ss) if err == nil { err = ioutil.WriteFile(path, r, 0600) if err != nil { log.Printf("Error writing session %s: \r\n %s \r\n", openID, r) log.Println(err) } else { log.Println("write session to " + path) } } else { log.Printf("Error encoding session %s ", openID) log.Println(err) } return } func isExpired(timestamp int32) bool { now := int32(time.Now().Unix()) return now > timestamp } func getSessionPath(openID string) (path string) { path = sessionDir + string(os.PathSeparator) + openID + ".json" ensurePathExist(path) return path } func getProcedurePath(openID, ProcedureName string) (path string) { path = procedureDir + string(os.PathSeparator) + ProcedureName + string(os.PathSeparator) + openID + ".json" ensurePathExist(path) return } func ensurePathExist(path string) { d := filepath.Dir(path) if !isFileExist(d) { log.Println("Creating path [" + d + "]") os.MkdirAll(d, 0700) } } func deleteSession(openID string) { path := getSessionPath(openID) if isFileExist(path) { err := os.Remove(path) if err != nil { log.Println(err) } } } type initProcFunc func(openid string) (newstate chatState) type sendProcMsgFunc func(openid string) (newstate chatState) type recvProcMsgFunc func(openid string) (newstate chatState) type cleanProcFunc func(openid string) (newstate chatState) //Procedure a description about all procedure type Procedure struct { init initProcFunc //init function send sendProcMsgFunc //function for sending customized message recv recvProcMsgFunc //function for receiving message, possibly transfer to new state clean cleanProcFunc //function for cleanning up } func (c *openIDSessionData) Save() { //invalid procedure if isExpired(c.Expire) || c.Procedure == "" { deleteSession(c.OpenID) } else { writeSession(*c) } } //read sessions/openID.json to check whether its a Procedure for ongoing dialog func getCurrentSesssion(openID string) (result openIDSessionData, err error) { result = createEmptySession(openID, 3600) path := getSessionPath(openID) if isFileExist(path) { log.Printf("read session from %s\r\n", path) body, err := ioutil.ReadFile(path) if err != nil { //read file error log.Println("Error session reading " + path) } else { err = json.Unmarshal(body, &result) if err != nil { log.Printf("Session Content [path=%s] not correct: ", path) result = createEmptySession(openID, 3600) } } } return } func createEmptySession(openID string, expire int32) (result openIDSessionData) { result.OpenID = openID result.Procedure = "" now := int32(time.Now().Unix()) result.CreateAt = now result.UpdateAt = now result.Expire = now + expire result.KvPair = map[string]string{} return } func (c *openIDSessionData) setProcedure(procedure string) { c.Procedure = procedure c.UpdateAt = int32(time.Now().Unix()) } func (c *openIDSessionData) setKvPair(key, val string) { c.KvPair[key] = val c.UpdateAt = int32(time.Now().Unix()) } //main entry point for processing each incoming message //this stage has session date available func (c *openIDSessionData) incomingMsg(v InWechatMsg) { openID := v.header.FromUserName //are we in an existing procedure inProc, state := isInProc(openID) //if inside a procedure, resume last saved state if inProc { c.state = serveProc(state, v) //transit to new state } else { state, processed := serveCommand(openID, v) //menu or txt command e.g. search if !processed { // transfer to Customer Service (kf) kfSendTxt(openID, "未识别的命令,已转接校友会理事会,稍后答复您") } c.state = state } if !isEndingState(state) { err := saveChatState(openID, state.Procedure, state) if err != nil { log.Println("Error Cannot Save chat sate") log.Println(err) log.Println(state) } } else { //state ending cleanProcedure(openID, state.Procedure) } return }