Plateforme de visualisation de données côté Web basée sur les données du robot d'exploration conjoint, accès en ligne
La source de données provient du projet Joint-spider
Back-end : Django-restframework
Front-end : Vue
# 环境配置需要 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/
Détails de la liste des logements communautaires
Rechercher la page de propriétés
Page de détails spécifiques de la propriété
Autour de la maison
# 项目依赖
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
Processus de base
Cycle de vie de Django :
Le frontal envoie une requête -> wsgi de Django -> middleware -> système de routage -> vue -> fonctionnement de la base de données ORM -> modèle -> renvoyer les données à l'utilisateur
cycle de vie du framework de repos :
Le frontal envoie une requête -> wsgi de Django -> Middleware -> Routing system_Execute as_view () de CBV, qui doit exécuter la méthode dispath interne -> Avant d'exécuter dispath, il y a une analyse de version et un moteur de rendu -> Dans dispath, encapsulez la requête-->Version-->Authentification-->Permissions-->Limit current-->View-->Si la vue utilise la mise en cache (request.data ou request.query_params ) utilise l'analyseur -> la vue traite les données et utilise la sérialisation (sérialisation ou vérification des données) -> la vue renvoie les données à l'aide de la pagination.
Arborescence des fichiers du projet :
# 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 " ,
}
}
}
Remarque : Ce projet est un développement d'interface et n'implique pas de modèles de modèles ni de ressources statiques, il n'est donc pas nécessaire de configurer ces deux éléments.
Entrez HouseAnalysisHouseAnalysisurls.py et configurez les informations de routage :
Ajoutez path('xadmin/', xadmin.site.urls)
, re_path(r'docs/', include_docs_urls(title="HA"))
à la liste urlpatterns
, xadmin est le routage de la page de gestion,
Docs est l'interface de routage des documents, accessible via http://localhost:8000/docs/, comme indiqué ci-dessous :
La conception du modèle Django est l'une des cinq conceptions de base de Django (conception du modèle, configuration de l'URL, écriture de la vue, conception du modèle, utilisation), et c'est également un lien important dans le modèle MVC (MVT).
Le modèle est un modèle de données, pas une base de données. Il décrit la structure des données et la relation logique entre elles. Dans Django, la conception de modèles est essentiellement une classe. Chaque modèle est mappé à une table de base de données.
Entrez HouseAnalysishousemodels.py et ajoutez les modèles Api,Elevator,Floor,Layout,Region,Decortion,Purposes,Orientation,Constructure,Community,CommunityRange
pour fournir des données pour la sérialisation.
Une fois le modèle créé, effectuez la migration des données :
# 进入项目根目录
python manage.py makemigrations
# 同步至数据库
python manage.py migrate
Créez un nouveau fichier serializer.py dans le dossier interne pour sérialiser les données.
Définissez un sérialiseur (essentiellement une classe), qui inclut généralement des champs de la classe modèle et possède ses propres règles de type de champ. Après avoir implémenté le sérialiseur, vous pouvez créer des objets sérialisés et des ensembles de requêtes pour les opérations de sérialisation, et obtenir des données via object.data sérialisé (vous n'avez pas besoin de construire vous-même un dictionnaire, puis de renvoyer des données Json)
Lorsqu'il est utilisé pour la sérialisation, transmettez l'objet de classe de modèle dans le paramètre d'instance
Lorsqu'il est utilisé pour la désérialisation, transmettez les données à désérialiser dans le paramètre data .
En plus des paramètres d'instance et de données, lors de la construction de l'objet Serializer, vous pouvez également ajouter des données supplémentaires via le paramètre de contexte .
Ici, nous utilisons le sérialiseur de modèle (ModelSerializer), qui est le même que le sérialiseur classique, mais fournit :
Par exemple : le contenu de la maison peut être sérialisé à l'aide d'un simple code
class HouseSerializer ( serializers . ModelSerializer ):
class Meta :
# 需要序列化的model
model = Api
# 需要序列化的字段
fields = "__all__"
De la même manière, sérialisez les modèles restants, notamment ElevatorSerializer,FloorSerializer,LayoutSerializer,RegionSerializer,RegionS,CommunitySerializer,CommunityRangeSerializer,DecortionSerializer,PurposesSerializer,ConstructureSerializer,OrientationSerializer
, parmi lesquels RegionS
sert de sérialiseur supérieur de CommunitySerializer
et CommunityRangeSerializer
.
La fonction de la vue : Pour parler franchement, il s'agit de recevoir des requêtes frontales et d'effectuer un traitement de données.
(Le traitement comprend ici : si le front-end est une requête GET, construisez un ensemble de requêtes et renvoyez les résultats. Ce processus est la sérialisation ; si le front-end est une requête POST, si vous souhaitez apporter des modifications à la base de données, vous devez obtenir les données envoyées par le front-end. Vérifiez et écrivez les données dans la base de données. Ce processus est appelé désérialisation .
La vue la plus originale peut implémenter un tel traitement logique, mais pour différentes requêtes, plusieurs méthodes doivent être définies dans la vue de classe pour implémenter leur propre traitement. Cela peut résoudre le problème, mais il y a un défaut, c'est-à-dire la méthode générale. chaque fonction La logique est similaire : lire la demande, obtenir des données de la base de données, écrire des éléments dans la base de données et renvoyer les résultats au front-end. Cela produira beaucoup de code en double.
Dans le cadre du développement de l'API REST, bien que les opérations de données spécifiques à chaque vue soient différentes, le processus de mise en œuvre d'ajout, de suppression, de modification et de vérification est fondamentalement routinier, cette partie du code peut donc également être réutilisée et simplifiée pour écrire :
Ajouté : Vérifier les données de la demande -> Exécuter le processus de désérialisation -> Enregistrer la base de données -> Sérialiser et renvoyer l'objet enregistré
Supprimer : Déterminer si les données à supprimer existent -> effectuer la suppression de la base de données
Modification : Déterminer si les données à modifier existent -> vérifier les données demandées -> effectuer le processus de désérialisation -> sauvegarder la base de données -> sérialiser et renvoyer l'objet enregistré
Requête : Interroger la base de données -> Sérialiser et renvoyer les données
Les vues sont écrites dans le fichier vues.py, et ici nous utilisons des diagrammes de reconnaissance de classes pour le développement.
http://127.0.0.1/api/v1/allhouse
, nous devons renvoyer et exposer les résultats sérialisés de toutes les informations de la maison.http://127.0.0.1/api/v1/allhouse
/1, nous devons modifier l'identifiant de la maison (l'identifiant à l'adresse GET/:ID). cette heure peut être configurée comme Spécifier le paramètre d'exigence) et le résultat de sérialisation de 1 est renvoyé et exposé.{id:xxx,values:xxx...}
, nous devons vérifier les informations actuelles et y ajouter les données actuelles. la base de données si elle est légale.exemple:
# 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
D'autres vues sont similaires, notamment : ElevaorViewSet,FloorViewSet,LayoutViewSet,RegionViewSet,CommunityViewSet,CommunityPagination,AllCommunityViewSet,CommunityRangeViewSet,DecortionViewSet,PurposesViewSet,ConstructureViewSet,OrientationViewSet
Pour la classe CommunityViewSet
, nous devons renvoyer toutes ses informations en fonction du community_id spécifique. Pour le moment, nous ne pouvons pas utiliser la méthode retrieve
de la classe RetrieveModelMixin
. Nous devons remplacer la méthode get_queryset
pour renvoyer un modèle spécifique.
def get_queryset ( self ):
if self . request . GET [ 'rid' ] == '0' :
# 获取所有
return Community . objects . all ()
else :
return Community . objects . filter ( region = self . request . GET [ 'rid' ])
Autres paramètres
Utilisez DefaultRouter
de rest_framework
pour l'enregistrement de l'itinéraire :
from rest_framework . routers import DefaultRouter
router = DefaultRouter ()
router . register ( r'v1/api/all_house' , HouseListViewSet , base_name = "house" )
......
Ajouter un routeur aux modèles d'URL :
path ( '' , include ( router . urls )),
À ce stade, lorsqu'une requête accède à la route existante, la route transmettra la requête à la classe de vue. Une fois la requête traitée dans la classe de vue, le résultat sérialisé est renvoyé, qui correspond aux données d'interface exposées par le backend.
[Le transfert de l'image du lien externe a échoué. Le site source peut avoir un mécanisme anti-sangsue. Il est recommandé de sauvegarder l'image et de la télécharger directement (img-JmH9EULX-1588257851195) (imgs/interface data.png)]
Description des données
Code d'état 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
Le front-end est construit à l'aide du framework de base Vue et développé sur la base de vue-admin-template. L'activité principale du front-end est utilisée pour l'affichage des graphiques et des informations cartographiques. Elle est principalement utilisée pour visualiser les données sur les prix des logements d'occasion à Chengdu et les informations associées, y compris l'affichage des informations globales, diverses caractéristiques telles que l'état de la décoration de la maison, la visualisation des informations graphiques, recherche de logement, logement de base Visualisation des informations sur la source, affichage de la carte et autres fonctions.
Descriptif du projet
Arborescence des fichiers de projet front-end :
Processus spécifique
# 安装到项目
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 >
Créez un nouveau dossier utils dans le répertoire src pour stocker nos fichiers d'outils personnalisés, créez un nouveau request.js et réencapsulez la méthode axios pour répondre aux opérations lorsque nous demandons l'interface. Ce fichier exposera un objet utilisé pour les requêtes asynchrones comme méthode d'accès à l'interface plus avancée. Cette méthode sera utilisée dans la conception du fichier d'interface.
# 新建一个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
Utilisez vue-router pour implémenter la fonction de routage, créez un nouveau router.js et configurez les éléments de routage.
// 定义路由列表
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 )
} )
Le fichier d'interface api.js définit toutes les demandes d'accès à l'interface liées au projet, et est encapsulé dans des fonctions et exposé pour être utilisé par les composants.
// 例如获取区划信息接口
export function getRegionInfo ( id , params ) {
return request ( {
url : '/region/' + id ,
method : 'get' ,
params
} )
}
La requête est l'objet d'accès à l'interface encapsulé ci-dessus, qui doit être importé avant utilisation. Lorsque vous utilisez l'interface dans le composant, importez-la à la demande, par exemple :
// charts.js是接口函数文件
import { getRegionInfo } from '@/api/charts'
// 使用
// 第一种方式,由于它本来就是一个promise对象,所以直接在then中获取响应
getRegionInfo ( 1 ) . then ( ( res , err ) => {
// 相关操作
} )
// 第二种方式,使用async await方式,包含await的方法中需要加上async 作为方法前缀
let res = await getRegionInfo ( 1 )