Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions server/controllers/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var (
qs services.QueryService
ls services.LocksService
rs services.RecordsService
vs services.VariantService
vs services.VariantsService
hs *services.HealthService
)

Expand All @@ -25,6 +25,6 @@ func InitAllComponents(storage *vfs.LogStructuredFS) error {
ls = services.NewLocksServiceImpl(storage)
qs = services.NewQueryServiceImpl(storage)
ts = services.NewTablesServiceImpl(storage)
vs = services.NewVariantServiceImpl(storage)
vs = services.NewVariantsServiceImpl(storage)
return nil
}
116 changes: 115 additions & 1 deletion server/controllers/records.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,127 @@
package controllers

import "github.com/gin-gonic/gin"
import (
"errors"
"net/http"

"github.com/auula/urnadb/server/response"
"github.com/auula/urnadb/server/services"
"github.com/auula/urnadb/types"
"github.com/auula/urnadb/utils"
"github.com/gin-gonic/gin"
)

func GetRecordsController(ctx *gin.Context) {
name := ctx.Param("key")
if !utils.NotNullString(name) {
ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam)
return
}

record, err := rs.GetRecord(name)
if err != nil {
ctx.IndentedJSON(http.StatusInternalServerError, response.Fail(
err.Error(),
))
}

ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{
"record": record,
}))
}

type CreateRecordRequest struct {
Record map[string]any `json:"record" binding:"required"`
TTLSeconds int64 `json:"ttl" binding:"omitempty"`
}

func PutRecordsController(ctx *gin.Context) {
name := ctx.Param("key")
if !utils.NotNullString(name) {
ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam)
return
}

var req CreateRecordRequest
err := ctx.ShouldBindJSON(&req)
if err != nil {
handlerRecordsError(ctx, err)
return
}

rd := types.AcquireRecord()
rd.Record = req.Record

defer rd.ReleaseToPool()

err = rs.CreateRecord(name, rd, req.TTLSeconds)
if err != nil {
handlerRecordsError(ctx, err)
return
}

ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{
"message": "record created successfully.",
}))
}

func DeleteRecordsController(ctx *gin.Context) {
name := ctx.Param("key")
if !utils.NotNullString(name) {
ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam)
return
}

err := rs.DeleteRecord(name)
if err != nil {
handlerRecordsError(ctx, err)
return
}

ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{
"message": "record deleted successfully.",
}))
}

type SearchRecordRequest struct {
Column string `json:"column" binding:"required"`
}

func SearchRecordsController(ctx *gin.Context) {
name := ctx.Param("key")
if !utils.NotNullString(name) {
ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam)
return
}

var req SearchRecordRequest
err := ctx.ShouldBindJSON(&req)
if err != nil {
handlerRecordsError(ctx, err)
return
}

res, err := rs.SearchRows(name, req.Column)
if err != nil {
handlerRecordsError(ctx, err)
return
}

ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{
"column": res,
}))
}

