当前位置: 首页 > news >正文

代理模式-对象的间接访问

 现在朋友圈有好多做香港代购的微商,大部分网民无法自己去香港购买想要的商品,于是委托这些微商,告诉他们想要的商品,让他们帮我们购买。我们只需要付钱给他们,他们就会去香港购买,然后把商品寄给我们。这就是一种代理模式。

1 代理模式概述

引入一个新的代理对象,在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者增添客户想要的额外服务。

图 代理模式结构图

  1. Subject:抽象主题角色。声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题进行编程。
  2. Proxy:代理主题角色。包含了对真实主题的引入,从而可以在任何时候操作真实主题对象。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅是单纯调用真实主题对象中的操作。
  3. RealSubject:真实主题角色。实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
public interface Subject {void buy(String goodsName);}public class RealSubject implements Subject{@Overridepublic void buy(String goodsName) {System.out.println("购买:" + goodsName);}}public class ProxyBuy implements Subject {private final RealSubject realSubject = new RealSubject();private void goHongKong() {System.out.println("前往香港");}@Overridepublic void buy(String goodsName) {goHongKong();realSubject.buy(goodsName);sendGoods();}private void sendGoods() {System.out.println("把商品寄送給客户");}
}public class Client {public static void main(String[] args) {Subject subject = new ProxyBuy();subject.buy("奶粉");System.out.println("----------");subject.buy("iphone");
//        运行结果:
//        前往香港
//        购买:奶粉
//        把商品寄送給客户
//        ----------
//        前往香港
//        购买:iphone
//        把商品寄送給客户}}

在实际开发中,代理类的实现比上述代码要复杂得到。代理模式根据其目的和实现方式的不同可分为很多类。

远程代理

为一个位于不同的地址空间的对象提供一个本地的代理对象。

虚拟代理

如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

保护代理

控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

缓存代理

为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

智能引用代理

当一个对象被引用时,提供一些额外的操作。例如将对象被调用的次数记录下来。

表 常用的5种代理模式

1.1 虚拟代理

对于一些占用系统资源较多或者加载时间较长的对象,可以给对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。

1.1.1 适用场景

1) 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间较短的代理对象来代表真实对象。在程序启动时,可以用代理对象代替真实对象初始化,大大加速系统的启动时间。

2) 当一个对象的加载是否耗费系统资源时。虚拟代理可以让那些占用大量内存或者处理起来非常复杂的对象推迟到使用它们的时候才场景,而在之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。

需求:在面试的时候,一般都最少有两轮面试,一面由项目经理负责,二面由技术总监面。而最终的录取结果是由技术总监决定的。(技术总监工资更高,如果每个面试者都由总监面,那么将会花费总监很多时间,进而给公司带来更多的花费)。

分析:技术总监属于占用资源较多的对象,不能频繁的调用。而产品经理占用的资源相对较小。在这个需求中,虚拟代理为产品经理。先一轮面试,把筛选后的名单给技术总监。

图 需求实现结构图

