聊城开发区建设局网站深圳网站建设选哪家
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:29
当前位置: 首页 > news >正文
聊城开发区建设局网站,深圳网站建设选哪家,论坛程序,wordpress+移动参考资料 曾探《JavaScript设计模式与开发实践》#xff1b;「设计模式 JavaScript 描述」中介者模式JavaScript 设计模式之中介者模式 定义 在我们生活的世界中#xff0c;每个人每个物体之间都会产生一些错综复杂的联系。在应用程序里也是一样#xff0c;程序由大大小小…参考资料 曾探《JavaScript设计模式与开发实践》「设计模式 JavaScript 描述」中介者模式JavaScript 设计模式之中介者模式 定义 在我们生活的世界中每个人每个物体之间都会产生一些错综复杂的联系。在应用程序里也是一样程序由大大小小的单一对象组成所有这些对象都按照某种关系和规则来通信。 平时我们大概能记住 10 个朋友的电话、30 家餐馆的位置。在程序里也许一个对象会和其他 10 个对象打交道所以它会保持 10 个对象的引用。当程序的规模增大对象会越来越多它们之间的关系也越来越复杂难免会形成网状的交叉引用。当我们改变或删除其中一个对象的时候很可能需要通知所有引用到它的对象。这样一来就像在心脏旁边拆掉一根毛细血管一般 即使一点很小的修改也必须小心翼翼如下图所示。 面向对象设计鼓励将行为分布到各个对象中把对象划分成更小的粒度有助于增强对象的可复用性但由于这些细粒度对象之间的联系激增又有可能会反过来降低它们的可复用性。 中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后所有的相关对象都通过中介者对象来通信而不是互相引用所以当一个对象发生改变时只需要通知中介者对象即可。中介者使各对象之间耦合松散而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系如下图所示。 在前面的图中如果对象 A 发生了改变则需要同时通知跟 A 发生引用关系的 B、D、E、F 这 4 个对象而在上图中使用中介者模式改进之后A 发生改变时则只需要通知这个中介者对象即可。 使用场景 DataXDataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。引用关系复杂 : 系统中 对象之间 存在 复杂的 引用关系 , 产生的 相互依赖关系 结构混乱 , 难以理解 ;改变行为 : 交互的 公共行为 , 如果 需要 改变行为 , 可以 增加新的 中介者 类 ; ( 通过增加新的中介者类 , 达到扩展的目的 )多人聊天室 就是一个 中介者模式 场景 , 一个人发言时 , 需要传达给每个人 , 如果没有聊天室 , 需要对每个人都说一遍 , 如果有中介者 , 就由中介者负责将发言传达给每个人 ; 现实中的中介者 1 机场指挥者 中介者也被称为调停者我们想象一下机场的指挥塔如果没有指挥塔的存在每一架飞机要和方圆 100 公里内的所有飞机通信才能确定航线以及飞行状况后果是不可想象的。现实中的情况是每架飞机都只需要和指挥塔通信。指挥塔作为调停者知道每一架飞机的飞行状况所以它可以安排所有飞机的起降时间及时做出航线调整。 2 博彩公司 在世界杯期间购买足球彩票博彩公司作为中介每个人只需和博彩公司发生关联博彩公司会根据所有人的投注情况计算好赔率彩民们赢了钱就从博彩公司拿输了钱就交给博彩公司。 中介者模式的例子——泡泡堂游戏 大家可能都还记得泡泡堂游戏现在我们来一起回顾这个游戏假设在游戏之初只支持两个玩家同时进行对战。 先定义一个玩家构造函数它有 3 个简单的原型方法Play.prototype.win、Play.prototype.lose 以及表示玩家死亡的 Play.prototype.die。 因为玩家的数目是 2所以当其中一个玩家死亡的时候游戏便结束, 同时通知它的对手胜利。 这段代码看起来很简单 function Player(name) {this.name namethis.enemy null; // 敌人 };Player.prototype.win function () {console.log(this.name won ); };Player.prototype.lose function () {console.log(this.name lost); };Player.prototype.die function () {this.lose();this.enemy.win(); };接下来创建 2 个玩家对象 const player1 new Player(玩家一); const player2 new Player(玩家二);给玩家相互设置敌人 player1.enemy player2; player2.enemy player1;当玩家 player1 被泡泡炸死的时候只需要调用这一句代码便完成了一局游戏 javascript player1.die();// 输出玩家一 lost、玩家二 won 然而真正的泡泡堂游戏至多可以有 8 个玩家并分成红蓝两队进行游戏。 为游戏增加队伍 现在我们改进一下游戏。因为玩家数量变多用下面的方式来设置队友和敌人无疑很低效 player1.partners [player1, player2, player3, player4]; player1.enemies [player5, player6, player7, player8]; Player5.partners [player5, player6, player7, player8]; Player5.enemies [player1, player2, player3, player4];所以我们定义一个数组 players 来保存所有的玩家在创建玩家之后循环 players 来给每个玩家设置队友和敌人: javascript const players []; 再改写构造函数 Player使每个玩家对象都增加一些属性分别是队友列表、敌人列表 、 玩家当前状态、角色名字以及玩家所在的队伍颜色 function Player(name, teamColor) {this.partners []; // 队友列表this.enemies []; // 敌人列表this.state live; // 玩家状态this.name name; // 角色名字this.teamColor teamColor; // 队伍颜色 };玩家胜利和失败之后的展现依然很简单只是在每个玩家的屏幕上简单地弹出提示 Player.prototype.win function () { // 玩家团队胜利console.log(winner: this.name); };Player.prototype.lose function () { // 玩家团队失败console.log(loser: this.name); };玩家死亡的方法要变得稍微复杂一点我们需要在每个玩家死亡的时候都遍历其他队友的生存状况如果队友全部死亡则这局游戏失败同时敌人队伍的所有玩家都取得胜利代码如下 Player.prototype.die function () { // 玩家死亡let all_dead true;this.state dead; // 设置玩家状态为死亡for (let i 0; i this.partners.length; i) { // 遍历队友列表if (this.partners[i].state ! dead) { // 如果还有一个队友没有死亡则游戏还未失败all_dead false;break;}}if (all_dead true) { // 如果队友全部死亡this.lose(); // 通知自己游戏失败for (let i 0; i this.partners.length; i) { // 通知所有队友玩家游戏失败this.partners[i].lose();}for (let i 0; i this.enemies.length; i) { // 通知所有敌人游戏胜利this.enemies[i].win();}} };最后定义一个工厂来创建玩家 const playerFactory function (name, teamColor) {const newPlayer new Player(name, teamColor); // 创建新玩家for (let i 0; i players.length; i) { // 通知所有的玩家有新角色加入if (players[i].teamColor newPlayer.teamColor) { // 如果是同一队的玩家players[i].partners.push(newPlayer); // 相互添加到队友列表newPlayer.partners.push(players[i]);} else {players[i].enemies.push(newPlayer); // 相互添加到敌人列表newPlayer.enemies.push(players[i]);}}players.push(newPlayer);return newPlayer; };现在来感受一下, 用这段代码创建 8 个玩家 //红队 var player1 playerFactory(皮蛋, red),player2 playerFactory(小乖, red),player3 playerFactory(宝宝, red),player4 playerFactory(小强, red);//蓝队 var player5 playerFactory(黑妞, blue),player6 playerFactory(葱头, blue),player7 playerFactory(胖墩, blue),player8 playerFactory(海盗, blue);让红队玩家全部死亡 player1.die(); player2.die(); player4.die(); player3.die();结果如下 makefileloser: 宝宝 loser: 皮蛋 loser: 小乖 loser: 小强 winner: 黑妞 winner: 葱头 winner: 胖墩 winner: 海盗玩家增多带来的困扰 现在我们已经可以随意地为游戏增加玩家或者队伍但问题是每个玩家和其他玩家都是紧紧耦合在一起的。在此段代码中每个玩家对象都有两个属性this.partners 和 this.enemies用来保存其他玩家对象的引用。当每个对象的状态发生改变比如角色移动、吃到道具或者死亡时都必须要显式地遍历通知其他对象。 在这个例子中只创建了 8 个玩家或许还没有对你产生足够多的困扰而如果在一个大型网络游戏中画面里有成百上千个玩家几十支队伍在互相厮杀。如果有一个玩家掉线必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家。游戏也许还有解除队伍和添加到别的队伍的功能红色玩家可以突然变成蓝色玩家这就不再仅仅是循环能够解决的问题了。面对这样的需求我们上面的代码可以迅速进入投降模式。 用中介者模式改造泡泡堂游戏 现在我们开始用中介者模式来改造上面的泡泡堂游戏 改造后的玩家对象和中介者的关系如下图所示。 首先仍然是定义 Player 构造函数和 player 对象的原型方法在 player 对象的这些原型方法 中不再负责具体的执行逻辑而是把操作转交给中介者对象我们把中介者对象命名为 playerDirector function Player(name, teamColor) {this.name name; // 角色名字this.teamColor teamColor; // 队伍颜色 this.state alive; // 玩家生存状态 };Player.prototype.win function () {console.log(this.name won ); };Player.prototype.lose function () {console.log(this.name lost); };/****************玩家死亡***********/Player.prototype.die function () {this.state dead;playerDirector.reciveMessage(playerDead, this); // 给中介者发送消息玩家死亡 };/*************移除玩家***********/Player.prototype.remove function () {playerDirector.reciveMessage(removePlayer, this); // 给中介者发送消息移除一个玩家 };/*************玩家换队***********/Player.prototype.changeTeam function (color) {playerDirector.reciveMessage(changeTeam, this, color); // 给中介者发送消息玩家换队 };再继续改写之前创建玩家对象的工厂函数可以看到因为工厂函数里不再需要给创建的玩家对象设置队友和敌人这个工厂函数几乎失去了工厂的意义 const playerFactory function (name, teamColor) {const newPlayer new Player(name, teamColor); // 创造一个新的玩家对象playerDirector.reciveMessage(addPlayer, newPlayer); // 给中介者发送消息新增玩家return newPlayer; };最后我们需要实现这个中介者 playerDirector 对象一般有以下两种方式。 利用发布—订阅模式。将 playerDirector 实现为订阅者各 player 作为发布者一旦 player 的状态发生改变便推送消息给 playerDirectorplayerDirector 处理消息后将反馈发送 给其他 player。在 playerDirector 中开放一些接收消息的接口各 player 可以直接调用该接口来给 playerDirector 发送消息player 只需传递一个参数给 playerDirector这个参数的目的是使 playerDirector 可以识别发送者。同样playerDirector 接收到消息之后会将处理结果反馈给其他 player。 这两种方式的实现没什么本质上的区别。在这里我们使用第二种方式playerDirector 开放一个对外暴露的接口 reciveMessage负责接收 player 对象发送的消息而 player 对象发送消息的时候总是把自身 this 作为参数发送给 playerDirector以便 playerDirector 识别消息来自于哪个玩家对象代码如下 const playerDirector (function () {const players {}, // 保存所有玩家operations {}; // 中介者可以执行的操作/ 新增一个玩家* param {Player} player 玩家/operations.addPlayer function (player) {const teamColor player.teamColor; // 玩家的队伍颜色// 如果该颜色的玩家还没有成立队伍则新成立一个队伍players[teamColor] players[teamColor] || [];players[teamColor].push(player); // 添加玩家进队伍};/** 移除一个玩家* param {Player} player 玩家/operations.removePlayer function (player) {const teamColor player.teamColor, // 玩家的队伍颜色teamPlayers players[teamColor] || []; // 该队伍所有成员for (let i teamPlayers.length - 1; i 0; i–) { // 遍历删除if (teamPlayers[i] player) {teamPlayers.splice(i, 1);}}};/** 玩家换队* param {Player} player 玩家* param {string} newTeamColor 队伍颜色/operations.changeTeam function (player, newTeamColor) { // 玩家换队operations.removePlayer(player); // 从原队伍中删除player.teamColor newTeamColor; // 改变队伍颜色operations.addPlayer(player); // 增加到新队伍中};/** 玩家死亡* param {Player} player 玩家*/operations.playerDead function (player) {const teamColor player.teamColor,teamPlayers players[teamColor]; // 玩家所在队伍let all_dead true;for (let i 0; i teamPlayers.length; i) {if (teamPlayers[i].state ! dead) {all_dead false;break;}}if (all_dead) { // 全部死亡for (let i 0; i teamPlayers.length; i) {teamPlayers[i].lose(); // 本队所有玩家 lose }for (const color in players) {if (color ! teamColor) {const teamPlayers players[color]; // 其他队伍的玩家for (let i 0; i teamPlayers.length; i) {teamPlayers[i].win(); // 其他队伍所有玩家 win }}}}};const reciveMessage function () {// arguments 的第一个参数为消息名称const message Array.prototype.shift.call(arguments); operations[message].apply(this, arguments);};return {reciveMessage}})();可以看到除了中介者本身没有一个玩家知道其他任何玩家的存在玩家与玩家之间的耦合关系已经完全解除某个玩家的任何操作都不需要通知其他玩家而只需要给中介者发送一个消息中介者处理完消息之后会把处理结果反馈给其他的玩家对象。我们还可以继续给中介者扩展更多功能以适应游戏需求的不断变化。 我们来看下测试结果 // 红队 var player1 playerFactory(皮蛋, red),player2 playerFactory(小乖, red),player3 playerFactory(宝宝, red),player4 playerFactory(小强, red);// 蓝队 var player5 playerFactory(黑妞, blue),player6 playerFactory(葱头, blue),player7 playerFactory(胖墩, blue),player8 playerFactory(海盗, blue);player1.die(); player2.die(); player3.die(); player4.die();运行结果如下。 皮蛋 lost 小乖 lost 宝宝 lost 小强 lost 黑妞 won 葱头 won 胖墩 won 海盗 won假设皮蛋和小乖掉线 player1.remove(); player2.remove(); player3.die(); player4.die(); 则结果如下。 宝宝 lost 小强 lost 黑妞 won 葱头 won 胖墩 won 海盗 won假设皮蛋从红队叛变到蓝队 player1.changeTeam( blue ); player2.die(); player3.die(); player4.die(); 则结果如下。 小乖 lost 宝宝 lost 小强 lost 黑妞 won 葱头 won 胖墩 won 海盗 won 皮蛋 won中介者模式的例子——购买商品 需求实现购买手机的页面在购买流程中可以选择手机的颜色以及输入购买数量同时页面中有两个展示区域分别向用户展示刚刚选择好的颜色和数量。还有一个按钮动态显示下一步的操作我们需要查询该颜色手机对应的库存如果库存数量少于这次的购买数量按钮将被禁用并且显示库存不足反之按钮可以点击并且显示放入购物车。 假设手机库存为 var goods {red: 3,blue: 6 }那么页面中会有一下几种场景 选择红色手机购买 4 个库存不足。选择蓝色手机购买 5 个库存充足可以加入购物车。没有输入购买数量的时候按钮将被禁用并显示相应提示。 那么基本上至少有5个节点 下拉选择框 colorSelect文本输入框 numberInput展示颜色信息 colorInfo展示购买数量信息 numberInfo决定下一步操作的按钮 nextBtn 开始编码 HTML代码 选择颜色: select idcolorSelectoption value请选择/optionoption valuered红色/optionoption valueblue蓝色/option /select 输入购买数量: input typetext idnumberInput / 您选择了颜色: div idcolorInfo/div 您输入了数量: div idnumberInfo/div button idnextBtn disabledtrue请选择手机颜色和购买数量/button接下来将分别监听 colorSelect 的 onchange 事件函数和 numberInput 的 oninput 事件函数然后在这两个事件中作出相应处理。 var colorSelect document.getElementById(colorSelect),numberInput document.getElementById(numberInput),colorInfo document.getElementById(colorInfo),numberInfo document.getElementById(numberInfo),nextBtn document.getElementById(nextBtn);var goods { // 手机库存 red: 3,blue: 6 }; colorSelect.onchange function () {var color this.value, // 颜色number numberInput.value,stock goods[color]; // 该颜色手机对应的当前库存colorInfo.innerHTML color;if (!color) {nextBtn.disabled true;nextBtn.innerHTML 请选择手机颜色;return;}if (((number - 0) | 0) ! number - 0) {nextBtn.disabled true;nextBtn.innerHTML 请输入正确的购买数量;return;}// 用户输入的购买数量是否为正整数if (number stock) { // 当前选择数量没有超过库存量 nextBtn.disabled true;nextBtn.innerHTML 库存不足;return;}nextBtn.disabled false;nextBtn.innerHTML 放入购物车; };对象之间的联系 考虑一下当触发了 colorSelect 的 onchange 之后会发生什么事情。首先我们要让 colorInfo 中显示当前选中的颜色然后获取用户当前输入的购买数量对用户的输入值进行一些合法性判断。再根据库存数量来判断 nextBtn 的显示状态。 numberInput.oninput function () {var color colorSelect.value, // 颜色number this.value, // 数量stock goods[color]; // 该颜色手机对应的当前库存numberInfo.innerHTML number;if (!color) {nextBtn.disabled true;nextBtn.innerHTML 请选择手机颜色;return;}if (((number - 0) | 0) ! number - 0) {nextBtn.disabled true;nextBtn.innerHTML 请输入正确的购买数量;return;}if (number stock) { // 当前选择数量没有超过库存量 nextBtn.disabled true;nextBtn.innerHTML 库存不足;return;}nextBtn.disabled false;nextBtn.innerHTML 放入购物车; };可能遇到的困难 虽然目前顺利完成了代码编写但随之而来的需求改变有可能给我们带来麻烦。假设现在要求去掉 colorInfo 和 numberInfo 这两个展示区域我们就要分别改动 colorSelect.onchange 和 numberInput.onput 里面的代码因为在先前的代码中这些对象确实是耦合在一起的。 那么现在我们页面中需要增加另一个下拉选择框代表选择手机内存我们需要计算颜色、内存和购买数量来判断 nextBtn 是显示库存不足还是放入购物车。 首先要增加两个 HTML 节点 选择内存: select idmemorySelectoption value请选择/optionoption value32G32G/optionoption value16G16G/option /select 您选择了内存: div idmemoryInfo/div script memorySelect document.getElementById(memorySelect), memoryInfo document.getElementById(memoryInfo) /script接下来修改表示存库的 JSON 对象以及修改 colorSelect 的 onchange 事件函数: var goods { // 手机库存red|32G: 3, // 红色 32G库存数量为 3 red|16G: 0,blue|32G: 1,blue|16G: 6 }; colorSelect.onchange function () { /// 除上述代码外还有以下判断var color this.value, // 颜色number numberInput.value,stock goods[color | memory]; // 该颜色手机对应的当前库存if (!memory) {nextBtn.disabled true;nextBtn.innerHTML 请选择内存大小;return;} }同样要改些 numberInput 事件的相关代码。 最后还要新增 memorySelect 的 onchange 事件函数 memorySelect.onchange function () {var color colorSelect.value,number numberInput.value,memory this.value,stock goods[color | memory];if (!color) {nextBtn.disabled true;nextBtn.innerHTML 请选择手机颜色;return;}if (!memory) {nextBtn.disabled true;nextBtn.innerHTML 请选择内存大小;return;}if (((number - 0) | 0) ! number - 0) {nextBtn.disabled true;nextBtn.innerHTML 请输入正确的购买数量;return;}if (number stock) { // 当前选择数量没有超过库存量nextBtn.disabled true;nextBtn.innerHTML 库存不足;return;}nextBtn.disabled false;nextBtn.innerHTML 放入购物车; }我们可以看到仅仅增加了一个内存的选择条件就需要修改如此多的代码这是因为在目前的实现中每个节点对象都是耦合在一起的改变或者增加任何一个节点对象都要通知到与其相关的对象。 引入中介者 现在引入中介者对象所有的节点对象只跟中介者通信。当下拉选择框 colorSelect、memorySelect 和文本输入框 numberInput 发生了事件行为时它们仅仅通知中介者它们被改变了同时把自身当作参数传入中介者以便中介者辨别是谁发生了改变。剩下的所有事情都交给中介者对象来完成。 var goods { // 手机库存 red|32G: 3,red|16G: 0,blue|32G: 1,blue|16G: 6 }; var mediator (function () {var colorSelect document.getElementById(colorSelect),memorySelect document.getElementById(memorySelect),numberInput document.getElementById(numberInput),colorInfo document.getElementById(colorInfo),memoryInfo document.getElementById(memoryInfo),numberInfo document.getElementById(numberInfo),nextBtn document.getElementById(nextBtn);return {changed: function (obj) {var color colorSelect.value, // 颜色 memory memorySelect.value,// 内存 number numberInput.value, // 数量 stock goods[color | memory]; // 颜色和内存对应的手机库存数量if (obj colorSelect) { // 如果改变的是选择颜色下拉框 colorInfo.innerHTML color;} else if (obj memorySelect) {memoryInfo.innerHTML memory;} else if (obj numberInput) {numberInfo.innerHTML number;}if (!color) {nextBtn.disabled true;nextBtn.innerHTML 请选择手机颜色;return;}if (!memory) {nextBtn.disabled true;nextBtn.innerHTML 请选择内存大小;return;}if (((number - 0) | 0) ! number - 0) {nextBtn.disabled true;nextBtn.innerHTML 请输入正确的购买数量;return;}nextBtn.disabled false;nextBtn.innerHTML 放入购物车;}} })(); // 事件函数: colorSelect.onchange function () {mediator.changed(this); }; memorySelect.onchange function () {mediator.changed(this); }; numberInput.oninput function () {mediator.changed(this); };可以想象某天我们又要新增一些跟需求相关的节点比如 CPU 型号那我们只需要稍稍改动 mediator 对象即可: var goods { // 手机库存red|32G|800: 3, // 颜色 red内存 32Gcpu800对应库存数量为 3red|16G|801: 0,blue|32G|800: 1,blue|16G|801: 6 }; var mediator (function () {var cpuSelect document.getElementById(cpuSelect);return {change: function (obj) {// 略var cpu cpuSelect.value,stock goods[color | memory | cpu];}}// 略if (obj cpuSelect) {cpuInfo.innerHTML cpu;} })();优缺点 优点 中介者模式使各个对象之间得以解耦以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身功能的实现对象之间的交互关系交给了中介者对象来实现和维护。 缺点 最大的缺点是系统中会新增一个中介者对象因为对象之间交互的复杂性转移成了中介者对象的复杂性使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。 总结 中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则是指一个对象应该尽可能少地了解另外的对象类似不和陌生人说话。如果对象之间的耦合性太高一个对象发生改变之后难免会影响到其他的对象跟“城门失火殃及池鱼”的道理是一样的。而在中介者模式里对象之间几乎不知道彼此的存在它们只能通过中介者对象来互相影响对方。 中介者模式可以非常方便地对模块或者对象进行解耦但对象之间并非一定需要解耦。在实际项目中模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产而不是堆砌模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。一般来说 如果对象之间的复杂耦合确实导致调用和维护出现了困难而且这些耦合度随项目的变化呈指数增长曲线那我们就可以考虑用中介者模式来重构代码。
- 上一篇: 聊城建设路小学网站网站开发去哪里找
- 下一篇: 聊城企业网站建设费用手机网站模板cms
相关文章
-
聊城建设路小学网站网站开发去哪里找
聊城建设路小学网站网站开发去哪里找
- 技术栈
- 2026年03月21日
-
聊城wap网站建设临沂建设规划局网站
聊城wap网站建设临沂建设规划局网站
- 技术栈
- 2026年03月21日
-
辽中网站建设泰安微信网站制作
辽中网站建设泰安微信网站制作
- 技术栈
- 2026年03月21日
-
聊城企业网站建设费用手机网站模板cms
聊城企业网站建设费用手机网站模板cms
- 技术栈
- 2026年03月21日
-
聊城手机网站建设解决方案高端网站设计高端网站制作
聊城手机网站建设解决方案高端网站设计高端网站制作
- 技术栈
- 2026年03月21日
-
聊城手机网站制作阿里云网站备案多少天
聊城手机网站制作阿里云网站备案多少天
- 技术栈
- 2026年03月21日
