【技术分享】Sanic+Amis:3天快速搭建一个web服务

本文介绍了如何使用轻量级的Peewee ORM和Sanic框架快速搭建一个设备管理的Web服务。通过Peewee定义数据模型,实现数据的增删改查,利用Sanic创建HTTP服务,处理业务逻辑,从而构建了一个高效的Web工具,提高了测试工作的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

测试工作中,需要搭建一些生产力小工具来提高工作效率。

基于web的服务,无需终端可多人协作,易部署,成为第一选择。

问题:搭建web服务需要全栈知识,编写http服务和前端样式需要一定技术门槛。

本文提供一套简单易用的解决方案,以一个测试机借还管理平台为例,展示如何快速落地一个web工具。

1.需求

预期功能

  • 设备列表页 --- 可查看设备信息,和设备维护(增删改)。
  • 使用历史记录页 --- 查看设备的借用历史
  • 借用页面 --- 给设备生成二维码地址,填写信息完成借用和交还

实现结果如下图,下面展示如何完成

2.设计

一个web服务,由(前端)(后端)(数据库)3部分组成,内部使用的工具,不考虑炫酷的页面,企业级的性能响应,效率优先,如何快速完成搭建和部署,考虑轻量+易用的框架。

1.前端(Amis)  ------ (数据展示)

2.后端(Sanic)  ------ (业务逻辑的处理)

3.数据库(peewee+DB)  ------ (数据持久化)

3.实现

3.1 数据库

ORM(Object–relational mapping)

ORM特点

  • ✅ 数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。
  • ✅ ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。
  • ✅ 它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
  • ✅ 代码比较简单,代码量少,语义性好,容易理解。
  • ❌ 对于复杂的查询,性能不如原生的 SQL,或者无法表达。
  • ❌ ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。

为什么选择peewee,ORM框架对比

框架✅优点❌缺点
SQLObject易于理解的模式不支持数据库会话以隔离工作单元
Storm轻便的API强制程序员编写手动表创建DDL语句
Django的ORM易于使用,学习时间短;与Django紧密集成不能很好地处理复杂的查询;强迫开发人员回到原始SQL
peewee易于使用,轻量级;支持多种数据库;多扩展多对多查询的编写不直观
SQLAlchemy企业级API;使代码健壮且适应性强;设计灵活,轻松编写复杂查询学习曲线漫长
PonyORM简化设置和使用不是为了同时处理数十万或数百万条记录而设计

 3.1.1示例代码

新建model.py文件,定义数据模型。

from peewee import DateTimeField, ForeignKeyField, MySQLDatabase, Model 
from peewee import CharField, PrimaryKeyField, TextField, IntegerField,FloatField


db=MySQLDatabase('db', user='username', password='password', host='11.22.11.111', port=3306)


class History(Model):
    id = PrimaryKeyField()
    device_id = ForeignKeyField(Device, backref='history', lazy_load=False)
    to = CharField() 
    from_ = CharField(column_name='from')
    date = DateTimeField(default=datetime.datetime.now(), formats='%Y-%m-%d %H:%M')

    class Meta:
        database = db 
        table_name = 'tb_history'

class Device(Model):
    ....

  操作数据

# 存储数据
History.create(device_id='123', from_='loda', to='baozi' ,date=date(1935, 3, 1))
 
# 查询数据
datas = History.select().where(History.to == 'baozi')
for h in datas:
    print(h.from_, h.to)

# 修改数据
user = datas[0]
user.to='amani'
user.save()

3.2 http-server

Sanic 提供一种简单且快速,集创建和启动于一体的方法,来实现一个易于修改和拓展的 HTTP 服务。可认为是高性能的Flask

Sanic 特点

  • 简单且快速(可看作基于协程的Flask)

  • 生产环境就绪

  • 高拓展性

  • 支持 ASGI

  • 简单直观的 API 设计

  • 社区保障

quickstart

创建 server.py

from sanic import Sanic
from sanic.response import text

app = Sanic("My Hello, world app")

@app.get("/")
async def hello_world(request):
    return text("Hello, world.")

运行

$ sanic server.app


$ curl http://127.0.0.1

StatusCode        : 200
StatusDescription : OK
Content           : hello world
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Keep-Alive: 5
                    Content-Length: 11
                    Content-Type: text/plain; charset=utf-8

                    hello world
