diff --git a/api/simulation.go b/api/simulation.go index a226b62..668c9d8 100644 --- a/api/simulation.go +++ b/api/simulation.go @@ -20,6 +20,7 @@ import ( apiproto "joylink.club/bj-rtsts-server/grpcproto" "joylink.club/bj-rtsts-server/middleware" "joylink.club/bj-rtsts-server/service" + "joylink.club/bj-rtsts-server/sys_error" ) func InitSimulationRouter(api *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { @@ -75,18 +76,24 @@ func initPublishMapInfo() { func createByProjectId(c *gin.Context) { req := dto.SimulationCreateReqDto{} if err := c.ShouldBind(&req); nil != err { - panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()}) + // panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()}) + panic(sys_error.New("测试启动失败,请求参数异常", err)) } mapInfos := service.QueryProjectPublishedGi(req.ProjectId) if len(mapInfos) == 0 { - panic(dto.ErrorDto{Code: dto.DataNotExist, Message: "项目未关联地图"}) + // panic(dto.ErrorDto{Code: dto.DataNotExist, Message: "项目未关联地图"}) + panic(sys_error.New("测试启动失败,项目未关联发布图")) } mapIds := make([]int32, len(mapInfos)) for i, mapInfo := range mapInfos { mapIds[i] = mapInfo.ID } rsp := dto.SimulationCreateRspDto{ProjectId: req.ProjectId, MapId: mapIds[0], MapIds: mapIds} - rsp.SimulationId = simulation.CreateSimulation(req.ProjectId, mapIds) + simulationId, err := simulation.CreateSimulation(req.ProjectId, mapIds) + if err != nil { + panic(sys_error.New("测试启动失败", err)) + } + rsp.SimulationId = simulationId c.JSON(http.StatusOK, &rsp) } diff --git a/api/user.go b/api/user.go index b370a71..d1e0080 100644 --- a/api/user.go +++ b/api/user.go @@ -10,6 +10,7 @@ import ( "joylink.club/bj-rtsts-server/dto" "joylink.club/bj-rtsts-server/middleware" "joylink.club/bj-rtsts-server/service" + "joylink.club/bj-rtsts-server/sys_error" ) func InitUserRouter(api *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { @@ -37,9 +38,12 @@ func register(c *gin.Context) { user := &dto.RegisterUser{} err := c.BindJSON(user) if err != nil { - slog.Warn("用户注册失败", err) + panic(sys_error.New("注册失败,参数错误", err)) + } + err = service.Register(user) + if err != nil { + panic(sys_error.New("注册失败", err)) } - service.Register(user) c.JSON(http.StatusOK, "ok") } diff --git a/ats/verify/simulation/simulation_manage.go b/ats/verify/simulation/simulation_manage.go index 2b716b8..46ddee7 100644 --- a/ats/verify/simulation/simulation_manage.go +++ b/ats/verify/simulation/simulation_manage.go @@ -2,7 +2,6 @@ package simulation import ( "encoding/binary" - "fmt" "strconv" "sync" "time" @@ -10,6 +9,7 @@ import ( "joylink.club/bj-rtsts-server/ats/verify/protos/state" "joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/memory" "joylink.club/bj-rtsts-server/config" + "joylink.club/bj-rtsts-server/sys_error" "joylink.club/bj-rtsts-server/third_party/dynamics" "joylink.club/bj-rtsts-server/third_party/semi_physical_train" @@ -69,16 +69,18 @@ func IsExistSimulation() bool { } // 创建仿真对象 -func CreateSimulation(projectId int32, mapIds []int32) string { +func CreateSimulation(projectId int32, mapIds []int32) (string, error) { simulationId := createSimulationId(projectId) _, e := simulationMap.Load(simulationId) if !e && IsExistSimulation() { - panic(dto.ErrorDto{Code: dto.DataAlreadyExist, Message: "已有仿真在运行"}) + // panic(dto.ErrorDto{Code: dto.DataAlreadyExist, Message: "已有仿真在运行"}) + return "", sys_error.New("一套环境同时只能运行一个仿真") } if !e { verifySimulation, err := memory.CreateSimulation(projectId, mapIds) if err != nil { - panic(fmt.Sprintf("创建仿真失败:%s", err.Error())) + return "", err + // panic(fmt.Sprintf("创建仿真失败:%s", err.Error())) } verifySimulation.SimulationId = simulationId if config.Config.Dynamics.Open { @@ -86,7 +88,8 @@ func CreateSimulation(projectId int32, mapIds []int32) string { lineBaseInfo := verifySimulation.BuildLineBaseInfo() err := dynamics.Default().RequestStartSimulation(lineBaseInfo) if err != nil { - panic(dto.ErrorDto{Code: dto.DynamicsError, Message: err.Error()}) + return "", err + // panic(dto.ErrorDto{Code: dto.DynamicsError, Message: err.Error()}) } dynamics.Default().Start(verifySimulation) } @@ -98,10 +101,13 @@ func CreateSimulation(projectId int32, mapIds []int32) string { // if httpCode != http.StatusOK || err != nil { // panic(dto.ErrorDto{Code: dto.DynamicsError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)}) // } - simulationMap.Store(simulationId, verifySimulation) // dynamicsRun(verifySimulation) + + simulationMap.Store(simulationId, verifySimulation) + // 全部成功,启动仿真 + verifySimulation.World.StartUp() } - return simulationId + return simulationId, nil } // 删除仿真对象 diff --git a/ats/verify/simulation/wayside/memory/wayside_memory_map.go b/ats/verify/simulation/wayside/memory/wayside_memory_map.go index 966e5a8..4986cc8 100644 --- a/ats/verify/simulation/wayside/memory/wayside_memory_map.go +++ b/ats/verify/simulation/wayside/memory/wayside_memory_map.go @@ -174,7 +174,10 @@ func turnoutMapToEcsLink(repo *repository.Repository, id string, port string, of } // 岔心公里标 crossKm = turnout.GetTurnoutKm(proto2.Port_None) - portKm = repo.ConvertKilometer(portKm, crossKm.CoordinateSystem) + portKm, err := repo.ConvertKilometer(portKm, crossKm.CoordinateSystem) + if err != nil { + panic(err) + } // 关联link link := portPosition.Link() isStart := link.ARelation().Device().Id() == id @@ -242,7 +245,10 @@ func ecsLinkMapToTurnout(repo *repository.Repository, isA bool, offset int64, up deviceId = tp.Turnout().Id() tpOffset := tp.Turnout().FindLinkPositionByPort(tp.Port()).Offset() crossKmInfo, portKmInfo := tp.Turnout().GetTurnoutKm(proto2.Port_None), tp.Turnout().GetTurnoutKm(tp.Port()) - portKmInfo = repo.ConvertKilometer(portKmInfo, crossKmInfo.CoordinateSystem) + portKmInfo, err := repo.ConvertKilometer(portKmInfo, crossKmInfo.CoordinateSystem) + if err != nil { + panic(err) + } crossKm, portKm := crossKmInfo.Value, portKmInfo.Value if isA { deviceOffset = tpOffset - (tpOffset - offset) diff --git a/ats/verify/simulation/wayside/memory/wayside_simulation.go b/ats/verify/simulation/wayside/memory/wayside_simulation.go index 9add2e5..f8a6be4 100644 --- a/ats/verify/simulation/wayside/memory/wayside_simulation.go +++ b/ats/verify/simulation/wayside/memory/wayside_simulation.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "math" + "runtime" "sort" "strconv" "strings" @@ -14,6 +15,7 @@ import ( "joylink.club/bj-rtsts-server/ats/verify/protos/graphicData" "joylink.club/bj-rtsts-server/ats/verify/protos/state" "joylink.club/bj-rtsts-server/dto" + "joylink.club/bj-rtsts-server/sys_error" "joylink.club/bj-rtsts-server/third_party/deprecated/vobc" "joylink.club/bj-rtsts-server/third_party/message" "joylink.club/ecs" @@ -99,12 +101,12 @@ func CreateSimulation(projectId int32, mapIds []int32) (*VerifySimulation, error if repo == nil { protoRepo, err := buildProtoRepository(mapIds) if err != nil { - return nil, err + return nil, sys_error.New("数据错误", err) } protoRepo.Id, protoRepo.Version = repoId, repoVersion newRepo, err := repository.BuildRepository(protoRepo) if err != nil { - return nil, err + return nil, sys_error.New("数据错误", err) } repo = newRepo } @@ -114,9 +116,10 @@ func CreateSimulation(projectId int32, mapIds []int32) (*VerifySimulation, error // 构建所有UID映射关系, allUidMap := buildRepositoryAllUidsMap(mapIds, repo) //创建仿真 - // worldId := world.CreateSimulation(repo) - w := rtss_simulation.NewSimulation(repo) - w.StartUp() + w, err := rtss_simulation.NewSimulation(repo) + if err != nil { + return nil, sys_error.New("仿真创建失败", err) + } verifySimulation := &VerifySimulation{ MapIds: mapIds, ProjectId: projectId, @@ -126,6 +129,11 @@ func CreateSimulation(projectId int32, mapIds []int32) (*VerifySimulation, error WorldId: w.Id(), uidMap: allUidMap, } + // 保证World关闭 + runtime.SetFinalizer(verifySimulation, func(verifySimulation *VerifySimulation) { + slog.Info("---关闭仿真World---") + verifySimulation.World.Close() + }) return verifySimulation, nil } diff --git a/bj-rtss-message b/bj-rtss-message index 43345d1..9b1c6d7 160000 --- a/bj-rtss-message +++ b/bj-rtss-message @@ -1 +1 @@ -Subproject commit 43345d1094b74b36e77d31ee3dc4b63557406604 +Subproject commit 9b1c6d78012eff4f9d300412ad41c8175751767d diff --git a/init.go b/init.go index a969e63..1e45d9b 100644 --- a/init.go +++ b/init.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log/slog" "net" "net/http" @@ -13,6 +14,7 @@ import ( "go.uber.org/zap" "joylink.club/bj-rtsts-server/db/dbquery" "joylink.club/bj-rtsts-server/dto" + "joylink.club/bj-rtsts-server/sys_error" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -45,19 +47,32 @@ func InitServer() *gin.Engine { engine.Use(cors.New(conf)) // gin panic 异常处理,默认处理为 engine.Use(CustomRecoveryWithSlog(slog.Default(), true, func(c *gin.Context, e interface{}) { - switch e := e.(type) { - case error: - c.JSON(http.StatusInternalServerError, &dto.ErrorDto{ - Code: dto.LogicError, - Tip: dto.ErrorTipMap[dto.LogicError], - Message: e.Error(), - }) - case dto.ErrorDto: - e.Tip = dto.ErrorTipMap[e.Code] - c.JSON(http.StatusInternalServerError, e) - default: - c.JSON(http.StatusInternalServerError, e) + be, ok := e.(*sys_error.BusinessError) + if !ok { + e, ok := e.(error) + if ok { + be = sys_error.New("未知错误", e) + } else { + be = sys_error.New("未知错误", fmt.Errorf("%v", e)) + } } + c.JSON(http.StatusInternalServerError, &dto.ErrorDto{ + Tip: be.UserMsg, + Message: be.Error(), + }) + // switch e := e.(type) { + // case error: + // c.JSON(http.StatusInternalServerError, &dto.ErrorDto{ + // Code: dto.LogicError, + // Tip: dto.ErrorTipMap[dto.LogicError], + // Message: e.Error(), + // }) + // case dto.ErrorDto: + // e.Tip = dto.ErrorTipMap[e.Code] + // c.JSON(http.StatusInternalServerError, e) + // default: + // c.JSON(http.StatusInternalServerError, e) + // } c.Writer.WriteHeaderNow() c.Abort() })) diff --git a/middleware/auth.go b/middleware/auth.go index 8f7a72f..fc74586 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -9,6 +9,7 @@ import ( "joylink.club/bj-rtsts-server/db/model" "joylink.club/bj-rtsts-server/dto" "joylink.club/bj-rtsts-server/service" + "joylink.club/bj-rtsts-server/sys_error" ) // 用户权限缓存 @@ -40,7 +41,7 @@ func permissionMiddleware() gin.HandlerFunc { return } slog.Error("无权限操作请求路径", "path", path, "method", method) - panic(dto.ErrorDto{Code: dto.NoAuthOperationError, Message: "无权限操作"}) + panic(sys_error.New("权限不足")) } } diff --git a/rtss_simulation b/rtss_simulation index 30c4210..7fb2536 160000 --- a/rtss_simulation +++ b/rtss_simulation @@ -1 +1 @@ -Subproject commit 30c4210a5d0411bb52d383bef7e77de7b9484800 +Subproject commit 7fb253612ec7ecfb0836e8c9c89cd6a4fc58f949 diff --git a/service/user.go b/service/user.go index 26f78b5..832f0df 100644 --- a/service/user.go +++ b/service/user.go @@ -2,13 +2,13 @@ package service import ( "fmt" - "log/slog" "sort" "time" "joylink.club/bj-rtsts-server/db/dbquery" "joylink.club/bj-rtsts-server/db/model" "joylink.club/bj-rtsts-server/dto" + "joylink.club/bj-rtsts-server/sys_error" ) // 分页查询用户列表 @@ -28,23 +28,23 @@ func PagingQueryUser(query *dto.PageUserReqDto) (*dto.PageDto, error) { return &dto.PageDto{Total: int(total), PageQueryDto: query.PageQueryDto, Records: linkUserRole(records)}, err } -func Register(user *dto.RegisterUser) { - defer func() { - err := recover() - if err != nil { - slog.Warn("用户注册失败", err) - panic(err) - } - }() +func Register(user *dto.RegisterUser) error { u := dbquery.User uq := u.Where() uq = uq.Where(u.Mobile.Eq(user.Mobile)) - findCounter, _ := uq.Count() + findCounter, err := uq.Count() + if err != nil { + return sys_error.New("数据服务异常", err) + } if findCounter > 0 { - panic(dto.ErrorDto{Code: dto.DataAlreadyExist, Message: "重复的手机号"}) + return sys_error.New("手机号已存在") } user.RegisterTime = time.Now() - u.Save(user) + err = u.Save(user) + if err != nil { + return sys_error.New("数据服务异常", err) + } + return nil } func FindUserInfo(userId int32) *dto.UserRspDto { diff --git a/sys_error/error.go b/sys_error/error.go new file mode 100644 index 0000000..fe89c01 --- /dev/null +++ b/sys_error/error.go @@ -0,0 +1,61 @@ +package sys_error + +import ( + "fmt" + "strings" +) + +// 业务错误定义 +type BusinessError struct { + // 用户提示信息 + UserMsg string + // 错误信息传递(用于开发回溯定位,不给用户展示) + Errors []string +} + +// 新建业务错误 +// 如果errs为空,则返回一个只包含用户提示信息的业务错误 +// 如果errs不为空,如果errs是一个业务错误,则附加错误信息,否则返回一个包含用户提示信息和错误信息的业务错误 +func New(userMsg string, errs ...error) *BusinessError { + if len(errs) == 1 { + be, ok := errs[0].(*BusinessError) + if ok { + be.prependUserMsg(userMsg) + return be + } else { + return &BusinessError{ + UserMsg: userMsg, + Errors: []string{errs[0].Error()}, + } + } + } + return &BusinessError{ + UserMsg: userMsg, + // Errors: convert(errs), + } +} + +func IsBusinessError(err error) bool { + _, ok := err.(*BusinessError) + return ok +} + +func (e *BusinessError) prependUserMsg(userMsg string) { + e.UserMsg = fmt.Sprintf("%s,%s", userMsg, e.UserMsg) +} + +// func convert(err []error) []string { +// s := []string{} +// for _, e := range err { +// s = append(s, e.Error()) +// } +// return s +// } + +func (e *BusinessError) Append(err error) { + e.Errors = append(e.Errors, err.Error()) +} + +func (e *BusinessError) Error() string { + return strings.Join(e.Errors, ", ") +} diff --git a/third_party/dynamics/dynamics.go b/third_party/dynamics/dynamics.go index 4b18cae..552c210 100644 --- a/third_party/dynamics/dynamics.go +++ b/third_party/dynamics/dynamics.go @@ -11,6 +11,7 @@ import ( "time" "joylink.club/bj-rtsts-server/config" + "joylink.club/bj-rtsts-server/sys_error" "joylink.club/bj-rtsts-server/third_party/message" "joylink.club/bj-rtsts-server/third_party/udp" ) @@ -117,14 +118,14 @@ func (d *dynamics) RequestStartSimulation(base *message.LineBaseInfo) error { data, _ := json.Marshal(base) resp, err := d.httpClient.Post(url, "application/json", bytes.NewBuffer(data)) if err != nil { - return fmt.Errorf("请求启动仿真异常: %v", err) + return sys_error.New("动力学开始仿真请求发送错误", err) } defer resp.Body.Close() var buf []byte _, err = resp.Body.Read(buf) if err != nil { - return fmt.Errorf("请求启动仿真读取相应异常: %v", err) + return sys_error.New("动力学开始仿真请求响应错误", err) } return nil }