Taking the social engineering library as an example to build a simple web front-end and back-end separation project

The so-called social work library may be the simplest web project, without logic such as login, registration, permissions, etc. Of course, this is just the simplest one. Take this as an example today to talk about the structure of the front-end and back-end separation projects.

The following is an open source social engineering library project found on Github https://github.com/Leezj9671/socialdb_vue_flask , socialdb_vue_flask, also just a technical description, no data is provided , and a small modification has been made: cascade search has been added , and multiple processes have been written to the database concurrently

Environment: front-end Nodejs Vue

​ Backend Python Flask

​ Database MongoDB

Conventional small web projects, such as Flask + Bootstrap, are different in that they can be independently developed and debugged after the front and back ends agree on the interface, and then deployed and integrated into a project

The original author's front-end page

image

backend interface

api_main.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)

The Flask server is started at the back end, and it listens to port 5000 by default. It is mainly written using the Flask Restful API, which is responsible for processing interface logic + database operations, and other operations such as writing to the database.

Write the get request business logic in the Get method of the Person class

The front end is also quite streamlined, even crude, with only two components, search.vue (search) and Analysis.vue (analysis)

Among them, search.vue adds cascading front end and request cascading data method (created / get_selector method)

<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>

The installation and configuration method refers to the README.md in the project. The original author used Netease 52G data to test, and I used Q to bind the data to test. The corresponding identity information-mobile phone number, the name of the identity information collection is info, the specific field name is in the front page, id ID card name gender address qq mobile phone number

          <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>

image-20210511202457846

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

Guess you like

Origin blog.csdn.net/wdsj_xh/article/details/116719637