package hotime import ( "context" "database/sql" "io/ioutil" "net/http" "net/url" "os" "os/signal" "path/filepath" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" . "code.hoteas.com/golang/hotime/cache" "code.hoteas.com/golang/hotime/code" . "code.hoteas.com/golang/hotime/common" . "code.hoteas.com/golang/hotime/db" "code.hoteas.com/golang/hotime/log" ) type Application struct { MakeCodeRouter map[string]*code.MakeCode MethodRouter Router Log *log.Logger WebConnectLog *log.Logger Port string //端口号 TLSPort string //ssl访问端口号 connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理 connectDbFunc func() (master, slave *sql.DB) configPath string Config Map Db HoTimeDB *HoTimeCache *http.Server http.Handler shuttingDown atomic.Bool // 停机标志,置为 true 后新请求立即返回 503 inFlight atomic.Int64 // 当前正在处理的请求数(不含已返回 503 的) shutdownOnce sync.Once // 保证多路触发(信号/关窗口)只执行一次 DrainTimeout time.Duration // 返回 503 后等 nginx 切流的最长时长,默认 5s(每 1s 检测在途请求,为 0 则立即进入 Shutdown) ShutdownTimeout time.Duration // 等在途请求完成的最长时间,默认 30s } func (that *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) { if that.shuttingDown.Load() { w.Header().Set("Connection", "close") http.Error(w, "Service Unavailable", http.StatusServiceUnavailable) return } that.inFlight.Add(1) defer that.inFlight.Add(-1) that.handler(w, req) } // initiateGracefulShutdown 触发优雅停机,通过 sync.Once 保证多路触发只执行一次。 // drain: 返回 503 后等待上游(nginx)完成流量切换的最长时长,每 1s 检测一次在途请求数, // 若为 0 则立即进入 Shutdown,避免无谓等待。 // shutdown: 等待在途请求完成的最长时间。 func (that *Application) initiateGracefulShutdown(drain, shutdown time.Duration) { that.shutdownOnce.Do(func() { that.shuttingDown.Store(true) that.Log.Infof("优雅停机:新请求已返回 503,最多等 %v 让 nginx 完成流量切换(每秒检测在途请求)...", drain) // 每秒检测一次,若无在途请求立即停机,否则最多等满 drain tick := time.NewTicker(1 * time.Second) defer tick.Stop() deadline := time.Now().Add(drain) for { if n := that.inFlight.Load(); n == 0 { that.Log.Infof("无在途请求,立即进入 Shutdown") break } else if time.Now().After(deadline) { that.Log.Infof("drain 等待到达上限 %v,仍有 %d 个在途请求,强制进入 Shutdown", drain, n) break } else { that.Log.Infof("drain 等待中,当前在途请求数=%d", n) } <-tick.C } ctx, cancel := context.WithTimeout(context.Background(), shutdown) defer cancel() that.Log.Infof("停止接受新连接,等进行中请求完成(最多 %v)...", shutdown) if err := that.Server.Shutdown(ctx); err != nil { that.Log.Errorf("优雅停机超时,强制关闭: %v", err) } else { that.Log.Infof("服务已安全关闭") } os.Exit(0) }) } // Run 启动实例 func (that *Application) Run(router Router) { //如果没有设置配置自动生成配置 if that.configPath == "" || len(that.Config) == 0 { that.SetConfig() } //防止手动设置缓存误伤 if that.HoTimeCache == nil { that.SetCache() } //防止手动设置session误伤 //if that.sessionShort == nil && that.sessionLong == nil { // if that.connectDbFunc == nil { // that.SetSession(CacheIns(&CacheMemory{}), nil) // } else { // that.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &that.Db, Time: that.Config.GetInt64("cacheLongTime")})) // } // //} //that.Router = router if that.Router == nil { that.Router = Router{} } for k, _ := range router { v := router[k] if that.Router[k] == nil { that.Router[k] = v } //直达接口层复用 for k1, _ := range v { v1 := v[k1] if that.Router[k][k1] == nil { that.Router[k][k1] = v1 } for k2, _ := range v1 { v2 := v1[k2] that.Router[k][k1][k2] = v2 } } } //重新设置MethodRouter//直达路由 that.MethodRouter = MethodRouter{} modeRouterStrict := true if that.Config.GetBool("modeRouterStrict") == false { modeRouterStrict = false } if that.Router != nil { for pk, pv := range that.Router { if !modeRouterStrict { pk = strings.ToLower(pk) } if pv != nil { for ck, cv := range pv { if !modeRouterStrict { ck = strings.ToLower(ck) } if cv != nil { for mk, mv := range cv { if !modeRouterStrict { mk = strings.ToLower(mk) } that.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv } } } } } } //that.Port = port that.Port = that.Config.GetString("port") that.TLSPort = that.Config.GetString("tlsPort") if that.connectDbFunc != nil && (that.Db.DB == nil || that.Db.DB.Ping() != nil) { that.Db.SetConnect(that.connectDbFunc) } //异常处理 defer func() { if err := recover(); err != nil { if that.shuttingDown.Load() { return // 停机过程中的 panic 不触发重启 } //that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT) that.Log.Warnf("%v", err) that.Run(router) } }() that.Server = &http.Server{} if !IsRun { IsRun = true } ch := make(chan int) if ObjToCeilInt(that.Port) != 0 { go func() { App[that.Port] = that that.Server.Handler = that //启动服务 that.Server.Addr = ":" + that.Port err := that.Server.ListenAndServe() if err != nil && err != http.ErrServerClosed { that.Log.Error().Err(err).Msg("HTTP 服务退出") ch <- 1 } }() } if ObjToCeilInt(that.TLSPort) != 0 { go func() { App[that.TLSPort] = that that.Server.Handler = that //启动服务 that.Server.Addr = ":" + that.TLSPort err := that.Server.ListenAndServeTLS(that.Config.GetString("tlsCert"), that.Config.GetString("tlsKey")) if err != nil && err != http.ErrServerClosed { that.Log.Error().Err(err).Msg("HTTPS 服务退出") ch <- 2 } }() } if ObjToCeilInt(that.Port) == 0 && ObjToCeilInt(that.TLSPort) == 0 { that.Log.Errorf("没有端口启用") return } // 注册 Windows 关窗口处理(Linux/macOS 上是空实现) that.registerWindowsCloseHandler() // 跨平台信号监听:Ctrl+C / SIGTERM(kill 命令)/ taskkill /PID go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) <-sigCh drain := that.DrainTimeout if drain == 0 { drain = 5 * time.Second } shutdown := that.ShutdownTimeout if shutdown == 0 { shutdown = 30 * time.Second } that.initiateGracefulShutdown(drain, shutdown) }() value := <-ch that.Log.Errorf("启动服务失败 : %s", ObjToStr(value)) } // SetConnectDB 启动实例 func (that *Application) SetConnectDB(connect func() (master, slave *sql.DB)) { that.connectDbFunc = connect that.Db.SetConnect(that.connectDbFunc) } // SetDefault 默认配置缓存和session实现 func (that *Application) SetDefault(connect func() (*sql.DB, *sql.DB)) { that.SetConfig() if connect != nil { that.connectDbFunc = connect that.Db.SetConnect(that.connectDbFunc) } } // SetCache 设置配置文件路径全路径或者相对路径 func (that *Application) SetCache() { cacheIns := HoTimeCache{} cacheIns.Init(that.Config.GetMap("cache"), HoTimeDBInterface(&that.Db), that.Log) that.HoTimeCache = &cacheIns that.Db.HoTimeCache = &cacheIns } // SetConfig 设置配置文件路径全路径或者相对路径 func (that *Application) SetConfig(configPath ...string) { that.Log = log.NewLogger(1, "", 100) if len(configPath) != 0 { that.configPath = configPath[0] } if that.configPath == "" { that.configPath = "config/config.json" } //加载配置文件 btes, err := ioutil.ReadFile(that.configPath) that.Config = DeepCopyMap(Config).(Map) configErr := false if err == nil { cmap := Map{} cmap.JsonToMap(string(btes)) for k, v := range cmap { that.Config[k] = v Config[k] = v } } else { that.Log.Errorf("配置文件不存在,或者配置出错,使用缺省默认配置") } if !configErr { configStr := that.Config.ToJsonString() if len(btes) != 0 && configStr == string(btes) { // 配置无变化,跳过写入 } else { configNoteStr := ConfigNote.ToJsonString() _ = os.MkdirAll(filepath.Dir(that.configPath), os.ModeDir) err = ioutil.WriteFile(that.configPath, []byte(configStr), os.ModePerm) if err != nil { that.Log.Error().Err(err).Msg("写入配置文件失败") configErr = true } _ = ioutil.WriteFile(filepath.Dir(that.configPath)+"/configNote.json", []byte(configNoteStr), os.ModePerm) } } logLevel := that.Config.GetCeilInt("logLevel") logFile := that.Config.GetString("logFile") logHistory := that.Config.GetCeilInt("logHistory") if logHistory == 0 { logHistory = 100 } that.Log = log.NewLogger(logLevel, logFile, logHistory) that.Db.Log = that.Log if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") { that.WebConnectLog = log.NewLoggerNoCaller(1, that.Config.GetString("webConnectLogFile"), 0) } } // SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输 func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) { that.connectListener = append(that.connectListener, lis) } //网络错误 //func (that *Application) session(w http.ResponseWriter, req *http.Request) { // //} // 序列化链接 func (that *Application) urlSer(url string) (string, []string) { q := strings.Index(url, "?") if q == -1 { q = len(url) } o := Substr(url, 0, q) r := strings.SplitN(o, "/", -1) var s = make([]string, 0) for i := 0; i < len(r); i++ { if !strings.EqualFold("", r[i]) { s = append(s, r[i]) } } return o, s } //访问 func (that *Application) handler(w http.ResponseWriter, req *http.Request) { nowUnixTime := time.Now() _, s := that.urlSer(req.RequestURI) //获取cookie // 如果cookie存在直接将sessionId赋值为cookie.Value // 如果cookie不存在就查找传入的参数中是否有token // 如果token不存在就生成随机的sessionId // 如果token存在就判断token是否在Session中有保存 // 如果有取出token并复制给cookie // 没有保存就生成随机的session cookie, err := req.Cookie(that.Config.GetString("sessionName")) sessionId := Md5(strconv.Itoa(Rand(10))) needSetCookie := "" token := req.Header.Get("Authorization") if len(token) != 32 { token = req.FormValue("token") } //没有cookie或者cookie不等于token //有token优先token if len(token) == 32 { sessionId = token //没有token,则查阅session if cookie == nil || cookie.Value != sessionId { needSetCookie = sessionId } } else if err == nil && cookie.Value != "" { sessionId = cookie.Value //session也没有则判断是否创建cookie } else { needSetCookie = sessionId } unescapeUrl, err := url.QueryUnescape(req.RequestURI) if err != nil { unescapeUrl = req.RequestURI } //访问实例 context := Context{SessionIns: SessionIns{SessionId: sessionId, HoTimeCache: that.HoTimeCache}, Resp: w, Req: req, Application: that, RouterString: s, Config: that.Config, Db: &that.Db, HandlerStr: unescapeUrl} //header默认设置 header := w.Header() header.Set("Content-Type", "text/html; charset=utf-8") //url去掉参数并序列化 context.HandlerStr, context.RouterString = that.urlSer(context.HandlerStr) //跨域设置 that.crossDomain(&context, needSetCookie) defer func() { //是否展示日志 if that.WebConnectLog != nil { //负载均衡优化 ipStr := "" if req.Header.Get("X-Forwarded-For") != "" { ipStr = req.Header.Get("X-Forwarded-For") } else if req.Header.Get("X-Real-IP") != "" { ipStr = req.Header.Get("X-Real-IP") } //负载均衡优化 if ipStr == "" { //RemoteAddr := that.Req.RemoteAddr ipStr = Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")) } that.WebConnectLog.Info(). Str("ip", ipStr). Str("method", context.Req.Method). Float64("cost_ms", ObjToFloat64(time.Now().UnixNano()-nowUnixTime.UnixNano())/1000000.00). Float64("size_kb", ObjToFloat64(context.DataSize)/1000.00). Msg(context.HandlerStr) } }() //访问拦截true继续false暂停 connectListenerLen := len(that.connectListener) - 1 for true { if connectListenerLen < 0 { break } if that.connectListener[connectListenerLen](&context) { context.View() return } connectListenerLen-- } //接口服务 //验证接口严格模式 modeRouterStrict := that.Config.GetBool("modeRouterStrict") tempHandlerStr := context.HandlerStr if !modeRouterStrict { tempHandlerStr = strings.ToLower(tempHandlerStr) } //执行接口 if that.MethodRouter[tempHandlerStr] != nil { that.MethodRouter[tempHandlerStr](&context) context.View() return } //url赋值,静态文件必须使用原始路径(Linux文件系统大小写敏感) path := that.Config.GetString("tpt") + context.HandlerStr //判断是否为默认 if path[len(path)-1] == '/' { defFile := that.Config.GetSlice("defFile") for i := 0; i < len(defFile); i++ { temp := path + defFile.GetString(i) _, err := os.Stat(temp) if err == nil { path = temp break } } if path[len(path)-1] == '/' { w.WriteHeader(404) return } } if strings.Contains(path, "/.") { w.WriteHeader(404) return } //设置header delete(header, "Content-Type") if that.Config.GetCeilInt("logLevel") == 0 { header.Set("Cache-Control", "public") } else { header.Set("Cache-Control", "no-cache") } t := strings.LastIndex(path, ".") if t != -1 { tt := strings.ToLower(path[t:]) if MimeMaps[tt] != "" { header.Add("Content-Type", MimeMaps[tt]) } } //w.Write(data) http.ServeFile(w, req, path) } func (that *Application) crossDomain(context *Context, sessionId string) { //没有跨域设置 if context.Config.GetString("crossDomain") == "" { if sessionId != "" { http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"}) //context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure") } return } header := context.Resp.Header() //不跨域,则不设置 remoteHost := context.Req.Host if context.Config.GetString("port") == "80" || context.Config.GetString("port") == "443" { remoteHost = remoteHost + ":" + context.Config.GetString("port") } if context.Config.GetString("crossDomain") != "auto" { //不跨域,则不设置 if strings.Contains(context.Config.GetString("crossDomain"), remoteHost) { if sessionId != "" { http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"}) } return } header.Set("Access-Control-Allow-Origin", that.Config.GetString("crossDomain")) // 后端设置,2592000单位秒,这里是30天 header.Set("Access-Control-Max-Age", "2592000") //header.Set("Access-Control-Allow-Origin", "*") header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE") header.Set("Access-Control-Allow-Credentials", "true") header.Set("Access-Control-Expose-Headers", "*") header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie") if sessionId != "" { //跨域允许需要设置cookie的允许跨域https才有效果 context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure") } return } origin := context.Req.Header.Get("Origin") refer := context.Req.Header.Get("Referer") if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) { if sessionId != "" { //http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"}) context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure") } return } if origin != "" { header.Set("Access-Control-Allow-Origin", origin) //return } else if refer != "" { tempInt := 0 lastInt := strings.IndexFunc(refer, func(r rune) bool { if r == '/' && tempInt > 8 { return true } tempInt++ return false }) if lastInt < 0 { lastInt = len(refer) } refer = Substr(refer, 0, lastInt) header.Set("Access-Control-Allow-Origin", refer) //header.Set("Access-Control-Allow-Origin", "*") } header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE") header.Set("Access-Control-Allow-Credentials", "true") header.Set("Access-Control-Expose-Headers", "*") header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie") if sessionId != "" { //跨域允许需要设置cookie的允许跨域https才有效果 context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure") } } // Init 初始化application func Init(config string) *Application { appIns := Application{} //手动模式, appIns.SetConfig(config) SetDB(&appIns) appIns.SetCache() codeConfig := appIns.Config.GetSlice("codeConfig") if codeConfig != nil { for k, _ := range codeConfig { codeMake := codeConfig.GetMap(k) if codeMake == nil { continue } //codeMake["table"] = k if appIns.MakeCodeRouter == nil { appIns.MakeCodeRouter = map[string]*code.MakeCode{} } if codeMake.GetString("name") == "" { codeMake["name"] = codeMake.GetString("table") } appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Log: appIns.Log} appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake) //接入动态代码层 if appIns.Router == nil { appIns.Router = Router{} } //appIns.Router[codeMake.GetString("name")] = TptProject appIns.Router[codeMake.GetString("name")] = Proj{} for k2, _ := range TptProject { if appIns.Router[codeMake.GetString("name")][k2] == nil { appIns.Router[codeMake.GetString("name")][k2] = Ctr{} } for k3, _ := range TptProject[k2] { v3 := TptProject[k2][k3] appIns.Router[codeMake.GetString("name")][k2][k3] = v3 } } for k1, _ := range appIns.MakeCodeRouter[codeMake.GetString("name")].TableColumns { if appIns.Router[codeMake.GetString("name")][k1] == nil { appIns.Router[codeMake.GetString("name")][k1] = Ctr{} } for k2, _ := range appIns.Router[codeMake.GetString("name")]["hotimeCommon"] { //golang毛病 v2 := appIns.Router[codeMake.GetString("name")]["hotimeCommon"][k2] appIns.Router[codeMake.GetString("name")][k1][k2] = v2 } } setMakeCodeListener(codeMake.GetString("name"), &appIns) } } return &appIns } // SetDB 智能数据库设置 func SetDB(appIns *Application) { db := appIns.Config.GetMap("db") dbSqlite := db.GetMap("sqlite") dbMysql := db.GetMap("mysql") dbDm := db.GetMap("dm") if db != nil && dbSqlite != nil { SetSqliteDB(appIns, dbSqlite) } if db != nil && dbMysql != nil { SetMysqlDB(appIns, dbMysql) } if db != nil && dbDm != nil { SetDmDB(appIns, dbDm) } } func SetMysqlDB(appIns *Application, config Map) { appIns.Db.Type = "mysql" appIns.Db.DBName = config.GetString("name") appIns.Db.Prefix = config.GetString("prefix") appIns.Db.Log = appIns.Log appIns.SetConnectDB(func() (master, slave *sql.DB) { query := config.GetString("user") + ":" + config.GetString("password") + "@tcp(" + config.GetString("host") + ":" + config.GetString("port") + ")/" + config.GetString("name") + "?charset=utf8" DB, e := sql.Open("mysql", query) if e != nil { appIns.Log.Error().Err(e).Msg("MySQL 主库连接失败") } master = DB configSlave := config.GetMap("slave") if configSlave != nil { query := configSlave.GetString("user") + ":" + configSlave.GetString("password") + "@tcp(" + config.GetString("host") + ":" + configSlave.GetString("port") + ")/" + configSlave.GetString("name") + "?charset=utf8" DB1, e := sql.Open("mysql", query) if e != nil { appIns.Log.Error().Err(e).Msg("MySQL 从库连接失败") } slave = DB1 } return master, slave }) } func SetSqliteDB(appIns *Application, config Map) { appIns.Db.Type = "sqlite" appIns.Db.Prefix = config.GetString("prefix") appIns.Db.Log = appIns.Log appIns.SetConnectDB(func() (master, slave *sql.DB) { db, e := sql.Open("sqlite3", config.GetString("path")) if e != nil { appIns.Log.Error().Err(e).Msg("SQLite 连接失败") } master = db return master, slave }) } func SetDmDB(appIns *Application, config Map) { appIns.Db.Type = "dm" appIns.Db.DBName = config.GetString("name") appIns.Db.Prefix = config.GetString("prefix") appIns.Db.Log = appIns.Log appIns.SetConnectDB(func() (master, slave *sql.DB) { query := "dm://" + config.GetString("user") + ":" + config.GetString("password") + "@" + config.GetString("host") + ":" + config.GetString("port") + "?schema=" + config.GetString("name") DB, e := sql.Open("dm", query) if e != nil { appIns.Log.Error().Err(e).Msg("达梦主库连接失败") } master = DB configSlave := config.GetMap("slave") if configSlave != nil { querySlave := "dm://" + configSlave.GetString("user") + ":" + configSlave.GetString("password") + "@" + configSlave.GetString("host") + ":" + configSlave.GetString("port") + "?schema=" + configSlave.GetString("name") DB1, e := sql.Open("dm", querySlave) if e != nil { appIns.Log.Error().Err(e).Msg("达梦从库连接失败") } slave = DB1 } return master, slave }) } func setMakeCodeListener(name string, appIns *Application) { appIns.SetConnectListener(func(context *Context) (isFinished bool) { codeIns := appIns.MakeCodeRouter[name] if len(context.RouterString) < 2 || appIns.MakeCodeRouter[context.RouterString[0]] == nil { return isFinished } if len(context.RouterString) > 1 && context.RouterString[0] == name { if context.RouterString[1] == "hotime" && context.RouterString[2] == "login" { return isFinished } if context.RouterString[1] == "hotime" && context.RouterString[2] == "logout" { return isFinished } if context.RouterString[1] == "hotime" && context.RouterString[2] == "config" { return isFinished } if context.RouterString[1] == "hotime" && context.RouterString[2] == "wallpaper" { return isFinished } if context.Session(codeIns.FileConfig.GetString("table")+"_id").Data == nil { context.Display(2, "你还没有登录") return true } } if context.RouterString[0] != name { return isFinished } if len(context.RouterString) < 2 || len(context.RouterString) > 3 || !(context.Router[context.RouterString[0]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]] != nil) { return isFinished } //排除无效操作 if len(context.RouterString) == 2 && context.Req.Method != "GET" && context.Req.Method != "POST" { return isFinished } //排除已有接口的无效操作 if len(context.RouterString) == 3 && context.Router[context.RouterString[0]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]][context.RouterString[2]] != nil { return isFinished } //列表检索 if len(context.RouterString) == 2 && context.Req.Method == "GET" { if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil { return isFinished } context.Router[context.RouterString[0]][context.RouterString[1]]["search"](context) } //新建 if len(context.RouterString) == 2 && context.Req.Method == "POST" { if context.Router[context.RouterString[0]][context.RouterString[1]]["add"] == nil { return true } context.Router[context.RouterString[0]][context.RouterString[1]]["add"](context) } if len(context.RouterString) == 3 && context.Req.Method == "POST" { return isFinished } //分析 if len(context.RouterString) == 3 && context.RouterString[2] == "analyse" && context.Req.Method == "GET" { if context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"] == nil { return isFinished } context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"](context) } //查询单条 if len(context.RouterString) == 3 && context.Req.Method == "GET" { if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil { return isFinished } context.Router[context.RouterString[0]][context.RouterString[1]]["info"](context) } //更新 if len(context.RouterString) == 3 && context.Req.Method == "PUT" { if context.Router[context.RouterString[0]][context.RouterString[1]]["update"] == nil { return true } context.Router[context.RouterString[0]][context.RouterString[1]]["update"](context) } //移除 if len(context.RouterString) == 3 && context.Req.Method == "DELETE" { if context.Router[context.RouterString[0]][context.RouterString[1]]["remove"] == nil { return true } context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context) } //context.View() return true }) }