120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
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
|
|
}
|