public interface Interviewer {boolean audition(String name, boolean isLastOne);}public class TechnicalDirector implements Interviewer{@Overridepublic boolean audition(String name, boolean isLastOne) {Random random = new Random();return random.nextInt() % 3 == 0;}}public class ProjectManager implements Interviewer{private final List<String> passList = new ArrayList<>();@Overridepublic boolean audition(String name, boolean isLastOne) {Random random = new Random();boolean pass = random.nextInt() % 5 == 0;if (pass) passList.add(name);if (isLastOne) cassTechnicalDirector();return pass;}/*** 一轮面试完成后,再叫技术总监来面试*/private void cassTechnicalDirector(){TechnicalDirector technicalDirector = new TechnicalDirector();Iterator<String> iterator = passList.iterator();List<String> tempList = new ArrayList<>();while (iterator.hasNext()) {String next = iterator.next();if (technicalDirector.audition(next, !iterator.hasNext())) {tempList.add(next);}}if (tempList.size() == 0) {System.out.println("没人通过面试");} else {System.out.println("以下人员通过面试:");System.out.println(tempList);}}
}public class Client {public static void main(String[] args) {Interviewer interviewer = new ProjectManager();for (int i = 0; i < 5000; i++) {interviewer.audition("路人" + i, i == 19);}
//        运行结果:
//        以下人员通过面试:
//        [路人5, 路人14, 路人16]}}

​​​​​​​1.2 Java动态代理

在传统的代理模式中,客户端通过Proxy类调用RealSubject类的request()方法,同时可以在代理类中封装其他方法。而代理类和真实主题类都应该是事先已经存在的。如果需要为不同的真实主题类提供代理类,或者代理一个真实主题类中不同的方法,都需要增加新的代理类,这将导致系统的类格式急剧增加。

动态代理可以让系统能够根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类,而且可以代理不同的方法。

从JDK1.3开始,Java提供了对动态代理的支持。

1.2.1 InvocationHandler接口

是代理处理程序类的实现接口。作为代理实例的调用处理者的公共父类,该接口只声明了一个方法:

invoke(proxy,method,args): 用于处理对代理实例的方法调用并返回相应结果。第1个参数表示代理类的实例,第2个参数表示需要代理的方法,第3个表示代理方法的参数数组。

1.2.2 Proxy类

图 Proxy类的部分方法

最常用的方法是:

  1. newProxyInstance(classLoader,interfaces,invocationHandler), 返回一个动态创建的代理实例,第1个参数表示代理类的类加载器,第2个参数表示代理类所实现的接口列表(与真实主题类的接口列表一致),第3个参数表示所指派的调用处理程序类。
  2. getProxyClass(classLoader,class[]),返回一个Class类型的代理类,第1个参数是代理类的类加载器,第2个参数表示代理类所实现的接口列表。

1.2.3 动态代理实战

创建动态代理的步骤如下:

1)和静态代理相同,首先定义一个抽象主机角色,来定义代理类和真实主题类的真实接口。

public interface BuyTicket {boolean buyTicket(String start, String end);}

2)创建被代理的类

public class Customer implements BuyTicket{@Overridepublic boolean buyTicket(String start, String end) {System.out.println("购买" + start + "到" + end + "的火车票");Random random = new Random();return random.nextInt()  % 2 == 0;}
}

3)创建InvocationHandler类

public class BuyTicketInvocationHandler implements InvocationHandler {private final BuyTicket buyTicket;public BuyTicketInvocationHandler(BuyTicket buyTicket) {this.buyTicket = buyTicket;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {int num = 0; // 最多尝试次数boolean res = false;while (++num < 10 && !res) {System.out.println("黄牛党第" + num + "次操作");res = (boolean)method.invoke(buyTicket, args);}return res;}
}

4)创建动态代理对象

public class Client {public static void main(String[] args) {BuyTicket buyTicket = new Customer();BuyTicketInvocationHandler invocationHandler = new BuyTicketInvocationHandler(buyTicket);BuyTicket buyTicket1 = (BuyTicket) Proxy.newProxyInstance(BuyTicket.class.getClassLoader(), new Class<?>[]{BuyTicket.class}, invocationHandler);boolean res = buyTicket1.buyTicket("深圳", "赣州");if (res) System.out.println("购票成功");else System.out.println("购票失败");
//        运行结果:
//        黄牛党第1次操作
//        购买深圳到赣州的火车票
//        黄牛党第2次操作
//        购买深圳到赣州的火车票
//        购票成功}}

2 远程代理

使得客户端可以访问远程主机(或另一个虚拟机)上的对象。远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求(或者是客户端不能直接访问远程主机中的业务对象)。

远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端完全可以认为被代理的远程对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。

图 远程代理示意图

客户端无需关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机的代理对象交互即可。

2.1 RMI

Remote Method Invocation,远程方法调用。Java通过这个机制实现远程代理,实现一个Java虚拟机中的对象调用另一个Java虚拟机中对象的方法。

客户端通过一个桩(Stub)(相当于代理类)对象与远程主机上的业务对象进行通信。而远程主机端有个Skeleton(骨架)对象来负责与Stub对象通信。RMI基本步骤如下:

图 RMI基本步骤

