第08章-插件系统开发
第八章:插件系统开发
8.1 插件系统概述
Astral3D采用模块化的插件架构,允许开发者通过插件扩展编辑器的功能。插件系统支持热插拔、沙箱隔离、依赖管理等特性,是Astral3D可扩展性的核心。
8.1.1 插件系统架构
┌─────────────────────────────────────────────────────────────────────┐
│ 插件系统架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Plugin Manager │ │
│ │ (插件管理器) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 注册 │ │ 加载 │ │ 卸载 │ │ 更新 │ │ │
│ │ │Register │ │ Load │ │ Unload │ │ Update │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Plugin A │ │ Plugin B │ │ Plugin C │ │
│ │ 内置插件 │ │ 第三方插件 │ │ 自定义插件 │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Plugin API │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
│ │ │ Viewer │ │ Scene │ │ UI │ │ Events │ │Storage│ │ │
│ │ │ API │ │ API │ │ API │ │ API │ │ API │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └───────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
8.1.2 插件类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 功能插件 | 扩展编辑器功能 | 测量工具、标注工具 |
| 格式插件 | 支持新的文件格式 | 自定义模型格式加载器 |
| UI插件 | 添加界面组件 | 自定义面板、工具栏 |
| 渲染插件 | 扩展渲染能力 | 自定义着色器、后处理 |
| 集成插件 | 第三方服务集成 | 云存储、数据接口 |
8.1.3 插件生命周期
// 插件生命周期接口
interface PluginLifecycle {
// 插件安装时调用
install(app: Application): void | Promise<void>;
// 插件激活时调用
activate(): void | Promise<void>;
// 插件停用时调用
deactivate(): void | Promise<void>;
// 插件卸载时调用
uninstall(): void | Promise<void>;
// 编辑器就绪时调用
onReady(): void;
// 场景加载时调用
onSceneLoad(scene: THREE.Scene): void;
// 对象选择变化时调用
onSelectionChange(objects: THREE.Object3D[]): void;
// 渲染帧更新时调用
onUpdate(delta: number): void;
}
8.2 创建插件项目
8.2.1 插件项目结构
my-plugin/
├── src/
│ ├── index.ts # 插件入口
│ ├── plugin.ts # 插件主类
│ ├── components/ # Vue组件
│ │ └── MyPanel.vue
│ ├── tools/ # 工具类
│ │ └── MyTool.ts
│ └── styles/ # 样式文件
│ └── index.css
├── package.json # 项目配置
├── tsconfig.json # TypeScript配置
├── vite.config.ts # 构建配置
└── README.md # 文档
8.2.2 插件清单(package.json)
{
"name": "@astral3d/plugin-my-feature",
"version": "1.0.0",
"description": "我的自定义插件",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"keywords": ["astral3d", "plugin"],
"author": "Your Name",
"license": "MIT",
"astral3d": {
"name": "我的功能插件",
"description": "这是一个示例插件",
"version": "1.0.0",
"icon": "icon.png",
"category": "tools",
"minVersion": "1.0.0",
"maxVersion": "2.0.0",
"permissions": [
"scene:read",
"scene:write",
"ui:panel",
"storage:local"
],
"dependencies": [
"@astral3d/plugin-base"
]
},
"peerDependencies": {
"@astral3d/engine": "^1.0.0",
"three": "^0.170.0",
"vue": "^3.5.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^5.0.0",
"@astral3d/plugin-dev-kit": "^1.0.0"
}
}
8.2.3 插件入口文件
// src/index.ts
import { Plugin, PluginContext } from '@astral3d/engine';
import MyPlugin from './plugin';
// 导出插件类
export default MyPlugin;
// 导出插件信息
export const pluginInfo = {
name: 'my-plugin',
displayName: '我的插件',
version: '1.0.0',
author: 'Your Name',
description: '插件描述'
};
// 自动注册(可选)
export function register(context: PluginContext) {
context.registerPlugin(new MyPlugin());
}
8.2.4 插件主类
// src/plugin.ts
import {
Plugin,
PluginContext,
Application,
useAddSignal,
useDispatchSignal
} from '@astral3d/engine';
import MyPanel from './components/MyPanel.vue';
import { MyTool } from './tools/MyTool';
export default class MyPlugin extends Plugin {
// 插件标识
static id = 'my-plugin';
// 插件上下文
private context: PluginContext;
private tool: MyTool;
// 安装插件
async install(app: Application) {
console.log('插件安装中...');
// 保存应用引用
this.app = app;
// 初始化工具
this.tool = new MyTool(app.viewer);
}
// 激活插件
async activate() {
console.log('插件激活');
// 注册UI面板
this.registerPanel();
// 注册工具栏按钮
this.registerToolbar();
// 注册事件监听
this.registerEvents();
// 注册命令
this.registerCommands();
}
// 停用插件
async deactivate() {
console.log('插件停用');
// 清理资源
this.unregisterPanel();
this.unregisterToolbar();
this.unregisterEvents();
}
// 卸载插件
async uninstall() {
console.log('插件卸载');
this.tool?.dispose();
}
// 注册面板
private registerPanel() {
this.app.ui.registerPanel({
id: 'my-panel',
title: '我的面板',
component: MyPanel,
position: 'right',
width: 300,
icon: 'tool-icon'
});
}
// 注册工具栏
private registerToolbar() {
this.app.ui.registerToolbarButton({
id: 'my-tool-btn',
title: '我的工具',
icon: 'my-icon',
group: 'tools',
onClick: () => this.activateTool()
});
}
// 注册事件
private registerEvents() {
useAddSignal('objectSelected', this.onObjectSelected.bind(this));
useAddSignal('sceneChanged', this.onSceneChanged.bind(this));
}
// 注册命令
private registerCommands() {
this.app.commands.register({
id: 'my-plugin:do-something',
name: '执行操作',
shortcut: 'Ctrl+Shift+M',
execute: () => this.doSomething()
});
}
// 事件处理
private onObjectSelected(object: THREE.Object3D) {
console.log('对象被选中:', object?.name);
}
private onSceneChanged() {
console.log('场景发生变化');
}
// 功能方法
private activateTool() {
this.tool.activate();
}
private doSomething() {
console.log('执行自定义操作');
}
}
8.3 插件API详解
8.3.1 Viewer API
// 获取Viewer实例
const viewer = this.app.viewer;
// 场景操作
viewer.scene.add(object);
viewer.scene.remove(object);
viewer.scene.traverse(callback);
// 相机操作
viewer.camera.position.set(x, y, z);
viewer.camera.lookAt(target);
viewer.cameraController.flyTo(position, target, duration);
viewer.cameraController.focusOn(object);
// 渲染操作
viewer.render();
viewer.setSize(width, height);
viewer.getScreenshot();
// 选择操作
viewer.selection.select(object);
viewer.selection.deselect();
viewer.selection.getSelected();
// 射线检测
viewer.raycast(x, y);
viewer.raycastAll(x, y);
// 坐标转换
viewer.worldToScreen(worldPos);
viewer.screenToWorld(screenPos, depth);
8.3.2 Scene API
// 获取Scene实例
const scene = this.app.scene;
// 对象查询
scene.getObjectByName(name);
scene.getObjectByUUID(uuid);
scene.getObjectsByType(type);
scene.getObjectsByProperty(key, value);
// 对象操作
scene.add(object, parent);
scene.remove(object);
scene.clone(object);
scene.group(objects);
scene.ungroup(group);
// 变换操作
scene.transform.move(object, delta);
scene.transform.rotate(object, euler);
scene.transform.scale(object, scale);
// 历史操作
scene.history.undo();
scene.history.redo();
scene.history.push(action);
// 序列化
scene.serialize();
scene.deserialize(data);
scene.export(format, options);
scene.import(file, options);
8.3.3 UI API
// 获取UI实例
const ui = this.app.ui;
// 面板管理
ui.registerPanel(config);
ui.unregisterPanel(id);
ui.showPanel(id);
ui.hidePanel(id);
ui.togglePanel(id);
// 工具栏
ui.registerToolbarButton(config);
ui.unregisterToolbarButton(id);
ui.setToolbarButtonState(id, state);
// 菜单
ui.registerMenuItem(config);
ui.unregisterMenuItem(id);
ui.showContextMenu(items, position);
// 对话框
ui.showDialog(config);
ui.showConfirm(message, options);
ui.showPrompt(message, options);
ui.showNotification(message, type);
ui.showToast(message, duration);
// 状态栏
ui.setStatusText(text);
ui.showProgress(value, max);
ui.hideProgress();
8.3.4 Events API
// 事件订阅
useAddSignal('eventName', callback);
useAddSignal('objectAdded', (obj) => console.log('添加:', obj));
useAddSignal('objectRemoved', (obj) => console.log('移除:', obj));
useAddSignal('objectSelected', (obj) => console.log('选中:', obj));
useAddSignal('sceneChanged', () => console.log('场景变化'));
useAddSignal('cameraChanged', (camera) => console.log('相机变化'));
// 事件派发
useDispatchSignal('eventName', data);
useDispatchSignal('customEvent', { message: 'hello' });
// 事件取消
useRemoveSignal('eventName', callback);
// 内置事件列表
const builtInEvents = [
// 场景事件
'objectAdded', 'objectRemoved', 'objectChanged',
'objectSelected', 'objectDeselected',
'sceneLoaded', 'sceneSaved', 'sceneCleared',
// 相机事件
'cameraChanged', 'cameraAnimationStart', 'cameraAnimationEnd',
// 渲染事件
'beforeRender', 'afterRender', 'resized',
// 加载事件
'loadStart', 'loadProgress', 'loadComplete', 'loadError',
// 工具事件
'toolActivated', 'toolDeactivated',
// 编辑事件
'historyChanged', 'undoPerformed', 'redoPerformed'
];
8.3.5 Storage API
// 获取Storage实例
const storage = this.app.storage;
// 本地存储
storage.local.set(key, value);
storage.local.get(key);
storage.local.remove(key);
storage.local.clear();
// 会话存储
storage.session.set(key, value);
storage.session.get(key);
// 插件存储(隔离的存储空间)
const pluginStorage = storage.plugin('my-plugin');
pluginStorage.set('settings', { theme: 'dark' });
pluginStorage.get('settings');
// 文件存储
await storage.file.save(blob, filename);
await storage.file.load(filename);
await storage.file.list(directory);
await storage.file.delete(filename);
8.4 创建UI组件
8.4.1 Vue组件示例
<!-- src/components/MyPanel.vue -->
<template>
<div class="my-panel">
<div class="panel-header">
<h3>{{ title }}</h3>
</div>
<div class="panel-content">
<!-- 选中对象信息 -->
<div v-if="selectedObject" class="object-info">
<p>名称: {{ selectedObject.name }}</p>
<p>类型: {{ selectedObject.type }}</p>
<p>位置: {{ formatPosition(selectedObject.position) }}</p>
</div>
<div v-else class="no-selection">
请选择一个对象
</div>
<!-- 操作按钮 -->
<div class="actions">
<n-button @click="doAction1">操作1</n-button>
<n-button @click="doAction2">操作2</n-button>
</div>
<!-- 设置选项 -->
<div class="settings">
<n-form>
<n-form-item label="选项1">
<n-switch v-model:value="settings.option1" />
</n-form-item>
<n-form-item label="选项2">
<n-slider v-model:value="settings.option2" :min="0" :max="100" />
</n-form-item>
<n-form-item label="选项3">
<n-color-picker v-model:value="settings.option3" />
</n-form-item>
</n-form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted, inject } from 'vue';
import { NButton, NForm, NFormItem, NSwitch, NSlider, NColorPicker } from 'naive-ui';
import { useAddSignal, useRemoveSignal } from '@astral3d/engine';
// 注入插件上下文
const context = inject('pluginContext');
// 响应式数据
const title = ref('我的面板');
const selectedObject = ref(null);
const settings = reactive({
option1: false,
option2: 50,
option3: '#ffffff'
});
// 格式化位置
const formatPosition = (pos) => {
return `(${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})`;
};
// 事件处理
const onSelectionChange = (objects) => {
selectedObject.value = objects[0] || null;
};
// 操作方法
const doAction1 = () => {
console.log('执行操作1');
context.emit('action1', { settings: settings });
};
const doAction2 = () => {
console.log('执行操作2');
if (selectedObject.value) {
// 对选中对象进行操作
selectedObject.value.visible = !selectedObject.value.visible;
}
};
// 生命周期
onMounted(() => {
useAddSignal('objectSelected', onSelectionChange);
});
onUnmounted(() => {
useRemoveSignal('objectSelected', onSelectionChange);
});
</script>
<style scoped>
.my-panel {
padding: 16px;
}
.panel-header {
border-bottom: 1px solid #eee;
margin-bottom: 16px;
}
.object-info {
background: #f5f5f5;
padding: 12px;
border-radius: 4px;
margin-bottom: 16px;
}
.no-selection {
color: #999;
text-align: center;
padding: 20px;
}
.actions {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.settings {
margin-top: 16px;
}
</style>
8.4.2 工具栏组件
<!-- src/components/MyToolbar.vue -->
<template>
<div class="my-toolbar">
<n-tooltip v-for="tool in tools" :key="tool.id">
<template #trigger>
<n-button
:type="activeTool === tool.id ? 'primary' : 'default'"
@click="selectTool(tool.id)"
>
<template #icon>
<n-icon :component="tool.icon" />
</template>
</n-button>
</template>
{{ tool.name }}
</n-tooltip>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { NButton, NTooltip, NIcon } from 'naive-ui';
import { MeasureIcon, AnnotateIcon, CutIcon } from './icons';
const tools = [
{ id: 'measure', name: '测量', icon: MeasureIcon },
{ id: 'annotate', name: '标注', icon: AnnotateIcon },
{ id: 'cut', name: '剖切', icon: CutIcon }
];
const activeTool = ref('');
const emit = defineEmits(['toolSelected']);
const selectTool = (id: string) => {
activeTool.value = activeTool.value === id ? '' : id;
emit('toolSelected', activeTool.value);
};
</script>
8.5 创建自定义工具
8.5.1 工具基类
// src/tools/BaseTool.ts
import { Viewer, useAddSignal, useRemoveSignal } from '@astral3d/engine';
export abstract class BaseTool {
protected viewer: Viewer;
protected active: boolean = false;
constructor(viewer: Viewer) {
this.viewer = viewer;
}
// 激活工具
activate() {
if (this.active) return;
this.active = true;
this.onActivate();
this.bindEvents();
}
// 停用工具
deactivate() {
if (!this.active) return;
this.active = false;
this.unbindEvents();
this.onDeactivate();
}
// 切换状态
toggle() {
if (this.active) {
this.deactivate();
} else {
this.activate();
}
}
// 子类实现
protected abstract onActivate(): void;
protected abstract onDeactivate(): void;
protected abstract bindEvents(): void;
protected abstract unbindEvents(): void;
// 销毁
dispose() {
this.deactivate();
}
}
8.5.2 测量工具示例
// src/tools/MeasureTool.ts
import * as THREE from 'three';
import { BaseTool } from './BaseTool';
export class MeasureTool extends BaseTool {
private points: THREE.Vector3[] = [];
private lines: THREE.Line[] = [];
private labels: HTMLElement[] = [];
private tempLine: THREE.Line | null = null;
protected onActivate() {
this.viewer.setCursor('crosshair');
this.showHelper();
}
protected onDeactivate() {
this.viewer.setCursor('default');
this.hideHelper();
this.clear();
}
protected bindEvents() {
this.viewer.domElement.addEventListener('click', this.onClick);
this.viewer.domElement.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('keydown', this.onKeyDown);
}
protected unbindEvents() {
this.viewer.domElement.removeEventListener('click', this.onClick);
this.viewer.domElement.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('keydown', this.onKeyDown);
}
private onClick = (event: MouseEvent) => {
const intersect = this.viewer.raycast(event.clientX, event.clientY);
if (intersect) {
this.addPoint(intersect.point);
}
};
private onMouseMove = (event: MouseEvent) => {
if (this.points.length === 0) return;
const intersect = this.viewer.raycast(event.clientX, event.clientY);
if (intersect) {
this.updateTempLine(intersect.point);
}
};
private onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
this.finishMeasure();
}
if (event.key === 'Delete') {
this.clear();
}
};
private addPoint(point: THREE.Vector3) {
this.points.push(point.clone());
if (this.points.length > 1) {
this.createSegment(
this.points[this.points.length - 2],
this.points[this.points.length - 1]
);
}
this.createPointMarker(point);
}
private createSegment(start: THREE.Vector3, end: THREE.Vector3) {
// 创建线段
const geometry = new THREE.BufferGeometry().setFromPoints([start, end]);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const line = new THREE.Line(geometry, material);
this.viewer.scene.add(line);
this.lines.push(line);
// 创建标签
const distance = start.distanceTo(end);
const midPoint = start.clone().add(end).multiplyScalar(0.5);
this.createLabel(midPoint, `${distance.toFixed(2)} m`);
}
private createLabel(position: THREE.Vector3, text: string) {
const label = document.createElement('div');
label.className = 'measure-label';
label.textContent = text;
const screenPos = this.viewer.worldToScreen(position);
label.style.left = `${screenPos.x}px`;
label.style.top = `${screenPos.y}px`;
this.viewer.domElement.parentElement?.appendChild(label);
this.labels.push(label);
}
private createPointMarker(position: THREE.Vector3) {
const geometry = new THREE.SphereGeometry(0.1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const marker = new THREE.Mesh(geometry, material);
marker.position.copy(position);
this.viewer.scene.add(marker);
}
private updateTempLine(endPoint: THREE.Vector3) {
if (this.tempLine) {
this.viewer.scene.remove(this.tempLine);
}
const startPoint = this.points[this.points.length - 1];
const geometry = new THREE.BufferGeometry().setFromPoints([startPoint, endPoint]);
const material = new THREE.LineDashedMaterial({
color: 0xff0000,
dashSize: 0.1,
gapSize: 0.1
});
this.tempLine = new THREE.Line(geometry, material);
this.tempLine.computeLineDistances();
this.viewer.scene.add(this.tempLine);
}
private finishMeasure() {
if (this.tempLine) {
this.viewer.scene.remove(this.tempLine);
this.tempLine = null;
}
this.points = [];
}
private clear() {
// 清除线段
this.lines.forEach(line => this.viewer.scene.remove(line));
this.lines = [];
// 清除标签
this.labels.forEach(label => label.remove());
this.labels = [];
// 清除临时线
if (this.tempLine) {
this.viewer.scene.remove(this.tempLine);
this.tempLine = null;
}
this.points = [];
}
private showHelper() {
// 显示辅助信息
}
private hideHelper() {
// 隐藏辅助信息
}
getTotalDistance(): number {
let total = 0;
for (let i = 1; i < this.points.length; i++) {
total += this.points[i - 1].distanceTo(this.points[i]);
}
return total;
}
}
8.6 插件通信
8.6.1 插件间通信
// 通过事件系统通信
// Plugin A
useDispatchSignal('pluginA:dataReady', { data: myData });
// Plugin B
useAddSignal('pluginA:dataReady', (payload) => {
console.log('收到Plugin A的数据:', payload.data);
});
// 通过插件API通信
// 获取其他插件实例
const otherPlugin = this.app.plugins.get('other-plugin');
if (otherPlugin) {
otherPlugin.someMethod();
}
// 插件服务注册
// Plugin A 注册服务
this.app.services.register('myService', {
getData: () => this.data,
setData: (data) => { this.data = data; }
});
// Plugin B 使用服务
const myService = this.app.services.get('myService');
const data = myService.getData();
8.6.2 与编辑器通信
// 发送消息到编辑器
this.app.postMessage({
type: 'PLUGIN_EVENT',
plugin: 'my-plugin',
action: 'action-name',
data: { ... }
});
// 接收编辑器消息
this.app.onMessage((message) => {
if (message.type === 'EDITOR_EVENT') {
// 处理编辑器消息
}
});
8.7 插件配置与设置
8.7.1 配置Schema
// 定义配置Schema
const configSchema = {
title: '插件设置',
type: 'object',
properties: {
enabled: {
type: 'boolean',
title: '启用',
default: true
},
color: {
type: 'string',
title: '颜色',
format: 'color',
default: '#ff0000'
},
size: {
type: 'number',
title: '大小',
minimum: 0,
maximum: 100,
default: 50
},
mode: {
type: 'string',
title: '模式',
enum: ['mode1', 'mode2', 'mode3'],
enumNames: ['模式1', '模式2', '模式3'],
default: 'mode1'
}
}
};
// 注册配置
this.app.settings.registerSchema('my-plugin', configSchema);
// 获取配置
const config = this.app.settings.get('my-plugin');
// 更新配置
this.app.settings.set('my-plugin', { color: '#00ff00' });
// 监听配置变化
this.app.settings.onChange('my-plugin', (newConfig, oldConfig) => {
console.log('配置变化:', newConfig);
this.applyConfig(newConfig);
});
8.8 打包与发布
8.8.1 构建配置
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import dts from 'vite-plugin-dts';
export default defineConfig({
plugins: [
vue(),
dts({
include: ['src/**/*.ts', 'src/**/*.vue']
})
],
build: {
lib: {
entry: 'src/index.ts',
name: 'MyPlugin',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'esm' : format}.js`
},
rollupOptions: {
external: ['vue', 'three', '@astral3d/engine', 'naive-ui'],
output: {
globals: {
vue: 'Vue',
three: 'THREE',
'@astral3d/engine': 'AstralEngine',
'naive-ui': 'NaiveUI'
}
}
}
}
});
8.8.2 发布到NPM
# 构建
pnpm build
# 登录NPM
npm login
# 发布
npm publish --access public
8.8.3 本地安装测试
# 在Astral3D项目中安装本地插件
pnpm add ../path/to/my-plugin
# 或使用link
cd my-plugin
pnpm link
cd ../Astral3D
pnpm link @astral3d/plugin-my-feature
8.9 本章小结
本章详细介绍了Astral3D的插件系统开发,主要内容包括:
- 插件系统架构:管理器、生命周期、插件类型
- 创建插件项目:项目结构、清单配置、入口文件
- 插件API:Viewer、Scene、UI、Events、Storage API
- UI组件开发:Vue组件、工具栏、面板
- 自定义工具:工具基类、测量工具示例
- 插件通信:插件间通信、与编辑器通信
- 配置管理:配置Schema、设置面板
- 打包发布:构建配置、NPM发布
通过本章的学习,读者应该能够独立开发Astral3D插件,扩展编辑器的功能。
下一章预告:第九章将介绍Astral3D的脚本运行时开发,包括脚本API、热更新机制、调试工具等内容。

浙公网安备 33010602011771号