service.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package service
  2. import (
  3. "crypto/rand"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "time"
  9. "wails-app/internal/dao"
  10. "wails-app/internal/modules/digital-ticket/repository"
  11. )
  12. const (
  13. StateCreated = "created"
  14. StatePendingPayment = "pending_payment"
  15. StatePaid = "paid"
  16. StateExited = "exited"
  17. StateExpired = "expired"
  18. )
  19. type TicketService struct {
  20. repo *repository.TicketRepository
  21. }
  22. func NewTicketService() *TicketService {
  23. return &TicketService{repo: &repository.TicketRepository{}}
  24. }
  25. func genTicketNo() string {
  26. b := make([]byte, 16)
  27. rand.Read(b)
  28. return hex.EncodeToString(b)
  29. }
  30. func appendEvent(log string, event map[string]interface{}) string {
  31. var events []map[string]interface{}
  32. json.Unmarshal([]byte(log), &events)
  33. event["at"] = time.Now().Format(time.RFC3339)
  34. events = append(events, event)
  35. data, _ := json.Marshal(events)
  36. return string(data)
  37. }
  38. // Create 创建数字票(所有进场方式统一入口)
  39. func (s *TicketService) Create(plateNumber, triggerMode string, recordID uint) (*dao.DigitalTicket, error) {
  40. log := appendEvent("[]", map[string]interface{}{"event": "created", "trigger": triggerMode})
  41. ticket := &dao.DigitalTicket{
  42. TicketNo: genTicketNo(), PlateNumber: plateNumber, TriggerMode: triggerMode,
  43. State: StatePendingPayment, VehicleRecordID: recordID, EntryTime: time.Now(), EventLog: log,
  44. }
  45. if err := s.repo.Create(ticket); err != nil {
  46. return nil, err
  47. }
  48. return ticket, nil
  49. }
  50. // Transition 状态转换(校验合法性,原子更新)
  51. func (s *TicketService) Transition(id uint, toState string, extra map[string]interface{}) error {
  52. ticket, err := s.repo.GetByID(id)
  53. if err != nil {
  54. return errors.New("票不存在")
  55. }
  56. validTransitions := map[string][]string{
  57. StateCreated: {StatePendingPayment},
  58. StatePendingPayment: {StatePaid, StateExpired},
  59. StatePaid: {StateExited, StateExpired},
  60. }
  61. allowed := false
  62. for _, s := range validTransitions[ticket.State] {
  63. if s == toState {
  64. allowed = true
  65. break
  66. }
  67. }
  68. if !allowed {
  69. return fmt.Errorf("状态转换不合法: %s → %s", ticket.State, toState)
  70. }
  71. updates := map[string]interface{}{}
  72. if toState == StatePaid {
  73. now := time.Now()
  74. updates["paid_at"] = now
  75. }
  76. if toState == StateExited {
  77. now := time.Now()
  78. updates["exit_time"] = now
  79. }
  80. for k, v := range extra {
  81. updates[k] = v
  82. }
  83. event := map[string]interface{}{"event": toState}
  84. for k, v := range extra {
  85. event[k] = v
  86. }
  87. updates["event_log"] = appendEvent(ticket.EventLog, event)
  88. return s.repo.UpdateState(id, toState, updates)
  89. }
  90. func (s *TicketService) GetByTicketNo(no string) (*dao.DigitalTicket, error) {
  91. return s.repo.GetByTicketNo(no)
  92. }
  93. func (s *TicketService) List(q repository.TicketListQuery) ([]dao.DigitalTicket, int64, error) {
  94. return s.repo.List(q)
  95. }