  1. 客户端发起请求,将请求转交至RMI客户端的Stub类。
  2. Stub类将请求的接口、方法、参数等信息进行序列化。
  3. 将序列化后的流使用Socket传输到服务端。
  4. 服务端将接收到的流转发至相应的Skeleton类。
  5. Skeleton类将信息反序列化后调用实际业务处理类。
  6. 业务处理类处理完毕后将结果返回给Skeleton。
  7. Skeleton将结果序列化,再通过Socket将流传送到客户端Stub。
  8. Stub将接收到的流进行反序列化,将反序列化后得到的结果返回给客户端调用者。

2.2 RMI 实战

实现RMI的调用分为两大步:1)发布RMI服务;2)调用RMI服务。

2.2.1 发布RMI服务

发布一个RMI服务只需要做三件事:

  1. 定义一个RMI接口。
  2. 编写RMI接口的实现类。
  3. 通过JNDI发布RMI服务。
// 必须继承java.rmi.Remote,此外每个接口必须声明抛出一个java.rmi.RemoteException 异常
public interface BuyService extends Remote {String buyGoods(String goodsName) throws RemoteException;}// 必须继承java.rmi.server.UnicastRemoteObject, 而且必须提供一个构造器,并且构造器
// 必须抛出java.rmi.server.UnicastRemoteObject 异常
public class BuyServiceImpl extends UnicastRemoteObject implements BuyService{protected BuyServiceImpl() throws RemoteException {}@Overridepublic String buyGoods(String goodsName) throws RemoteException {return "远程购买:" + goodsName;}
}public class JNDIPublisher {public static void main(String[] args) throws RemoteException, MalformedURLException {BuyService buyService = new BuyServiceImpl();int port = 8089; //rmi服务端口String url = "rmi://localhost:" + port + "/BuyService"; //服务类的寻址符LocateRegistry.createRegistry(port);Naming.rebind(url,buyService);}}

2.2.2 调用RMI服务

在另外一个项目(主机)中调用上面已发布的RMI服务。在调用服务时只有做两件事:

1)查找服务类的接口。

Object obj = Naming.lookup(url); // url 为上面定义的服务类的寻址符。

2)调用这个接口的方法。

调用接口方法有两个方法,1)通过反射;2)通过导入服务端的接口类(一定要导入,重写无效)。

public class RMICaller {public static void main(String[] args) {String url = "rmi://localhost:8089/BuyService";try {Object lookup = Naming.lookup(url);// 通过反射来调用方法Method method = lookup.getClass().getMethod("buyGoods",String.class);System.out.println(method.invoke(lookup, "奶粉"));;
//            运行结果:
//            远程购买:奶粉} catch (Exception e) {throw new RuntimeException(e);}}
}

3 代码模式优缺点

优点:

  1. 能协调调用者和被调用者,降低系统耦合度,符合迪米特法则。
  2. 客户端针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,复合开闭原则。
  3. 远程代理为位于不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提供系统的整体运行效率。
  4. 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  5. 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

缺点:

  1. 增加了代理对象,可能会造成请求的处理速度变慢。
  2. 实现代理需要额外的工作,有些代理模式的实现非常复杂。

4 适用场景

