Files
hotime/application.go
T

971 lines
28 KiB
Go
Raw Permalink Normal View History

2017-08-17 02:14:59 +00:00
package hotime
import (
2026-04-14 14:07:59 +08:00
"context"
2017-08-17 02:14:59 +00:00
"database/sql"
"fmt"
2017-08-17 02:14:59 +00:00
"io/ioutil"
stdlog "log"
"net"
2017-08-17 02:14:59 +00:00
"net/http"
2019-05-19 15:33:01 +00:00
"net/url"
2017-08-17 02:14:59 +00:00
"os"
2026-04-14 14:07:59 +08:00
"os/signal"
2017-08-17 02:14:59 +00:00
"path/filepath"
"strconv"
"strings"
2026-04-14 14:07:59 +08:00
"sync"
"sync/atomic"
"syscall"
2021-11-08 06:49:34 +08:00
"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"
mysql "github.com/go-sql-driver/mysql"
logrus "github.com/sirupsen/logrus"
"github.com/rs/zerolog"
2017-08-17 02:14:59 +00:00
)
type Application struct {
2022-03-13 05:06:28 +08:00
MakeCodeRouter map[string]*code.MakeCode
2018-11-14 02:31:08 +00:00
MethodRouter
2017-08-17 02:14:59 +00:00
Router
Log *log.Logger
WebConnectLog *log.Logger
2017-08-17 02:14:59 +00:00
Port string //端口号
2019-11-10 18:00:45 +08:00
TLSPort string //ssl访问端口号
2022-03-13 01:48:54 +08:00
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectDbFunc func() (master, slave *sql.DB)
2017-08-17 02:14:59 +00:00
configPath string
Config Map
Db HoTimeDB
*HoTimeCache
2021-05-25 05:08:17 +08:00
*http.Server
2017-10-27 04:28:47 +00:00
http.Handler
2026-04-14 14:07:59 +08:00
shuttingDown atomic.Bool // 停机标志,置为 true 后新请求立即返回 503
inFlight atomic.Int64 // 当前正在处理的请求数(不含已返回 503 的)
2026-04-14 14:07:59 +08:00
shutdownOnce sync.Once // 保证多路触发(信号/关窗口)只执行一次
DrainTimeout time.Duration // 已停止接收新请求后、等老请求自然结束的最长时长,默认 5s(每 1s 检测在途请求,为 0 则立即进入 Shutdown)
ShutdownTimeout time.Duration // drain 超时后再给老请求的最长容忍时间,默认 10s(到期后强制关闭)
2017-10-27 04:28:47 +00:00
}
2021-05-25 05:08:17 +08:00
func (that *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
2026-04-14 14:07:59 +08:00
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)
2021-05-25 05:08:17 +08:00
that.handler(w, req)
2017-08-17 02:14:59 +00:00
}
2026-04-14 14:07:59 +08:00
// initiateGracefulShutdown 触发优雅停机,通过 sync.Once 保证多路触发只执行一次。
// drain: 已停止接收新请求后、等老请求自然结束的最长时长,每 1s 检测一次在途请求数,
// 为 0 则立即进入 Shutdown,避免无谓等待。
// shutdown: drain 超时后再给老请求的最长容忍时间,到期强制关闭。
2026-04-14 14:07:59 +08:00
func (that *Application) initiateGracefulShutdown(drain, shutdown time.Duration) {
that.shutdownOnce.Do(func() {
that.shuttingDown.Store(true)
that.Log.Infof("优雅停机:已停止接收新请求(后续请求一律返回 503),等待老请求自然结束,最多再等 %v...", drain)
// 每秒检测一次,若无在途请求立即停机,否则最多等满 drain
tick := time.NewTicker(1 * time.Second)
defer tick.Stop()
deadline := time.Now().Add(drain)
for {
n := that.inFlight.Load()
if n == 0 {
that.Log.Infof("已无在途请求,立即停机")
break
}
if time.Now().After(deadline) {
that.Log.Infof("已停止接收新请求,仍有 %d 个老请求未结束,将在最多 %v 后强制结束这些请求", n, shutdown)
break
}
that.Log.Infof("已停止接收新请求,当前仍有 %d 个老请求在执行,继续等待...", n)
<-tick.C
}
2026-04-14 14:07:59 +08:00
ctx, cancel := context.WithTimeout(context.Background(), shutdown)
defer cancel()
if err := that.Server.Shutdown(ctx); err != nil {
that.Log.Errorf("等待超时,强制关闭仍在执行的 %d 个请求: %v", that.inFlight.Load(), err)
2026-04-14 14:07:59 +08:00
} else {
that.Log.Infof("服务已安全关闭")
}
os.Exit(0)
})
}
2021-05-25 05:08:17 +08:00
// Run 启动实例
func (that *Application) Run(router Router) {
2020-02-21 21:44:53 +08:00
//如果没有设置配置自动生成配置
2021-05-25 05:08:17 +08:00
if that.configPath == "" || len(that.Config) == 0 {
that.SetConfig()
2020-02-21 21:44:53 +08:00
}
//防止手动设置缓存误伤
if that.HoTimeCache == nil {
that.SetCache()
2020-02-21 21:44:53 +08:00
}
//防止手动设置session误伤
2021-05-25 05:08:17 +08:00
//if that.sessionShort == nil && that.sessionLong == nil {
// if that.connectDbFunc == nil {
// that.SetSession(CacheIns(&CacheMemory{}), nil)
2021-05-24 07:27:41 +08:00
// } else {
2021-05-25 05:08:17 +08:00
// that.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &that.Db, Time: that.Config.GetInt64("cacheLongTime")}))
2021-05-24 07:27:41 +08:00
// }
//
//}
2018-04-03 17:54:27 +00:00
2021-12-27 20:40:16 +08:00
//that.Router = router
if that.Router == nil {
that.Router = Router{}
}
for k, _ := range router {
v := router[k]
2022-03-16 09:58:24 +08:00
if that.Router[k] == nil {
that.Router[k] = v
}
2022-06-09 03:54:33 +08:00
//直达接口层复用
for k1, _ := range v {
v1 := v[k1]
2022-06-09 03:54:33 +08:00
if that.Router[k][k1] == nil {
that.Router[k][k1] = v1
}
for k2, _ := range v1 {
v2 := v1[k2]
2022-06-09 03:54:33 +08:00
that.Router[k][k1][k2] = v2
}
2022-03-16 09:58:24 +08:00
}
2022-06-09 03:54:33 +08:00
2021-12-27 20:40:16 +08:00
}
2018-11-14 02:31:08 +00:00
//重新设置MethodRouter//直达路由
2021-05-25 05:08:17 +08:00
that.MethodRouter = MethodRouter{}
2019-05-19 15:33:01 +00:00
modeRouterStrict := true
2021-05-25 05:08:17 +08:00
if that.Config.GetBool("modeRouterStrict") == false {
2019-05-19 15:33:01 +00:00
modeRouterStrict = false
2018-11-15 03:44:50 +00:00
}
2021-12-27 20:40:16 +08:00
if that.Router != nil {
for pk, pv := range that.Router {
2019-05-19 15:33:01 +00:00
if !modeRouterStrict {
pk = strings.ToLower(pk)
2018-11-15 03:44:50 +00:00
}
2019-05-19 15:33:01 +00:00
if pv != nil {
for ck, cv := range pv {
if !modeRouterStrict {
ck = strings.ToLower(ck)
2018-11-15 03:44:50 +00:00
}
2019-05-19 15:33:01 +00:00
if cv != nil {
for mk, mv := range cv {
if !modeRouterStrict {
mk = strings.ToLower(mk)
2018-11-14 02:31:08 +00:00
}
2021-05-25 05:08:17 +08:00
that.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
2018-11-14 02:31:08 +00:00
}
2019-05-19 15:33:01 +00:00
}
2018-11-14 02:31:08 +00:00
}
}
}
}
2021-05-25 05:08:17 +08:00
//that.Port = port
that.Port = that.Config.GetString("port")
that.TLSPort = that.Config.GetString("tlsPort")
2017-08-17 02:14:59 +00:00
2021-05-25 05:08:17 +08:00
if that.connectDbFunc != nil && (that.Db.DB == nil || that.Db.DB.Ping() != nil) {
that.Db.SetConnect(that.connectDbFunc)
2017-08-17 02:14:59 +00:00
}
2017-10-13 09:52:34 +00:00
//异常处理
defer func() {
if err := recover(); err != nil {
2026-04-14 14:07:59 +08:00
if that.shuttingDown.Load() {
return // 停机过程中的 panic 不触发重启
}
2021-05-25 05:08:17 +08:00
//that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
that.Log.EmitRecoveredPanic(err)
2021-05-25 05:08:17 +08:00
that.Run(router)
2017-10-13 09:52:34 +00:00
}
}()
2021-05-25 05:08:17 +08:00
that.Server = &http.Server{}
2017-10-27 04:28:47 +00:00
if !IsRun {
IsRun = true
}
2019-05-27 05:46:03 +00:00
ch := make(chan int)
2021-05-25 05:08:17 +08:00
if ObjToCeilInt(that.Port) != 0 {
2019-11-10 18:00:45 +08:00
go func() {
2019-07-01 04:35:04 +00:00
2021-05-25 05:08:17 +08:00
App[that.Port] = that
that.Server.Handler = that
2019-05-27 05:46:03 +00:00
//启动服务
2021-05-25 05:08:17 +08:00
that.Server.Addr = ":" + that.Port
err := that.Server.ListenAndServe()
2026-04-14 14:07:59 +08:00
if err != nil && err != http.ErrServerClosed {
that.Log.Error().Err(err).Msg("HTTP 服务退出")
ch <- 1
}
2019-05-27 05:46:03 +00:00
2019-11-10 18:00:45 +08:00
}()
2020-07-17 01:10:28 +08:00
}
2021-05-25 05:08:17 +08:00
if ObjToCeilInt(that.TLSPort) != 0 {
2019-07-01 04:35:04 +00:00
go func() {
2019-05-27 05:46:03 +00:00
2021-05-25 05:08:17 +08:00
App[that.TLSPort] = that
that.Server.Handler = that
2019-11-10 18:00:45 +08:00
//启动服务
2021-05-25 05:08:17 +08:00
that.Server.Addr = ":" + that.TLSPort
err := that.Server.ListenAndServeTLS(that.Config.GetString("tlsCert"), that.Config.GetString("tlsKey"))
2026-04-14 14:07:59 +08:00
if err != nil && err != http.ErrServerClosed {
that.Log.Error().Err(err).Msg("HTTPS 服务退出")
ch <- 2
}
2019-07-01 04:35:04 +00:00
}()
2020-07-17 01:10:28 +08:00
}
2021-05-25 05:08:17 +08:00
if ObjToCeilInt(that.Port) == 0 && ObjToCeilInt(that.TLSPort) == 0 {
that.Log.Errorf("没有端口启用")
2019-07-01 04:35:04 +00:00
return
}
2026-04-14 14:07:59 +08:00
// 注册 Windows 关窗口处理(Linux/macOS 上是空实现)
that.registerWindowsCloseHandler()
// 跨平台信号监听:Ctrl+C / SIGTERMkill 命令)/ 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 = 10 * time.Second
2026-04-14 14:07:59 +08:00
}
that.initiateGracefulShutdown(drain, shutdown)
}()
2019-11-10 18:00:45 +08:00
value := <-ch
2017-08-17 02:14:59 +00:00
that.Log.Errorf("启动服务失败 : %s", ObjToStr(value))
2017-08-17 02:14:59 +00:00
}
2021-05-25 05:08:17 +08:00
// SetConnectDB 启动实例
func (that *Application) SetConnectDB(connect func() (master, slave *sql.DB)) {
2019-05-27 17:01:13 +00:00
2021-05-25 05:08:17 +08:00
that.connectDbFunc = connect
2021-05-25 19:53:34 +08:00
that.Db.SetConnect(that.connectDbFunc)
2019-05-27 17:01:13 +00:00
2017-08-17 02:14:59 +00:00
}
2021-05-25 05:08:17 +08:00
// SetDefault 默认配置缓存和session实现
func (that *Application) SetDefault(connect func() (*sql.DB, *sql.DB)) {
2021-05-25 05:08:17 +08:00
that.SetConfig()
2019-11-10 18:10:26 +08:00
if connect != nil {
2021-05-25 05:08:17 +08:00
that.connectDbFunc = connect
that.Db.SetConnect(that.connectDbFunc)
2017-10-24 01:31:20 +00:00
}
2017-08-17 02:14:59 +00:00
}
2021-05-25 05:08:17 +08:00
// 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
2017-08-17 02:14:59 +00:00
}
2021-05-25 05:08:17 +08:00
// SetConfig 设置配置文件路径全路径或者相对路径
func (that *Application) SetConfig(configPath ...string) {
that.Log = log.NewLogger(1, "", 100)
2017-08-17 02:14:59 +00:00
if len(configPath) != 0 {
2021-05-25 05:08:17 +08:00
that.configPath = configPath[0]
2017-08-17 02:14:59 +00:00
}
2021-05-25 05:08:17 +08:00
if that.configPath == "" {
that.configPath = "config/config.json"
2017-08-17 02:14:59 +00:00
}
//加载配置文件
2021-05-25 05:08:17 +08:00
btes, err := ioutil.ReadFile(that.configPath)
that.Config = DeepCopyMap(Config).(Map)
configErr := false
2017-08-17 02:14:59 +00:00
if err == nil {
cmap := Map{}
cmap.JsonToMap(string(btes))
2017-08-17 02:14:59 +00:00
for k, v := range cmap {
that.Config[k] = v
Config[k] = v
2017-08-17 02:14:59 +00:00
}
2019-11-10 18:00:45 +08:00
} else {
that.Log.Errorf("配置文件不存在,或者配置出错,使用缺省默认配置")
2017-08-17 02:14:59 +00:00
}
2019-09-03 03:55:05 +00:00
if !configErr {
2021-06-05 02:18:56 +08:00
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)
2017-08-17 02:14:59 +00:00
}
}
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
2022-08-03 12:07:55 +08:00
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
2026-04-14 14:07:59 +08:00
that.WebConnectLog = log.NewLoggerNoCaller(1, that.Config.GetString("webConnectLogFile"), 0)
2022-08-03 12:07:55 +08:00
}
// 重定向 os.Stdout 和标准 log 包,捕获 fmt.Println 等绕过 HoTime Logger 的输出
stdoutW := redirectStdout(that.Log)
bridgeThirdPartyStdout(stdoutW)
// 重定向 os.Stderr,捕获 panic 栈、MySQL driver errLog 等写 stderr 的输出
// 注意:Logger 在此之前已创建,ConsoleWriter.Out 已绑定到原始 os.Stderr,无循环风险
redirectStderr(that.Log)
// 接入 Seqinstance = ip:port,同机多进程靠端口区分,跨机器靠 IP 区分
if seqUrl := that.Config.GetString("seqUrl"); seqUrl != "" {
instance := getLocalIP() + ":" + that.Config.GetString("port")
that.Log.SetSeqWriter(seqUrl, that.Config.GetString("seqApiKey"), instance)
if that.WebConnectLog != nil {
that.WebConnectLog.SetSeqWriter(seqUrl, that.Config.GetString("seqApiKey"), instance)
}
that.Log.Infof("Seq 日志推送已启动: url=%s instance=%s", seqUrl, instance)
}
// 把 MySQL 驱动内部 errLog(连接/包错误)接入 HoTime Logger,经 SeqWriter 推送到 Seq
_ = mysql.SetLogger(&mysqlLogAdapter{l: that.Log})
2017-08-17 02:14:59 +00:00
}
2022-03-13 17:02:19 +08:00
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) {
2021-05-25 05:08:17 +08:00
that.connectListener = append(that.connectListener, lis)
2017-08-17 02:14:59 +00:00
}
//网络错误
2022-03-13 01:48:54 +08:00
//func (that *Application) session(w http.ResponseWriter, req *http.Request) {
2018-04-03 17:54:27 +00:00
//
//}
2017-08-17 02:14:59 +00:00
2025-04-10 22:09:53 +08:00
// 序列化链接
2021-05-25 05:08:17 +08:00
func (that *Application) urlSer(url string) (string, []string) {
2017-08-22 08:24:55 +00:00
q := strings.Index(url, "?")
2017-08-17 02:14:59 +00:00
if q == -1 {
2017-08-22 08:24:55 +00:00
q = len(url)
2017-08-17 02:14:59 +00:00
}
2017-08-22 08:24:55 +00:00
o := Substr(url, 0, q)
2017-08-17 02:14:59 +00:00
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])
}
}
2017-08-22 08:24:55 +00:00
return o, s
}
//访问
2018-11-15 03:44:50 +00:00
2021-05-25 05:08:17 +08:00
func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
2021-11-08 06:49:34 +08:00
nowUnixTime := time.Now()
2017-08-22 08:24:55 +00:00
2021-05-25 05:08:17 +08:00
_, s := that.urlSer(req.RequestURI)
2017-08-22 08:24:55 +00:00
//获取cookie
// 如果cookie存在直接将sessionId赋值为cookie.Value
// 如果cookie不存在就查找传入的参数中是否有token
// 如果token不存在就生成随机的sessionId
// 如果token存在就判断token是否在Session中有保存
// 如果有取出token并复制给cookie
// 没有保存就生成随机的session
2021-05-25 05:08:17 +08:00
cookie, err := req.Cookie(that.Config.GetString("sessionName"))
2017-08-22 08:24:55 +00:00
sessionId := Md5(strconv.Itoa(Rand(10)))
2021-12-28 09:26:26 +08:00
needSetCookie := ""
2021-12-11 17:59:02 +08:00
token := req.Header.Get("Authorization")
2021-12-09 11:16:15 +08:00
if len(token) != 32 {
2021-05-29 02:57:03 +08:00
2021-12-11 17:59:02 +08:00
token = req.FormValue("token")
}
//没有cookie或者cookie不等于token
//有token优先token
if len(token) == 32 {
2021-12-09 11:16:15 +08:00
sessionId = token
2021-12-11 17:59:02 +08:00
//没有token,则查阅session
2022-11-14 10:38:15 +08:00
if cookie == nil || cookie.Value != sessionId {
needSetCookie = sessionId
}
2021-12-11 17:59:02 +08:00
} else if err == nil && cookie.Value != "" {
2021-12-09 11:16:15 +08:00
sessionId = cookie.Value
2021-12-11 17:59:02 +08:00
//session也没有则判断是否创建cookie
} else {
2021-12-28 09:26:26 +08:00
needSetCookie = sessionId
2017-08-22 08:24:55 +00:00
}
2017-08-17 02:14:59 +00:00
2019-05-19 15:33:01 +00:00
unescapeUrl, err := url.QueryUnescape(req.RequestURI)
if err != nil {
unescapeUrl = req.RequestURI
2018-11-06 14:57:53 +00:00
}
2017-08-22 08:24:55 +00:00
//访问实例
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}
2017-10-11 09:45:22 +00:00
//header默认设置
header := w.Header()
2017-10-12 02:07:51 +00:00
header.Set("Content-Type", "text/html; charset=utf-8")
2018-11-14 16:10:41 +00:00
//url去掉参数并序列化
2021-05-25 05:08:17 +08:00
context.HandlerStr, context.RouterString = that.urlSer(context.HandlerStr)
2018-11-14 16:10:41 +00:00
//跨域设置
2021-12-28 09:26:26 +08:00
that.crossDomain(&context, needSetCookie)
2021-11-08 06:49:34 +08:00
defer func() {
//是否展示日志
if that.WebConnectLog != nil {
2022-09-02 00:04:23 +08:00
2021-11-08 06:49:34 +08:00
//负载均衡优化
2022-09-02 00:04:23 +08:00
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, ":"))
2021-11-08 06:49:34 +08:00
}
2021-12-28 09:26:26 +08:00
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)
2021-11-08 06:49:34 +08:00
}
}()
2020-02-20 15:06:39 +08:00
2017-08-22 08:24:55 +00:00
//访问拦截true继续false暂停
2022-08-25 13:06:26 +08:00
connectListenerLen := len(that.connectListener) - 1
2022-03-13 17:02:19 +08:00
2022-08-25 13:06:26 +08:00
for true {
if connectListenerLen < 0 {
break
}
if that.connectListener[connectListenerLen](&context) {
2022-03-13 17:02:19 +08:00
context.View()
return
2017-08-22 08:24:55 +00:00
}
2022-08-25 13:06:26 +08:00
connectListenerLen--
2017-08-22 08:24:55 +00:00
}
2018-11-14 16:10:41 +00:00
2017-08-17 02:14:59 +00:00
//接口服务
2018-11-15 03:44:50 +00:00
//验证接口严格模式
2021-05-25 05:08:17 +08:00
modeRouterStrict := that.Config.GetBool("modeRouterStrict")
2019-05-19 15:33:01 +00:00
tempHandlerStr := context.HandlerStr
if !modeRouterStrict {
tempHandlerStr = strings.ToLower(tempHandlerStr)
2018-11-15 03:44:50 +00:00
}
2017-08-17 02:14:59 +00:00
2018-11-15 03:44:50 +00:00
//执行接口
2021-05-25 05:08:17 +08:00
if that.MethodRouter[tempHandlerStr] != nil {
that.MethodRouter[tempHandlerStr](&context)
2019-05-19 15:33:01 +00:00
context.View()
return
2017-08-17 02:14:59 +00:00
}
//url赋值,静态文件必须使用原始路径(Linux文件系统大小写敏感)
path := that.Config.GetString("tpt") + context.HandlerStr
2017-08-17 02:14:59 +00:00
//判断是否为默认
if path[len(path)-1] == '/' {
2021-05-25 05:08:17 +08:00
defFile := that.Config.GetSlice("defFile")
2017-10-27 04:28:47 +00:00
2017-08-17 02:14:59 +00:00
for i := 0; i < len(defFile); i++ {
2017-10-27 04:28:47 +00:00
temp := path + defFile.GetString(i)
2017-08-17 02:14:59 +00:00
_, 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
}
2017-10-12 02:07:51 +00:00
//设置header
2019-05-19 15:33:01 +00:00
delete(header, "Content-Type")
if that.Config.GetCeilInt("logLevel") == 0 {
2018-05-29 17:39:37 +00:00
header.Set("Cache-Control", "public")
} else {
header.Set("Cache-Control", "no-cache")
2018-05-29 17:39:37 +00:00
}
2017-10-12 02:07:51 +00:00
2022-03-02 12:14:07 +08:00
t := strings.LastIndex(path, ".")
if t != -1 {
tt := strings.ToLower(path[t:])
2022-03-02 12:14:07 +08:00
if MimeMaps[tt] != "" {
header.Add("Content-Type", MimeMaps[tt])
}
2019-05-19 15:33:01 +00:00
}
2017-08-17 02:14:59 +00:00
//w.Write(data)
http.ServeFile(w, req, path)
}
2021-12-28 09:26:26 +08:00
func (that *Application) crossDomain(context *Context, sessionId string) {
//没有跨域设置
2020-02-21 21:44:53 +08:00
if context.Config.GetString("crossDomain") == "" {
2021-12-28 09:26:26 +08:00
if sessionId != "" {
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
2025-04-10 22:09:53 +08:00
//context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
2021-12-28 09:26:26 +08:00
}
return
}
2020-02-21 21:44:53 +08:00
header := context.Resp.Header()
2021-12-17 14:37:41 +08:00
//不跨域,则不设置
remoteHost := context.Req.Host
2021-12-27 14:00:08 +08:00
if context.Config.GetString("port") == "80" || context.Config.GetString("port") == "443" {
2021-12-17 14:37:41 +08:00
remoteHost = remoteHost + ":" + context.Config.GetString("port")
}
2021-05-23 03:35:49 +08:00
if context.Config.GetString("crossDomain") != "auto" {
2021-12-17 14:37:41 +08:00
//不跨域,则不设置
if strings.Contains(context.Config.GetString("crossDomain"), remoteHost) {
2021-12-28 09:26:26 +08:00
if sessionId != "" {
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
}
2021-12-17 14:37:41 +08:00
return
}
2021-05-25 05:08:17 +08:00
header.Set("Access-Control-Allow-Origin", that.Config.GetString("crossDomain"))
2021-07-05 02:20:10 +08:00
// 后端设置,2592000单位秒,这里是30天
header.Set("Access-Control-Max-Age", "2592000")
2021-12-17 14:37:41 +08:00
//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", "*")
2025-04-10 22:09:53 +08:00
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
2021-12-17 14:37:41 +08:00
2021-12-28 09:26:26 +08:00
if sessionId != "" {
//跨域允许需要设置cookie的允许跨域https才有效果
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
}
return
}
2020-02-21 21:44:53 +08:00
origin := context.Req.Header.Get("Origin")
2021-12-17 14:37:41 +08:00
refer := context.Req.Header.Get("Referer")
2021-12-28 09:26:26 +08:00
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
if sessionId != "" {
2025-04-10 22:09:53 +08:00
//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")
2021-12-28 09:26:26 +08:00
}
2021-12-17 14:37:41 +08:00
return
}
if origin != "" {
header.Set("Access-Control-Allow-Origin", origin)
2021-12-27 14:00:08 +08:00
//return
2021-12-28 09:26:26 +08:00
} 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)
2021-12-17 14:37:41 +08:00
//header.Set("Access-Control-Allow-Origin", "*")
2021-12-28 09:26:26 +08:00
}
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
header.Set("Access-Control-Allow-Credentials", "true")
header.Set("Access-Control-Expose-Headers", "*")
2025-04-10 22:09:53 +08:00
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
2021-12-28 09:26:26 +08:00
if sessionId != "" {
//跨域允许需要设置cookie的允许跨域https才有效果
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
}
2021-12-28 09:26:26 +08:00
}
2021-05-24 07:27:41 +08:00
// getLocalIP 通过 UDP dial 探测方式获取本机出口 IP(不发送任何数据,无副作用)。
// 用于构建 Seq instance 字段,格式为 ip:port,支持跨机器集群识别。
// 失败时返回 "unknown"。
func getLocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "unknown"
}
defer conn.Close()
return conn.LocalAddr().(*net.UDPAddr).IP.String()
}
// redirectStdout 重定向 os.Stdout 和标准 log 包到 HoTime Logger。
// 返回管道写端,供 logrus 等第三方库桥接。
func redirectStdout(l *log.Logger) *os.File {
r, w, err := os.Pipe()
if err != nil {
return nil
}
os.Stdout = w
stdlog.SetOutput(w)
go log.CaptureStream(l, zerolog.InfoLevel, "stdout", r)
return w
}
// bridgeThirdPartyStdout 将 import 时钉死原始 stdout 的第三方日志指到捕获管道。
func bridgeThirdPartyStdout(w *os.File) {
if w == nil {
return
}
logrus.SetOutput(w)
}
// redirectStderr 重定向 os.Stderr 到 HoTime Loggerlevel=error, source=stderr)。
func redirectStderr(l *log.Logger) {
r, w, err := os.Pipe()
if err != nil {
l.EmitCaptured(zerolog.WarnLevel, "stderr-capture", fmt.Sprintf("create pipe failed: %v", err))
return
}
os.Stderr = w
go log.CaptureStream(l, zerolog.ErrorLevel, "stderr", r)
}
2025-04-10 22:09:53 +08:00
// Init 初始化application
2022-04-20 22:47:58 +08:00
func Init(config string) *Application {
2021-05-24 05:47:56 +08:00
appIns := Application{}
//手动模式,
appIns.SetConfig(config)
2021-05-25 19:53:34 +08:00
2021-05-24 06:14:58 +08:00
SetDB(&appIns)
appIns.SetCache()
2022-03-13 17:02:19 +08:00
codeConfig := appIns.Config.GetSlice("codeConfig")
2022-03-13 05:06:28 +08:00
if codeConfig != nil {
2022-03-03 21:23:57 +08:00
for k, _ := range codeConfig {
2022-03-13 05:06:28 +08:00
codeMake := codeConfig.GetMap(k)
if codeMake == nil {
continue
}
2022-03-13 17:02:19 +08:00
//codeMake["table"] = k
2022-03-13 05:06:28 +08:00
if appIns.MakeCodeRouter == nil {
appIns.MakeCodeRouter = map[string]*code.MakeCode{}
}
2022-03-13 17:02:19 +08:00
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)
2022-07-11 19:13:20 +08:00
2021-12-27 20:40:16 +08:00
//接入动态代码层
if appIns.Router == nil {
appIns.Router = Router{}
}
2022-03-13 05:06:28 +08:00
//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
}
}
2022-03-13 05:06:28 +08:00
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
}
2021-09-12 05:35:14 +08:00
}
2021-12-27 20:40:16 +08:00
2022-08-25 13:02:44 +08:00
setMakeCodeListener(codeMake.GetString("name"), &appIns)
2021-12-27 20:40:16 +08:00
}
}
2022-04-20 22:47:58 +08:00
return &appIns
2021-05-24 05:47:56 +08:00
}
2021-05-24 07:27:41 +08:00
2021-05-25 05:08:17 +08:00
// SetDB 智能数据库设置
2021-05-24 07:27:41 +08:00
func SetDB(appIns *Application) {
db := appIns.Config.GetMap("db")
dbSqlite := db.GetMap("sqlite")
dbMysql := db.GetMap("mysql")
2026-03-20 10:46:51 +08:00
dbDm := db.GetMap("dm")
2021-05-24 07:27:41 +08:00
if db != nil && dbSqlite != nil {
SetSqliteDB(appIns, dbSqlite)
}
if db != nil && dbMysql != nil {
SetMysqlDB(appIns, dbMysql)
}
2026-03-20 10:46:51 +08:00
if db != nil && dbDm != nil {
SetDmDB(appIns, dbDm)
}
2021-05-24 07:27:41 +08:00
}
func SetMysqlDB(appIns *Application, config Map) {
2021-05-25 19:53:34 +08:00
appIns.Db.Type = "mysql"
2021-06-05 02:18:56 +08:00
appIns.Db.DBName = config.GetString("name")
2021-05-29 00:37:20 +08:00
appIns.Db.Prefix = config.GetString("prefix")
2022-07-11 11:07:17 +08:00
appIns.Db.Log = appIns.Log
appIns.SetConnectDB(func() (master, slave *sql.DB) {
2021-05-24 07:27:41 +08:00
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 主库连接失败")
2021-05-24 07:27:41 +08:00
}
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 从库连接失败")
2021-05-24 07:27:41 +08:00
}
slave = DB1
}
return master, slave
})
}
func SetSqliteDB(appIns *Application, config Map) {
2021-05-25 19:53:34 +08:00
appIns.Db.Type = "sqlite"
2021-05-29 00:37:20 +08:00
appIns.Db.Prefix = config.GetString("prefix")
2022-07-11 11:07:17 +08:00
appIns.Db.Log = appIns.Log
appIns.SetConnectDB(func() (master, slave *sql.DB) {
2021-05-24 07:27:41 +08:00
db, e := sql.Open("sqlite3", config.GetString("path"))
if e != nil {
appIns.Log.Error().Err(e).Msg("SQLite 连接失败")
2021-05-24 07:27:41 +08:00
}
master = db
return master, slave
})
}
2021-12-27 20:40:16 +08:00
2026-03-20 10:46:51 +08:00
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) {
2026-03-20 10:46:51 +08:00
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("达梦主库连接失败")
2026-03-20 10:46:51 +08:00
}
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("达梦从库连接失败")
2026-03-20 10:46:51 +08:00
}
slave = DB1
}
return master, slave
})
}
2022-08-25 13:02:44 +08:00
func setMakeCodeListener(name string, appIns *Application) {
2022-03-13 17:02:19 +08:00
appIns.SetConnectListener(func(context *Context) (isFinished bool) {
2021-12-27 20:40:16 +08:00
2022-03-14 16:48:19 +08:00
codeIns := appIns.MakeCodeRouter[name]
2021-12-27 20:40:16 +08:00
2022-03-14 16:48:19 +08:00
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
}
2022-07-21 00:24:39 +08:00
if context.RouterString[1] == "hotime" && context.RouterString[2] == "config" {
return isFinished
}
2022-10-08 16:52:42 +08:00
if context.RouterString[1] == "hotime" && context.RouterString[2] == "wallpaper" {
return isFinished
}
2022-03-14 16:48:19 +08:00
if context.Session(codeIns.FileConfig.GetString("table")+"_id").Data == nil {
context.Display(2, "你还没有登录")
return true
}
}
if context.RouterString[0] != name {
return isFinished
}
2021-12-27 20:40:16 +08:00
if len(context.RouterString) < 2 || len(context.RouterString) > 3 ||
!(context.Router[context.RouterString[0]] != nil &&
context.Router[context.RouterString[0]][context.RouterString[1]] != nil) {
2022-03-13 17:02:19 +08:00
return isFinished
2021-12-27 20:40:16 +08:00
}
//排除无效操作
if len(context.RouterString) == 2 &&
context.Req.Method != "GET" &&
context.Req.Method != "POST" {
2022-03-13 17:02:19 +08:00
return isFinished
2021-12-27 20:40:16 +08:00
}
2022-06-09 04:05:50 +08:00
//排除已有接口的无效操作
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
}
2021-12-27 20:40:16 +08:00
//列表检索
if len(context.RouterString) == 2 &&
context.Req.Method == "GET" {
if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil {
2022-03-13 17:02:19 +08:00
return isFinished
2021-12-27 20:40:16 +08:00
}
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" {
2022-03-13 17:02:19 +08:00
return isFinished
2021-12-27 20:40:16 +08:00
}
//分析
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)
}
2021-12-27 20:40:16 +08:00
//查询单条
if len(context.RouterString) == 3 &&
context.Req.Method == "GET" {
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
2022-03-13 17:02:19 +08:00
return isFinished
2021-12-27 20:40:16 +08:00
}
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)
}
2022-03-13 17:02:19 +08:00
2022-08-25 14:06:31 +08:00
//context.View()
2022-03-13 17:02:19 +08:00
return true
2021-12-27 20:40:16 +08:00
})
}
// mysqlLogAdapter 将 go-sql-driver/mysql 的内部 errLog 桥接到 HoTime Logger。
// MySQL 驱动连接/包级别错误通过此适配器以 error 级别推送到 Seq。
type mysqlLogAdapter struct{ l *log.Logger }
func (a *mysqlLogAdapter) Print(v ...interface{}) {
a.l.Error().Str("source", "mysql-driver").Msg(fmt.Sprint(v...))
}