package main import ( "encoding/json" "errors" "io/ioutil" "log" "os" "time" ) //chat state that we might be use for collecting infomation from user type chatState struct { //back pointer style information OpenID string `json:"OpenID"` //whom is this belongs to Procedure string `json:"Procedure"` //which procedure this belongs to //real state information Name string `json:"Name"` //state name Expire int32 `json:"Expire"` //unix timestamp when this state expire Send struct { //anything we need to send? Type string `json:"Type"` //what type of message Message map[string]string `json:"Message"` //the message to be sent,key value pair to describe the message } `json:"Send"` Receive struct { //anything we expect to receive Validator string `json:"Validator"` Hint string `json:"Hint"` Message map[string]string `json:"Message"` //the description for receiving message } `json:"Receive"` Save map[string]string `json:"Save"` //the state save some data for later usage } //for individual state func getCurrentState(openID string, procedure string) (result chatState, err error) { path := getProcedurePath(openID, procedure) log.Printf("read state from %s\r\n", path) body, err := ioutil.ReadFile(path) if err != nil { //read file error if isFileExist(path) { log.Println("Error session reading " + path) } return //empty and expired session } err = json.Unmarshal(body, &result) if err != nil { log.Printf("Session Content [path=%s] not correct: ", path) log.Println(err) } //we don't check Expire, we give the caller full control on //how to deal wiht expired session //check whether state is for the correct openID and procedure if result.OpenID != openID { err = errors.New("Error: State for " + openID + " is actually for " + result.OpenID) return } if result.Procedure != procedure { err = errors.New("Error: Proecdure for " + procedure + " is actually for " + result.Procedure) return } return } func setCurrentState(openID, procedure string, state chatState) (newState chatState, err error) { state.OpenID = openID state.Procedure = procedure j, err := json.Marshal(state) if err != nil { return } path := getProcedurePath(openID, procedure) err = ioutil.WriteFile(path, j, 0600) if err != nil { log.Println("write state error" + path) log.Println(err) return } newState = state return } func deleteChatState(openID, procedure string) (err error) { path := getProcedurePath(openID, procedure) err = os.Remove(path) return } //ValidationResult After input validation, what is the result type ValidationResult struct { accept bool Hint string Error string Warning string } //Validator function type for validating all wechat inputs type Validator func(s chatState) ValidationResult //start a procedure func startProcedure(openID, procedure string) (err error) { //init procedure state init := getProcedureInit(openID, procedure) if init == nil { msg := "FATAL: cannot initialize procedure [" + procedure + "] " err = errors.New(msg) return } //init and get initial state state := init(openID) //do the real concret work for processing the state err = processProcedureState(state) return } //resume a previous Procedure's state func resumeProcedure(openID, procedure string) (err error) { state, err := getCurrentState(openID, procedure) if err != nil { return } return processProcedureState(state) } //finish a procedure, regardless its been finished or not //normally not finished normally func stopProcedure(openID, procedure string) { path := getProcedurePath(openID, procedure) os.Remove(path) log.Println("Clearing [" + openID + "] @ [" + procedure + "]") } func processProcedureState(state chatState) (err error) { //send what we need to send if isExpired(state.Expire) { return errors.New("State has expired " + state.Name) } //mark we have sent. //do we need input? waiting for input //if not, what is next state log.Println(state) return } type initProcedureFunction func(openid string) (initState chatState) func getProcedureInit(openID, procedure string) initProcedureFunction { initFunc := map[string]initProcedureFunction{ "TestDummy": nil, "TestEcho": initTestEcho, "GetBasicUserInfo": initGetBasicUserInfo, "GetEmailAddr": initGetBasicUserInfo, } return initFunc[procedure] } func initTestEcho(openid string) (r chatState) { r.Name = openid r.Expire = int32(time.Now().Unix() + 100) return }