  1. 客户端对象需要访问远程主机中的对象。
  2. 需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象。例如一个对象需要很长时间才能完成加载时。
  3. 需要控制对一个对象的访问。
  4. 需要为某个被频繁访问的操作结果提供一个临时存储空间,以供对各客户端共享访问这些结果时。可以通过缓存代理。
  5. 需要为一个对象的访问提供一些额外的操作时。

相关文章:

代理模式-对象的间接访问

现在朋友圈有好多做香港代购的微商&#xff0c;大部分网民无法自己去香港购买想要的商品&#xff0c;于是委托这些微商&#xff0c;告诉他们想要的商品&#xff0c;让他们帮我们购买。我们只需要付钱给他们&#xff0c;他们就会去香港购买&#xff0c;然后把商品寄给我们。这就…...

汽车产业链面临重大变革 大运乘用车加强产业布局 助力低碳出行

当前&#xff0c;国家“双碳”战略的全面实施&#xff0c;全球绿色产业发展理念的不断加深以及汽车产品形态、交通出行模式、能源消费结构变革所呈现的发展机遇等诸多因素&#xff0c;持续推动新能源汽车产业全面转型提速。据悉&#xff0c;2022年&#xff0c;中国新能源汽车销…...

simulink与遗传算法结合求解TSP问题

前言&#xff1a;刚开始入门学习simulink&#xff0c;了解了基本的模块功能后想尝试从自己熟悉的领域入手&#xff0c;自己出题使用simulink搭建模型。选择的是TSP问题的遗传算法&#xff0c;考虑如何用simulink建模思想来实现一个简单TSP问题的遗传算法。 TSP问题描述 一个配…...

环境搭建-Ubuntu18.04.6系统TensorFlow BenchMark的GPU测试

1. 下载Ubuntu18.04.6镜像 登录阿里云官方镜像站&#xff1a;阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 2. 测试环境 Server OS&#xff1a;Ubuntu 20.04.6 LTS Kernel: Linux 5.4.0-155-generic x86-64 Docker Version&#xff1a;24.0.5, build ced0996 docker-com…...

C# 汇总区间

228 汇总区间 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围…...

加利福尼亚大学|3D-LLM:将3D世界于大规模语言模型结合

来自加利福尼亚大学的3D-LLM项目团队提到&#xff1a;大型语言模型 (LLM) 和视觉语言模型 (VLM) 已被证明在多项任务上表现出色&#xff0c;例如常识推理。尽管这些模型非常强大&#xff0c;但它们并不以 3D 物理世界为基础&#xff0c;而 3D 物理世界涉及更丰富的概念&#xf…...

HCIA实验四

一.实验要求&#xff1a; 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff1b;R4与其他所有直连设备间均使用共有IP&#xff1b; 2、R3 - R5/6/7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个网络配置OSPF环境&#xff0c;IP基于172.16.0.0/16网段划分&#x…...

常见的算法

查找算法 基本查找 Demo1 public static boolean basicSearch(int index,int[] arr){for (int i 0; i < arr.length; i) {if (indexarr[i]){return true;}}return false; } Demo2 //顺序查找&#xff0c;考虑重复&#xff0c;返回查找内容的索引 public static ArrayLis…...

Jetbrains 2023.2教程

IDEA 2023.2 激活演示 Pycharm 2023.2 激活演示 WebStorm 2023.2 激活演示 Clion 2023.2 激活演示 DataGrip 2023.2 PhpStorm 2023.1.4 激活演示&#xff08;2023.2尚未发布&#xff09; RubyMine 2023.2 激活演示 获取方式 仔细看每一个工具演示的图片 本文由 mdnice …...

OpenLayers入门,OpenLayers地图初始化时如何设置默认缩放级别、设置默认地图中心点、最大缩放级别和最小缩放级别以及默认坐标系

专栏目录: OpenLayers入门教程汇总目录 前言 OpenLayers地图初始化时如何设置默认缩放级别、初始化时设置默认地图中心点、设置最大缩放级别和最小缩放级别,超过缩放级别用户无法再放大和缩小,和设置默认坐标系。 二、依赖和使用 "ol": "^6.15.1"使用…...

css实现步骤条中的横线

实现步骤中的横线&#xff0c;我们使用css中的after选择器&#xff0c;content写空&#xff0c;然后给这个范围设定一个绝对定位&#xff0c;相当于和它设置伪类选择的元素的位置&#xff0c;直接看代码&#xff1a; const commonStyle useMemo(() > ({fontSize: 30px}),[]…...

【业务功能篇57】Springboot + Spring Security 权限管理 【上篇】

4.权限管理模块开发 4.1 权限管理概述 4.1.1 权限管理的意义 后台管理系统中&#xff0c;通常需要控制不同的登录用户可以操作的内容。权限管理用于管理系统资源&#xff0c;分配用户菜单、资源权限&#xff0c;以及验证用户是否有访问资源权限。 4.1.2 RBAC权限设计模型 …...

云计算需求激增带来的基础设施挑战及解决方案

云计算的指数级增长迅速改变了我们消费和存储数字信息的方式。随着企业和个人越来越依赖基于云的服务和数据存储&#xff0c;对支持这些服务的强大且可扩展的基础设施的需求已达到前所未有的水平。 云计算需求的快速增长 我们的日常生活越来越多地被新技术所渗透。流媒体服务、…...

R语言中的函数23:zoo::rollmean, rollmax, rollmedian, rollsum等等

文章目录 函数介绍rollmean()rollmax()rollmedianrollsum 函数介绍 rollmean(x, k, fill if (na.pad) NA, na.pad FALSE, align c("center", "left", "right"), ...)rollmax(x, k, fill if (na.pad) NA, na.pad FALSE, align c("cen…...

数据结构—数组和广义表

4.2数组 数组&#xff1a;按一定格式排列起来的&#xff0c;具有相同类型的数据元素的集合。 **一维数组&#xff1a;**若线性表中的数据元素为非结果的简单元素&#xff0c;则称为一维数组。 **一维数组的逻辑结构&#xff1a;**线性结构&#xff0c;定长的线性表。 **声明…...

服务器负载均衡算法有哪些

算法举例 服务器负载均衡算法是用于分配网络流量到多个服务器的策略&#xff0c;以实现负载均衡和提高系统性能。以下是一些常见的服务器负载均衡算法的详细说明&#xff1a; 轮询&#xff08;Round Robin&#xff09;算法&#xff1a; 轮询算法是最简单且常见的负载均衡算法之…...

2023年深圳杯数学建模B题电子资源版权保护问题

2023年深圳杯数学建模 B题 电子资源版权保护问题 原题再现&#xff1a; 版权又称著作权&#xff0c;包括发表权、署名权、修改权、保护作品完整权、复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权及应当由著作权人…...

Easyui中datagrid切换页码后,再次根据其他条件查询,重置为第一页,序号从1开始显示

Easyui中datagrid切换页码后&#xff0c;再次根据其他条件查询&#xff0c;无法将序号重置为1开始显示 1、查询按钮2、datagrid的查询方法3、datagrid点击分页4、重置方法 1、查询按钮 <a href"javascript:Query(1,true)" id"btnQuery" class"eas…...

随笔03 考研笔记整理

图源&#xff1a;文心一言 上半年的博文整理&#xff0c;下半年依然会更新考研类的文章&#xff0c;有需要的小伙伴看向这里~~&#x1f9e9;&#x1f9e9; 另外&#xff0c;这篇文章可能是我上半年的努力成果之一&#xff0c;因此仅关注博主的小伙伴能够查看它~~&#x1f9e…...

一次线上OOM问题的个人复盘

我们一个java服务上线后&#xff0c;偶尔会发生内存OOM(Out Of Memory)问题&#xff0c;但由于OOM导致服务不响应请求&#xff0c;健康检查多次不通过&#xff0c;最后部署平台kill了java进程&#xff0c;这导致定位这次OOM问题也变得困难起来。 最终&#xff0c;在多次review代…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...