client.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Package service implements the panel's business-logic layer.
  2. //
  3. // ClientService owns the lifecycle of VPN clients: creation, update, deletion,
  4. // attach/detach to inbounds, bulk operations, group membership, traffic resets,
  5. // and the paginated clients listing. Its surface is split across client_*.go
  6. // files by responsibility (see each file's contents); they all belong to the
  7. // same package, so the split is purely organizational. ClientService and
  8. // InboundService are mutually dependent — most ClientService methods take an
  9. // *InboundService and InboundService embeds a ClientService — which is why the
  10. // client code lives in package service rather than a sub-package.
  11. package service
  12. import (
  13. "encoding/json"
  14. "errors"
  15. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  16. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  17. )
  18. type ClientWithAttachments struct {
  19. model.ClientRecord
  20. InboundIds []int `json:"inboundIds"`
  21. Traffic *xray.ClientTraffic `json:"traffic,omitempty"`
  22. }
  23. // MarshalJSON is required because model.ClientRecord defines its own
  24. // MarshalJSON. Go promotes the embedded method to the outer struct, so without
  25. // this the encoder would call ClientRecord.MarshalJSON for the whole value and
  26. // silently drop InboundIds and Traffic from the API response.
  27. func (c ClientWithAttachments) MarshalJSON() ([]byte, error) {
  28. rec, err := json.Marshal(c.ClientRecord)
  29. if err != nil {
  30. return nil, err
  31. }
  32. extras := struct {
  33. InboundIds []int `json:"inboundIds"`
  34. Traffic *xray.ClientTraffic `json:"traffic,omitempty"`
  35. }{InboundIds: c.InboundIds, Traffic: c.Traffic}
  36. extra, err := json.Marshal(extras)
  37. if err != nil {
  38. return nil, err
  39. }
  40. if len(rec) < 2 || rec[len(rec)-1] != '}' || len(extra) <= 2 {
  41. return rec, nil
  42. }
  43. const maxMarshalSize = 256 << 20
  44. if len(rec) > maxMarshalSize || len(extra) > maxMarshalSize {
  45. return rec, nil
  46. }
  47. out := make([]byte, 0, len(rec)+len(extra))
  48. out = append(out, rec[:len(rec)-1]...)
  49. if len(rec) > 2 {
  50. out = append(out, ',')
  51. }
  52. out = append(out, extra[1:]...)
  53. return out, nil
  54. }
  55. type ClientService struct{}
  56. // ErrClientNotInInbound is returned (wrapped) when a client cannot be located
  57. // in an inbound's settings during deletion. Deletion treats it as non-fatal so
  58. // the operation stays idempotent and tolerant of pre-existing data drift
  59. // between the clients table and the inbound's settings JSON.
  60. var ErrClientNotInInbound = errors.New("client not found in inbound")
  61. type ClientCreatePayload struct {
  62. Client model.Client `json:"client"`
  63. InboundIds []int `json:"inboundIds"`
  64. LimitHwid int `json:"-"`
  65. Traffic *ClientPortableTraffic `json:"traffic,omitempty"`
  66. }
  67. const sqlInChunk = 400
  68. type clientPayloadWithHwid struct {
  69. model.Client
  70. LimitHwid int `json:"limitHwid"`
  71. }
  72. func (p *ClientCreatePayload) UnmarshalJSON(data []byte) error {
  73. var raw struct {
  74. Client json.RawMessage `json:"client"`
  75. InboundIds []int `json:"inboundIds"`
  76. Traffic *ClientPortableTraffic `json:"traffic"`
  77. }
  78. if err := json.Unmarshal(data, &raw); err != nil {
  79. return err
  80. }
  81. var withHwid clientPayloadWithHwid
  82. if len(raw.Client) > 0 {
  83. if err := json.Unmarshal(raw.Client, &withHwid); err != nil {
  84. return err
  85. }
  86. }
  87. p.Client = withHwid.Client
  88. p.InboundIds = raw.InboundIds
  89. p.LimitHwid = withHwid.LimitHwid
  90. p.Traffic = raw.Traffic
  91. // Omit enable → true (legacy API); explicit false is preserved (#6478).
  92. var keys map[string]json.RawMessage
  93. if len(raw.Client) > 0 && json.Unmarshal(raw.Client, &keys) == nil {
  94. if _, ok := keys["enable"]; !ok {
  95. p.Client.Enable = true
  96. }
  97. }
  98. return nil
  99. }
  100. func (p ClientCreatePayload) MarshalJSON() ([]byte, error) {
  101. return json.Marshal(struct {
  102. Client clientPayloadWithHwid `json:"client"`
  103. InboundIds []int `json:"inboundIds"`
  104. Traffic *ClientPortableTraffic `json:"traffic,omitempty"`
  105. }{
  106. Client: clientPayloadWithHwid{Client: p.Client, LimitHwid: p.LimitHwid},
  107. InboundIds: p.InboundIds,
  108. Traffic: p.Traffic,
  109. })
  110. }