這是一個(gè)比較大的過(guò)程,我盡量把相關(guān)的重要知識(shí)點(diǎn)連貫起來(lái)缤言。
一宝当,先上效果圖
二,后端Model定義
import uuid
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
# 指標(biāo)集
class ViewSet(models.Model):
view_id = models.CharField(max_length=64,
verbose_name='指標(biāo)集id')
view_name = models.CharField(max_length=64,
verbose_name='指標(biāo)集名稱')
description = models.CharField(max_length=1024,
null=True,
blank=True,
verbose_name='指標(biāo)集描述')
create_user = models.ForeignKey(User,
null=True,
blank=True,
related_name='ra_view_set',
on_delete=models.CASCADE,
verbose_name='創(chuàng)建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
status = models.BooleanField(default=True, verbose_name='狀態(tài)')
@property
def username(self):
return self.create_user.username
def __str__(self):
return self.view_name
class Meta:
db_table = 'ViewSet'
ordering = ('-update_date', )
# 指標(biāo)
class Attr(models.Model):
attr_id = models.CharField(max_length=64,
verbose_name='指標(biāo)id')
attr_name = models.CharField(max_length=64,
verbose_name='指標(biāo)名稱')
view_set = models.ForeignKey(ViewSet,
related_name='ra_attr',
on_delete=models.CASCADE,
verbose_name='指標(biāo)集')
description = models.CharField(max_length=1024,
null=True,
blank=True,
verbose_name='指標(biāo)集描述')
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='監(jiān)控url')
create_user = models.ForeignKey(User,
null=True,
blank=True,
related_name='ra_attr',
on_delete=models.CASCADE,
verbose_name='創(chuàng)建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
status = models.BooleanField(default=True, verbose_name='狀態(tài)')
@property
def username(self):
return self.create_user.username
def __str__(self):
return self.attr_name
class Meta:
db_table = 'Attr'
ordering = ('-update_date',)
# 異常庫(kù)
class Anomaly(models.Model):
attr = models.ForeignKey(Attr,
related_name='ra_anomaly',
on_delete=models.CASCADE,
verbose_name='指標(biāo)')
anomaly_time = models.DateTimeField(verbose_name='異常檢測(cè)時(shí)間')
data_a = models.TextField(verbose_name='當(dāng)天180分鐘數(shù)據(jù)')
data_b = models.TextField(verbose_name='一天前180分鐘數(shù)據(jù)')
data_c = models.TextField(verbose_name='一周前180分鐘數(shù)據(jù)')
mark_flag = models.CharField(max_length=16,
null=True,
blank=True,
verbose_name='標(biāo)注正負(fù)樣本')
create_user = models.ForeignKey(User,
related_name='ra_anomaly',
on_delete=models.CASCADE,
verbose_name='創(chuàng)建者')
create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
status = models.BooleanField(default=True, verbose_name='狀態(tài)')
@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', )
這里主要關(guān)注的是異常庫(kù)Anomaly有外鍵關(guān)聯(lián)Attr指標(biāo)庫(kù)胆萧,而Attr庫(kù)有外鍵關(guān)聯(lián)指標(biāo)集庫(kù)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及其它幾個(gè)generics View,是封裝度合適的蚌吸。相比于API VIEW锈拨,很多方法和變量都已有內(nèi)置實(shí)現(xiàn)。相比于VIEWSET羹唠,又保留了很多清晰明白的自定義實(shí)現(xiàn)功能奕枢。
其中build_ret_data函數(shù),用于統(tǒng)一封裝返回?cái)?shù)據(jù)佩微,定義如下:
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: "數(shù)據(jù)庫(kù)操作失敗",
1002: "參數(shù)檢查失敗",
1003: "文件格式有誤",
1004: "非post請(qǐng)求",
1005: "非get請(qǐng)求",
2001: "特征計(jì)算出錯(cuò)",
2002: "讀取特征數(shù)據(jù)失敗",
2003: "訓(xùn)練出錯(cuò)",
2004: "缺少正樣本或負(fù)樣本"
}
其中render_json缝彬,用于將返回?cái)?shù)據(jù)json化,函數(shù)定義如下:
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的返回谷浅,都要作自定義操作。
這樣或許增加了一些代碼量奶卓,但可以在整個(gè)應(yīng)用內(nèi)實(shí)現(xiàn)返回碼一疯,消息體的統(tǒng)一。
五寝杖,序列化文件
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僅作反序列化寫入瑟幕。
六,分頁(yè)文件
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中的頁(yè)碼參數(shù)
page_size_query_param:表示url中每頁(yè)數(shù)量參數(shù)
page_size:表示每頁(yè)的默認(rèn)顯示數(shù)量
max_page_size:表示每頁(yè)最大顯示數(shù)量,做限制使用只盹,避免突然大量的查詢數(shù)據(jù)辣往,數(shù)據(jù)庫(kù)崩潰
'''
class LOPagination(LimitOffsetPagination):
default_limit = 10
max_limit = 100
limit_query_param = 'pageSize'
offset_query_param = 'currentPage'
'''
default_limit:表示默認(rèn)每頁(yè)顯示幾條數(shù)據(jù)
limit_query_param:表示url中本頁(yè)需要顯示數(shù)量參數(shù)
offset_query_param:表示從數(shù)據(jù)庫(kù)中的第幾條數(shù)據(jù)開(kāi)始顯示參數(shù)
max_limit:表示每頁(yè)最大顯示數(shù)量,做限制使用殖卑,避免突然大量的查詢數(shù)據(jù)站削,數(shù)據(jù)庫(kù)崩潰
'''
這里,使用了PageNumber的分頁(yè)孵稽,我們可以傳入類似下面的頁(yè)面參數(shù)
/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许起,要和定義的參數(shù)對(duì)應(yīng)。
七菩鲜,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這個(gè)庫(kù)比較在代表性的實(shí)現(xiàn)了园细。
attr字符串,使用了外鍵的關(guān)聯(lián)接校,模糊搜索猛频。
view_set字符串,使用了外鍵的外鍵關(guān)聯(lián)蛛勉,模糊搜索鹿寻。
begin_time和end_time日期時(shí)間,范圍的選擇诽凌。
mark_flag字符串毡熏,使用了自定義過(guò)濾。其中queryset為已過(guò)濾集合皿淋,value為取到的field字段的值招刹。Q是django orm的魔法功能,此處用于包含或不包含窝趣。
八疯暑,前端搜索框定義
<div>
<a-form-model layout="horizontal" ref="searchKey" :model="pagination.searchKey" @submit="handleSubmit">
<div class="fold">
<a-row >
<a-col :md="8" :sm="24" >
<a-form-model-item
label="指標(biāo)集"
:labelCol="{span: 5}"
:wrapperCol="{span: 18, offset: 1}"
>
<a-input placeholder="請(qǐng)輸入" v-model="pagination.searchKey.viewSet" />
</a-form-model-item>
</a-col>
<a-col :md="8" :sm="24" >
<a-form-model-item
label="指標(biāo)"
:labelCol="{span: 5}"
:wrapperCol="{span: 18, offset: 1}"
>
<a-input placeholder="請(qǐng)輸入" v-model="pagination.searchKey.attr" />
</a-form-model-item>
</a-col>
<a-col :md="8" :sm="24" >
<a-form-model-item
label="時(shí)間范圍"
:labelCol="{span: 5}"
:wrapperCol="{span: 18, offset: 1}"
>
<a-range-picker
:ranges='timeRange'
:placeholder="['開(kāi)始時(shí)間', '結(jié)束時(shí)間']"
@change="createChange"
style="width: 100%"
/>
</a-form-model-item>
</a-col>
</a-row>
</div>
<span style="float: right; margin-top: 3px;">
<a-button type="primary" html-type="submit">查詢</a-button>
<a-button style="margin-left: 8px" html-type="reset" @click="resetForm">重置</a-button>
</span>
</a-form-model>
</div>
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);
},
},
這個(gè)定義中哑舒,不但包含了分頁(yè)參數(shù)妇拯,還包含了搜索過(guò)濾的參數(shù)。
onChange用于分頁(yè)技巧洗鸵。
十越锈,methods中時(shí)間選擇時(shí)觸發(fā)的變量更新
moment,
createChange(dates, dateStrings) {
this.pagination.searchKey.beginTime = dateStrings[0]
this.pagination.searchKey.endTime = dateStrings[1]
},
十一,前端向后端請(qǐng)求數(shù)據(jù)時(shí)膘滨,參數(shù)作一次理順甘凭。
import {ANOMALY_LIST, ANOMALY_UPDATE} from '@/services/api'
import {request, METHOD} from '@/utils/request'
/**
* 獲取所有異常時(shí)序
*/
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丹弱,守衛(wèi)路由這些都有德撬。
十二,一個(gè)請(qǐng)求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={}參數(shù)蜓洪,就是傳的pagination變量。