func handlerRecordsError(ctx *gin.Context, err error) {
switch {
case errors.Is(err, services.ErrRecordUpdateFailed):
ctx.IndentedJSON(http.StatusConflict, response.Fail(err.Error()))
case errors.Is(err, services.ErrRecordNotFound):
ctx.IndentedJSON(http.StatusNotFound, response.Fail(err.Error()))
case errors.Is(err, services.ErrRecordExpired):
ctx.IndentedJSON(http.StatusGone, response.Fail(err.Error()))
default:
// 所有其他错误都统一返回 500 内部服务器错误
ctx.IndentedJSON(http.StatusInternalServerError, response.Fail(err.Error()))
}
}
1 change: 1 addition & 0 deletions server/routes/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func SetupRoutes() *gin.Engine {
{
records.GET("/:key", controllers.GetRecordsController)
records.PUT("/:key", controllers.PutRecordsController)
records.POST("/:key", controllers.SearchRecordsController)
records.DELETE("/:key", controllers.DeleteRecordsController)
}

Expand Down
10 changes: 6 additions & 4 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func init() {
}

// const local IPv4 address
ipv4, err = getIPv4Address(addrs)
ipv4, err = detectIPv4(addrs)
if err != nil {
clog.Errorf("Get server IPv4 address failed: %s", err)
}
Expand Down Expand Up @@ -204,23 +204,25 @@ func closeStorage() error {
return nil
}

func getIPv4Address(addrs []net.Interface) (string, error) {
func detectIPv4(addrs []net.Interface) (string, error) {
ip := ""

for _, face := range addrs {
adders, err := face.Addrs()
if err != nil {
return ip, err
}

for _, addr := range adders {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
ipNet, ok := addr.(*net.IPNet)
if ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
ip = ipNet.IP.String()
break
}
}

}
}

return ip, nil
}
6 changes: 3 additions & 3 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,14 @@ func TestHttpServer_Shutdown(t *testing.T) {

// 测试 getIPv4Address 函数
func TestGetIPv4Address_EmptyInterfaces(t *testing.T) {
result, err := getIPv4Address([]net.Interface{})
result, err := detectIPv4([]net.Interface{})
assert.NoError(t, err)
assert.Equal(t, "", result)
}

func TestGetIPv4Address_RealInterfaces(t *testing.T) {
interfaces, _ := net.Interfaces()
result, err := getIPv4Address(interfaces)
result, err := detectIPv4(interfaces)
assert.NoError(t, err)
// 结果可能为空字符串
if result != "" {
Expand All @@ -192,7 +192,7 @@ func TestInitIPv4Logic(t *testing.T) {
// 测试正常情况
interfaces, err := net.Interfaces()
if err == nil {
result, err := getIPv4Address(interfaces)
result, err := detectIPv4(interfaces)
assert.NoError(t, err)
// 验证结果是有效的 IP 地址或空字符串
if result != "" {
Expand Down
7 changes: 5 additions & 2 deletions server/services/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ type HealthService struct {

func NewHealthService(storage *vfs.LogStructuredFS) *HealthService {
mem, _ := mem.VirtualMemory()
disk, _ := disk.Usage(storage.GetDirectory())
return &HealthService{mem: mem, disk: disk, storage: storage}
var diskUsage *disk.UsageStat
if storage != nil {
diskUsage, _ = disk.Usage(storage.GetDirectory())
}
return &HealthService{mem: mem, disk: diskUsage, storage: storage}
}

func (h *HealthService) RegionCompactStatus() uint8 {
Expand Down
18 changes: 9 additions & 9 deletions server/services/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ func (s *LeaseLockService) ReleaseLock(name string, token string) error {
_, seg, err := s.storage.FetchSegment(name)
if err != nil {
s.acquireLeaseLock(name).Unlock()
clog.Errorf("Locks service release lock: %v", err)
clog.Errorf("[LocksService.ReleaseLock] %v", err)
return err
}

slock, err := seg.ToLeaseLock()
if err != nil {
seg.ReleaseToPool()
s.acquireLeaseLock(name).Unlock()
clog.Errorf("Locks service release lock: %v", err)
clog.Errorf("[LocksService.ReleaseLock] %v", err)
return err
}

Expand All @@ -73,7 +73,7 @@ func (s *LeaseLockService) ReleaseLock(name string, token string) error {
err = s.storage.DeleteSegment(name)
if err != nil {
s.acquireLeaseLock(name).Unlock()
clog.Errorf("Locks service release lock: %v", err)
clog.Errorf("[LocksService.ReleaseLock] %v", err)
return err
}

Expand Down Expand Up @@ -101,15 +101,15 @@ func (s *LeaseLockService) AcquireLock(name string, ttl int64) (*types.LeaseLock
seg, err := vfs.AcquirePoolSegment(name, lease, ttl)
if err != nil {
utils.ReleaseToPool(lease)
clog.Errorf("Locks service acquire lock: %v", err)
clog.Errorf("[LocksService.AcquireLock] %v", err)
return nil, err
}

// 持久化这把租期锁
err = s.storage.PutSegment(name, seg)
if err != nil {
utils.ReleaseToPool(lease, seg)
clog.Errorf("Locks service acquire lock: %v", err)
clog.Errorf("[LocksService.AcquireLock] %v", err)
return nil, err
}

Expand All @@ -130,14 +130,14 @@ func (s *LeaseLockService) DoLeaseLock(name string, token string) (*types.LeaseL

_, seg, err := s.storage.FetchSegment(name)
if err != nil {
clog.Errorf("Locks service do lease lock: %v", err)
clog.Errorf("[LocksService.DoLeaseLock] %v", err)
return nil, err
}

old, err := seg.ToLeaseLock()
if err != nil {
seg.ReleaseToPool()
clog.Errorf("Locks service do lease lock: %v", err)
clog.Errorf("[LocksService.DoLeaseLock] %v", err)
return nil, err
}

Expand All @@ -159,13 +159,13 @@ func (s *LeaseLockService) DoLeaseLock(name string, token string) (*types.LeaseL
newseg, err := vfs.AcquirePoolSegment(name, newlease, newttl)
if err != nil {
utils.ReleaseToPool(newlease)
clog.Errorf("Locks service do lease lock: %v", err)
clog.Errorf("[LocksService.DoLeaseLock] %v", err)
return nil, err
}

err = s.storage.PutSegment(name, newseg)
if err != nil {
clog.Errorf("Locks service do lease lock: %v", err)
clog.Errorf("[LocksService.DoLeaseLock] %v", err)
return nil, err
}

Expand Down
Loading