314 lines
8.3 KiB
Go
314 lines
8.3 KiB
Go
package command
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/Superdanda/hade/framework"
|
||
"github.com/Superdanda/hade/framework/cobra"
|
||
"github.com/Superdanda/hade/framework/contract"
|
||
"github.com/Superdanda/hade/framework/util"
|
||
"github.com/sevlyar/go-daemon"
|
||
"io/ioutil"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"path/filepath"
|
||
"strconv"
|
||
"syscall"
|
||
"time"
|
||
)
|
||
|
||
var appAddress = ""
|
||
var appDaemon = false
|
||
|
||
func initAppCommand() *cobra.Command {
|
||
appStartCommand.Flags().BoolVarP(&appDaemon, "daemon", "d", false, "start app daemon")
|
||
appStartCommand.Flags().StringVar(&appAddress, "address", "", "设置app启动的地址,默认为:8888")
|
||
|
||
appCommand.AddCommand(appStartCommand)
|
||
appCommand.AddCommand(appRestartCommand)
|
||
appCommand.AddCommand(appStopCommand)
|
||
appCommand.AddCommand(appStateCommand)
|
||
return appCommand
|
||
}
|
||
|
||
var appCommand = &cobra.Command{
|
||
Use: "app",
|
||
Short: "业务应用控制命令",
|
||
Long: "业务应用控制命令,其包含业务启动,关闭,重启,查询等功能",
|
||
RunE: func(c *cobra.Command, args []string) error {
|
||
c.Help()
|
||
return nil
|
||
},
|
||
}
|
||
|
||
// 启动AppServer, 这个函数会将当前goroutine阻塞
|
||
func startAppServe(server *http.Server, c framework.Container) error {
|
||
// 这个goroutine是启动服务的goroutine
|
||
go func() {
|
||
server.ListenAndServe()
|
||
}()
|
||
|
||
// 当前的goroutine等待信号量
|
||
quit := make(chan os.Signal)
|
||
// 监控信号:SIGINT, SIGTERM, SIGQUIT
|
||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||
// 这里会阻塞当前goroutine等待信号
|
||
<-quit
|
||
|
||
// 调用Server.Shutdown graceful结束
|
||
closeWait := 5
|
||
configService := c.MustMake(contract.ConfigKey).(contract.Config)
|
||
if configService.IsExist("app.close_wait") {
|
||
closeWait = configService.GetInt("app.close_wait")
|
||
}
|
||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(closeWait)*time.Second)
|
||
defer cancel()
|
||
|
||
if err := server.Shutdown(timeoutCtx); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
var appStartCommand = &cobra.Command{
|
||
Use: "start",
|
||
Short: "启动一个Web服务",
|
||
RunE: func(c *cobra.Command, args []string) error {
|
||
// 从Command中获取服务容器
|
||
container := c.GetContainer()
|
||
// 从服务容器中获取kernel的服务实例
|
||
kernelService := container.MustMake(contract.KernelKey).(contract.Kernel)
|
||
// 从kernel服务实例中获取引擎
|
||
core := kernelService.HttpEngine()
|
||
|
||
if appAddress == "" {
|
||
envService := container.MustMake(contract.EnvKey).(contract.Env)
|
||
if envService.Get("ADDRESS") != "" {
|
||
appAddress = envService.Get("ADDRESS")
|
||
} else {
|
||
configService := container.MustMake(contract.ConfigKey).(contract.Config)
|
||
if configService.IsExist("app.address") {
|
||
appAddress = configService.GetString("app.address")
|
||
} else {
|
||
appAddress = ":8888"
|
||
}
|
||
}
|
||
}
|
||
// 创建一个Server服务
|
||
server := &http.Server{
|
||
Handler: core,
|
||
Addr: appAddress,
|
||
}
|
||
|
||
// 这个goroutine是启动服务的goroutine
|
||
go func() {
|
||
server.ListenAndServe()
|
||
}()
|
||
|
||
appService := container.MustMake(contract.AppKey).(contract.App)
|
||
|
||
pidFolder := appService.RuntimeFolder()
|
||
if !util.Exists(pidFolder) {
|
||
if err := os.MkdirAll(pidFolder, os.ModePerm); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
serverPidFile := filepath.Join(pidFolder, "app.pid")
|
||
logFolder := appService.LogFolder()
|
||
if !util.Exists(logFolder) {
|
||
if err := os.MkdirAll(logFolder, os.ModePerm); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 应用日志
|
||
serverLogFile := filepath.Join(logFolder, "app.log")
|
||
currentFolder := util.GetExecDirectory()
|
||
|
||
// daemon 模式
|
||
if appDaemon {
|
||
// 创建一个Context
|
||
cntxt := &daemon.Context{
|
||
// 设置pid文件
|
||
PidFileName: serverPidFile,
|
||
PidFilePerm: 0664,
|
||
// 设置日志文件
|
||
LogFileName: serverLogFile,
|
||
LogFilePerm: 0640,
|
||
// 设置工作路径
|
||
WorkDir: currentFolder,
|
||
// 设置所有设置文件的mask,默认为750
|
||
Umask: 027,
|
||
// 子进程的参数,按照这个参数设置,子进程的命令为 ./hade app start --daemon=true
|
||
Args: []string{"", "app", "start", "--daemon=true"},
|
||
}
|
||
// 启动子进程,d不为空表示当前是父进程,d为空表示当前是子进程
|
||
d, err := cntxt.Reborn()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if d != nil {
|
||
// 父进程直接打印启动成功信息,不做任何操作
|
||
fmt.Println("app启动成功,pid:", d.Pid)
|
||
fmt.Println("日志文件:", serverLogFile)
|
||
return nil
|
||
}
|
||
defer cntxt.Release()
|
||
// 子进程执行真正的app启动操作
|
||
fmt.Println("deamon started")
|
||
//gspt.SetProcTitle("hade app")
|
||
if err := startAppServe(server, container); err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 非deamon模式,直接执行
|
||
content := strconv.Itoa(os.Getpid())
|
||
fmt.Println("[PID]", content)
|
||
err := ioutil.WriteFile(serverPidFile, []byte(content), 0644)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
//gspt.SetProcTitle("hade app")
|
||
|
||
fmt.Println("app serve url:", appAddress)
|
||
if err := startAppServe(server, container); err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
return nil
|
||
},
|
||
}
|
||
|
||
// 重新启动一个app服务
|
||
var appRestartCommand = &cobra.Command{
|
||
Use: "restart",
|
||
Short: "重新启动一个app服务",
|
||
RunE: func(c *cobra.Command, args []string) error {
|
||
container := c.GetContainer()
|
||
appService := container.MustMake(contract.AppKey).(contract.App)
|
||
|
||
// GetPid
|
||
serverPidFile := filepath.Join(appService.RuntimeFolder(), "app.pid")
|
||
|
||
content, err := ioutil.ReadFile(serverPidFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if content != nil && len(content) != 0 {
|
||
pid, err := strconv.Atoi(string(content))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if util.CheckProcessExist(pid) {
|
||
// 杀死进程
|
||
proc, _ := os.FindProcess(pid)
|
||
proc.Signal(syscall.SIGTERM)
|
||
if err := proc.Kill(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 获取closeWait
|
||
closeWait := 5
|
||
configService := container.MustMake(contract.ConfigKey).(contract.Config)
|
||
if configService.IsExist("app.close_wait") {
|
||
closeWait = configService.GetInt("app.close_wait")
|
||
}
|
||
|
||
// 确认进程已经关闭,每秒检测一次, 最多检测closeWait * 2秒
|
||
for i := 0; i < closeWait*2; i++ {
|
||
if util.CheckProcessExist(pid) == false {
|
||
break
|
||
}
|
||
time.Sleep(1 * time.Second)
|
||
}
|
||
|
||
// 如果进程等待了2*closeWait之后还没结束,返回错误,不进行后续的操作
|
||
if util.CheckProcessExist(pid) == true {
|
||
fmt.Println("结束进程失败:"+strconv.Itoa(pid), "请查看原因")
|
||
return errors.New("结束进程失败")
|
||
}
|
||
if err := ioutil.WriteFile(serverPidFile, []byte{}, 0644); err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println("结束进程成功:" + strconv.Itoa(pid))
|
||
}
|
||
}
|
||
|
||
appDaemon = true
|
||
// 直接daemon方式启动apps
|
||
return appStartCommand.RunE(c, args)
|
||
},
|
||
}
|
||
|
||
// 停止一个已经启动的app服务
|
||
var appStopCommand = &cobra.Command{
|
||
Use: "stop",
|
||
Short: "停止一个已经启动的app服务",
|
||
RunE: func(c *cobra.Command, args []string) error {
|
||
container := c.GetContainer()
|
||
appService := container.MustMake(contract.AppKey).(contract.App)
|
||
|
||
// GetPid
|
||
serverPidFile := filepath.Join(appService.RuntimeFolder(), "app.pid")
|
||
|
||
content, err := ioutil.ReadFile(serverPidFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if content != nil && len(content) != 0 {
|
||
pid, err := strconv.Atoi(string(content))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// 发送SIGTERM命令
|
||
process, err := os.FindProcess(pid)
|
||
process.Signal(syscall.SIGTERM)
|
||
if err := process.Kill(); err != nil {
|
||
return err
|
||
}
|
||
if err := ioutil.WriteFile(serverPidFile, []byte{}, 0644); err != nil {
|
||
return err
|
||
}
|
||
fmt.Println("停止进程:", pid)
|
||
}
|
||
return nil
|
||
},
|
||
}
|
||
|
||
// 获取启动的app的pid
|
||
var appStateCommand = &cobra.Command{
|
||
Use: "state",
|
||
Short: "获取启动的app的pid",
|
||
RunE: func(c *cobra.Command, args []string) error {
|
||
container := c.GetContainer()
|
||
appService := container.MustMake(contract.AppKey).(contract.App)
|
||
|
||
// 获取pid
|
||
serverPidFile := filepath.Join(appService.RuntimeFolder(), "app.pid")
|
||
|
||
content, err := ioutil.ReadFile(serverPidFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if content != nil && len(content) > 0 {
|
||
pid, err := strconv.Atoi(string(content))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if util.CheckProcessExist(pid) {
|
||
fmt.Println("app服务已经启动, pid:", pid)
|
||
return nil
|
||
}
|
||
}
|
||
fmt.Println("没有app服务存在")
|
||
return nil
|
||
},
|
||
}
|