Tomando la biblioteca de ingeniería social como ejemplo para construir un proyecto simple de separación de front-end y back-end web

La llamada biblioteca de trabajo social puede ser el proyecto web más simple, sin lógica como inicio de sesión, registro, permisos, etc. Por supuesto, este es solo el más simple. Tome esto como ejemplo hoy para hablar sobre la estructura de los proyectos de separación de front-end y back-end.

El siguiente es un proyecto de biblioteca de ingeniería social de código abierto que se encuentra en Github https://github.com/Leezj9671/socialdb_vue_flask , socialdb_vue_flask, también es solo una descripción técnica, no se proporcionan datos y se ha realizado una pequeña modificación: se ha agregado la búsqueda en cascada y se han escrito varios procesos en la base de datos al mismo tiempo

Entorno: front-end Nodejs Vue

Frasco de Python de fondo

Base de datos MongoDB

Los proyectos web pequeños convencionales, como Flask + Bootstrap, son diferentes en el sentido de que se pueden desarrollar y depurar de forma independiente después de que los extremos delantero y trasero acuerden la interfaz, y luego se implementen e integren en un proyecto.

La portada del autor original

imagen

interfaz de fondo

api_principal.py

'''
api
存在问题:
- 并发请求时,且前一请求正在查询会导致卡死
'''

import time
from pymongo import MongoClient
from flask import Flask, request, jsonify, redirect, url_for
from flask_restful import Api, Resource, reqparse
from conf.config import MongoDBConfig

app = Flask(__name__)
client = MongoClient(MongoDBConfig.g_server_ip, MongoDBConfig.g_server_port)
db = client[MongoDBConfig.g_db_name]


def response_cors(data=None, datacnts=None, status=None):
    '''为返回的json格式进行跨域请求'''
    if data:
        resp = jsonify({
    
    "status": status, "data": data, "datacounts": datacnts})
    else:
        resp = jsonify({
    
    "status": status})
    resp.headers['Access-Control-Allow-Origin'] = '*'
    return resp


