QGIS Server 插件开发指南
一、插件机制
1.1 插件发现
QGIS Server 启动时,扫描 QGIS_PLUGINPATH 环境变量指定的目录,对每个包含 metadata.txt 且 server=True 的子目录,执行其 __init__.py 中的 serverClassFactory(serverIface) 函数。
QGIS_PLUGINPATH=/io/plugins
│
├── rest_map_service/ ← 含 metadata.txt (server=True),加载
├── qgis-helloserver/ ← 含 metadata.txt (server=True),加载
├── template_service/ ← 含 metadata.txt (server=True),加载
└── some_dir/ ← 无 metadata.txt,跳过
插件最小文件集合:
my_plugin/
├── metadata.txt # 元信息(server=True 是必须的)
└── __init__.py # 包含 serverClassFactory(serverIface) 函数
1.2 入口函数约定
python
# __init__.py --- QGIS Server 唯一识别入口
def serverClassFactory(serverIface):
"""serverIface 是 QgsServerInterface 实例,提供注册 API"""
from .impl import MyPlugin
return MyPlugin(serverIface)
serverIface 提供三种注册方法,对应三种插件类型:
| 注册方法 | 插件类型 | 基类 |
|---|---|---|
serverIface.serviceRegistry().registerService(obj) |
Service(服务) | QgsService |
serverIface.registerFilter(obj, priority) |
Filter(过滤器) | QgsServerFilter |
serverIface.registerAccessControl(obj, priority) |
Access Control(访问控制) | QgsAccessControlFilter |
一个 serverClassFactory 内可以同时注册多种类型。
1.3 架构全景
HTTP Request
│
▼
┌──────────────────────┐
│ Nginx │
│ URL 重写 / 反向代理 │
└──────────┬───────────┘
│ /ows/?SERVICE=...&MAP=...
▼
┌──────────────────────┐
│ QGIS Server │
│ │
│ ┌─────────────────┐ │
│ │ requestReady() │ │ ← Filter 钩子 1
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Access Control │ │ ← 权限检查(在渲染前)
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Service / WMS │ │ ← 核心处理或 Service 接管
│ │ / WFS / WCS ... │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ sendResponse() │ │ ← Filter 钩子 2
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │responseComplete │ │ ← Filter 钩子 3
│ │ () │ │
│ └────────┬────────┘ │
│ │
└───────────┬───────────┘
▼
HTTP Response
二、三种插件类型
2.1 Service(服务) --- 完全接管
基类 :QgsService
激活 :SERVICE=XXX 参数匹配 name() 返回值
适用场景:自定义 REST API、替代 WMS/WFS 的自定义协议
请求: /ows/?SERVICE=MYAPI&OPERATION=xxx
│
▼
QGIS Server 查找注册了 name()=="MYAPI" 的 Service
│
▼
调用 executeRequest(request, response, project)
│
▼
插件完全控制响应内容
必须实现的方法:
| 方法 | 说明 |
|---|---|
name() |
返回服务名,对应 SERVICE= 参数 |
version() |
返回版本号字符串 |
executeRequest(request, response, project) |
核心:处理请求、写入响应 |
关键 API:
python
# 请求
request.parameter("KEY") # 获取参数(仅标准 OWS 参数)
request.method() # QgsServerRequest.GetMethod / PostMethod
request.url() # PyQt6 返回 QUrl,需 .toString()
# 响应
response.setStatusCode(200)
response.setHeader("Content-Type", "application/json")
response.write(b"...body...")
注意 :request.parameter() 仅返回 QGIS Server 识别的标准参数(SERVICE、MAP、REQUEST 等)。自定义参数需通过解析 URL 获取,见 [4.1 常见问题](#4.1 常见问题)。
2.2 Filter(过滤器) --- 旁路拦截
基类 :QgsServerFilter
激活 :所有请求自动经过(不需要特定 SERVICE)
适用场景:日志、鉴权、水印、响应注入、参数预处理
请求到达
│
▼
requestReady() ← 可修改请求参数、做鉴权
│
▼
[QGIS Server 核心处理]
│
▼
sendResponse() ← 可修改响应头
│
▼
responseComplete() ← 可修改响应体(加水印、替换内容)
│
▼
响应发出
三个钩子按固定顺序触发 ,每个钩子的 handler 对象提供不同能力:
python
handler = self.serverInterface().requestHandler()
params = handler.parameterMap() # 读所有参数
handler.setParameter('K', 'V') # 注入参数 (requestReady 中)
handler.setResponseHeader('K', 'V') # 设置响应头 (sendResponse 中)
handler.setServiceException(ex) # 抛出受控异常
handler.body() # 读响应体 (responseComplete 中)
handler.clearBody() # 清空
handler.appendBody(b'...') # 追加
handler.clear() # 清空全部
优先级 :registerFilter(filter, priority) 的值越小越先执行。
2.3 Access Control(访问控制) --- 权限管控
基类 :QgsAccessControlFilter
激活 :在渲染/查询之前 自动执行
适用场景:图层级权限、字段过滤、行级数据过滤
特点:在 Service 或 WMS/WFS 处理之前运行,被限制的数据根本不会进入渲染管线,比 Filter 更安全。
可覆写的方法:
python
# 图层级权限------控制增删改查
def layerPermissions(self, layer) -> LayerPermissions:
# canRead / canInsert / canUpdate / canDelete
# 字段级权限------控制哪些字段可见
def authorizedLayerAttributes(self, layer, attributes) -> list:
# 返回允许的字段列表
# 行级权限------SQL 过滤表达式(可选)
def layerFilterExpression(self, layer) -> str:
# 返回 SQL WHERE 子句
三、三个模板插件
3.1 template_service --- Service 模板
路径 :qgis-server/plugins/template_service/
python
# service_impl.py(核心代码)
from qgis.server import QgsService
class TemplateService(QgsService):
def name(self):
return "TEMPLATE" # 请求中 SERVICE=TEMPLATE
def version(self):
return "1.0.0"
def executeRequest(self, request, response, project):
operation = request.parameter("OPERATION") or ""
if not operation:
self._send_json(response, { # 默认:返回服务描述
"service": "TEMPLATE",
"operations": ["ping", "echo", "info"],
})
elif operation == "ping":
self._send_json(response, {"pong": True})
elif operation == "echo":
msg = request.parameter("msg") or ""
self._send_json(response, {"echo": msg})
elif operation == "info":
# 返回项目元信息
layers = [{"id": lid, "name": l.name()} for lid, l in project.mapLayers().items()]
self._send_json(response, {"project": ..., "layers": layers})
else:
self._send_json(response, {"error": "Unknown"}, 404)
测试:
bash
curl "localhost:8050/ows/?SERVICE=TEMPLATE&MAP=/io/data/china.qgs"
# → {"service":"TEMPLATE","version":"1.0.0","operations":["ping","echo","info"],...}
curl "localhost:8050/ows/?SERVICE=TEMPLATE&OPERATION=ping&MAP=/io/data/china.qgs"
# → {"pong":true}
curl "localhost:8050/ows/?SERVICE=TEMPLATE&OPERATION=info&MAP=/io/data/china.qgs"
# → {"project":"/io/data/china.qgs","crs":"EPSG:4490","layerCount":1,...}
扩展指南 :在 executeRequest 中添加新的 elif operation == "xxx": 分支,或改为 RESTful 路由(参考 rest_map_service)。
3.2 template_filter --- Filter 模板
路径 :qgis-server/plugins/template_filter/
python
# filter_impl.py(核心代码)
from qgis.server import QgsServerFilter
class TemplateFilter(QgsServerFilter):
def requestReady(self):
# 钩子 1:请求到达,可注入参数
params = handler.parameterMap()
log(f"--> {params.get('SERVICE')} / {params.get('REQUEST')}")
def sendResponse(self):
# 钩子 2:响应头发送前
handler.setResponseHeader("X-Template-Filter", "v1.0")
def responseComplete(self):
# 钩子 3:响应生成完毕,可修改响应体
elapsed = (time.time() - self._start) * 1000
log(f"<-- done in {elapsed:.1f}ms")
测试:
bash
curl "localhost:8050/ows/?SERVICE=WMS&REQUEST=GetCapabilities&MAP=/io/data/china.qgs"
# 查看 QGIS Server 日志:
# docker logs qgis-server | grep TemplateFilter
# → --> WMS / GetCapabilities from /io/data/china.qgs
# → <-- WMS / GetCapabilities done in 1.9ms
扩展指南:
- 鉴权 :在
requestReady中检查 token,不合法则handler.setServiceException() - 水印 :在
responseComplete中读handler.body()→ QPainter 画水印 → 写回 - 参数注入 :在
requestReady中handler.setParameter('KEY', 'VALUE')
3.3 template_access_control --- Access Control 模板
路径 :qgis-server/plugins/template_access_control/
python
# access_impl.py(核心代码)
from qgis.server import QgsAccessControlFilter
class TemplateAccessControl(QgsAccessControlFilter):
def layerPermissions(self, layer):
perms = QgsAccessControlFilter.LayerPermissions()
perms.canRead = True # 允许读取
perms.canInsert = True
perms.canUpdate = True # 改为 False 可禁止编辑
perms.canDelete = True
return perms
def authorizedLayerAttributes(self, layer, attributes):
# 返回允许可见的字段列表
return attributes
# def layerFilterExpression(self, layer):
# return "" # 行级过滤 SQL
特性 :无直接 HTTP 入口------它对所有 WMS/WFS 请求静默生效。权限检查发生在渲染之前,被限制的数据不会进入渲染管线。
测试方法:
bash
# 1. 将 china_province 层的 canRead 改为 False
# 2. 请求 WMS GetMap → 该图层不会渲染
curl "localhost:8050/ows/?SERVICE=WMS&REQUEST=GetMap&MAP=/io/data/china.qgs&LAYERS=china_province&..."
# → 空白图片(图层被 Access Control 屏蔽)
扩展指南:
- 按图层控制 :在
layerPermissions中if layer.name() == "机密数据": perms.canRead = False - 字段脱敏 :在
authorizedLayerAttributes中过滤敏感字段 - 多租户隔离 :在
layerFilterExpression中追加WHERE tenant_id = :current_tenant
四、从模板创建新插件
4.1 快速开始
bash
# 1. 拷贝模板
cp -r plugins/template_service plugins/my_api
# 2. 修改 metadata.txt
# name=My API Service
# 3. 重命名实现文件(可选)
mv plugins/my_api/service_impl.py plugins/my_api/my_api.py
# 4. 修改 __init__.py 的 import 路径
# from .my_api import MyApiService
# 5. 修改 my_api.py
# 类名、name()、executeRequest 逻辑
# 6. 重启
docker compose restart qgis-server
# 7. 测试
curl "localhost:8050/ows/?SERVICE=???&MAP=/io/data/china.qgs"
4.2 常见问题
request.parameter() 获取不到自定义参数
QGIS Server 的 request.parameter() 只返回已知的 OWS 参数(SERVICE、MAP、REQUEST 等)。自定义参数需从 URL 解析:
python
from urllib.parse import urlparse, parse_qs
def get_all_params(request):
url_str = request.url().toString() # PyQt6 中 url() 返回 QUrl
query = urlparse(url_str).query
params = {}
for k, vals in parse_qs(query).items():
params[k] = vals[0]
return params
实现文件名和包名冲突
python
# ❌ 错误:template_service/__init__.py 中
from .template_service import Xxx # 包名=模块名,导入歧义
# ✅ 正确:用不同文件名
from .service_impl import Xxx
PyQt6 枚举位置变化
python
# PyQt5:
from qgis.PyQt.QtCore import QIODevice
QIODevice.WriteOnly
# PyQt6 (QGIS 4.x):
from qgis.PyQt.QtCore import QIODeviceBase
QIODeviceBase.OpenModeFlag.WriteOnly
五、三种模式对比总结
| Service | Filter | Access Control | |
|---|---|---|---|
| 基类 | QgsService |
QgsServerFilter |
QgsAccessControlFilter |
| 激活时机 | SERVICE= 匹配 |
所有请求 | 渲染/查询前 |
| 对请求的影响 | 完全接管 | 旁路拦截 | 静默过滤 |
| 能做什么 | 自定义 API | 日志/鉴权/水印 | 图层/字段/行级权限 |
| 不能做什么 | 影响 WMS/WFS | 完全取代 Service | 自定义响应格式 |
| 运行时开销 | 仅匹配 SERVICE 时 | 每个请求 | 每次渲染/查询 |
| 模板路径 | template_service/ |
template_filter/ |
template_access_control/ |
| 可混合使用 | ✅ | ✅ | ✅ |
三种机制在同一请求中的执行顺序:Filter (requestReady) → Access Control → Service/WMS → Filter (sendResponse) → Filter (responseComplete)。
QGIS Server 插件开发指南
一、插件机制
1.1 插件发现
QGIS Server 启动时,扫描 QGIS_PLUGINPATH 环境变量指定的目录,对每个包含 metadata.txt 且 server=True 的子目录,执行其 __init__.py 中的 serverClassFactory(serverIface) 函数。
QGIS_PLUGINPATH=/io/plugins
│
├── rest_map_service/ ← 含 metadata.txt (server=True),加载
├── qgis-helloserver/ ← 含 metadata.txt (server=True),加载
├── template_service/ ← 含 metadata.txt (server=True),加载
└── some_dir/ ← 无 metadata.txt,跳过
插件最小文件集合:
my_plugin/
├── metadata.txt # 元信息(server=True 是必须的)
└── __init__.py # 包含 serverClassFactory(serverIface) 函数
1.2 入口函数约定
python
# __init__.py --- QGIS Server 唯一识别入口
def serverClassFactory(serverIface):
"""serverIface 是 QgsServerInterface 实例,提供注册 API"""
from .impl import MyPlugin
return MyPlugin(serverIface)
serverIface 提供三种注册方法,对应三种插件类型:
| 注册方法 | 插件类型 | 基类 |
|---|---|---|
serverIface.serviceRegistry().registerService(obj) |
Service(服务) | QgsService |
serverIface.registerFilter(obj, priority) |
Filter(过滤器) | QgsServerFilter |
serverIface.registerAccessControl(obj, priority) |
Access Control(访问控制) | QgsAccessControlFilter |
一个 serverClassFactory 内可以同时注册多种类型。
1.3 架构全景
HTTP Request
│
▼
┌──────────────────────┐
│ Nginx │
│ URL 重写 / 反向代理 │
└──────────┬───────────┘
│ /ows/?SERVICE=...&MAP=...
▼
┌──────────────────────┐
│ QGIS Server │
│ │
│ ┌─────────────────┐ │
│ │ requestReady() │ │ ← Filter 钩子 1
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Access Control │ │ ← 权限检查(在渲染前)
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Service / WMS │ │ ← 核心处理或 Service 接管
│ │ / WFS / WCS ... │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ sendResponse() │ │ ← Filter 钩子 2
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │responseComplete │ │ ← Filter 钩子 3
│ │ () │ │
│ └────────┬────────┘ │
│ │
└───────────┬───────────┘
▼
HTTP Response
二、三种插件类型
2.1 Service(服务) --- 完全接管
基类 :QgsService
激活 :SERVICE=XXX 参数匹配 name() 返回值
适用场景:自定义 REST API、替代 WMS/WFS 的自定义协议
请求: /ows/?SERVICE=MYAPI&OPERATION=xxx
│
▼
QGIS Server 查找注册了 name()=="MYAPI" 的 Service
│
▼
调用 executeRequest(request, response, project)
│
▼
插件完全控制响应内容
必须实现的方法:
| 方法 | 说明 |
|---|---|
name() |
返回服务名,对应 SERVICE= 参数 |
version() |
返回版本号字符串 |
executeRequest(request, response, project) |
核心:处理请求、写入响应 |
关键 API:
python
# 请求
request.parameter("KEY") # 获取参数(仅标准 OWS 参数)
request.method() # QgsServerRequest.GetMethod / PostMethod
request.url() # PyQt6 返回 QUrl,需 .toString()
# 响应
response.setStatusCode(200)
response.setHeader("Content-Type", "application/json")
response.write(b"...body...")
注意 :request.parameter() 仅返回 QGIS Server 识别的标准参数(SERVICE、MAP、REQUEST 等)。自定义参数需通过解析 URL 获取,见 [4.1 常见问题](#4.1 常见问题)。
2.2 Filter(过滤器) --- 旁路拦截
基类 :QgsServerFilter
激活 :所有请求自动经过(不需要特定 SERVICE)
适用场景:日志、鉴权、水印、响应注入、参数预处理
请求到达
│
▼
requestReady() ← 可修改请求参数、做鉴权
│
▼
[QGIS Server 核心处理]
│
▼
sendResponse() ← 可修改响应头
│
▼
responseComplete() ← 可修改响应体(加水印、替换内容)
│
▼
响应发出
三个钩子按固定顺序触发 ,每个钩子的 handler 对象提供不同能力:
python
handler = self.serverInterface().requestHandler()
params = handler.parameterMap() # 读所有参数
handler.setParameter('K', 'V') # 注入参数 (requestReady 中)
handler.setResponseHeader('K', 'V') # 设置响应头 (sendResponse 中)
handler.setServiceException(ex) # 抛出受控异常
handler.body() # 读响应体 (responseComplete 中)
handler.clearBody() # 清空
handler.appendBody(b'...') # 追加
handler.clear() # 清空全部
优先级 :registerFilter(filter, priority) 的值越小越先执行。
2.3 Access Control(访问控制) --- 权限管控
基类 :QgsAccessControlFilter
激活 :在渲染/查询之前 自动执行
适用场景:图层级权限、字段过滤、行级数据过滤
特点:在 Service 或 WMS/WFS 处理之前运行,被限制的数据根本不会进入渲染管线,比 Filter 更安全。
可覆写的方法:
python
# 图层级权限------控制增删改查
def layerPermissions(self, layer) -> LayerPermissions:
# canRead / canInsert / canUpdate / canDelete
# 字段级权限------控制哪些字段可见
def authorizedLayerAttributes(self, layer, attributes) -> list:
# 返回允许的字段列表
# 行级权限------SQL 过滤表达式(可选)
def layerFilterExpression(self, layer) -> str:
# 返回 SQL WHERE 子句
三、三个模板插件
3.1 template_service --- Service 模板
路径 :qgis-server/plugins/template_service/
python
# service_impl.py(核心代码)
from qgis.server import QgsService
class TemplateService(QgsService):
def name(self):
return "TEMPLATE" # 请求中 SERVICE=TEMPLATE
def version(self):
return "1.0.0"
def executeRequest(self, request, response, project):
operation = request.parameter("OPERATION") or ""
if not operation:
self._send_json(response, { # 默认:返回服务描述
"service": "TEMPLATE",
"operations": ["ping", "echo", "info"],
})
elif operation == "ping":
self._send_json(response, {"pong": True})
elif operation == "echo":
msg = request.parameter("msg") or ""
self._send_json(response, {"echo": msg})
elif operation == "info":
# 返回项目元信息
layers = [{"id": lid, "name": l.name()} for lid, l in project.mapLayers().items()]
self._send_json(response, {"project": ..., "layers": layers})
else:
self._send_json(response, {"error": "Unknown"}, 404)
测试:
bash
curl "localhost:8050/ows/?SERVICE=TEMPLATE&MAP=/io/data/china.qgs"
# → {"service":"TEMPLATE","version":"1.0.0","operations":["ping","echo","info"],...}
curl "localhost:8050/ows/?SERVICE=TEMPLATE&OPERATION=ping&MAP=/io/data/china.qgs"
# → {"pong":true}
curl "localhost:8050/ows/?SERVICE=TEMPLATE&OPERATION=info&MAP=/io/data/china.qgs"
# → {"project":"/io/data/china.qgs","crs":"EPSG:4490","layerCount":1,...}
扩展指南 :在 executeRequest 中添加新的 elif operation == "xxx": 分支,或改为 RESTful 路由(参考 rest_map_service)。
3.2 template_filter --- Filter 模板
路径 :qgis-server/plugins/template_filter/
python
# filter_impl.py(核心代码)
from qgis.server import QgsServerFilter
class TemplateFilter(QgsServerFilter):
def requestReady(self):
# 钩子 1:请求到达,可注入参数
params = handler.parameterMap()
log(f"--> {params.get('SERVICE')} / {params.get('REQUEST')}")
def sendResponse(self):
# 钩子 2:响应头发送前
handler.setResponseHeader("X-Template-Filter", "v1.0")
def responseComplete(self):
# 钩子 3:响应生成完毕,可修改响应体
elapsed = (time.time() - self._start) * 1000
log(f"<-- done in {elapsed:.1f}ms")
测试:
bash
curl "localhost:8050/ows/?SERVICE=WMS&REQUEST=GetCapabilities&MAP=/io/data/china.qgs"
# 查看 QGIS Server 日志:
# docker logs qgis-server | grep TemplateFilter
# → --> WMS / GetCapabilities from /io/data/china.qgs
# → <-- WMS / GetCapabilities done in 1.9ms
扩展指南:
- 鉴权 :在
requestReady中检查 token,不合法则handler.setServiceException() - 水印 :在
responseComplete中读handler.body()→ QPainter 画水印 → 写回 - 参数注入 :在
requestReady中handler.setParameter('KEY', 'VALUE')
3.3 template_access_control --- Access Control 模板
路径 :qgis-server/plugins/template_access_control/
python
# access_impl.py(核心代码)
from qgis.server import QgsAccessControlFilter
class TemplateAccessControl(QgsAccessControlFilter):
def layerPermissions(self, layer):
perms = QgsAccessControlFilter.LayerPermissions()
perms.canRead = True # 允许读取
perms.canInsert = True
perms.canUpdate = True # 改为 False 可禁止编辑
perms.canDelete = True
return perms
def authorizedLayerAttributes(self, layer, attributes):
# 返回允许可见的字段列表
return attributes
# def layerFilterExpression(self, layer):
# return "" # 行级过滤 SQL
特性 :无直接 HTTP 入口------它对所有 WMS/WFS 请求静默生效。权限检查发生在渲染之前,被限制的数据不会进入渲染管线。
测试方法:
bash
# 1. 将 china_province 层的 canRead 改为 False
# 2. 请求 WMS GetMap → 该图层不会渲染
curl "localhost:8050/ows/?SERVICE=WMS&REQUEST=GetMap&MAP=/io/data/china.qgs&LAYERS=china_province&..."
# → 空白图片(图层被 Access Control 屏蔽)
扩展指南:
- 按图层控制 :在
layerPermissions中if layer.name() == "机密数据": perms.canRead = False - 字段脱敏 :在
authorizedLayerAttributes中过滤敏感字段 - 多租户隔离 :在
layerFilterExpression中追加WHERE tenant_id = :current_tenant
四、从模板创建新插件
4.1 快速开始
bash
# 1. 拷贝模板
cp -r plugins/template_service plugins/my_api
# 2. 修改 metadata.txt
# name=My API Service
# 3. 重命名实现文件(可选)
mv plugins/my_api/service_impl.py plugins/my_api/my_api.py
# 4. 修改 __init__.py 的 import 路径
# from .my_api import MyApiService
# 5. 修改 my_api.py
# 类名、name()、executeRequest 逻辑
# 6. 重启
docker compose restart qgis-server
# 7. 测试
curl "localhost:8050/ows/?SERVICE=???&MAP=/io/data/china.qgs"
4.2 常见问题
request.parameter() 获取不到自定义参数
QGIS Server 的 request.parameter() 只返回已知的 OWS 参数(SERVICE、MAP、REQUEST 等)。自定义参数需从 URL 解析:
python
from urllib.parse import urlparse, parse_qs
def get_all_params(request):
url_str = request.url().toString() # PyQt6 中 url() 返回 QUrl
query = urlparse(url_str).query
params = {}
for k, vals in parse_qs(query).items():
params[k] = vals[0]
return params
实现文件名和包名冲突
python
# ❌ 错误:template_service/__init__.py 中
from .template_service import Xxx # 包名=模块名,导入歧义
# ✅ 正确:用不同文件名
from .service_impl import Xxx
PyQt6 枚举位置变化
python
# PyQt5:
from qgis.PyQt.QtCore import QIODevice
QIODevice.WriteOnly
# PyQt6 (QGIS 4.x):
from qgis.PyQt.QtCore import QIODeviceBase
QIODeviceBase.OpenModeFlag.WriteOnly
五、三种模式对比总结
| Service | Filter | Access Control | |
|---|---|---|---|
| 基类 | QgsService |
QgsServerFilter |
QgsAccessControlFilter |
| 激活时机 | SERVICE= 匹配 |
所有请求 | 渲染/查询前 |
| 对请求的影响 | 完全接管 | 旁路拦截 | 静默过滤 |
| 能做什么 | 自定义 API | 日志/鉴权/水印 | 图层/字段/行级权限 |
| 不能做什么 | 影响 WMS/WFS | 完全取代 Service | 自定义响应格式 |
| 运行时开销 | 仅匹配 SERVICE 时 | 每个请求 | 每次渲染/查询 |
| 模板路径 | template_service/ |
template_filter/ |
template_access_control/ |
| 可混合使用 | ✅ | ✅ | ✅ |
三种机制在同一请求中的执行顺序:Filter (requestReady) → Access Control → Service/WMS → Filter (sendResponse) → Filter (responseComplete)。