framework1/framework/command/app.go

314 lines
8.3 KiB
Go
Raw Normal View History

2024-10-19 22:02:19 +08:00
package command
import (
"context"
2024-10-23 17:00:42 +08:00
"errors"
"fmt"
"github.com/Superdanda/hade/framework"
2024-10-19 22:02:19 +08:00
"github.com/Superdanda/hade/framework/cobra"
"github.com/Superdanda/hade/framework/contract"
2024-10-23 17:00:42 +08:00
"github.com/Superdanda/hade/framework/util"
"github.com/sevlyar/go-daemon"
"io/ioutil"
2024-10-19 22:02:19 +08:00
"net/http"
"os"
"os/signal"
2024-10-23 17:00:42 +08:00
"path/filepath"
"strconv"
2024-10-19 22:02:19 +08:00
"syscall"
"time"
)
2024-10-22 17:06:36 +08:00
var appAddress = ""
2024-10-23 17:00:42 +08:00
var appDaemon = false
2024-10-22 17:06:36 +08:00
2024-10-19 22:02:19 +08:00
func initAppCommand() *cobra.Command {
2024-10-23 17:00:42 +08:00
appStartCommand.Flags().BoolVarP(&appDaemon, "daemon", "d", false, "start app daemon")
appStartCommand.Flags().StringVar(&appAddress, "address", "", "设置app启动的地址默认为:8888")
2024-10-22 17:06:36 +08:00
2024-10-19 22:02:19 +08:00
appCommand.AddCommand(appStartCommand)
2024-10-23 17:00:42 +08:00
appCommand.AddCommand(appRestartCommand)
appCommand.AddCommand(appStopCommand)
appCommand.AddCommand(appStateCommand)
2024-10-19 22:02:19 +08:00
return appCommand
}
var appCommand = &cobra.Command{
Use: "app",
Short: "业务应用控制命令",
Long: "业务应用控制命令,其包含业务启动,关闭,重启,查询等功能",
RunE: func(c *cobra.Command, args []string) error {
c.Help()
return nil
},
}
2024-10-23 17:00:42 +08:00
// 启动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
}
2024-10-19 22:02:19 +08:00
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()
2024-10-22 17:06:36 +08:00
if appAddress == "" {
2024-10-23 17:00:42 +08:00
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"
}
}
2024-10-22 17:06:36 +08:00
}
2024-10-19 22:02:19 +08:00
// 创建一个Server服务
server := &http.Server{
Handler: core,
2024-10-23 17:00:42 +08:00
Addr: appAddress,
2024-10-19 22:02:19 +08:00
}
// 这个goroutine是启动服务的goroutine
go func() {
server.ListenAndServe()
}()
2024-10-23 17:00:42 +08:00
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)
2024-10-19 22:02:19 +08:00
2024-10-23 17:00:42 +08:00
// 获取pid
serverPidFile := filepath.Join(appService.RuntimeFolder(), "app.pid")
2024-10-19 22:02:19 +08:00
2024-10-23 17:00:42 +08:00
content, err := ioutil.ReadFile(serverPidFile)
if err != nil {
return err
2024-10-19 22:02:19 +08:00
}
2024-10-23 17:00:42 +08:00
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服务存在")
2024-10-19 22:02:19 +08:00
return nil
},
}