class Person(Resource):
    '''人员类'''

    def get(self, user=None, email=None, password=None, passwordHash=None, source=None, xtime=None):
        # 该处可能存在安全问题,做出限制会更好
        # print(user)
        parser = reqparse.RequestParser()
        parser.add_argument('limit', type=int, help='Show [limitn] datas in one page')
        parser.add_argument('skip', type=int, help='Skip [skipn] datas')
        args = parser.parse_args()
        limitn = 10 if args['limit'] is None else args['limit']
        skipn = 0 if args['skip'] is None else args['skip']

        # data用于存储获取到的信息
        data = []
        datacnts = 0

        # 待改进
        if user:
            persons_info = db.person.find({
    
    "user": {
    
    "$regex": user, "$options": "$i"}}, {
    
    "_id": 0}).limit(limitn).skip(
                skipn)
            datacnts = db.person.find({
    
    "user": {
    
    "$regex": user, "$options": "$i"}}, {
    
    "_id": 0}).count()

        elif email:
            persons_info = db.person.find({
    
    "email": {
    
    "$regex": email, "$options": "$i"}}, {
    
    "_id": 0}).limit(
                limitn).skip(skipn)
            datacnts = db.person.find({
    
    "email": {
    
    "$regex": email, "$options": "$i"}}, {
    
    "_id": 0}).count()

        elif password:
            persons_info = db.person.find({
    
    "password": {
    
    "$regex": password, "$options": "$i"}}, {
    
    "_id": 0}).limit(
                limitn).skip(skipn)
            datacnts = db.person.find({
    
    "password": {
    
    "$regex": password, "$options": "$i"}}, {
    
    "_id": 0}).count()

        elif passwordHash:
            persons_info = db.person.find({
    
    "passwordHash": {
    
    "$regex": passwordHash, "$options": "$i"}},
                                          {
    
    "_id": 0}).limit(limitn).skip(skipn)
            datacnts = db.person.find({
    
    "passwordHash": {
    
    "$regex": passwordHash, "$options": "$i"}}, {
    
    "_id": 0}).count()

        # elif source:
        #     persons_info = db.person.find({"source": {"$regex": source, "$options":"$i"}}, {"_id": 0}).limit(limitn).skip(skipn)

        # elif xtime:
        #     persons_info = db.person.find({"xtime": {"$regex": xtime, "$options":"$i"}}, {"_id": 0}).limit(limitn).skip(skipn)

        else:
            # 限制只能查询10个
            persons_info = db.person.find({
    
    }, {
    
    "_id": 0, "update_time": 0}).limit(10)

        for person in persons_info:
            data.append(person)

        # 判断有无数据返回
        if data:
            return response_cors(data, datacnts, "ok")
        else:
            return response_cors(data, datacnts, "not found")

    def post(self):
        '''
        以json格式进行提交文档
        '''
        data = request.get_json()
        if not data:
            return {
    
    "response": "ERROR DATA"}
        else:
            user = data.get('user')
            email = data.get('email')

            if user and email:
                if db.person.find_one({
    
    "user": user, "email": email}, {
    
    "_id": 0}):
                    return {
    
    "response": "{
    
    {} {} already exists.".format(user, email)}
                else:
                    data.create_time = time.strftime('%Y%m%d', time.localtime(time.time()))
                    db.person.insert(data)
            else:
                return redirect(url_for("person"))

    # 暂时关闭高危操作
    # def put(self, user, email):
    #     '''
    #     根据user和email进行定位更新数据
    #     '''
    #     data = request.get_json()
    #     db.person.update({'user': user, 'email': email},{'$set': data},)
    #     return redirect(url_for("person"))

    # def delete(self, email):
    #     '''
    #     email作为唯一值, 对其进行删除
    #     '''
    #     db.person.remove({'email': email})
    #     return redirect(url_for("person"))


class Info(Resource):
    '''个人信息类'''

    def get(self, id=None, name=None, sex=None, qq=None, phonenumber=None):
        # 该处可能存在安全问题,做出限制会更好
        parser = reqparse.RequestParser()
        parser.add_argument('limit', type=int, help='Show [limitn] datas in one page')
        parser.add_argument('skip', type=int, help='Skip [skipn] datas')
        args = parser.parse_args()
        limitn = 10 if args['limit'] is None else args['limit']
        skipn = 0 if args['skip'] is None else args['skip']

        # data用于存储获取到的信息
        data = []
        datacnts = 0

        # 待改进
        if id:
            my_info = db.info.find({
    
    "id": id}, {
    
    "_id": 0}).limit(limitn).skip(
                skipn)
            datacnts = db.info.find({
    
    "id": id}, {
    
    "_id": 0}).count()

        elif name:
            my_info = db.info.find({
    
    "name": {
    
    "$regex": name, "$options": "$i"}}, {
    
    "_id": 0}).limit(
                limitn).skip(skipn)
            datacnts = db.info.find({
    
    "name": {
    
    "$regex": name, "$options": "$i"}}, {
    
    "_id": 0}).count()

        elif sex:
            my_info = db.info.find({
    
    "sex": {
    
    "$regex": sex, "$options": "$i"}}, {
    
    "_id": 0}).limit(
                limitn).skip(skipn)
            datacnts = db.info.find({
    
    "sex": {
    
    "$regex": sex, "$options": "$i"}}, {
    
    "_id": 0}).count()

        elif qq:
            my_info = db.info.find({
    
    "qq": qq},
                                   {
    
    "_id": 0}).limit(limitn).skip(skipn)
            datacnts = db.info.find({
    
    "qq": qq}, {
    
    "_id": 0}).count()

        elif phonenumber:
            my_info = db.info.find({
    
    "phonenumber": phonenumber},
                                   {
    
    "_id": 0}).limit(limitn).skip(skipn)
            datacnts = db.info.find({
    
    "phonenumber": phonenumber}, {
    
    "_id": 0}).count()

        else:
            # 限制只能查询10个
            my_info = db.info.find({
    
    }, {
    
    "_id": 0, "update_time": 0}).limit(10)

        for person in my_info:
            data.append(person)

        # 判断有无数据返回
        if data:
            return response_cors(data, datacnts, "ok")
        else:
            return response_cors(data, datacnts, "not found")

    def post(self):
        '''
        以json格式进行提交文档
        '''
        data = request.get_json()
        if not data:
            return {
    
    "response": "ERROR DATA"}
        else:
            user = data.get('user')
            email = data.get('email')

            if user and email:
                if db.person.find_one({
    
    "user": user, "email": email}, {
    
    "_id": 0}):
                    return {
    
    "response": "{
    
    {} {} already exists.".format(user, email)}
                else:
                    data.create_time = time.strftime('%Y%m%d', time.localtime(time.time()))
                    db.person.insert(data)
            else:
                return redirect(url_for("person"))

    # 暂时关闭高危操作
    # def put(self, user, email):
    #     '''
    #     根据user和email进行定位更新数据
    #     '''
    #     data = request.get_json()
    #     db.person.update({'user': user, 'email': email},{'$set': data},)
    #     return redirect(url_for("person"))

    # def delete(self, email):
    #     '''
    #     email作为唯一值, 对其进行删除
    #     '''
    #     db.person.remove({'email': email})
    #     return redirect(url_for("person"))


class Analysis(Resource):
    '''
    分析功能
    '''

    def get(self, type_analyze):
        '''
        type为分析类型,包括邮箱后缀、泄漏来源、泄漏时间
        type: [suffix_email, source, xtime, create_time]
        '''
        if type_analyze in ["source", "xtime", "suffix_email", "create_time"]:
            pipeline = [{
    
    "$group": {
    
    "_id": '$' + type_analyze, "sum": {
    
    "$sum": 1}}}]
            return response_cors(list(db.person.aggregate(pipeline)), None, "ok")

        else:
            return response_cors("use /api/analysis/[source, xtime, suffix_email] to get analysis data.", None, "error")


class Getselector(Resource):
    '''
    获取级联数据功能
    '''

    def get(self):
        '''
        type为分析类型,包括邮箱后缀、泄漏来源、泄漏时间
        type: [suffix_email, source, xtime, create_time]
        '''
        subject = [
            {
    
    
                "id": 1,
                "name": "账密",
                "select": "find",
                "obj": [
                    {
    
    
                        "id": 3,
                        "name": "用户名",
                        "select": "user"
                    },
                    {
    
    
                        "id": 4,
                        "name": "密码",
                        "select": "password"
                    },
                    {
    
    
                        "id": 5,
                        "name": "邮箱",
                        "select": "email"
                    },
                    {
    
    
                        "id": 6,
                        "name": "哈希密码",
                        "select": "passwordHash"
                    }
                ]
            },
            {
    
    
                "id": 2,
                "name": "身份信息",
                "select": "info",
                "obj": [
                    {
    
    
                        "id": 7,
                        "name": "手机号",
                        "select": "phonenumber"
                    },
                    {
    
    
                        "id": 8,
                        "name": "QQ",
                        "select": "qq"
                    },
                    {
    
    
                        "id": 9,
                        "name": "身份证",
                        "select": "id"
                    },
                    {
    
    
                        "id": 10,
                        "name": "姓名",
                        "select": "name"
                    }
                ]
            }
        ]
        return response_cors(subject, None, "ok")


# 添加api资源
api = Api(app)
api.add_resource(Person, "/api/find")
api.add_resource(Person, "/api/find/user/<string:user>", endpoint="user")
api.add_resource(Person, "/api/find/email/<string:email>", endpoint="email")
api.add_resource(Person, "/api/find/password/<string:password>", endpoint="password")
api.add_resource(Person, "/api/find/passwordHash/<string:passwordHash>", endpoint="passwordHash")
api.add_resource(Person, "/api/find/source/<string:source>", endpoint="source")
api.add_resource(Person, "/api/find/time/<string:xtime>", endpoint="xtime")
api.add_resource(Info, "/api/info")
api.add_resource(Info, "/api/info/id/<int:id>", endpoint="id")
api.add_resource(Info, "/api/info/name/<string:name>", endpoint="name")
api.add_resource(Info, "/api/info/sex/<string:sex>", endpoint="sex")
api.add_resource(Info, "/api/info/qq/<int:qq>", endpoint="qq")
api.add_resource(Info, "/api/info/phonenumber/<int:phonenumber>", endpoint="phonenumber")
api.add_resource(Analysis, "/api/analysis/<string:type_analyze>", endpoint="type_analyze")
api.add_resource(Getselector, "/api/get_selector")

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

El servidor Flask se inicia en el back-end y, de forma predeterminada, escucha el puerto 5000. Se escribe principalmente con Flask Restful API, que es responsable de procesar la lógica de la interfaz + las operaciones de la base de datos y otras operaciones, como escribir en la base de datos.

Escriba la lógica empresarial de la solicitud de obtención en el método Get de la clase Person

El front-end también es bastante simplificado, incluso tosco, con solo dos componentes, search.vue (búsqueda) y Analysis.vue (análisis)

Entre ellos, search.vue agrega un front-end en cascada y solicita un método de datos en cascada (método creado / get_selector)

<template>
  <div class="Search">

    <select name="province" id="province" class="Select" v-on:change="indexSelect01" v-model="indexId">
      <option :value="item.select" v-for="(item,index) in select01" class="options">{
    
    {
    
    item.name}}</option>  <!---->
    </select>
    <!--二级菜单-->
    <select name="city" id="city" class="Select" v-model="indexId2" >
      <option :value="k.select" v-for="k in select02" class="options">{
    
    {
    
    k.name}}</option>
    </select>

    <input
        placeholder="请输入并按下回车进行搜索(忽略大小写)"
        type="text"
        class="searchInput"
        v-model="searchStr"
        v-on:keyup.enter="search"
    />
    <h2 v-show="errorinfo">暂无数据,请重新输入</h2>
    <div v-if="indexId=== 'find'" class="container" v-show="retItems.length">
        <table>
            <thead>
            <tr>
                <th width="10%">用户名</th>
                <th width="15%">邮箱</th>
                <th width="10%">来源</th>
                <th width="10%">泄漏时间</th>
                <th width="20%">密码</th>
                <th width="35%">hash密码</th>
            </tr>
            </thead>
            <tbody>
                <tr v-for="item in retItems">
                    <td width="10%">{
    
    {
    
     item.user }}</td>
                    <td width="15%">{
    
    {
    
     item.email }}</td>
                    <td width="10%">{
    
    {
    
     item.source }}</td>
                    <td width="10%">{
    
    {
    
     item.xtime }}</td>
                    <td width="20%">{
    
    {
    
     item.password }}</td>
                    <td width="35%">{
    
    {
    
     item.passwordHash    }}</td>
                </tr>
            </tbody>
        </table>

        <div v-show="datacnts>10" class="pageselect">
            <select class="showpages" @change="changepages" v-model="selectedP">
                <option v-for="opt in pageoptions" v-bind:value="opt.value" class="options">
                    {
    
    {
    
     opt.text }}
                </option>
            </select>
            <p>每页显示数据条数:
                <input
                    type="int"
                    class="limitInput"
                    v-model="limit"
                    v-on:keyup.enter="changepages"
                />
            </p>
        </div>
        <p v-model="datacnts">查询结果有 {
    
    {
    
     datacnts }} 条数据</p>
    </div>
    <div v-else-if="indexId=== 'info'" class="container" v-show="retItems.length">
      <table>
        <thead>
        <tr>
          <th width="10%">身份证</th>
          <th width="15%">姓名</th>
          <th width="10%">性别</th>
          <th width="10%">地址</th>
          <th width="20%">QQ</th>
          <th width="35%">手机号</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="item in retItems">
          <td width="10%">{
    
    {
    
     item.id }}</td>
          <td width="15%">{
    
    {
    
     item.name }}</td>
          <td width="10%">{
    
    {
    
     item.sex }}</td>
          <td width="10%">{
    
    {
    
     item.address }}</td>
          <td width="20%">{
    
    {
    
     item.qq }}</td>
          <td width="35%">{
    
    {
    
     item.phonenumber }}</td>
        </tr>
        </tbody>
      </table>

      <div v-show="datacnts>10" class="pageselect">
        <select class="showpages" @change="changepages" v-model="selectedP">
          <option v-for="opt in pageoptions" v-bind:value="opt.value" class="options">
            {
    
    {
    
     opt.text }}
          </option>
        </select>
        <p>每页显示数据条数:
          <input
            type="int"
            class="limitInput"
            v-model="limit"
            v-on:keyup.enter="changepages"
          />
        </p>
      </div>
      <p v-model="datacnts">查询结果有 {
    
    {
    
     datacnts }} 条数据</p>
    </div>
    <div v-else>
      查询错误
    </div>
  </div>
</template>

<script>
// 改为CDN引入
// import axios from 'axios'
export default {
    
    
  name: 'Search',
  data () {
    
    
    return {
    
    
      limit : 10,
      selectedP: 1,
      searchStr: '',
      pageStr: '',
      errorinfo: '',
      datacnts: 0,
      pageoptions: [],
      options: [
        {
    
     text: '用户名', value: 'user' },
        {
    
     text: '密码', value: 'password' },
        {
    
     text: '邮箱', value: 'email' },
        {
    
     text: '哈希密码', value: 'passwordHash' }
      ],
      retItems: [],
      analysisInfos: [],

      select01: [],//获取的一级数组数据
      select02: [],//获取的二级数组数据
      indexId:'账密',//定义分类一的默认值
      indexId2:'用户名',
      indexNum:0,//定义一级菜单的下标


    }
  },
  created() {
    
    
    axios.get('/get_selector')
      .then(response => {
    
    
        if(response.data.status === 'ok'){
    
    
          let mes = response.data;
          this.select01 = mes.data;
          console.log("省级")
          console.log(this.select01)
          this.indexSelect01();
        }
        else{
    
    
          this.errorinfo = '初始化级联数据错误';
        }
      })
      .catch(error => {
    
    
        console.log(error);
      });
  },

  methods:{
    
    

        search: function () {
    
    
            this.pageoptions = [];
            this.limit = 10;
            this.selectedP = 1;
            this.errorinfo = '';
            console.log(this.indexId)
            console.log(this.indexId2)

            // axios.get('/find/user/' + this.searchStr)
          // axios.get('/find/'+ this.indexId2 + '/' + this.searchStr)
          axios.get('/'+ this.indexId +  '/'+ this.indexId2 + '/' + this.searchStr)
                .then(response => {
    
    
                    if(response.data.status === 'ok'){
    
    
                        this.retItems = response.data.data.concat();
                        this.pageStr = this.searchStr;
                        this.searchStr = '';
                        this.datacnts = response.data.datacounts;
                        var n = 0;
                        while ( n < Math.ceil(this.datacnts/this.limit)) {
    
    
                            n = n + 1;
                            this.pageoptions.push({
    
    
                                text: '第 ' +  n + ' 页',
                                value: n
                            });
                        }
                    }
                    else{
    
    
                        this.retItems = [];
                        this.searchStr = [];
                        this.datacnts = 0;
                        this.errorinfo = '输入错误';
                    }
                })
                .catch(error => {
    
    
                    console.log(error);
                });
        },
        changepages: function() {
    
    
            axios.get('/'  + this.indexId + '/'+ this.indexId2 + '/' + this.pageStr + '?limit=' + this.limit + '&skip=' + this.limit * (this.selectedP-1))
                .then(response => {
    
    
                    if(response.data.status === 'ok'){
    
    
                        this.pageoptions = [];
                        var n = 0;
                        while ( n <  Math.ceil(this.datacnts/this.limit)) {
    
    
                            n = n + 1;
                            this.pageoptions.push({
    
    
                                text: '第 ' +  n + ' 页',
                                value: n
                            });
                        }
                        this.retItems = response.data.data.concat();
                        this.searchStr = '';
                        this.datacnts = response.data.datacounts;
                    }
                    else{
    
    
                        this.retItems = [];
                        this.searchStr = [];
                        this.datacnts = 0;
                        this.errorinfo = '输入错误';
                    }
                })
                .catch(error => {
    
    
                    console.log(error);
                });
        }
        ,
        indexSelect01(){
    
    
          let i = 0;
          for (i = 0;i<this.select01.length;i++) {
    
    
            if (this.select01[i].select == this.indexId){
    
    
              // if (this.select01[i].id == this.indexId){
    
    
              this.indexNum = i;
              break
            }
          }

          this.select02 = this.select01[this.indexNum].obj;
          console.log("市级数据")
          console.log(this.select02)
        }

    }
}
</script>

<style>

h1, h2 {
    
    
  font-weight: normal;
}

h1 {
    
    
  color: #fff;
}

ul {
    
    
  list-style-type: none;
  padding: 0;
}

li {
    
    
  display: inline-block;
  margin: 0 10px;
}

table{
    
    
    margin-top: 2em;
    border:1px solid ;
    padding: 20px;
    border-collapse: collapse;
    font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
    font-size: 0.8em;
}

.container {
    
    
  text-align: center;
  overflow: hidden;
  width: 60em;
  margin: 0 auto;
}

.container table {
    
    
  width: 100%;
}

.container td {
    
    
    font-size: 10px;
}
.container td, .container th {
    
    
  font-size: 1.2em;
  overflow: auto;
  padding: 10px;
}

.container th {
    
    
  border-bottom: 1px solid #ddd;
  position: relative;
  width: 30px;
}
.searchInput {
    
    
    outline: none;
    height: 30px;
    width: 680px;
    border : 1px solid  #FFFFFF;
    padding : 15px 30px 15px 30px;
    font-size: 1em;
    font-family: BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
.searchInput:focus {
    
    
    box-shadow: 2px 2px 2px #336633;
}
.limitInput {
    
    
    outline: none;
    height: 15px;
    width: 20px;
    border : 1px solid  #FFFFFF;
    padding : 5px 5px 5px 5px;
    font-size: 1em;
    font-family: BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
.limitInput:focus {
    
    
    box-shadow: 2px 2px 2px #336633;
}
.Select {
    
    
    border : 1px solid  #FFFFFF;
    font-size: 1em;
    font-family: BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
    outline: none;
    border: none;
    padding: 0 0 0 10px;
}
.Select .options {
    
    
    outline: none;
    border: none;
}
.Select {
    
    
    height: 62px;
    width: 100px;
}
.showpages {
    
    
    border : 1px solid  #FFFFFF;
    font-size: 1em;
    font-family: BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
    outline: none;
    border: none;
    padding: 0 0 0 10px;
    position: relative;
    margin: 0 auto;
    margin-top: 1em;
}
.pageselect input{
    
    
}
</style>

El método de instalación y configuración se refiere a README.md en el proyecto. El autor original usó los datos de Netease 52G para probar, y usé Q para vincular los datos a la prueba. La información de identidad correspondiente: número de teléfono móvil, el nombre de recopilación de información de identidad es info, el nombre de campo específico está en la página de inicio, id.

          <td width="10%">{
    
    {
    
     item.id }}</td>
          <td width="15%">{
    
    {
    
     item.name }}</td>
          <td width="10%">{
    
    {
    
     item.sex }}</td>
          <td width="10%">{
    
    {
    
     item.address }}</td>
          <td width="20%">{
    
    {
    
     item.qq }}</td>
          <td width="35%">{
    
    {
    
     item.phonenumber }}</td>

imagen-20210511202457846

Enlace Github: https://github.com/wdsjxh/socialdb_vue_flask_new

Supongo que te gusta

Origin blog.csdn.net/wdsj_xh/article/details/116719637
Recomendado
Clasificación