Forms             : {}
Headers           : {[Connection, keep-alive], [Keep-Alive, 5], [Content-Length, 11], [Content-Type, text/plain; charset=utf-8]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 11

3.2.1 Sanic应用

实例:Sanic() 是最基础的组成部分

from sanic import Sanic

app = Sanic("My Hello, world app")

3.2.2 响应函数(handler)

  • 功能:响应对指定端点的访问,即这承载业务逻辑的代码的地方
  • 组成:响应函数需要至少一个 request 实例作为参数, 并返回一个 HTTPResponse.
    • 可以是普通函数或者异步的函数
def i_am_a_handler(request):
    return HTTPResponse()

async def i_am_ALSO_a_handler(request):
    return HTTPResponse()

3.2.3 路由(routing)

通过url找到对应的handler处理请求

 给app添加路由 app.add_route(handler, path, methods)

def i_am_a_handler(request):
    return HTTPResponse()

app.add_route(i_am_a_handler, '/test', methods=["POST", "PUT"])

装饰器格式 @app.[method](path, methods=["GET", "PUT", ...])

@app.route("/stairway")  # 默认method是get
...

@app.route('/test', methods=["POST", "PUT"]) # 支持post和put请求
...

@app.get("/to")
...

@app.delete("/heaven/<id>")
...

3.2.4 路由参数

Sanic 允许模式匹配,并从 URL 中提取值。然后将这些参数作为关键字参数传递到响应函数中。

@app.get("/tag/<tag>")
async def tag_handler(request, tag):
    return text("Tag - {}".format(tag))

# 路由参数指定类型,它将在匹配时进行强制类型转换
@app.route("/path/to/<foo:int>")
async def handler(request, foo: int):
    ...

3.2.5 静态文件

第一个参数是静态文件所需要匹配的路由 第二个参数是渲染文件所在的文件(夹)路径

app.static("/static", "/path/to/directory")

app.static("/", "/path/to/index.html")

3.2.6 请求(request)

除了路由参数,一次请求的其他信息,可以从request中获得
访问请求参数

  • request.args
  • request.query_args
$ curl http://localhost:8000\?key1\=val1\&key2\=val2\&key1\=val3

>>> print(request.args)
{'key1': ['val1', 'val3'], 'key2': ['val2']}

>>> print(request.args.get("key1"))
val1

>>> print(request.args.getlist("key1"))
['val1', 'val3']

>>> print(request.query_args)
[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')]

>>> print(request.query_string)
key1=val1&key2=val2&key1=val3

3.2.7 请求body

  • request.raw
  • request.json
  • request.form
$ curl localhost:8000 -d '{"foo": "bar"}'

>>> print(request.body)
b'{"foo": "bar"}'

>>> print(request.json)
{'foo': 'bar'}

>>> print(request.form)
{'foo': ['bar']}

3.2.8 返回(response)

Sanic 内置了 9 种常用的返回类型

  • response.text
    • text("Hi 😎")
  • response.html
    • html('<!DOCTYPE html><html lang="en"><meta charset="UTF-8"><div>Hi 😎')
  • response.json
    • json({"foo": "bar"})
  • response.file
    • file("/path/to/whatever.png")
  • response.streaming
  • response.file_stream
  • response.raw
  • response.redirect
  • response.empty

3.2.9 自定义状态码

@app.post("/")
async def create_new(request):
    new_thing = await do_create(request)
    return json({"created": True, "id": new_thing.thing_id}, status=201)

3.2.10 基于类的视图

为什么需要基于类的视图?

在日常的 API 设计过程中,将不同的响应函数通过不同的 HTTP 方法挂载到同一路由上是一种常用的设计模式。

@app.get("/foo")
async def foo_get(request):
    ...

@app.post("/foo")
async def foo_post(request):
    ...

@app.put("/foo")
async def foo_put(request):
    ...

@app.route("/bar", methods=["GET", "POST", "PATCH"])
async def bar(request):
    if request.method == "GET":
        ...
    elif request.method == "POST":
        ...
    elif request.method == "PATCH":
        ...

 MethodView实现

from sanic.views import HTTPMethodView
from sanic.response import text

class SimpleView(HTTPMethodView):

  def get(self, request):
      return text("I am get method")

  # You can also use async syntax
  async def post(self, request):
      return text("I am post method")

  def put(self, request):
      return text("I am put method")

  def patch(self, request):
      return text("I am patch method")

  def delete(self, request):
      return text("I am delete method")

app.add_route(SimpleView.as_view(), "/")

3.2.11 使用sanic实现设备管理的增删改查接口

# 定义返回格式
def amis_response(data=''):
    return response.json({
        "status": 0,
        "msg": "ok",
        "data": data
        })


class DeviceListView(HTTPMethodView):
    # 设备列表接口
    def get(self, request):
        args = dict(request.query_args)
        page = int(args.get('page', 1))
        size = int(args.get('perPage', 10))
        select = Device.select()

        # 支持按照参数进行筛选
        if args.get('os'):
            select = select.where(Device.os == args['os'])
        if args.get('version'):
            select = select.where(Device.version == args['version'])
        ret = select.paginate(page, size)
        data = {
            'items': [d.to_dict() for d in ret],
            'total': select.count()
        }
        return amis_response(data)

    # 新增设备接口
    async def post(self, request):
        Device.create(**request.json)
        return amis_response()


class DeviceView(HTTPMethodView):
    # 修改设备信息
    async def put(self, request, id):
        query = Device.update(**request.json).where(Device.id==id)
        code = query.execute()
        return amis_response()

    # 设备流转 action 1:借  2:还
    async def post(self, request, id: int):
        action = request.args.get('action')
        user = request.args.get('user')
        device = Device.get(Device.id==id)
        if action == '1':
            from_ = device.user
            to_ = user
        elif action == '2':
            from_ = user
            to_ = Device.get(Device.id==id).owner
        History.create(device_id=id, from_=from_, to=to_)
        device.user = to_
        device.save()
        return amis_response()
    
    # 查询设备详情
    async def get(self, request, id: int):
        dev = Device.get(Device.id==id)
        return amis_response(dev.to_dict())

3.3  前端展示 Amis

amis 是一个baidu开源的低代码前端框架,它使用 JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率

特点

  • ✅ 不需要懂前端
  • ✅ 完整的界面解决方案
  • ✅ 大量内置组件
  • ❌ 大量定制的UI
  • ❌ 极为复杂的交互

 简单的配置即可生成一个页面,例如设备历史页的代码

!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
    <title>设备管理</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <link rel="stylesheet" href="sdk\sdk.css" />
    <style>
        html,
        body,
        .app-wrapper {
            position: relative;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <div id="root" class="app-wrapper"></div>
    <script src="sdk\sdk.js"></script>
    <script type="text/javascript">
        (function () {
            let amis = amisRequire('amis/embed');
            // 借用管理页面
            let amisScoped = amis.embed('#root',
                {
                    "type": "page",
                    "body": {
                        "type": "service",
                        "api": "${host}/device/api/history/${id}",
                        "body": [
                            {
                                "type": "panel",
                                "title": "设备使用记录",
                                "body": {
                                    "type": "list",
                                    "source": "$items",
                                    "listItem": {
                                        "body": [
                                            {
                                                "type": "hbox",
                                                "columns": [
                                                    {
                                                        "label": "From",
                                                        "name": "from"
                                                    },
                                                    {
                                                        "label": "To",
                                                        "name": "to",
                                                    },
                                                    {
                                                        "label": "Date",
                                                        "name": "date",
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                }
                            }
                        ]
                    }
                }
            );
        })();
    </script>
</body>

</html>

页面效果 

4. 项目代码

代码地址:https://github.com/Be5yond/device_manage

4.1 使用方法

  1. 建立数据库表,语句见git仓库中

  2. 修改config.ini mysql连接信息

  3. 运行服务 python main.py 

  4. 访问服务 http://127.0.0.1:9300/device/page/index.html

### 配置虚拟机连接外部无线网卡并运行 Fern WiFi Cracker 工具 要在虚拟机中通过无线网卡使用 Fern WiFi Cracker 进行无线攻击,需要完成以下几个方面的配置: #### 虚拟机网络适配器设置 在虚拟机软件(如 VMware 或 VirtualBox)中,需将主机上的物理无线网卡传递给虚拟机。此操作通常涉及 USB 设备直通功能或 PCI 直通技术。 对于大多数主流虚拟机平台而言,可以通过以下方式实现: - **VirtualBox**: 将无线网卡作为 USB 设备附加到虚拟机上。进入虚拟机的设置界面,在“USB”选项卡中启用 USB 控制器并将目标无线网卡分配至该虚拟机。 - **VMware**: 同样支持将 USB 无线网卡映射到虚拟机内部。具体方法是在虚拟机启动前,于硬件设置中的 USB 控制器部分绑定对应的设备[^2]。 #### 安装驱动程序和支持库 一旦成功挂载了实际存在的 Wi-Fi 接口,则需要安装适用于 Linux 的相应驱动以及 AirCrack-ng 套件来激活监控模式(monitormode),这是执行任何类型的捕获活动所必需的前提条件之一。例如 Atheros AR9271 卡可能依赖 ath9k_htc 模块加载才能正常工作;而 Realtek RTL88xx 系列芯片组则往往需要用到 rtl8812au/rtl88x2bu 开源项目所提供的补丁版本固件文件才行[^3]。 #### 设置 Fern WiFi Cracker 当确认无线网卡已被识别并且能够切换成监听状态之后,就可以着手准备部署 Fern WiFi Cracker 图形化环境啦! 以下是几个关键步骤概述: 1. 更新包管理索引数据库 `sudo apt update && sudo apt upgrade`; 2. 如果尚未存在的话,请先获取 git 版本控制系统以便克隆远程仓库资源下来:`sudo apt install git`; 3. 下载最新发行版代码副本 `git clone https://github.com/savio-code/fern-wifi-cracker.git` 并跳转目录 cd fern-wifi-cracker/; 4. 执行脚本来初始化必要的依赖关系构建过程 ./install.sh; 5. 最终调用 python 应用入口点开始 GUI session —— python fern-wifi-cracker.py. ```bash # 此处展示命令序列用于引导用户顺利完成上述流程 sudo apt-get update sudo apt-get install -y build-essential autoconf libtool pkg-config \ zlib1g-dev libnl-3-dev libssl-dev iw ethtool usbutils macchanger tcpdump aircrack-ng tshark wireshark cd /opt/ git clone https://github.com/savio-code/fern-wifi-cracker.git chmod +x ./fern-wifi-cracker/install.sh ./fern-wifi-cracker/install.sh python ./fern-wifi-cracker/fern-wifi-cracker.py ``` 以上即为整个过程中涉及到的主要环节说明文档内容总结][^[^23].
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值