منصة لتصور البيانات على جانب الويب تعتمد على بيانات الزاحف العنكبوتية المشتركة، والوصول عبر الإنترنت
مصدر البيانات يأتي من مشروع Joint-spider
الخلفية: جانغو-ريستفرومورك
الواجهة الأمامية: فيو
# 环境配置需要 python3.7 redis mysql vue vue-cli3.x
#
# 项目启动
# 1. 后端服务启动,进入项目HouseAnalysis
python manage.py runserver
# 2. 前端项目启动,进入项目目录HouseAnalysisWeb
npm run dev
# 3. 前端项目打包
npm run build-prod
# 4. 访问地址:http://localhost:9527/
# 5. 后端接口地址: http://localhost:8000/
تفاصيل قائمة الإسكان المجتمعي
صفحة البحث عن الممتلكات
صفحة تفاصيل الملكية المحددة
حول المنزل
# 项目依赖
Django==2.1.5
django-cors-headers==2.4.0
django-crispy-forms==1.7.2
django-filter==2.0.0
django-formtools==2.1
django-guardian==1.4.9
django-import-export==1.1.0
django-redis==4.10.0
django-reversion==3.0.2
djangorestframework==3.9.0
djangorestframework-jwt==1.11.0
drf-extensions==0.4.0
PyMySQL==0.9.3
redis==3.0.1
SQLAlchemy==1.3.2
العملية الأساسية
دورة حياة جانغو:
ترسل الواجهة الأمامية طلبًا-->wsgi الخاص بـDjango-->البرامج الوسيطة-->نظام التوجيه-->عرض-->تشغيل قاعدة بيانات ORM-->القالب-->إرجاع البيانات إلى المستخدم
دورة حياة إطار الراحة:
ترسل الواجهة الأمامية طلبًا-->wsgi الخاص بـDjango-->البرامج الوسيطة-->نظام التوجيه as_view() الخاص بـ CBV، وهو تنفيذ طريقة الإرسال الداخلي-->قبل تنفيذ الإرسال، يوجد تحليل الإصدار والعارض--> في dispath، قم بتغليف الطلب-->الإصدار-->المصادقة-->الأذونات-->الحد الحالي-->عرض-->إذا كان العرض يستخدم التخزين المؤقت (request.data أو request.query_params ) يستخدم المحلل اللغوي -> يقوم العرض بمعالجة البيانات ويستخدم التسلسل (تسلسل البيانات أو التحقق منها) -> يقوم العرض بإرجاع البيانات باستخدام الترحيل.
شجرة ملفات المشروع:
# 1. 初始化项目
django-admin startproject HouseAnalysis
# 2. 创建相关应用
cd HouseAnalysis
django-admin startapp house
# 项目根目录下创建extra_app 文件夹,存放xadmin压缩文件,运行
pip3 install xadmin
# 1. 配置settings.py 文件 位于\HouseAnalysisHouseAnalysissettings.py
# 找到INSTALLED_APPS,加入配置项
' corsheaders ' ,
' xadmin ' ,
' crispy_forms ' ,
' rest_framework ' ,
' django_filters ' ,
' house '
# 上述配置向是用于将app注册到项目中
# 2. 增加跨域支持
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
' * '
)
CORS_ALLOW_METHODS = (
' DELETE ' ,
' GET ' ,
' OPTIONS ' ,
' PATCH ' ,
' POST ' ,
' PUT ' ,
' VIEW ' ,
)
CORS_ALLOW_HEADERS = (
' XMLHttpRequest ' ,
' X_FILENAME ' ,
' accept-encoding ' ,
' authorization ' ,
' content-type ' ,
' dnt ' ,
' origin ' ,
' user-agent ' ,
' x-csrftoken ' ,
' x-requested-with ' ,
' Pragma ' ,
)
# 3. 在DATABASES中配置MySQL数据库信息
DATABASES = {
' default ' : {
' ENGINE ' : ' django.db.backends.mysql ' ,
' NAME ' : ' house ' ,
' USER ' : ' root ' ,
' PASSWORD ' : ' 123456 ' ,
' HOST ' : ' 127.0.0.1 ' ,
' PORT ' : ' 3306 ' ,
' OPTIONS ' :{
' init_command ' : " SET sql_mode='STRICT_TRANS_TABLES' "
},
}
}
# 4. 配置drf缓存
REST_FRAMEWORK_EXTENSIONS = {
# 10s后失效
' DEFAULT_CACHE_RESPONSE_TIMEOUT ' : 10
}
# 5. 配置redis缓存
CACHES = {
" default " : {
" BACKEND " : " django_redis.cache.RedisCache " ,
" LOCATION " : " redis://127.0.0.1:6379/3 " ,
" OPTIONS " : {
" CLIENT_CLASS " : " django_redis.client.DefaultClient " ,
}
}
}
ملاحظة: هذا المشروع عبارة عن تطوير للواجهة ولا يتضمن قوالب قوالب وموارد ثابتة، لذلك ليست هناك حاجة لتكوين هذين العنصرين.
أدخل HouseAnapsyHouseAnalogyurls.py وقم بتكوين معلومات التوجيه:
أضف path('xadmin/', xadmin.site.urls)
, re_path(r'docs/', include_docs_urls(title="HA"))
إلى قائمة urlpatterns
، xadmin هو توجيه صفحة الإدارة،
Docs هو واجهة توجيه المستندات، والتي يمكن الوصول إليها من خلال http://localhost:8000/docs/، كما هو موضح أدناه:
يعد تصميم نموذج Django أحد التصميمات الأساسية الخمسة لـ Django (تصميم النموذج، تكوين عنوان URL، كتابة العرض، تصميم القالب، من الاستخدام)، وهو أيضًا رابط مهم في نموذج MVC (MVT).
النموذج هو نموذج بيانات، وليس قاعدة بيانات، وهو يصف بنية البيانات والعلاقة المنطقية بينها. في جانغو، تصميم النموذج هو في الأساس فئة. يتم تعيين كل نموذج إلى جدول قاعدة البيانات.
أدخل HouseAnapsyhousemodels.py وأضف نماذج Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
لتوفير بيانات للتسلسل.
بعد إنشاء النموذج، قم بإجراء ترحيل البيانات:
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
قم بإنشاء ملف serializer.py جديد في المجلد الرئيسي لإجراء تسلسل للبيانات.
قم بتعريف مُسلسِل (فئة بشكل أساسي)، والذي يتضمن عمومًا حقول فئة النموذج وله قواعد نوع الحقل الخاصة به. بعد تنفيذ المُسلسل، يمكنك إنشاء كائنات متسلسلة ومجموعات استعلام لعمليات التسلسل، والحصول على البيانات من خلال بيانات الكائن المتسلسلة (لا تحتاج إلى إنشاء قاموس بنفسك ثم إرجاع بيانات Json)
عند استخدامه للتسلسل، قم بتمرير كائن فئة النموذج إلى معلمة المثيل
عند استخدامها لإلغاء التسلسل، قم بتمرير البيانات المراد إلغاء تسلسلها إلى معلمة البيانات .
بالإضافة إلى معلمات المثيل والبيانات، عند إنشاء كائن Serializer، يمكنك أيضًا إضافة بيانات إضافية من خلال معلمة السياق .
نستخدم هنا المُسلسِل النموذجي (ModelSerializer)، وهو نفس المُسلسل العادي، ولكنه يوفر:
على سبيل المثال: يمكن إجراء تسلسل لمحتوى المنزل باستخدام رمز بسيط
class HouseSerializer ( serializers . ModelSerializer ):
class Meta :
# 需要序列化的model
model = Api
# 需要序列化的字段
fields = "__all__"
بنفس الطريقة، قم بإجراء تسلسل للنماذج المتبقية، بما في ذلك ElevatorSerializer,FloorSerializer,LayoutSerializer,RegionSerializer,RegionS,CommunitySerializer,CommunityRangeSerializer,DecortionSerializer,PurposesSerializer,ConstructureSerializer,OrientationSerializer
، ومن بينها يعمل مُسلسِل RegionS
كمُسلسل متفوق لـ CommunitySerializer
و CommunityRangeSerializer
.
وظيفة العرض: بصراحة، هي تلقي طلبات الواجهة الأمامية وتنفيذ معالجة البيانات.
(تتضمن المعالجة هنا: إذا كانت الواجهة الأمامية عبارة عن طلب GET، فقم بإنشاء مجموعة استعلام وإرجاع النتائج. هذه العملية هي تسلسل ؛ إذا كانت الواجهة الأمامية عبارة عن طلب POST، وإذا كنت تريد إجراء تغييرات على قاعدة البيانات، تحتاج إلى الحصول على البيانات المرسلة من الواجهة الأمامية والتحقق من البيانات وكتابتها في قاعدة البيانات.
يمكن للعرض الأكثر أصالة تنفيذ مثل هذه المعالجة المنطقية، ولكن بالنسبة للطلبات المختلفة، يجب تحديد طرق متعددة في عرض الفصل لتنفيذ المعالجة الخاصة بها، وهذا يمكن أن يحل المشكلة، ولكن هناك عيبًا، وهو الطريقة العامة المنطق متشابه لكل وظيفة: قراءة الطلب، والحصول على البيانات من قاعدة البيانات، وكتابة الأشياء إلى قاعدة البيانات، وإرجاع النتائج إلى الواجهة الأمامية. سيؤدي هذا إلى إنتاج الكثير من التعليمات البرمجية المكررة.
في طرق عرض تطوير REST API، على الرغم من اختلاف عمليات البيانات المحددة لكل عرض، فإن عملية تنفيذ الإضافة والحذف والتعديل والتحقق تكون روتينية في الأساس، لذلك يمكن أيضًا إعادة استخدام هذا الجزء من التعليمات البرمجية وتبسيطه للكتابة:
تمت الإضافة : التحقق من بيانات الطلب -> تنفيذ عملية إلغاء التسلسل -> حفظ قاعدة البيانات -> تسلسل وإرجاع الكائن المحفوظ
حذف : تحديد ما إذا كانت البيانات المراد حذفها موجودة أم لا -> إجراء حذف قاعدة البيانات
التعديل : تحديد ما إذا كانت البيانات المطلوب تعديلها موجودة -> التحقق من البيانات المطلوبة -> إجراء عملية إلغاء التسلسل -> حفظ قاعدة البيانات -> إجراء تسلسل للكائن المحفوظ وإعادته
الاستعلام : الاستعلام عن قاعدة البيانات -> تسلسل البيانات وإرجاعها
تتم كتابة طرق العرض في ملف view.py، وهنا نستخدم مخططات التعرف على الفصل من أجل التطوير.
http://127.0.0.1/api/v1/allhouse
، نحتاج إلى إرجاع النتائج المتسلسلة لجميع معلومات المنزل وكشفها.http://127.0.0.1/api/v1/allhouse
/1، فنحن بحاجة إلى تغيير معرف المنزل (المعرف الموجود في). يمكن تكوين هذه المرة على أنها تحديد معلمة المتطلبات) ويتم إرجاع نتيجة التسلسل 1 وعرضها.{id:xxx,values:xxx...}
، فنحن بحاجة إلى التحقق من المعلومات الحالية وإضافة البيانات الحالية إلى. قاعدة البيانات إذا كانت قانونية.مثال:
# CacheResponseMixin: 缓存,内存级别
# ListModelMixin处理GET(all)请求
# RetrieveModelMixin处理(id)请求
# 基础类GenericViewSet,提供分页,过滤,排序等功能
class HouseListViewSet ( CacheResponseMixin , mixins . ListModelMixin , mixins . RetrieveModelMixin , GenericViewSet ):
queryset = Api . objects . all ()
# 房源序列化
serializer_class = HouseSerializer
# 房源分页
pagination_class = HousePagination
# 自定义排序,过滤类
filter_backends = ( DjangoFilterBackend , filters . SearchFilter , filters . OrderingFilter )
# 过滤信息字段
filter_fields = ( 'price' ,)
# 搜搜字段
search_fields = ( 'title' , 'region' , 'community_name' )
# 排序字段
ordering_fields = ( 'price' , 'unit_price' , 'construction_area' )
# 重写retrieve方法,实现获取单个序列化对象信息
def retrieve ( self , request , * args , ** kwargs ):
instance = self . get_object ()
serializer = self . get_serializer ( instance )
return Response ( serializer . data )
# 分页配置项
class HousePagination ( PageNumberPagination ):
page_size = 30
page_size_query_param = 'page_size'
page_query_param = "page"
max_page_size = 100
طرق العرض الأخرى متشابهة، بما في ذلك: ElevaorViewSet,FloorViewSet,LayoutViewSet,RegionViewSet,CommunityViewSet,CommunityPagination,AllCommunityViewSet,CommunityRangeViewSet,DecortionViewSet,PurposesViewSet,ConstructureViewSet,OrientationViewSet
بالنسبة لفئة CommunityViewSet
، نحتاج إلى إرجاع جميع معلوماتها بناءً على Community_id المحدد، في الوقت الحالي، لا يمكننا استخدام طريقة retrieve
لفئة RetrieveModelMixin
، نحتاج إلى تجاوز طريقة get_queryset
لإرجاع نموذج محدد.
def get_queryset ( self ):
if self . request . GET [ 'rid' ] == '0' :
# 获取所有
return Community . objects . all ()
else :
return Community . objects . filter ( region = self . request . GET [ 'rid' ])
معلمات أخرى
استخدم DefaultRouter
الخاص بـ rest_framework
لتسجيل المسار:
from rest_framework . routers import DefaultRouter
router = DefaultRouter ()
router . register ( r'v1/api/all_house' , HouseListViewSet , base_name = "house" )
......
إضافة جهاز التوجيه إلى أنماط url:
path ( '' , include ( router . urls )),
في هذا الوقت، عندما يصل الطلب إلى المسار الحالي، سيقوم المسار بإعادة توجيه الطلب إلى فئة العرض. بعد معالجة الطلب في فئة العرض، يتم إرجاع النتيجة التسلسلية، وهي بيانات الواجهة التي كشفتها الواجهة الخلفية.
[فشل نقل صورة الرابط الخارجي. قد يكون لدى الموقع المصدر آلية لمكافحة التسرب. يوصى بحفظ الصورة وتحميلها مباشرة (img-JmH9EULX-1588257851195) (imgs/interface data.png)]
وصف البيانات
رمز حالة HTTP:
# 将所有房源数,小区数,总体单价价均价,总体总价均价,存入redis
def insertIndexInfo ():
all_number = house . count ()
com_number = Api . objects . values ( 'community_name' ). distinct (). count ()
mean_price = format ( house . aggregate ( Avg ( 'price' ))[ 'price__avg' ], '.3f' )
mean_unit_price = format ( house . aggregate ( Avg ( 'unit_price' ))[ 'unit_price__avg' ], '.3f' )
data = {
"all_number" : all_number ,
"com_number" : com_number ,
"mean_price" : mean_price ,
"mean_unit_price" : mean_unit_price
}
redis_conn . mset ( data )
# 1. 定义专门数据库操作类InsertIntoMysql
class InsertIntoMysql ():
# 属性中定义sql语句获取数据
def __init__ ( self ):
self . elevator_sql = 'select unit_price, price, elevator from house_api'
# ......
# 定义数据插入方法
def insertElevator ( self ):
index , values , price_mean , unit_price_mean = multiple ( self . elevator_sql , engine , 'elevator' )
for i in range ( len ( index )):
elevator = Elevator ()
elevator . version = 'v1'
elevator . title = '电梯分布情况'
elevator . has_ele = index [ i ]
elevator . el_num = values [ i ]
elevator . mean_price = price_mean [ i ]
elevator . mean_unit_price = unit_price_mean [ i ]
elevator . save ()
# 其他剩余数据插入方法
# ......
# 2. 数据处理模块,包括单因子和对因子处理
# 利用数据处理模块pandas
def single ( sql , engine , feature ):
"""
:param sql: sql语句
:param engine: mysql引擎
:param feature: 数据特征
:return: 特征,数量
"""
df_sql = sql
df = pd . read_sql ( df_sql , engine )
df [ feature ] = df [ feature ]. astype ( str )
if feature == 'floor' :
df [ df [ feature ] == '暂无数据' ] = '18'
df [ feature ] = df [ feature ]. apply ( lambda x : re . findall ( 'd+' , x )[ 0 ])
feature_res = df [ feature ]. value_counts ()
index = feature_res . index
values = feature_res . values
return index , values
def multiple ( sql , engine , feature ):
"""
:param sql: sql语句
:param engine: mysql引擎
:param feature: 数据特征
:return: 特征,数量,总价均价,单价均价
"""
df_sql = sql
df = pd . read_sql ( df_sql , engine )
df [ feature ] = df [ feature ]. astype ( str )
if feature == 'region' :
df [ feature ] = df [ feature ]. apply ( lambda x : re . sub ( r"[|]|'" , '' , x ). split ( ',' )[ 0 ])
feature_res = df [ feature ]. value_counts ()
index = feature_res . index
values = feature_res . values
price_mean = []
unit_price_mean = []
max_unit_price = []
min_unit_price = []
for inx , val in zip ( index , values ):
price_mean . append ( format ( df [ df [ feature ] == inx ][ 'price' ]. astype ( float ). mean (), '.3f' ))
unit_price_mean . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). mean (), '.3f' ))
max_unit_price . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). max (), '.3f' ))
min_unit_price . append ( format ( df [ df [ feature ] == inx ][ 'unit_price' ]. astype ( float ). min (), '.3f' ))
return index , values , price_mean , unit_price_mean , max_unit_price , min_unit_price
تم إنشاء الواجهة الأمامية باستخدام إطار العمل الأساسي Vue وتم تطويرها بناءً على قالب vue-admin. يتم استخدام الأعمال الأمامية الرئيسية لعرض المخطط ومعلومات الخريطة، وهي تستخدم بشكل أساسي لتصور بيانات أسعار المنازل المستعملة في تشنغدو والمعلومات ذات الصلة، بما في ذلك عرض المعلومات الشاملة والخصائص المختلفة - مثل حالة ديكور المنزل وتصور معلومات المخطط، البحث عن سكن، تصور معلومات مصدر الإسكان الأساسي، عرض الخريطة وغيرها من المهام.
وصف المشروع
شجرة ملفات المشروع الأمامية:
عملية محددة
# 安装到项目
npm install -S element-ui
# 全局加载: 在main.js中
import ElementUI from ' element-ui '
import ' element-ui/lib/theme-chalk/index.css '
import locale from ' element-ui/lib/locale/lang/en ' // i18n
Vue.use(ElementUI, { locale })
# 这样我们据可以在vue文件中使用UI组件,如: 按钮组件<Button />
# 安装到项目
npm install -S vue-baidu-map
# 局部加载: 在任意vue文件中
import BaiduMap from ' vue-baidu-map/components/map/Map.vue '
# 地图标注组件
import { BmMarker } from ' vue-baidu-map '
# 使用,例如:
< baidu-map ref= " baidumap "
class= " bm-view "
style= " width:100%;height:93vh "
ak= " YOUR AK "
:center= " {lng: 104.07, lat: 30.67} "
:scroll-wheel-zoom= " true "
:zoom= " 16 " >
< bm-navigation anchor= " BMAP_ANCHOR_TOP_LEFT " ></bm-navigation >
< bm-marker v-for= " spoi in searchResults " -- >
< ! --:position= " {lng: spoi.lng, lat: spoi.lat} " -- >
< ! --:title= " spoi.community_name " > -- >
< /bm-marker >
< /baidu-map >
قم بإنشاء مجلد جديد utils في دليل src لتخزين ملفات الأدوات المخصصة لدينا، وإنشاء request.js جديد، وإعادة تغليف طريقة axios لتلبية العمليات عندما نطلب الواجهة. سيكشف هذا الملف عن كائن يستخدم للطلبات غير المتزامنة كطريقة أكثر تقدمًا للوصول إلى الواجهة. سيتم استخدام هذه الطريقة في تصميم ملف الواجهة.
# 新建一个servie对象
const service = axios . create ( {
// 项目基本配置文件中定义的VUE_APP_BASE_API,开发和发布环境各部相同,在.env.devolopment和.env.product文件中进行配置
baseURL : process . env . VUE_APP_BASE_API ,
// 直接写死的url,无论环境如何都不会改变
// baseURL: 'http://127.0.0.1:8000/v1/api',
timeout : 8000 // 最大请求时间
} )
service . interceptors . request . use ( config => {
// 请求方法为POST时,配置请求头
config . method === 'post'
? config . data = qs . stringify ( { ... config . data } )
: config . params = { ... config . params }
config . headers [ 'Content-Type' ] = 'application/x-www-form-urlencoded'
return config
} , error => {
// 错误时提示信息
Message ( {
message : error ,
type : 'error' ,
duration : 3 * 1000
} )
Promise . reject ( error )
} )
service . interceptors . response . use (
response => {
if ( response . statusText === 'OK' ) {
// 正确响应时,直接返回数据
return response . data
} else {
Notification ( {
title : '错误' ,
message : '访问api错误' , // TODO 后端拦截错误请求
type : 'error' ,
duration : 3 * 1000
} )
}
} ,
// 错误处理
error => {
let dtext = JSON . parse ( JSON . stringify ( error ) )
try {
if ( dtext . response . status === 404 ) {
Notification ( {
type : 'error' ,
title : '出问题404' ,
message : '访问api错误或服务器忙' ,
duration : 3 * 1000
} )
}
} catch ( err ) {
Notification ( {
title : '错误' ,
message : '请求超时,请稍后再试或刷新页面' ,
type : 'error' ,
duration : 3 * 1000
} )
}
return Promise . reject ( error )
}
)
export default service
استخدم vue-router لتنفيذ وظيفة التوجيه، وإنشاء router.js جديد، وتكوين عناصر التوجيه.
// 定义路由列表
export const constantRoutes = [
{
path : '/404' ,
component : ( ) => import ( '@/views/404' ) ,
hidden : true
} ,
{
path : '/' ,
component : Layout ,
redirect : '/dashboard' ,
children : [ {
path : 'dashboard' ,
name : 'Dashboard' ,
// 采用路由懒加载的方式
component : ( ) => import ( '@/views/dashboard/index' ) ,
meta : { title : '房源信息' , icon : 'house' }
} ]
} ,
// ......
]
// 构造路由对象
const createRouter = ( ) => new Router ( {
// mode: 'history', // require service support
scrollBehavior : ( ) => ( { y : 0 } ) ,
routes : constantRoutes
} )
// 在main.js 中引入并挂载
import router from './router'
new Vue ( {
el : '#app' ,
router ,
render : h => h ( App )
} )
يحدد ملف الواجهة api.js جميع طلبات الوصول إلى الواجهة المتعلقة بالمشروع، ويتم تغليفه في وظائف وعرضه للاستخدام بواسطة المكونات.
// 例如获取区划信息接口
export function getRegionInfo ( id , params ) {
return request ( {
url : '/region/' + id ,
method : 'get' ,
params
} )
}
الطلب هو كائن الوصول إلى الواجهة المغلف أعلاه، والذي يجب استيراده قبل الاستخدام، عند استخدام الواجهة في المكون، قم باستيراده عند الطلب، على سبيل المثال:
// charts.js是接口函数文件
import { getRegionInfo } from '@/api/charts'
// 使用
// 第一种方式,由于它本来就是一个promise对象,所以直接在then中获取响应
getRegionInfo ( 1 ) . then ( ( res , err ) => {
// 相关操作
} )
// 第二种方式,使用async await方式,包含await的方法中需要加上async 作为方法前缀
let res = await getRegionInfo ( 1 )