以社工库为例的简单web前后端分离项目搭建

所谓的社工库可能是最简单的web项目了,没有登录、注册、权限等逻辑,当然这里只是最简单的那种。今天拿这个为例聊下前后端分离项目的结构。

下面是Github上搜到的社工库项目https://github.com/Leezj9671/socialdb_vue_flask,socialdb_vue_flask,同样只是技术说明,不提供数据,并做了下小修改:添加了级联搜索多进程并发写入数据库

环境:前端 Nodejs Vue

​ 后端 Python Flask

​ 数据库 MongoDB

常规小的web项目,比如Flask + Bootstrap,不同的是可以前后端约定后接口后分别独立开发调试,然后部署整合成一个项目

原作者的前端页面

image

后端接口

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)

后端起了Flask服务器,默认监听5000端口,主要使用Flask Restful API编写,负责处理接口逻辑+数据库操作,其他还有写入数据库操作。

在 Person类Get方法中编写get请求业务逻辑

前端也相当精简,甚至简陋,只有两个component,search.vue(搜索)和 Analysis.vue(分析)

其中search.vue加了级联前端和请求级联数据方法(created /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>

安装配置方法参考项目中README.md就好,原作者使用网易52G数据测试,我这边拿Q绑数据测试,对应的身份信息-手机号,身份信息collection名是info,具体字段名在前端页面里面,id 身份证 姓名 性别 地址 qq 手机号

          <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链接:https://github.com/wdsjxh/socialdb_vue_flask_new

注意:网络资源有一定失效性,请以实际为准!
本站telegram群组 https://t.me/digter8 @digter8
仅供学习交流,严禁用于商业用途,请于24小时内删除。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