chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/concurrency"
|
||||
"data-terminal-cloud/internal/device"
|
||||
"data-terminal-cloud/internal/deviceauth"
|
||||
)
|
||||
|
||||
type DeviceService interface {
|
||||
ReportBootstrap(context.Context, deviceauth.Identity, device.BootstrapReport) (device.Bootstrap, error)
|
||||
GetConfiguration(context.Context, deviceauth.Identity) (device.Configuration, error)
|
||||
}
|
||||
|
||||
type bootstrapResponse struct {
|
||||
ServerTime time.Time `json:"server_time"`
|
||||
ClockSkewMS int64 `json:"clock_skew_ms"`
|
||||
Device device.Bootstrap `json:"device"`
|
||||
}
|
||||
|
||||
func DeviceBootstrapHandler(service DeviceService) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
identity, ok := deviceauth.FromContext(request.Context())
|
||||
if !ok {
|
||||
WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败")
|
||||
return
|
||||
}
|
||||
report, fieldErrors := parseBootstrapReport(request)
|
||||
if len(fieldErrors) > 0 {
|
||||
WriteAPIError(writer, request, http.StatusBadRequest, Error{
|
||||
Code: "VALIDATION_FAILED", Message: "设备启动参数不正确", FieldErrors: fieldErrors,
|
||||
})
|
||||
return
|
||||
}
|
||||
result, err := service.ReportBootstrap(request.Context(), identity, report)
|
||||
if err != nil {
|
||||
WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{
|
||||
Code: "DEVICE_BOOTSTRAP_FAILED", Message: "暂时无法完成设备启动", Retryable: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
WriteJSON(writer, request, http.StatusOK, bootstrapResponse{
|
||||
ServerTime: now, ClockSkewMS: now.Sub(report.DeviceTime).Milliseconds(), Device: result,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func DeviceConfigHandler(service DeviceService) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
identity, ok := deviceauth.FromContext(request.Context())
|
||||
if !ok {
|
||||
WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败")
|
||||
return
|
||||
}
|
||||
result, err := service.GetConfiguration(request.Context(), identity)
|
||||
if err != nil {
|
||||
WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{
|
||||
Code: "DEVICE_CONFIG_UNAVAILABLE", Message: "暂时无法读取设备配置", Retryable: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
etag := concurrency.FormatETag(result.Revision)
|
||||
writer.Header().Set("ETag", etag)
|
||||
if request.Header.Get("If-None-Match") == etag {
|
||||
writer.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
WriteJSON(writer, request, http.StatusOK, result)
|
||||
})
|
||||
}
|
||||
|
||||
func parseBootstrapReport(request *http.Request) (device.BootstrapReport, []FieldError) {
|
||||
query := request.URL.Query()
|
||||
report := device.BootstrapReport{
|
||||
FirmwareVersion: strings.TrimSpace(query.Get("firmware_version")),
|
||||
BoardRevision: strings.TrimSpace(query.Get("board_rev")),
|
||||
RTCSDKVersion: strings.TrimSpace(query.Get("rtc_sdk_version")),
|
||||
Codec: strings.TrimSpace(query.Get("codec")),
|
||||
Network: strings.TrimSpace(query.Get("network")),
|
||||
}
|
||||
errors := make([]FieldError, 0)
|
||||
if report.FirmwareVersion == "" {
|
||||
errors = append(errors, FieldError{Field: "firmware_version", Message: "不能为空"})
|
||||
}
|
||||
for _, field := range []struct {
|
||||
name string
|
||||
value string
|
||||
max int
|
||||
}{
|
||||
{"firmware_version", report.FirmwareVersion, 80},
|
||||
{"board_rev", report.BoardRevision, 80},
|
||||
{"rtc_sdk_version", report.RTCSDKVersion, 80},
|
||||
{"codec", report.Codec, 40},
|
||||
{"network", report.Network, 80},
|
||||
} {
|
||||
if len(field.value) > field.max {
|
||||
errors = append(errors, FieldError{Field: field.name, Message: "长度超出限制"})
|
||||
}
|
||||
}
|
||||
deviceTime, err := time.Parse(time.RFC3339, query.Get("device_time"))
|
||||
if err != nil {
|
||||
errors = append(errors, FieldError{Field: "device_time", Message: "必须是RFC3339时间"})
|
||||
} else {
|
||||
report.DeviceTime = deviceTime
|
||||
}
|
||||
if value := query.Get("sample_rate"); value != "" {
|
||||
report.SampleRate, err = strconv.Atoi(value)
|
||||
if err != nil || report.SampleRate <= 0 {
|
||||
errors = append(errors, FieldError{Field: "sample_rate", Message: "必须是正整数"})
|
||||
}
|
||||
}
|
||||
return report, errors
|
||||
}
|
||||
Reference in New Issue
Block a user