这是一个比较大的过程,我尽量把相关的重要知识点连贯起来。
一,先上效果图
二,后端Model定义
import uuid
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
# 指标集
class ViewSet(models.Model):
view_id = models.CharField(max_length=64,
verbose_name='指标集id')
view_name = models.CharField(max_length=64,
verbose_name='指标集名称')
description = models.CharField(max_length=1024,
null=True,
blank=True,
verbose_name='指标集描述')
create_user = models.ForeignKey(User,
null=True,
blank=True,
related_name='ra_view_set',
on_delete=models.CASCADE,
verbose_name='创建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建时间')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新时间')
status = models.BooleanField(default=True, verbose_name='状态')
@property
def username(self):
return self.create_user.username
def __str__(self):
return self.view_name
class Meta:
db_table = 'ViewSet'
ordering = ('-update_date', )
# 指标
class Attr(models.Model):
attr_id = models.CharField(max_length=64,
verbose_name='指标id')
attr_name = models.CharField(max_length=64,
verbose_name='指标名称')
view_set = models.ForeignKey(ViewSet,
related_name='ra_attr',
on_delete=models.CASCADE,
verbose_name='指标集')
description = models.CharField(max_length=1024,
null=True,
blank=True,
verbose_name='指标集描述')
security_token = models.CharField(max_length=64,
null=True,
blank=True,
verbose_name='连接token')
url = models.CharField(max_length=1024,
null=True,
blank=True,
verbose_name='监控url')
create_user = models.ForeignKey(User,
null=True,
blank=True,
related_name='ra_attr',
on_delete=models.CASCADE,
verbose_name='创建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建时间')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新时间')
status = models.BooleanField(default=True, verbose_name='状态')
@property
def username(self):
return self.create_user.username
def __str__(self):
return self.attr_name
class Meta:
db_table = 'Attr'
ordering = ('-update_date',)
# 异常库
class Anomaly(models.Model):
attr = models.ForeignKey(Attr,
related_name='ra_anomaly',
on_delete=models.CASCADE,
verbose_name='指标')
anomaly_time = models.DateTimeField(verbose_name='异常检测时间')
data_a = models.TextField(verbose_name='当天180分钟数据')
data_b = models.TextField(verbose_name='一天前180分钟数据')
data_c = models.TextField(verbose_name='一周前180分钟数据')
mark_flag = models.CharField(max_length=16,
null=True,
blank=True,
verbose_name='标注正负样本')
create_user = models.ForeignKey(User,
related_name='ra_anomaly',
on_delete=models.CASCADE,
verbose_name='创建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建时间')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新时间')
status = models.BooleanField(default=True, verbose_name='状态')
@property
def username(self):
return self.create_user.username
@property
def attr_name(self):
return self.attr.attr_name
@property
def view_set_name(self):
return self.attr.view_set.view_name
def __str__(self):
return self.attr.attr_name
class Meta:
db_table = 'Anomaly'
ordering = ('-update_date', )
这里主要关注的是异常库Anomaly有外键关联Attr指标库,而Attr库有外键关联指标集库ViewSet。
三,urls.py里定义路由
path('list/', api_views.AnomalyListView.as_view(), name='list'),
as_view()用于将类方法化。
四,DRF中的类视图定义
class AnomalyListView(ListAPIView):
queryset = Anomaly.objects.all()
serializer_class = AnomalySerializer
pagination_class = PNPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filter_class = AnomalyFilter
ordering_fields = ['id']
def get(self, request, *args, **kwargs):
res = super().get(self, request, *args, **kwargs)
return_dict = build_ret_data(OP_SUCCESS, res.data)
return render_json(return_dict)
DRF的ListAPIView及其它几个generics View,是封装度合适的。相比于API VIEW,很多方法和变量都已有内置实现。相比于VIEWSET,又保留了很多清晰明白的自定义实现功能。
其中build_ret_data函数,用于统一封装返回数据,定义如下:
def build_ret_data(ret_code, data=''):
return {'code': ret_code, 'message': ERR_CODE[ret_code], 'data': data}
ERROR_CODE字典定义如下:
# coding:utf-8
OP_SUCCESS = 0
THROW_EXP = 1000
OP_DB_FAILED = 1001
CHECK_PARAM_FAILED = 1002
FILE_FORMAT_ERR = 1003
NOT_POST = 1004
NOT_GET = 1005
CAL_FEATURE_ERR = 2001
READ_FEATURE_FAILED = 2002
TRAIN_ERR = 2003
LACK_SAMPLE = 2004
ERR_CODE = {
0: '操作成功',
1000: "抛出异常",
1001: "数据库操作失败",
1002: "参数检查失败",
1003: "文件格式有误",
1004: "非post请求",
1005: "非get请求",
2001: "特征计算出错",
2002: "读取特征数据失败",
2003: "训练出错",
2004: "缺少正样本或负样本"
}
其中render_json,用于将返回数据json化,函数定义如下:
def render_json(dictionary={}):
response = HttpResponse(json.dumps(dictionary), content_type="application/json")
response['Access-Control-Allow-Origin'] = '*'
response["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type"
response["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
return response
由于使用了自定义的json返回,所以DRF的返回,都要作自定义操作。
这样或许增加了一些代码量,但可以在整个应用内实现返回码,消息体的统一。
五,序列化文件
from rest_framework import serializers
from MetisModels.models import Anomaly
class AnomalySerializer(serializers.ModelSerializer):
class Meta:
model = Anomaly
# fields = '__all__'
fields = ['id', 'attr', 'mark_flag', 'anomaly_time',
'data_a', 'data_b', 'data_c',
'attr_name', 'view_set_name',
'create_user', 'username']
extra_kwargs = {
'username': {
'read_only': True,
},
'attr_name': {
'read_only': True,
},
'view_set_name': {
'read_only': True,
},
'create_user': {
'write_only': True,
},
}
在序列化文件,read_only用于提示此字段仅作序列化显示,write_only仅作反序列化写入。
六,分页文件
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.pagination import PageNumberPagination
class PNPagination(PageNumberPagination):
page_size = 10
max_page_size = 100
page_size_query_param = 'pageSize'
page_query_param = 'currentPage'
'''
age_query_param:表示url中的页码参数
page_size_query_param:表示url中每页数量参数
page_size:表示每页的默认显示数量
max_page_size:表示每页最大显示数量,做限制使用,避免突然大量的查询数据,数据库崩溃
'''
class LOPagination(LimitOffsetPagination):
default_limit = 10
max_limit = 100
limit_query_param = 'pageSize'
offset_query_param = 'currentPage'
'''
default_limit:表示默认每页显示几条数据
limit_query_param:表示url中本页需要显示数量参数
offset_query_param:表示从数据库中的第几条数据开始显示参数
max_limit:表示每页最大显示数量,做限制使用,避免突然大量的查询数据,数据库崩溃
'''
这里,使用了PageNumber的分页,我们可以传入类似下面的页面参数
/anomaly/list/?pageSize=6¤tPage=1&ordering=-id&mark_flag=all&begin_time=&end_time=&attr=Kafka&view_set=%E4%B8%AD%E9%97%B4
其中的pageSize和currentPage,要和定义的参数对应。
七,filters定义文件
class AnomalyFilter(FilterSet):
attr = filters.CharFilter(field_name='attr__attr_name', lookup_expr='icontains',)
view_set = filters.CharFilter(field_name='attr__view_set__view_name', lookup_expr='icontains',)
begin_time = filters.CharFilter(field_name='anomaly_time', lookup_expr='gte',)
end_time = filters.DateTimeFilter(field_name='anomaly_time', lookup_expr='lte',)
mark_flag = filters.CharFilter(field_name='mark_flag', method='mark_flag_filter',)
def mark_flag_filter(self, queryset, field_name, value):
print(value)
if value == 'no':
return queryset.filter(~Q(mark_flag='negative') & ~Q(mark_flag='positive'))
elif value == 'yes':
return queryset.filter(Q(mark_flag='negative') | Q(mark_flag='positive'))
elif value == 'all':
return queryset
else:
return queryset
class Meta:
model = Anomaly
fields = ['attr', 'view_set', 'begin_time', 'end_time', 'mark_flag']
算是django_filters这个库比较在代表性的实现了。
attr字符串,使用了外键的关联,模糊搜索。
view_set字符串,使用了外键的外键关联,模糊搜索。
begin_time和end_time日期时间,范围的选择。
mark_flag字符串,使用了自定义过滤。其中queryset为已过滤集合,value为取到的field字段的值。Q是django orm的魔法功能,此处用于包含或不包含。
八,前端搜索框定义
查询
重置
v-model作双向绑定
九,data中的pagination定义
pagination: {
'total': 0,
'pageSize': 6,
'currentPage': 1,
'ordering': '-id',
'searchKey': {
'markFlag': this.$route.meta.label,
'viewSet': '',
'attr': '',
'beginTime': '',
'endTime': ''
},
onChange: page => {
const pager = { ...this.pagination };
pager.currentPage = page;
this.pagination = pager;
this.fetch(this.pagination);
},
},
这个定义中,不但包含了分页参数,还包含了搜索过滤的参数。
onChange用于分页技巧。
十,methods中时间选择时触发的变量更新
moment,
createChange(dates, dateStrings) {
this.pagination.searchKey.beginTime = dateStrings[0]
this.pagination.searchKey.endTime = dateStrings[1]
},
十一,前端向后端请求数据时,参数作一次理顺。
import {ANOMALY_LIST, ANOMALY_UPDATE} from '@/services/api'
import {request, METHOD} from '@/utils/request'
/**
* 获取所有异常时序
*/
export async function getAnomalyList(data) {
const pageSize = data['pageSize']
const currentPage = data['currentPage']
const ordering = data['ordering']
const markFlag = data['searchKey']['markFlag']
const beginTime = data['searchKey']['beginTime']
const endTime = data['searchKey']['endTime']
const viewSet = data['searchKey']['viewSet']
const attr = data['searchKey']['attr']
return request(ANOMALY_LIST, METHOD.GET, {
pageSize,
currentPage,
ordering,
'mark_flag': markFlag,
'begin_time': beginTime,
'end_time': endTime,
'attr': attr,
'view_set': viewSet,
})
}
这里面,作了axios的封装,token,守卫路由这些都有。
十二,一个请求ajax的demo。
fetch(params={}) {
this.loading = true;
getAnomalyList(params).then(resp => {
let retData = resp.data
if (retData.code == 0) {
this.dataSource = []
this.dataAbc = []
this.dataGraphAbc = {}
const results = retData.data.results
for (let i = 0; i < results.length; i++) {
const anomalyTime = `${this.$options.filters.secFormat(results[i].anomaly_time)}`
const attrName = results[i].attr_name
const view_set_name = results[i].view_set_name
this.dataSource.push({
key: i,
id: results[i].id,
attrName: attrName,
viewSetName: view_set_name,
anomalyTime: anomalyTime,
markFlag: results[i].mark_flag,
titleSample: `[${view_set_name}-${attrName}]:${anomalyTime}`,
dataGraphAbc: dataSeries(anomalyTime,
results[i].data_a,
results[i].data_b,
results[i].data_c),
createDate: results[i].create_date,
createUser: results[i].username
})
this.dataAbc.push(dataSeries(anomalyTime,
results[i].data_a,
results[i].data_b,
results[i].data_c))
this.titleSamples.push(`[${view_set_name}-${attrName}]:${anomalyTime}`)
}
const pager = { ...this.pagination };
// Read total count from server
pager.total = retData.data.count;
this.pagination = pager;
this.loading = false;
} else {
this.loading = false;
this.$message.error(createRes.message, 3)
}
})
},
这里的params={}参数,就是传的pagination变量。