1. Flask搭建个人代码仓库

语言:python3

平台:Windows10

涉及技术名词:Flask,MongoDB,RESTful,Bootstrap3,简单GUI,GoogleHack,Ajax懒加载等等

项目代码:https://download.csdn.net/download/qq_40929683/22743862

一、前言

1. 背景

在日常编码中,由于某些代码片段(例如:驱动程序,算法程序等)可以多次重用、借鉴,所以把它们归类存储起来。但是需要的时候就要去文件夹中查找,而且有新的代码需要存储时,需要不断管理分类(给文件夹分类)

刚好在学Python,就想做一个代码仓库(这里的仓库就是字面上意思的仓库),方便管理自己的代码,需要的时候可以直接拿到想要的代码,有新的代码可以方便入库

于是就有了CodeSpace(软件的名称)

2. 注意

由于这个软件是一个课程作业练习作品,当基本可以使用后就没有继续完善。有很多功能不完善、也有一些bug,但是所有的基本功能都有了

这个项目只介绍了Flask的路由部分,借助路由,搭建的项目。

通过这个软件(包含启动器,前端,后端开发)的开发,包含:

  1. Flask的路由基本使用
  2. 什么是RESTful
  3. MongoDB的简单使用
  4. 前端页面的快速搭建(使用Bootstrap3某些模板
  5. 简单Python的GUI设计
  6. 了解GoogleHack
  7. 前后端的请求处理的简单流程

软件的核心是围绕学习Flask框架的,其他技术只是简单涉及和使用

由于这篇文章核心是介绍Flask的使用,所以前端设计、数据设计等只会粗略介绍。 

3. 技术介绍

数据来源:百度百科,教程数据来源:互联网

3.1 Flask

Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。

3.2 MongoDB

MongoDB是一个基于分布式文件存储 [1] 的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似jsonbson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引

3.3 RESTful

RESTful架构是对MVC架构改进后所形成的一种架构,通过使用事先定义好的接口与不同的服务联系起来。在RESTful架构中,浏览器使用POST,DELETE,PUT和GET四种请求方式分别对指定的URL资源进行增删改查操作。因此,RESTful是通过URI实现对资源的管理及访问,具有扩展性强、结构清晰的特点。

RESTful架构将服务器分成前端服务器和后端服务器两部分,前端服务器为用户提供无模型的视图;后端服务器为前端服务器提供接口。浏览器向前端服务器请求视图,通过视图中包含的AJAX函数发起接口请求获取模型。

项目开发引入RESTful架构,利于团队并行开发。在RESTful架构中,将多数HTTP请求转移到前端服务器上,降低服务器的负荷,使视图获取后端模型失败也能呈现。但RESTful架构却不适用于所有的项目,当项目比较小时无需使用RESTful架构,项目变得更加复杂

我们的项目很小,但是使用了RESTful,是为了简单了解、学习RESTful。

3.4 GUI设计

图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。 [1]

图形用户界面是一种人与计算机通信的界面显示格式,允许用户使用鼠标等输入设备操纵屏幕上的图标或菜单选项,以选择命令、调用文件、启动程序或执行其它一些日常任务。与通过键盘输入文本或字符命令来完成例行任务的字符界面相比,图形用户界面有许多优点。图形用户界面由窗口、下拉菜单、对话框及其相应的控制机制构成,在各种新式应用程序中都是标准化的,即相同的操作总是以同样的方式来完成,在图形用户界面,用户看到和操作的都是图形对象,应用的是计算机图形学的技术。

我们使用的是Tkinter,当前也可以使用其他的GUI设计包,例如:PySimpleGUI等。

3.5 GoogleHack

google hack是指使用Google等搜索引擎对某些特定的网络主机漏洞(通常是服务器上的脚本漏洞)进行搜索,以达到快速找到漏洞主机或特定主机的漏洞的目的。

例如:

site:xx.com 返回所有与该站有关的url

link:xx.com 返回所有与该站做了链接的站

site:xx.com filetype:txt 查找TXT文件 其他的以此类推

我们借用的GoogleHack的想法,利用搜索关键词进行组合,帮助我们快速定位代码,查找代码

3.6 BootStrap3

Bootstrap是美国Twitter公司的设计师Mark Otto和Jacob Thornton合作基于HTML、CSS、JavaScript 开发的简洁、直观、强悍的前端开发框架,使得 Web 开发更加快捷。Bootstrap提供了优雅的HTML和CSS规范,它即是由动态CSS语言Less写成。Bootstrap一经推出后颇受欢迎,一直是GitHub上的热门开源项目,包括NASA的MSNBC(微软全国广播公司)的Breaking News都使用了该项目。 [1] 国内一些移动开发者较为熟悉的框架,如WeX5前端开源框架等,也是基于Bootstrap源码进行性能优化而来。

3.7 懒加载

懒加载(Load On Demand)是一种独特而又强大的数据获取方法,它能够在用户滚动页面的时候自动获取更多的数据,而新得到的数据不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。

二、设计

有些设计是在系统设计中做的,但是为了看的层次清晰,放在了对应的设计中。

2.1 系统设计

首先我们的系统是有前后端的,我们不仅要考虑后端设计,还要考虑前端设计,数据库设计。

我们设计、使用如下非常简单的技术架构(物理存储不需要我们设计):

使用BootStrap3作为前端设计,Flask作为后端,MongoDB作为数据库,整体使用RESTful,软件通过启动器启动、控制系统的运行。(防止有写读者不熟悉某些技术,相关的技术介绍所以写在前面了) 

2.2 数据库设计

使用MongoDB建立数据库CodeSpace。

建立多个数据集,每一个数据集代表一种分类。

每条代码数据包含字段名:

  • _id:唯一的代码id,数据库自动生成
  • code_name: 代码名称
  • code_type:代码分类
  • code_des:代码功能的简单介绍,描述
  • code_cnt:代码内容,可以说简单的代码,也可以部分代码,详细代码在附件中
  • code_file:代码附件有无,true为有附件,false为附件

由于这里的_id是唯一的那么可以使用_id进行文件夹命名,存储附件,下载时相应的代码附件时,直接定位到对应文件夹进行下载即可。

2.3 前端设计

1. index.html

我们需要一个首页,进行交互。这个首页应该包含功能:

  • 上传代码(支持上传多个代码附件)
  • 代码检索(支持关键词组合搜索)
  • 仓库容量显示

2. search_res.html

代码查找后,需要一个页面返回显示的结果,我们使用的是懒加载(这里采用点击加载更多的方式),其中包含的功能:

  • 检索结果懒加载
  • 匹配代码的查看、修改、删除
  • 返回首页

 点击展开,弹出卡片小窗口,显示详细的代码信息,并提供下载附件功能。

 同理,点击编辑,提供一个表单卡片,用于提交修改。

3. code_info.html

既然使用懒加载,我们使用code_info.html作为一条检索结果,n个结果进行返回。(n这里为5)

使用方法就是在code_info.html设置多个标志位预先占位,后端检索到数据后(json格式),依次替换生成一个code_info,同理n个code_info为一组返回到前端进行展示。

2.4 后端设计

根据RESTful的风格,我们使用请求方法(GET,PUSH,PUT,DELETE等)区分操作,这样也便于代码设计。

1. 路由设计

/ 就是路由,表示如果用户输入了这个地址,那么 Flask 就会调用对应的对于的函数来进行处理。我们可以给定义多个路由,这样不同的 URL 就会有不同的处理函数:

@app1.route('/')
def fun1():
    return 'hi, flask'
@app1.fun1('/hello/')        
def fun2():
    return 'hello, flask'

当我们启动代码,浏览器输入127.0.0.1会路由到根目录(/),后端会执行fun1,显示hi, flask。

同理我们输入127.0.0.1/hello/,后端会执行fun2,显示hello,flask。

同时可以使用methods参数,能接受哪些类型请求,根据不同请求进行代码设计。

1.1 /和/index/

由于对于主页,我们打开主页只有一个获取(GET)需求(请求一个页面),所有只有GET方法可以使用。设置如下路由:

@app.route("/", methods=["GET"])
@app.route("/index/", methods=["GET"])

1.2 /code_data/

这里路径是表示代码数据(这里是代码数据,检索不属于这里的功能)相关的,对于代码的操作我们包含提交(PUST),修改(PUT),删除(DELETE),于是有以下路由:

@app.route("/code_data/", methods=["POST", "PUT", "DELETE"])

1.3 /search_data/

对于检索,我们的操作只有获取(GET)操作,用来请求对数据进行检索。于是有如下路由:

@app.route("/search_data/", methods=["POST"])

1.4 /load_more/

由于懒加载也需要请求,我们单独设计一个路由给它(我们使用POST方式提交数据)。

@app.route("/load_more/", methods=["POST"])

1.5 /uploads/<type>/<id>/

代码附件的下载也是一个单独的路由,它的路径动态生成,更具代码类型和代码的id生成。

@app.route("/uploads/<type>/<id>", methods=["GET"])

2. 功能设计

2.1 index()

首页路由到后,调用index()方法,读取首页html代码和配置信息展示到前端即可。

2.2 code_data()

路由到/code_data/,需要首先同步仓库状态、检测配置信息,在根据请求方式的不同对数据进行添加、修改、删除操作。使用如下代码进行处理。

if request.method == "POST":
    # 数据保存
    pass
elif request.method == "PUT":
    # 数据修改
    pass
elif request.method == "DELETE":
    # 数据删除
    pass

其中附件上传后会被再次压缩,压缩名为代码id名。 定义代码压缩函数zip_dir()。

  • 删除数据,定义函数:remove_files()
  • 保存数据,定义函数:save_code_info()
  • 修改数据,使用remove_files()和save_code_info()组合实现

2.3 load_more()

 懒加载大致流程如下: 

请求到/load_mode/路由,有load_more()进行懒加载的数据操作。

2.4 download_files()

文件下载函数主要是根据请求的地址(/uploads/<type>/<id>),进行下载。

2.5 search_data()

需要对前端传入的搜索关键词组进行解析,定义函数search_data_analysis(),进行参数分析后。根据关键词组去数据库中查询。

# 关键词组
search_para = {"+": [], "-": [], "in": [], "text": []}
  • +测试:必须包含文本"测试"
  • -测试:必须不包含文件"测试"
  • in:Java :在Java的代码库中查找
  • text:为普通文件

必须包含、必须不包含、普通文本使用正则表达式实现文件的匹配

2.6 MongoDBCtrl类

上述操作必须依靠数据库进行数据操作,我们定义MongoDBCtrl类,进行数据操作。包含如下方法:

  • 数据库连接:mongodb_init()
  • 数据插入:insert()
  • 数据更新:update()
  • 数据检索:find()

使用配置文件进行对数据库的配置,数据库的初始化中需要载入配置文件进行配置。

2.7 配置文件设计

使用ini类型文件作为配置文件,定义两个配置文件:

  • 仓库信息配置:space_info.ini
  • 数据库和调试模式配置:config.ini

space_info.ini

; 仓库内代码数量统计
[CodeSpace_Info]
cs_count = 0

[CodeSpace_State]
state = 1

config.ini

; mongodb 的配置
[MongoDB]
; 连接的地址
mongodbHost = 127.0.0.1
; 连接的端口
mongodbPort = 27017
; 连接的数据库
database = CodeSpace
; 测试模式 true 开启测试模式 ;  false 关闭测试模式
runtest = false

2.6 桌面启动器

桌面启动器主要是为了方便我们控制系统的使用,使用Tkinter进行GUI设计。

2.7 目录结构设计

  •  lib为库文件:这里存储的是操作MongoDB的类
  • static:为flask的静态文件,例如js,css,img等静态文件
  • template:html文件
  • tmp:临时文件
  • uploads:上传的数据存储的地方
  • app.py:核心代码
  • start.pyw:启动器代码
  • *.ini:配置文件

三、编码

3.1 前端编码

1. /和/index/

页面采用网页模板和bootstrap3快速搭建的。

模板名称:首页 - 光年(Light Year Admin)后台管理系统模板

隐藏提交表单

显示提交表单

2. /search_data/

删除

编辑

展开

3.2 后端编码(部分)

后端部分主要是使用Flask的路由部分,理解了路由的含义,其他的代码就是基础开发了,例如:

  • 文件压缩
  • 配置文件操作
  • json
  • 正则匹配
  • ...

1. 编码(部分)

@app.route("/", methods=["GET"])
@app.route("/index/", methods=["GET"])
# 主页
def index():
    cp = configparser.ConfigParser()
    cp.read("./space_info.ini", encoding="utf-8-sig")
    code_count = int(cp.get("CodeSpace_Info", "cs_count"))
    return render_template("index.html", code_count=code_count)


@app.route("/code_data/", methods=["POST", "PUT", "DELETE"])
# 数据的新增,修改,删除
def code_data():
    # ini文件解析
    cp = configparser.ConfigParser()
    cp.read("./space_info.ini", encoding="utf-8-sig")

    # 仓库代码数量
    count = int(cp.get("CodeSpace_Info", "cs_count"))

    if request.method == "POST":
        # 新增
        count += 1

        save_code_info(request=request)

        # 更新记录文件(.ini)
        cp.set("CodeSpace_Info", "cs_count", str(count))
        cp.write(open("./space_info.ini", "w"))

        # 返回成功信息(json)
        return jsonify({"upload_status": "true"})

    elif request.method == "PUT":
        # 修改
        if mdb_ctrl.del_by_id(request.form['_id'], request.form['old_type']) == 'ok':
            save_code_info(request=request)

            path_dir = os.getcwd().replace('\\', '/') + r"/uploads/{}/{}/".format(request.form['old_type'],
                                                                                  request.form['_id'])
            if os.path.isdir(path_dir):
                remove_files(path_dir)

            return jsonify({'update_status': 'true'})
        else:
            return jsonify({'update_status': 'false'})

    elif request.method == "DELETE":
        # 删除
        print(request.form['d_id'], request.form['d_type'])
        if mdb_ctrl.del_by_id(request.form['d_id'], request.form['d_type']) == 'ok':
            path_dir = os.getcwd().replace('\\', '/') + r"/uploads/{}/{}/".format(request.form['d_type'],
                                                                                  request.form['d_id'])
            if os.path.isdir(path_dir):
                remove_files(path_dir)
            count -= 1
            # 更新记录文件(.ini)
            cp.set("CodeSpace_Info", "cs_count", str(count))
            cp.write(open("./space_info.ini", "w"))
            return jsonify({'del_status': 'true'})
        else:
            return jsonify({'del_status': 'false'})


@app.route("/search_data/", methods=["POST"])
# 提交要查询的数据,展示查询结果
def search_data():
    res_list = []
    if request.method == "POST":
        para = search_data_analysis(request.form["search_data"])
        if len(para["text"]) == 0:
            return jsonify({"search_status": "false"})
        print(para)
        res = mdb_ctrl.find(para=para)
        print("END-----")

        for r in res.values():
            res_list.append(r)

        res.clear()
        res["res"] = res_list

        with open(sys.path[0] + r"\CodeSpace\tmp\code.json", "w", encoding="utf-8") as f:
            f.write(str(res).replace('\'', '"'))

    return render_template("search_res.html", code_collection=",".join(para["in"])
                           , in_text=",".join(para["+"])
                           , not_in_text=",".join(para["-"])
                           , text_cnt=",".join(para["text"]))
    # return jsonify({"search_status": "true"})


@app.route("/load_more/", methods=["POST"])
def load_more():
    if request.method == 'POST':
        if int(request.form['next']) <= 5:
            with open(sys.path[0] + r"\CodeSpace\tmp\code.json", "r", encoding="utf-8") as f:
                load_dict = json.load(f)
                print(load_dict)

            if len(load_dict['res']) == 0:
                return jsonify({"get_more_status": "empty"})

            if len(load_dict['res']) > int(request.form['next']):
                load_more_cnt = int(request.form['next'])
            else:
                load_more_cnt = len(load_dict['res'])

            if load_more_cnt == 0:
                return jsonify({"get_more_status": "end"})

            with open("./static/code_info.html", "r", encoding="utf-8") as f:
                str_res = f.read()
            str_res_copy = str_res
            res_list_str = ''

            for i in range(0, load_more_cnt, 1):
                c_id = load_dict['res'][i]['_id']
                c_name = load_dict['res'][i]['code_name']
                c_type = load_dict['res'][i]['code_type']
                c_des = load_dict['res'][i]['code_des']
                c_cnt = load_dict['res'][i]['code_cnt']
                c_file = load_dict['res'][i]['code_file']

                str_tmp = ''

                if len(c_file) > 0:
                    str_tmp = str_res.replace('__FILE__', '<a href="/uploads/__TYPE__/__ID__" style="display: block; '
                                                          'float: right;">  下载附件</a>')
                else:
                    str_tmp = str_res.replace('__FILE__', '<p style="display: block; float: right;">  无附件</p>')

                str_tmp = str_tmp.replace('__ID__', c_id).replace('__NAME__', c_name).replace('__TYPE__', c_type) \
                    .replace('__DES__', c_des).replace('__CNT__', c_cnt).replace('__TYPE__', c_type)

                res_list_str += str_tmp

                str_res = str_res_copy

            for i in range(0, load_more_cnt, 1):
                load_dict['res'].pop(0)

            with open(sys.path[0] + r"\CodeSpace\tmp\code.json", "w", encoding="utf-8") as f:
                f.write(str(load_dict).replace('\'', '"'))

            return jsonify({"code_info": res_list_str})
        else:
            return jsonify({"get_more_status": "false"})


@app.route("/uploads/<type>/<id>", methods=["GET"])
def download_files(type, id):
    if request.method == 'GET':
        dri_path = app.config["UPLOAD_FOLDER"] + r"{}/{}/".format(type, str(id))
        print(type, id)
        print(dri_path)
        return send_from_directory(dri_path, filename="{}.zip".format(str(id)), as_attachment=True)


mdb_ctrl = mongodb_tools.MongoDBCtrl()

3.3 启动器编码

1. 编码(部分)

top_flag = False
run_flag = False
threadLock = threading.Lock()
ls = None
window = tk.Tk()
text = StringVar()
text.set('-')
cp = configparser.ConfigParser()

ls = tk.Label(window, textvariable=text, fg='white', bg='red', font=('宋体', 10), width=2, height=1)


class myThread(threading.Thread):

    def __init__(self, threadID, name):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name

    def run(self):
        print("开始线程:" + self.name)
        thread_rcs(self.name)
        print("退出线程:" + self.name)

threadCS = None

def run_CS():
    global threadCS, run_flag
    cp = configparser.ConfigParser()
    cp.read("./space_info.ini", encoding="utf-8-sig")
    state = cp.get("CodeSpace_State", "state")
    if state == '0' and not run_flag:
        run_flag = True
        cp.set("CodeSpace_State", "state", "1")
        cp.write(open("./space_info.ini", "w"))
        threadCS = myThread(1, "run_cs")
        threadCS.setDaemon(True)
        threadCS.start()
        text.set('O')
        ls.configure(bg='green')
        b1.configure(state=tk.DISABLED)
        b2.configure(state=tk.NORMAL)

    else:
        tkinter.messagebox.showerror('失败', 'CodeSpace启动失败')
        return


def thread_rcs(threadName):
    print(threadName)
    os.system('flask run')


b1 = tk.Button(window, text="启动CodeSpace", command=run_CS)
b2 = tk.Button(window, text="停止CodeSpace", command=stop_CS)



def main():
    cp.read("./space_info.ini", encoding="utf-8-sig")
    cp.set("CodeSpace_State", "state", "0")
    cp.write(open("./space_info.ini", "w"))

    window.title('CSC')
    window.iconbitmap('.\\favicon.ico')
    window.protocol('WM_DELETE_WINDOW', closeWindow)
    window.resizable(False, False)

    menubar = tk.Menu(window)

    filemenu = tk.Menu(menubar, tearoff=0)
    menubar.add_cascade(label='更多', menu=filemenu)
    filemenu.add_checkbutton(label='窗口置顶', command=top)
    filemenu.add_command(label='关于软件', command=more)
    filemenu.add_command(label='退出', command=window.quit)
    window.config(menu=menubar)

    l = tk.Label(window, text='CodeSpace Console', bg='white', font=('宋体', 10), width=30, height=2)
    l.pack()

    lt = tk.Label(window, text='如果发现启动失败、CS无法使用', fg='red', font=('宋体', 8), width=30, height=2)
    lt.place(x=10, y=160)
    lt2 = tk.Label(window, text='检查本地的MongoDB服务是否启动', fg='red', font=('宋体', 8), width=30, height=2)
    lt2.place(x=10, y=190)

    ls.place(x=35, y=65)

    b1.place(x=80, y=40)
    b2.place(x=80, y=80)
    b3 = tk.Button(window, text="打开首页", command=open_home)
    b3.place(x=120, y=120)
    b4 = tk.Button(window, text=" 退 出 ", command=exit_CS)
    b4.place(x=70, y=120)

    b5 = tk.Button(window, text=" 重 置 ", command=reset_count)
    b5.place(x=10, y=120)

    window.geometry("200x230+600+300")

    window.mainloop()


if __name__ == '__main__':
    main()

2. 效果

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值