美文网首页
以社工库为例的简单web前后端分离项目搭建

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

作者: lalada | 来源:发表于2021-05-13 18:34 被阅读0次

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

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

    环境:前端 Nodejs Vue

    后端 Python Flask

    数据库 MongoDB

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

    原作者的前端页面

    image image

    后端接口

    api_main.py

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="python" cid="n15" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">'''
    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": '/pre> + 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)
    </pre>

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

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

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

    其中search.vue加了级联前端和请求级联数据方法(created /get_selector方法)

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n20" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><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>
    </pre>

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

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n22" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <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></pre>

    image-20210511202457846

    Github链接:https://github.com/wdsjxh/socialdb_vue_flask_new

    相关文章

      网友评论

          本文标题:以社工库为例的简单web前后端分离项目搭建

          本文链接:https://www.haomeiwen.com/subject/gpyxjltx.html