add chart package
This commit is contained in:
parent
04053391ef
commit
447c8e12cb
4
go.mod
4
go.mod
|
@ -5,8 +5,8 @@ go 1.23.0
|
||||||
toolchain go1.23.10
|
toolchain go1.23.10
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.nobla.cn/golang/aeus v0.0.10
|
git.nobla.cn/golang/aeus v0.0.11
|
||||||
git.nobla.cn/golang/rest v0.1.3
|
git.nobla.cn/golang/rest v0.1.4
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1
|
github.com/envoyproxy/protoc-gen-validate v1.2.1
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,11 +1,11 @@
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
git.nobla.cn/golang/aeus v0.0.10 h1:MDov2GQgV4dxFcjhiJDMwWgfEX0ROwarH3/ETP/9/ik=
|
git.nobla.cn/golang/aeus v0.0.11 h1:gbXIOVOQRDTIQTjw9wPVfNC9nXBaTJCABeDYmrHW2Oc=
|
||||||
git.nobla.cn/golang/aeus v0.0.10/go.mod h1:oOEwqIp6AhKKqj6sLFO8x7IycOROYHCb/2/CjF4+9CU=
|
git.nobla.cn/golang/aeus v0.0.11/go.mod h1:oOEwqIp6AhKKqj6sLFO8x7IycOROYHCb/2/CjF4+9CU=
|
||||||
git.nobla.cn/golang/kos v0.1.32 h1:sFVCA7vKc8dPUd0cxzwExOSPX2mmMh2IuwL6cYS1pBc=
|
git.nobla.cn/golang/kos v0.1.32 h1:sFVCA7vKc8dPUd0cxzwExOSPX2mmMh2IuwL6cYS1pBc=
|
||||||
git.nobla.cn/golang/kos v0.1.32/go.mod h1:35Z070+5oB39WcVrh5DDlnVeftL/Ccmscw2MZFe9fUg=
|
git.nobla.cn/golang/kos v0.1.32/go.mod h1:35Z070+5oB39WcVrh5DDlnVeftL/Ccmscw2MZFe9fUg=
|
||||||
git.nobla.cn/golang/rest v0.1.3 h1:B+MX1teFURVxol77Ho5+SxyF61VfvXbzsh8IIajoGG0=
|
git.nobla.cn/golang/rest v0.1.4 h1:9/XscfNXI3aPESpy8CPtVl17VSMxU9BihhedeG+h8YY=
|
||||||
git.nobla.cn/golang/rest v0.1.3/go.mod h1:4viDk7VujDokpUeHQGbnSp2bkkVZEoIkWQIs/l/TTPQ=
|
git.nobla.cn/golang/rest v0.1.4/go.mod h1:4viDk7VujDokpUeHQGbnSp2bkkVZEoIkWQIs/l/TTPQ=
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package chartjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataCounter 计数统计, 实现类似Pie 和 Doughnut 之类的图形
|
||||||
|
type DataCounter struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
legends map[string]*counterValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataCounter) Inc(leg string, label string, value float64) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
v, ok := c.legends[leg]
|
||||||
|
if !ok {
|
||||||
|
v = &counterValue{
|
||||||
|
Label: leg,
|
||||||
|
Values: make(map[string]float64),
|
||||||
|
}
|
||||||
|
c.legends[leg] = v
|
||||||
|
}
|
||||||
|
v.Values[label] += value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataCounter) Dec(legend string, label string, value float64) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
v, ok := c.legends[legend]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := v.Values[label]; ok {
|
||||||
|
v.Values[label] -= value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataCounter) Data() *CounterData {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
data := &CounterData{
|
||||||
|
Lables: make([]string, 0, 5),
|
||||||
|
Datasets: make([]*CounterValue, 0, len(c.legends)),
|
||||||
|
}
|
||||||
|
for _, row := range c.legends {
|
||||||
|
for k, _ := range row.Values {
|
||||||
|
if !slices.Contains(data.Lables, k) {
|
||||||
|
data.Lables = append(data.Lables, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, row := range c.legends {
|
||||||
|
set := &CounterValue{
|
||||||
|
Label: row.Label,
|
||||||
|
Data: make([]float64, 0, len(data.Lables)),
|
||||||
|
}
|
||||||
|
for _, label := range data.Lables {
|
||||||
|
set.Data = append(set.Data, row.Values[label])
|
||||||
|
}
|
||||||
|
data.Datasets = append(data.Datasets, set)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataCounter(legends ...string) *DataCounter {
|
||||||
|
c := &DataCounter{
|
||||||
|
legends: make(map[string]*counterValue),
|
||||||
|
}
|
||||||
|
for _, legend := range legends {
|
||||||
|
c.legends[legend] = &counterValue{
|
||||||
|
Label: legend,
|
||||||
|
Values: make(map[string]float64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package chartjs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// TimeSeriesGroup 时间计算数据, 实现类似以时间为维度的折线图
|
||||||
|
type TimeSeriesGroup struct {
|
||||||
|
step string
|
||||||
|
values map[string]*TimeSeries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *TimeSeriesGroup) Inc(tm time.Time, key string, value float64) {
|
||||||
|
v, ok := g.values[key]
|
||||||
|
if !ok {
|
||||||
|
v = NewTimeSeries(g.step)
|
||||||
|
g.values[key] = v
|
||||||
|
}
|
||||||
|
v.Inc(tm, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *TimeSeriesGroup) Dec(tm time.Time, key string, value float64) {
|
||||||
|
v, ok := g.values[key]
|
||||||
|
if !ok {
|
||||||
|
v = NewTimeSeries(g.step)
|
||||||
|
g.values[key] = v
|
||||||
|
}
|
||||||
|
v.Dec(tm, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data 生成chart.js 的图表dataset
|
||||||
|
func (g *TimeSeriesGroup) Data(sts, ets int64) (values []*TimeseriesData) {
|
||||||
|
for key, row := range g.values {
|
||||||
|
data := &TimeseriesData{
|
||||||
|
Label: key,
|
||||||
|
Data: row.Values(sts, ets),
|
||||||
|
}
|
||||||
|
values = append(values, data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeSeriesGroup(step string) *TimeSeriesGroup {
|
||||||
|
return &TimeSeriesGroup{
|
||||||
|
step: step,
|
||||||
|
values: make(map[string]*TimeSeries),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package chartjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Minute = "minute"
|
||||||
|
Hour = "hour"
|
||||||
|
Day = "day"
|
||||||
|
Method = "method"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 时间序列值, 实现了按时间进行分组的值计算
|
||||||
|
type TimeSeries struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
step string
|
||||||
|
values []*TimeseriesValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) cutTimeSeries(tm time.Time) int64 {
|
||||||
|
switch s.step {
|
||||||
|
case Method:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), 0, 0, 0, 0, 0, time.Local).Unix()
|
||||||
|
case Day:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), 0, 0, 0, 0, time.Local).Unix()
|
||||||
|
case Hour:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), tm.Hour(), 0, 0, 0, time.Local).Unix()
|
||||||
|
default:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), tm.Hour(), tm.Minute(), 0, 0, time.Local).Unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) truncateTime(ts int64) int64 {
|
||||||
|
tm := time.Unix(ts, 0)
|
||||||
|
switch s.step {
|
||||||
|
case Method:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), 0, 0, 0, 0, 0, time.Local).Unix()
|
||||||
|
case Day:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), 0, 0, 0, 0, time.Local).Unix()
|
||||||
|
case Hour:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), tm.Hour(), 0, 0, 0, time.Local).Unix()
|
||||||
|
default:
|
||||||
|
return time.Date(tm.Year(), tm.Month(), tm.Day(), tm.Hour(), tm.Minute(), 0, 0, time.Local).Unix()
|
||||||
|
}
|
||||||
|
return tm.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) nextTimestamp(ts int64) int64 {
|
||||||
|
tm := time.Unix(ts, 0)
|
||||||
|
switch s.step {
|
||||||
|
case Method:
|
||||||
|
return tm.AddDate(0, 1, 0).Unix()
|
||||||
|
case Day:
|
||||||
|
return tm.AddDate(0, 0, 1).Unix()
|
||||||
|
case Hour:
|
||||||
|
return ts + 3600
|
||||||
|
default:
|
||||||
|
return ts + 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) Inc(tm time.Time, value float64) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
ts := s.cutTimeSeries(tm)
|
||||||
|
for _, v := range s.values {
|
||||||
|
if v.Timestamp == ts {
|
||||||
|
v.Value += value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.values = append(s.values, &TimeseriesValue{
|
||||||
|
Timestamp: ts,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) Dec(tm time.Time, value float64) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
ts := s.cutTimeSeries(tm)
|
||||||
|
for _, v := range s.values {
|
||||||
|
if v.Timestamp == ts {
|
||||||
|
v.Value -= value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimeSeries) Values(sts, ets int64) []*TimeseriesValue {
|
||||||
|
var (
|
||||||
|
nextStamp int64
|
||||||
|
)
|
||||||
|
sts = s.truncateTime(sts)
|
||||||
|
ets = s.truncateTime(ets)
|
||||||
|
series := make([]*TimeseriesValue, 0, len(s.values))
|
||||||
|
nextStamp = sts
|
||||||
|
for _, row := range s.values {
|
||||||
|
for row.Timestamp > nextStamp {
|
||||||
|
series = append(series, &TimeseriesValue{
|
||||||
|
Timestamp: nextStamp,
|
||||||
|
Value: 0,
|
||||||
|
})
|
||||||
|
nextStamp = s.nextTimestamp(nextStamp)
|
||||||
|
}
|
||||||
|
series = append(series, row)
|
||||||
|
nextStamp = s.nextTimestamp(nextStamp)
|
||||||
|
}
|
||||||
|
for ets > nextStamp {
|
||||||
|
series = append(series, &TimeseriesValue{
|
||||||
|
Timestamp: nextStamp,
|
||||||
|
Value: 0,
|
||||||
|
})
|
||||||
|
nextStamp = s.nextTimestamp(nextStamp)
|
||||||
|
}
|
||||||
|
return series
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeSeries(step string) *TimeSeries {
|
||||||
|
return &TimeSeries{
|
||||||
|
step: step,
|
||||||
|
values: make([]*TimeseriesValue, 0, 64),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package chartjs
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
label: 'My Time Series Data',
|
||||||
|
data: [
|
||||||
|
{ x: '2025-01-01', y: 10 },
|
||||||
|
{ x: '2025-01-02', y: 15 },
|
||||||
|
{ x: '2025-01-03', y: 8 },
|
||||||
|
{ x: '2025-01-04', y: 20 },
|
||||||
|
{ x: '2025-01-05', y: 12 }
|
||||||
|
],
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
tension: 0.1
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type TimeseriesValue struct {
|
||||||
|
Timestamp int64 `json:"x"`
|
||||||
|
Value float64 `json:"y"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeseriesData 时间序列的数据
|
||||||
|
type TimeseriesData struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Data []*TimeseriesValue `json:"data"`
|
||||||
|
BorderColor string `json:"borderColor,omitempty"`
|
||||||
|
BackgroundColor string `json:"backgroundColor,omitempty"`
|
||||||
|
Tension float64 `json:"tension,omitempty"`
|
||||||
|
Fill bool `json:"fill,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Dataset 1',
|
||||||
|
data: Utils.numbers(NUMBER_CFG),
|
||||||
|
backgroundColor: [
|
||||||
|
Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
|
||||||
|
Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),
|
||||||
|
Utils.transparentize(Utils.CHART_COLORS.yellow, 0.5),
|
||||||
|
Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
|
||||||
|
Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type counterValue struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Values map[string]float64 `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CounterValue struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Data []float64 `json:"data"`
|
||||||
|
BackgroundColor []string `json:"backgroundColor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CounterData struct {
|
||||||
|
Lables []string `json:"labels"`
|
||||||
|
Datasets []*CounterValue `json:"datasets"`
|
||||||
|
}
|
144
server.go
144
server.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.nobla.cn/golang/aeus-admin/migrate"
|
"git.nobla.cn/golang/aeus-admin/migrate"
|
||||||
"git.nobla.cn/golang/aeus-admin/models"
|
"git.nobla.cn/golang/aeus-admin/models"
|
||||||
|
@ -334,10 +335,151 @@ func registerRESTRoute(domain string, db *gorm.DB, hs *http.Server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeyValues := func(ctx *http.Context) (err error) {
|
||||||
|
var (
|
||||||
|
dbDependency dbcache.CacheDependency
|
||||||
|
pairs []*restTypes.TypeValue[any]
|
||||||
|
modelValue any
|
||||||
|
)
|
||||||
|
moduleName := ctx.Param("module")
|
||||||
|
tableName := ctx.Param("table")
|
||||||
|
entities := rest.GetModels()
|
||||||
|
for _, entry := range entities {
|
||||||
|
if entry.ModuleName() == moduleName && entry.TableName() == tableName {
|
||||||
|
modelValue = reflect.New(entry.Value().Type()).Interface()
|
||||||
|
for _, field := range entry.Fields() {
|
||||||
|
if field.AutoUpdateTime > 0 {
|
||||||
|
dbDependency = dbcache.NewSqlDependency(fmt.Sprintf("SELECT MAX(`%s`) FROM `%s`", field.DBName, entry.TableName()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modelValue == nil {
|
||||||
|
return ctx.Error(errors.NotFound, "model not found")
|
||||||
|
}
|
||||||
|
labelColumn := ctx.Param("label")
|
||||||
|
valueColumn := ctx.Param("value")
|
||||||
|
opts := make([]dbcache.CacheOption, 0, 4)
|
||||||
|
opts = append(opts, dbcache.WithDB(db))
|
||||||
|
if dbDependency != nil {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute*30))
|
||||||
|
opts = append(opts, dbcache.WithDependency(dbDependency))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute))
|
||||||
|
}
|
||||||
|
if pairs, err = dbcache.TryCache(ctx.Context(), fmt.Sprintf("rest:kvpairs:%s:%s:%s:%s", moduleName, tableName, labelColumn, valueColumn), func(tx *gorm.DB) ([]*restTypes.TypeValue[any], error) {
|
||||||
|
return rest.ModelTypes[any](ctx.Context(), db, modelValue, "", labelColumn, valueColumn)
|
||||||
|
}, opts...); err == nil {
|
||||||
|
return ctx.Success(pairs)
|
||||||
|
} else {
|
||||||
|
return ctx.Error(errors.Unavailable, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTierPairs := func(ctx *http.Context) (err error) {
|
||||||
|
var (
|
||||||
|
dbDependency dbcache.CacheDependency
|
||||||
|
pairs []*restTypes.TierValue[string]
|
||||||
|
modelValue any
|
||||||
|
)
|
||||||
|
moduleName := ctx.Param("module")
|
||||||
|
tableName := ctx.Param("table")
|
||||||
|
entities := rest.GetModels()
|
||||||
|
for _, entry := range entities {
|
||||||
|
if entry.ModuleName() == moduleName && entry.TableName() == tableName {
|
||||||
|
// 权限控制
|
||||||
|
if err = entry.HasPermission(ctx.Context(), entry.Permission(restTypes.ScenarioList)); err != nil {
|
||||||
|
return ctx.Error(errors.AccessDenied, err.Error())
|
||||||
|
}
|
||||||
|
modelValue = reflect.New(entry.Value().Type()).Interface()
|
||||||
|
for _, field := range entry.Fields() {
|
||||||
|
if field.AutoUpdateTime > 0 {
|
||||||
|
dbDependency = dbcache.NewSqlDependency(fmt.Sprintf("SELECT MAX(`%s`) FROM `%s`", field.DBName, entry.TableName()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modelValue == nil {
|
||||||
|
return ctx.Error(errors.NotFound, "model not found")
|
||||||
|
}
|
||||||
|
parentColumn := ctx.Param("parent")
|
||||||
|
labelColumn := ctx.Param("label")
|
||||||
|
valueColumn := ctx.Param("value")
|
||||||
|
opts := make([]dbcache.CacheOption, 0, 4)
|
||||||
|
opts = append(opts, dbcache.WithDB(db))
|
||||||
|
if dbDependency != nil {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute*30))
|
||||||
|
opts = append(opts, dbcache.WithDependency(dbDependency))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute))
|
||||||
|
}
|
||||||
|
if pairs, err = dbcache.TryCache(ctx.Context(), fmt.Sprintf("rest:tierpairs:%s:%s:%s:%s", moduleName, tableName, labelColumn, valueColumn), func(tx *gorm.DB) ([]*restTypes.TierValue[string], error) {
|
||||||
|
return rest.ModelTiers[string](ctx.Context(), db, modelValue, "", parentColumn, labelColumn, valueColumn)
|
||||||
|
}, opts...); err == nil {
|
||||||
|
return ctx.Success(pairs)
|
||||||
|
} else {
|
||||||
|
return ctx.Error(errors.Unavailable, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNumberTierPairs := func(ctx *http.Context) (err error) {
|
||||||
|
var (
|
||||||
|
dbDependency dbcache.CacheDependency
|
||||||
|
pairs []*restTypes.TierValue[int64]
|
||||||
|
modelValue any
|
||||||
|
)
|
||||||
|
moduleName := ctx.Param("module")
|
||||||
|
tableName := ctx.Param("table")
|
||||||
|
entities := rest.GetModels()
|
||||||
|
for _, entry := range entities {
|
||||||
|
if entry.ModuleName() == moduleName && entry.TableName() == tableName {
|
||||||
|
// 权限控制
|
||||||
|
if err = entry.HasPermission(ctx.Context(), entry.Permission(restTypes.ScenarioList)); err != nil {
|
||||||
|
return ctx.Error(errors.AccessDenied, err.Error())
|
||||||
|
}
|
||||||
|
modelValue = reflect.New(entry.Value().Type()).Interface()
|
||||||
|
for _, field := range entry.Fields() {
|
||||||
|
if field.AutoUpdateTime > 0 {
|
||||||
|
dbDependency = dbcache.NewSqlDependency(fmt.Sprintf("SELECT MAX(`%s`) FROM `%s`", field.DBName, entry.TableName()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modelValue == nil {
|
||||||
|
return ctx.Error(errors.NotFound, "model not found")
|
||||||
|
}
|
||||||
|
parentColumn := ctx.Param("parent")
|
||||||
|
labelColumn := ctx.Param("label")
|
||||||
|
valueColumn := ctx.Param("value")
|
||||||
|
opts := make([]dbcache.CacheOption, 0, 4)
|
||||||
|
opts = append(opts, dbcache.WithDB(db))
|
||||||
|
if dbDependency != nil {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute*30))
|
||||||
|
opts = append(opts, dbcache.WithDependency(dbDependency))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, dbcache.WithCacheDuration(time.Minute))
|
||||||
|
}
|
||||||
|
if pairs, err = dbcache.TryCache(ctx.Context(), fmt.Sprintf("rest:tierpairs:%s:%s:%s:%s", moduleName, tableName, labelColumn, valueColumn), func(tx *gorm.DB) ([]*restTypes.TierValue[int64], error) {
|
||||||
|
return rest.ModelTiers[int64](ctx.Context(), db, modelValue, "", parentColumn, labelColumn, valueColumn)
|
||||||
|
}, opts...); err == nil {
|
||||||
|
return ctx.Success(pairs)
|
||||||
|
} else {
|
||||||
|
return ctx.Error(errors.Unavailable, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hs.GET("/rest/schema/:module/:table", handleListSchemas)
|
hs.GET("/rest/schema/:module/:table", handleListSchemas)
|
||||||
hs.PUT("/rest/schema/:module/:table", handleUpdateSchemas)
|
hs.PUT("/rest/schema/:module/:table", handleUpdateSchemas)
|
||||||
hs.DELETE("/rest/schema/:id", handleDeleteSchema)
|
hs.DELETE("/rest/schema/:id", handleDeleteSchema)
|
||||||
|
hs.GET("/rest/kvpairs/:module/:table/:value/:label", handleKeyValues) //处理键值对数据
|
||||||
|
hs.GET("/rest/tierpairs/str/:module/:table/:parent/:value/:label", handleTierPairs) //处理字符串类型的层级数据
|
||||||
|
hs.GET("/rest/tierpairs/num/:module/:table/:parent/:value/:label", handleNumberTierPairs) //处理数字类型的层级数据, 只支持int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoMigrate 自动生成一个模型的schema和权限的定义
|
// AutoMigrate 自动生成一个模型的schema和权限的定义
|
||||||
|
|
|
@ -17,6 +17,7 @@ type (
|
||||||
|
|
||||||
SettingOption func(o *settingOptions)
|
SettingOption func(o *settingOptions)
|
||||||
)
|
)
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
opts *settingOptions
|
opts *settingOptions
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue