开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

无影,会是接近未来的工作场景吗?

发表于 2021-09-15

简介: 云上办公、企业桌面、安全可溯,无需前期传统硬件投资,快速构建安全、高性能、低成本的企业桌面办公体系。可广泛应用于具有高数据安全管控、高性能计算等要求的安全办公、金融、设计、影视、教育等领域。

程序员:你为什么要带办公电脑回家?

提起21世纪的程序员我们能脑海中能浮现出什么形象?格子衫、POLO领、电脑包、996……无论是西二旗、知春路、望京还是陆家嘴、虹桥国际中心,早高峰的地铁上,十个人中有九位背着大大的双肩包,毫无疑问,里面是日日夜夜相伴的办公电脑。我们随机采访了100位“背包人”,试图了解他们与办公电脑的“爱恨情仇”:

“公司的电脑当然好用啊,性能比家里的高,运行速度快,我做动画渲染,不卡顿不丢失是最重要的。”

“不得不背呀,数据安全是红线,数据资产全部在公司内部工作站和机房存储中,用自己的设备是无法在家展开工作的。”

“其实还是挺辛苦的,夏天天气热,背包久了后背都是汗;时间长了肩膀也酸,最近驼背加重了。”

虽然大家普遍觉得辛苦,但是考虑到电脑性能与工作要求、数据资产安全还是会义无反顾每天背着自己的办公本回家,对于程序员来说,电脑不离手更是出差、旅行、度假伴侣。

其实对于企业本身来说,他们也有自己的顾虑,作为南京一家创业公司的管理人,晓丰讲到了自己和同事因为异地远程办公数据丢包差点延迟交付的事情,“惊出一身冷汗”,同时,因为疫情,上百名实习生与外包员工在家中无法正常开展工作,给公司运行带来了不小的困扰。

与此同时,有很多企业其实已经在尝试云上办公,在云端开启一台“超级电脑”给员工,让员工能随时居家办公,同时,这台“超级电脑”不受性能限制,既能支持办公,也能支持图像、视频制作等场景。

MORE VFX:《流浪地球》们是这样生产出来的

说起来MORE VFX,我们可能不太熟悉,但是提起《流浪地球》、《刺杀小说家》、《唐人街探案3》、《你好,李焕英》几乎无人不知,他们的后期特效就是MORE VFX主力提供的。单就《刺杀小说家》而言,MORE VFX在其中提供了超过95%的特效镜头,制作参与人数有七八百位。

在之前的制作过程中,设计制作公司往往面临着高峰期渲染算力不足、存储性能不够、数据传输瓶颈、素材安全风险等各种问题。对于MORE VFX而言,制作过程恰逢疫情期间,要如期交付项目,就需要员工在家远程办公。但是,一方面,大多员工家用电脑性能不能满足制作要求,另一方面项目数据资产全部都在公司的专用工作站和机房存储中,出于数据安全考虑不能拷贝到员工家庭电脑。

在这个背景下,MORE VFX选择了云桌面的模式,视效艺术家可以通过公网在家访问阿里云的云桌面实例,再通过专线到办公室工作站进行生产制作,制作渲染任务直接提交云渲染,实现全国不同地域城市乃至海外的的艺术家及视效总监通过阿里云远程在线生产、审看、渲染、协同。

阿里云在远程办公方案中提供了普通远程桌面工具所不具备的数据安全管控能力,如可溯源的水印功能、可调整设备准入策略、禁用文件上传下载、且支持手绘板等行业专用的制作外设,云桌面的用户权限管理功能也能保证每个艺术家仅能登陆自己的工作站进行远程制作。MORE VFX 在保证行业数据安全合规的前提下,实现远程生产制作的目标。

未来:云办公能成为现实吗?

阿尔文·托夫勒的“第三次浪潮”提出已经过去40年了,信息化时代正在一次次刷新我们的视野和想象。后工业社会互联网络正在改变我们的工作方式、生活方式、思考方式。在劳作方面,智人从单打独斗到集中作业,再到现在,我们看到了弹性工作的更多可能,不是无尽头的加班与无序的个人主义,这是在时间、效率与个人实现合理考量下围绕共同目标的计划与安排。

我们都曾幻想过未来,每个人都能自由安排自己的工作与生活时间而不必活在格子间里透过钢化玻璃才得以窥见一片蓝天;我们都能够从工作中获得摄入与提升而不单纯是消磨既有的知识与输出。

其实沉下心来想一想,说不定未来已来,安了一个无影云桌面,下班说走就走,让工作电脑留在工作场景,肩膀和双手得到解放,不用携带电脑,也能安全高效完成工作;不用担心电脑性能不够,电脑折损更新的费用可以忽略不计,为芯片、驱动、显卡、屏显花的钱可以省下来,外包、实习、协同作业都可以实现高效管理,对于开发者、设计者、计算者,最基础的硬件配置能够获得最高性能的使用,至于消费主义的那一套,让它随风去吧。

链接:www.aliyun.com/product/ecs…

无影云电脑 (ALibaba Cloud Workspace),是一种易用、安全、高效的云上办公模式。它支持快速便捷的桌面环境创建、部署、统一管控与运维。无需前期传统硬件投资,帮您快速构建安全、高性能、低成本的企业桌面办公体系。可广泛应用于具有高数据安全管控、高性能计算等要求的安全办公、金融、设计、影视、教育等领域。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

用好 Java 中的枚举,让你的工作效率飞起来!

发表于 2021-09-15

1.概览

在本文中,我们将看到什么是 Java 枚举,它们解决了哪些问题以及如何在实践中使用 Java 枚举实现一些设计模式。

enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类,更多内容可以自行查看其官方文档。

枚举在很多时候会和常量拿来对比,可能因为本身我们大量实际使用枚举的地方就是为了替代常量。那么这种方式由什么优势呢?

以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。

下面示例定义一个简单的枚举类型 pizza 订单的状态,共有三种 ORDERED, READY, DELIVERED状态:

1
2
3
4
5
6
7
ini复制代码package shuang.kou.enumdemo.enumtest;

publicenum PizzaStatus {
    ORDERED,
    READY,
    DELIVERED;
}

简单来说,我们通过上面的代码避免了定义常量,我们将所有和 pizza 订单的状态的常量都统一放到了一个枚举类型里面。

1
2
3
4
scss复制代码System.out.println(PizzaStatus.ORDERED.name());//ORDERED
System.out.println(PizzaStatus.ORDERED);//ORDERED
System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus

2.自定义枚举方法

现在我们对枚举是什么以及如何使用它们有了基本的了解,让我们通过在枚举上定义一些额外的API方法,将上一个示例提升到一个新的水平:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typescript复制代码publicclass Pizza {
    private PizzaStatus status;
    publicenum PizzaStatus {
        ORDERED,
        READY,
        DELIVERED;
    }

    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            returntrue;
        }
        returnfalse;
    }

    // Methods that set and get the status variable.
}

3.使用 == 比较枚举类型

由于枚举类型确保JVM中仅存在一个常量实例,因此我们可以安全地使用“ ==”运算符比较两个变量,如上例所示;此外,“ ==”运算符可提供编译时和运行时的安全性。

首先,让我们看一下以下代码段中的运行时安全性,其中“ ==”运算符用于比较状态,并且如果两个值均为null 都不会引发 NullPointerException。相反,如果使用equals方法,将抛出 NullPointerException:

1
2
scss复制代码if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);

对于编译时安全性,我们看另一个示例,两个不同枚举类型进行比较,使用equal方法比较结果确定为true,因为getStatus方法的枚举值与另一个类型枚举值一致,但逻辑上应该为false。这个问题可以使用==操作符避免。因为编译器会表示类型不兼容错误:

1
2
scss复制代码if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);

4.在 switch 语句中使用枚举类型

1
2
3
4
5
6
7
8
csharp复制代码public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return5;
        case READY: return2;
        case DELIVERED: return0;
    }
    return0;
}

5.枚举类型的属性,方法和构造函数

你可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。

下面,让我们扩展上面的示例,实现从比萨的一个阶段到另一个阶段的过渡,并了解如何摆脱之前使用的if语句和switch语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
typescript复制代码publicclass Pizza {

    private PizzaStatus status;
    publicenum PizzaStatus {
        ORDERED (5){
            @Override
            public boolean isOrdered() {
                returntrue;
            }
        },
        READY (2){
            @Override
            public boolean isReady() {
                returntrue;
            }
        },
        DELIVERED (0){
            @Override
            public boolean isDelivered() {
                returntrue;
            }
        };

        privateint timeToDelivery;

        public boolean isOrdered() {returnfalse;}

        public boolean isReady() {returnfalse;}

        public boolean isDelivered(){returnfalse;}

        public int getTimeToDelivery() {
            return timeToDelivery;
        }

        PizzaStatus (int timeToDelivery) {
            this.timeToDelivery = timeToDelivery;
        }
    }

    public boolean isDeliverable() {
        returnthis.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " +
          this.getStatus().getTimeToDelivery());
    }

    // Methods that set and get the status variable.
}

下面这段代码展示它是如何 work 的:

1
2
3
4
5
6
scss复制代码@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
    Pizza testPz = new Pizza();
    testPz.setStatus(Pizza.PizzaStatus.READY);
    assertTrue(testPz.isDeliverable());
}

6.EnumSet and EnumMap

6.1. EnumSet

EnumSet 是一种专门为枚举类型所设计的 Set 类型。

与HashSet相比,由于使用了内部位向量表示,因此它是特定 Enum 常量集的非常有效且紧凑的表示形式。

它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码。

EnumSet 是抽象类,其有两个实现:RegularEnumSet 、JumboEnumSet,选择哪一个取决于实例化时枚举中常量的数量。

在很多场景中的枚举常量集合操作(如:取子集、增加、删除、containsAll和removeAll批操作)使用EnumSet非常合适;如果需要迭代所有可能的常量则使用Enum.values()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typescript复制代码publicclass Pizza {

    privatestatic EnumSet<PizzaStatus> undeliveredPizzaStatuses =
      EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);

    private PizzaStatus status;

    publicenum PizzaStatus {
        ...
    }

    public boolean isDeliverable() {
        returnthis.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " +
          this.getStatus().getTimeToDelivery() + " days");
    }

    public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
        return input.stream().filter(
          (s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
            .collect(Collectors.toList());
    }

    public void deliver() {
        if (isDeliverable()) {
            PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
              .deliver(this);
            this.setStatus(PizzaStatus.DELIVERED);
        }
    }

    // Methods that set and get the status variable.
}

下面的测试演示了展示了 EnumSet 在某些场景下的强大功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ini复制代码@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
    assertTrue(undeliveredPzs.size() == 3);
}

6.2. EnumMap

EnumMap是一个专门化的映射实现,用于将枚举常量用作键。与对应的 HashMap 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组:

1
arduino复制代码EnumMap<Pizza.PizzaStatus, Pizza> map;

让我们快速看一个真实的示例,该示例演示如何在实践中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ini复制代码publicstatic EnumMap<PizzaStatus, List<Pizza>>
  groupPizzaByStatus(List<Pizza> pizzaList) {
    EnumMap<PizzaStatus, List<Pizza>> pzByStatus =
      new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);

    for (Pizza pz : pizzaList) {
        PizzaStatus status = pz.getStatus();
        if (pzByStatus.containsKey(status)) {
            pzByStatus.get(status).add(pz);
        } else {
            List<Pizza> newPzList = new ArrayList<Pizza>();
            newPzList.add(pz);
            pzByStatus.put(status, newPzList);
        }
    }
    return pzByStatus;
}

下面的测试演示了展示了 EnumMap 在某些场景下的强大功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ini复制代码@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    EnumMap<Pizza.PizzaStatus,List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
    assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
    assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
    assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}
  1. 通过枚举实现一些设计模式

7.1 单例模式

通常,使用类实现 Singleton 模式并非易事,枚举提供了一种实现单例的简便方法。

《Effective Java 》和《Java与模式》都非常推荐这种方式,使用这种方式方式实现枚举可以有什么好处呢?

《Effective Java》

“

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton的最佳方法。—-《Effective Java 中文版 第二版》

《Java与模式》

“

《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

下面的代码段显示了如何使用枚举实现单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csharp复制代码publicenum PizzaDeliverySystemConfiguration {
    INSTANCE;
    PizzaDeliverySystemConfiguration() {
        // Initialization configuration which involves
        // overriding defaults like delivery strategy
    }

    private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;

    public static PizzaDeliverySystemConfiguration getInstance() {
        return INSTANCE;
    }

    public PizzaDeliveryStrategy getDeliveryStrategy() {
        return deliveryStrategy;
    }
}

如何使用呢?请看下面的代码:

1
ini复制代码PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();

通过 PizzaDeliverySystemConfiguration.getInstance() 获取的就是单例的 PizzaDeliverySystemConfiguration

7.2 策略模式

通常,策略模式由不同类实现同一个接口来实现的。

这也就意味着添加新策略意味着添加新的实现类。使用枚举,可以轻松完成此任务,添加新的实现意味着只定义具有某个实现的另一个实例。

下面的代码段显示了如何使用枚举实现策略模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typescript复制代码publicenum PizzaDeliveryStrategy {
    EXPRESS {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in express mode");
        }
    },
    NORMAL {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in normal mode");
        }
    };

    public abstract void deliver(Pizza pz);
}

给 Pizza增加下面的方法:

1
2
3
4
5
6
7
scss复制代码public void deliver() {
    if (isDeliverable()) {
        PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
          .deliver(this);
        this.setStatus(PizzaStatus.DELIVERED);
    }
}

如何使用呢?请看下面的代码:

1
2
3
4
5
6
7
scss复制代码@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
    Pizza pz = new Pizza();
    pz.setStatus(Pizza.PizzaStatus.READY);
    pz.deliver();
    assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}
  1. Java 8 与枚举

Pizza 类可以用Java 8重写,您可以看到方法 lambda 和Stream API如何使 getAllUndeliveredPizzas()和groupPizzaByStatus()方法变得如此简洁:

getAllUndeliveredPizzas():

1
2
3
4
5
scss复制代码public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
    return input.stream().filter(
      (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
        .collect(Collectors.toList());
}

groupPizzaByStatus() :

1
2
3
4
5
6
7
swift复制代码publicstatic EnumMap<PizzaStatus, List<Pizza>>
  groupPizzaByStatus(List<Pizza> pzList) {
    EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
      Collectors.groupingBy(Pizza::getStatus,
      () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
    return map;
}
  1. Enum 类型的 JSON 表现形式

使用Jackson库,可以将枚举类型的JSON表示为POJO。下面的代码段显示了可以用于同一目的的Jackson批注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
typescript复制代码@JsonFormat(shape = JsonFormat.Shape.OBJECT)
publicenum PizzaStatus {
    ORDERED (5){
        @Override
        public boolean isOrdered() {
            returntrue;
        }
    },
    READY (2){
        @Override
        public boolean isReady() {
            returntrue;
        }
    },
    DELIVERED (0){
        @Override
        public boolean isDelivered() {
            returntrue;
        }
    };

    privateint timeToDelivery;

    public boolean isOrdered() {returnfalse;}

    public boolean isReady() {returnfalse;}

    public boolean isDelivered(){returnfalse;}

    @JsonProperty("timeToDelivery")
    public int getTimeToDelivery() {
        return timeToDelivery;
    }

    private PizzaStatus (int timeToDelivery) {
        this.timeToDelivery = timeToDelivery;
    }
}

我们可以按如下方式使用 Pizza 和 PizzaStatus:

1
2
3
ini复制代码Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));

生成 Pizza 状态以以下JSON展示:

1
2
3
4
5
6
7
8
9
json复制代码{
  "status" : {
    "timeToDelivery" : 2,
    "ready" : true,
    "ordered" : false,
    "delivered" : false
  },
  "deliverable" : true
}

有关枚举类型的JSON序列化/反序列化(包括自定义)的更多信息,请参阅Jackson-将枚举序列化为JSON对象。

10.总结

本文我们讨论了Java枚举类型,从基础知识到高级应用以及实际应用场景,让我们感受到枚举的强大功能。

  1. 补充

我们在上面讲到了,我们可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。

下面我通过一个实际的例子展示一下,当我们调用短信验证码的时候可能有几种不同的用途,我们在下面这样定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typescript复制代码publicenum PinType {

    REGISTER(100000, "注册使用"),
    FORGET_PASSWORD(100001, "忘记密码使用"),
    UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");

    privatefinalint code;
    privatefinal String message;

    PinType(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return"PinType{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

实际使用:

1
2
3
csharp复制代码System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());

Output:

1
2
3
ini复制代码100001
忘记密码使用
PinType{code=100001, message='忘记密码使用'}

这样的话,在实际使用起来就会非常灵活方便!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

linux系列之 告诉他,他根本不懂kill 简介 使用ki

发表于 2021-09-15

本文正在参与 “走过Linux 三十年”话题征文活动

简介

和很多程序员打过交道,这些程序员可能熟知for遍历的好几种写法,但是却对写出来的程序部署的环境一无所知。我敢打赌,在spring boot出现之后,已经很少有程序员知道tomcat到底是怎么运行的了。对于他们来说,运行一个jar包就完事了。

工具的先进性确实带给我们很多便利,也提升了程序员的开发效率,同时也降低了程序员的进入门槛。今天想和大家一起讨论一下,linux中的kill命令到底是做什么用的。

可能很很多小伙伴第一次接触kill命令是同事告诉他,把进程kill掉。那么kill真的是用来杀进程的吗?

使用kill来杀死进程

先来看一个kill最基本,也是最常见的应用就是杀死进程。在杀死进程之前,我们需要找到这个进程ID。

一般情况下是使用ps命令找到这个进程ID。加入这个进程ID=54321。

那么接下来就可以使用kill 54321来杀死这个进程了。

更资深一点的同学,可能还会使用kill -9 54321来强制杀死这个进程。

有没有更深入的用法呢?有的,一起来看看。

kill的深入用法

先看一下kill的命令参数到底有那些:

1
2
bash复制代码kill 
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

可以看到kill的参数是sig,也就是信号。也就是说kill的本质是向程序传递信号的。

如果使用 kill -l ,我们可以得到到底kill可以传递多少信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash复制代码kill -l 
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

总共64个信号,可能不同的kill版本,信号有所不同,但是基本上都覆盖了常用的信号。

下面是一些常用信号的含义:

1
2
3
4
5
6
7
bash复制代码HUP     1    终端断线
INT 2 中断(同 Ctrl + C)
QUIT 3 退出(同 Ctrl + \)
TERM 15 终止
KILL 9 强制终止
CONT 18 继续(与STOP相反, fg/bg命令)
STOP 19 暂停(同 Ctrl + Z)

怎么看kill的版本呢?

1
2
bash复制代码/bin/kill --version
kill from util-linux 2.23.2

如果kill不传sig,那么将会传默认的sig=TERM,也就是15。所以上面的kill 54321和 kill -15 54321是等价的。

一般情况下,我们优先使用SIGTERM信号。这是因为当程序收到了SIGTERM信号之后,会做一些程序的清理操作,或者说是优雅的关闭。

如果传入kill -9 也就是SIGKILL,那么应用程序将无法捕捉这个信号,从而导致程序强制被关闭,有可能会照成一些异常情况,比如数据还没有保存,数据传输还没有结束等等。

sig还有一个特殊值叫做0,如果传入0的话,那么并不会发送实际的信号,这个只是做异常检测用的。

pid就是process id,可以理解为是进程号。除了进程号之外,还可以传入一些特殊值,比如:

  • 0 表示当前进程group的所有进程
  • -1 表示所有PID>1的进程

还有一个特殊的pid=1,这个pid表示的是初始进程init,这个进程是不可被杀死的。

除了PID之外,我们看到kill还可以接受jobspec。job id可以使用jobs命令来列出。

僵尸进程和kill

上面讲到了pid=1的初始进程是不能被kill的。还有一种不能被kill的进程叫做僵尸进程。

僵尸进程是linux程序中一个非常独特的状态,它表示的是进程已经结束了,但是又还没有完全死亡,就像僵尸一样。

linux中的5大进程状态分别是:RUNNING:正在运行或等待运行状态,UNINTERRUPTABLE:不可中断阻塞状态,INTERRUPTABLE:可中断阻塞状态,STOPPED:挂起状态和ZOMBIE:僵尸状态。

那么什么是僵尸进程呢?

僵尸进程指的是程序在退出之后,该进程并不是马上消失的,而是会保留一个被称为僵尸的数据结构。这个数据结构很特殊,因为其没有内存空间,没有可执行的代码,当然也不可以被调度。它只是在进程列表中占有一个位置,记录了该进程退出时候的各种信息。

僵尸进程主要是保留进程退出的现场,供父进程或者系统管理员进行分析使用的,所以僵尸进程是交由父进程来进行收集和释放的。因为僵尸进程已经退出了,所以使用kill是没有用的,只能等待其父进程退出,才能真正的退出。

怎么查看僵尸进程呢?最简单的方法就是使用top命令:

1
2
3
4
5
yaml复制代码top - 14:34:38 up 305 days,  4:23,  2 users,  load average: 0.20, 0.29, 0.47
Tasks: 93 total, 1 running, 92 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.0 us, 0.7 sy, 0.0 ni, 97.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1882008 total, 525524 free, 311440 used, 1045044 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1382560 avail Mem

上面的输出,我们可以看到里面有0个zombie。

java thread dump

kill还有一个非常有用的地方就是生成java程序的thread dump,将当前java程序的线程信息dump出来,可以进行一些有用的分析,比如死锁分析等。

怎么对java进程做thread dump呢?很简单使用kill -3 命令即可:

1
bash复制代码kill -3 <pid>

从上面我们的介绍可以指定3代表的信号是SIGQUIT。这说明JVM内置了这个信号的捕捉,如果接收到了这个信号,则会dump当前的线程信息。

java thread dump在对java进行线程分析的时候非常有用。

总结

本文介绍了kill的深入用法和底层的工作原理,还介绍了kill的几个应用,希望下次有人再问你kill到底是什么的时候,大家都可以很自豪的告诉他!

本文已收录于 www.flydean.com/01-that-is-…

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java 17 正式发布,Oracle推出免费JDK许可证,

发表于 2021-09-15

Java 17 正式发布,Java迈入新时代

一个小时前,JAVA 17 正式发布,这是一个LTS(长期支持)版本,带来了不少有用的新特性。关于Java 17 的一些新特性,胖哥已经在往期的文章中进行了介绍,这里就不再赘述了,有兴趣的可以移步文章 Java 17 新特性确定。

Oracle 免费 JDK

JAVA 17 带来的不仅仅是新功能。更快的 LTS 节奏和免费的 Oracle JDK 使其成为有史以来支持最好的现代版本。Oracle JDK收费为人诟病,此次Oracle推出了Free Java License ,大致摘要:

  • Oracle 正在免费提供行业领先的Oracle JDK,包括所有季度安全更新。这包括商业和生产用途。
  • 新许可是“Oracle 免费条款和条件”(NFTC) 许可。此 Oracle JDK 许可证允许所有用户免费使用,甚至可以用于商业和生产用途。只要不收费,再分发是允许的。
  • 开发人员和组织现在无需点击即可轻松下载、使用、共享和重新分发 Oracle JDK。
  • Oracle 将从Oracle JDK 17开始提供这些免费版本和更新,并在下一个 LTS 版本之后继续提供整整一年。以前的版本不受此更改的影响。
  • Oracle 将继续按照自 Java 9 以来的相同版本和时间表提供GPL下的Oracle OpenJDK 版本。

Spring 支持

在此之前,Spring官方也宣布,明年发布的Spring framework 6 和Spring Boot 3 都将基于JAVA 17,你还要坚守JAVA 8吗?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

详细介绍java关键字enum 一、定义常量 二、 枚举类的

发表于 2021-09-14

hello,大家好,这里是可傥。今天,咱们来聊一下枚举enmu。枚举作为JDK1.5引入了新的类型,相信大家都不陌生,那么,具体有哪些用法呢,下面展开讲讲:

一、定义常量

在1.5之前,我们定义一个常量,通常用final关键字。而有了枚举之后,我们可以将一类常量定义在枚举类中,这样方便查找,代码逻辑也更清晰。如:

1
2
3
4
java复制代码public enum ErrorCodeEnum {
CODE_ACCOUNT_ERROR, CODE_TOKEN_ERROR,
CODE_PARAM_EMPTY, CODE_PARAM_ERROR, CODE_SUCCESS
}

这样找一类常量就会非常方便。

二、 枚举类的使用

这是比较常用的方法,在枚举类中添加自己的一些方法,也可以覆盖枚举的方法。这时,注意,先定义枚举,然后最后的枚举用分号和下面隔开。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
java复制代码/**
* @author ketang
* 错误代码枚举
*/
public enum ErrorCodeEnum {

CODE_SUCCESS("1", "success"),
CODE_FAILED("0", "failed"),
CODE_ACCOUNT_ERROR("1001", "账号错误"),
CODE_TOKEN_ERROR("1002", "token已过期"),
CODE_PARAM_EMPTY("1003", "必选参数为空"),
CODE_PARAM_ERROR("1004", "参数格式错误"),
FILE_NOT_EXIST("1101", "文件不存在"),
//注意分号
SYSTEM_UNKNOWN_ERROR("-1", "系统繁忙,请稍后再试....");
//成员变量
private String code;
private String desc;
//构造方法
ErrorCodeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
//普通方法
/**
*
* @param code
* @return
*/
public static ErrorCodeEnum getByCode(String code) {
for (ErrorCodeEnum errorCodeEnum : ErrorCodeEnum.values()) {
if(StringUtils.equals(errorCodeEnum.code, code)) {
return errorCodeEnum;
}
}
return null;
}

//getter方法
/**
* Getter method for property <tt>code</tt>.
*
* @return property value of code
*/
public String getCode() {
return this.code;
}

/**
* Getter method for property <tt>desc</tt>.
*
* @return property value of desc
*/
public String getDesc() {
return desc;
}

//覆盖方法
@Override
public String toString() {
return "ErrorCodeEnum{" + "code='" + code + '\'' + ", desc='" + desc + '\'' + '}';
}
}

将枚举类以这种方式展开,逻辑将会更加清晰。

三、枚举类实现接口

所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类,但是却可以去实现接口。如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
java复制代码public interface ErrorBase{ 
void print();
}
public enum ErrorCodeEnum implements ErrorBase{

CODE_SUCCESS("1", "success"),
CODE_FAILED("0", "failed"),
CODE_ACCOUNT_ERROR("1001", "账号错误"),
CODE_TOKEN_ERROR("1002", "token已过期"),
CODE_PARAM_EMPTY("1003", "必选参数为空"),
CODE_PARAM_ERROR("1004", "参数格式错误"),
FILE_NOT_EXIST("1101", "文件不存在"),
//注意分号
SYSTEM_UNKNOWN_ERROR("-1", "系统繁忙,请稍后再试....");
//成员变量
private String code;
private String desc;
//构造方法
ErrorCodeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}

//getter方法
/**
* Getter method for property <tt>code</tt>.
*
* @return property value of code
*/
public String getCode() {
return this.code;
}

/**
* Getter method for property <tt>desc</tt>.
*
* @return property value of desc
*/
public String getDesc() {
return desc;
}

//接口方法
@Override
public void print() {
System.out.println(this.code+":"+this.desc);
}

}

这是实现接口,还有一种方式则是在接口中组织枚举。

四、接口组织枚举

在接口中定义和该接口相关的枚举集合
如以下代码:

1
2
3
4
5
6
7
8
9
java复制代码public interface ErrorBase{ 
enum BaseErrorCode implements Food{
CODE_SUCCESS,CODE_FAILED
}
enum CommonErrorCode implements Food{
CODE_ACCOUNT_ERROR, CODE_TOKEN_ERROR,
CODE_PARAM_EMPTY
}
}

枚举的主要使用就讲到这里,在switch case判断中中,也可以将枚举当做条件加入。tips:switch中的条件也允许枚举哦。
枚举今天咱们就聊到这,这里是可傥,将会分享自己的所学以及所得,欢迎大家一起交流。CSDN地址为:blog.csdn.net/kaneandblan…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Activiti 学习(四)—— Activiti 结合实际

发表于 2021-09-14

流程实例

流程实例(ProcessInstance)代表流程定义的执行实例。一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例

流程定义和流程实例的图解:

流程实例.png

启动流程实例并添加 BusinessKey(业务标识)

流程定义部署在 activiti 后,就可以在系统中通过 activiti 去管理该流程的执行,执行流程表示流程的一次执行

比如部署系统出差流程后,如果某用户要申请出差这时就需要执行这个流程,如果另外一个用户也要申请出差则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例

启动流程实例时,指定的 businesskey,就会在 act_ru_execution 流程实例的执行表中存储businesskey

Businesskey,业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据

比如:出差流程启动一个流程实例,就可以将出差单的 id 作为业务标识存储到 activiti 中,将来查询 activiti 的流程实例信息就可以获取出差单的 id 从而关联查询业务系统数据库得到出差单信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码/**
* 添加业务 key 到 Activiti 的表
*/
@Test
public void addBusinessKey() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3. 启动流程,添加 BusinessKey
ProcessInstance instance = runtimeService.startProcessInstanceByKey("evection", "1001");
// 4. 输出
System.out.println("businessKey: " + instance.getBusinessKey());
}

流程定义的其他操作

1. 流程定义查询

查询流程相关信息,包含流程定义,流程部署,流程定义版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码public void queryProcessDefinition() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3. 查询当前所有的流程定义,返回流程定义信息的集合
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> definitions = definitionQuery.processDefinitionKey("evection")
.orderByProcessDefinitionVersion() // 进行排序
.desc() // 倒序
.list();
// 4. 输出信息
for (ProcessDefinition definition : definitions) {
System.out.println("流程定义 id = " + definition.getId());
System.out.println("流程定义名称 = " + definition.getName());
System.out.println("流程定义 key = " + definition.getKey());
System.out.println("流程定义版本 = " + definition.getVersion());
}
}

2. 流程定义删除

1
2
3
4
5
6
7
8
9
10
11
java复制代码public void deleteDeployment() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3. 通过部署 id 删除流程部署信息,如果该流程定义已有流程实例启动则删除时出错
String deploymentId = "2501";
repositoryService.deleteDeployment(deploymentId);
// 设置 true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置 false 非级别删除方式
// repositoryService.deleteDeployment(deploymentId, true);
}

3. 流程资源下载

我们把流程资源文件上传到数据库,如果其他用户想要查看这些资源文件,可以从数据库下载资源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码/**
* 下载资源文件,使用 Activiti 提供的 api 下载资源文件,保存到文件目录
*/
public void getDeployment() throws IOException {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3. 获取查询对象 ProcessDefinitionQuery,查询流程定义信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("evection")
.singleResult();
// 4. 通过流程定义信息,获取部署 id
String deploymentId = processDefinition.getDeploymentId();
// 5. 通过 RepositoryService,传递部署 id 参数,读取资源信息
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
// 6. 保存资源文件
File file = new File("g:/evectionFlow.bpmn");
FileOutputStream outputStream = new FileOutputStream(file);
IOUtils.copy(bpmnInput, outputStream);
bpmnInput.close();
outputStream.close();
}

4. 历史记录查询

即使流程定义已经删除了,流程执行的历史信息依然保存在 activiti 的 act_hi_* 相关的表中,我们还是可以查询流程执行的历史信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public void findHistoryInfo() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 HistoryService
HistoryService historyService = processEngine.getHistoryService();
// 3. 获取 actinst 表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
// 4. 查询 actinst 表
instanceQuery.processInstanceId("5001");
instanceQuery.orderByHistoricActivityInstanceStartTime();
// 5. 查询所有内容
List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
// 6. 输出
for (HistoricActivityInstance hi : activityInstanceList) {
System.out.println(hi.getActivityId());
System.out.println(hi.getActivityName());
System.out.println(hi.getProcessDefinitionId());
System.out.println(hi.getProcessInstanceId());
System.out.println("----------------------------");
}
}

5. 流程的挂起与激活

某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码/**
* 全部流程实例的挂起和激活
*/
@Test
public void suspendAllProcessInstance() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3. 查询流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("evection").singleResult();
// 4. 获取当前流程定义的实例是否都是挂起状态
boolean suspended = processDefinition.isSuspended();
// 5. 获取流程定义的 id
String definitionId = processDefinition.getId();
// 6. 如果是挂起,改为激活状态,如果是激活状态,改为挂起状态
if (suspended) {
repositoryService.activateProcessDefinitionById(definitionId, true, null);
} else {
repositoryService.suspendProcessDefinitionById(definitionId, true, null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码/**
* 挂起或激活单个流程实例
*/
@Test
public void suspendSingleProcessInstance() {
// 1. 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3. 得到当前流程实例的暂停状态
ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId("27501").singleResult();
boolean suspended = instance.isSuspended();
// 4. 获取流程实例 id
String instanceId = instance.getId();
// 5. 如果是挂起,改为激活状态,如果是激活状态,改为挂起状态
if (suspended) {
runtimeService.activateProcessInstanceById(instanceId);
} else {
runtimeService.suspendProcessInstanceById(instanceId);
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot学习笔记-----整合EasyExcel

发表于 2021-09-14

maven 引入依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>

导出数据

controller层

1
2
3
4
5
6
7
8
9
java复制代码
@Autowired
private DictService dictService;

@ApiOperation(value = "字典表数据的导出")
@GetMapping(value = "exportData")
public void exportData(HttpServletResponse response){
dictService.exportData(response);
}

service层:这里只给了 实现类里面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
java复制代码/**
* 字段数据 导出成 excel
* @param response
*/
@Override
public void exportData(HttpServletResponse response) {


try {
//设置返回的数据格式
response.setContentType("application/vnd.ms-excel");
//设置返回的数据编码
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("数据字典", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");

//从数据空中 查询字典数据列表
//dict代表数据库表的映射实体类 dictEeVo代表excel数据映射的实体类
//现在查询到设置的泛型是 dict 需要将数据列表的泛型变成dictEeVo 才能向Excel文件中写入数据
List<Dict> dictList = baseMapper.selectList(null);

List<DictEeVo> dictEeVoList = new ArrayList<>(dictList.size());

for(Dict dict : dictList) {
DictEeVo dictVo = new DictEeVo();

//将一个实体类的数据 复制到另一个实体类 【查出来映射的是数据库表的实体类 现在要映射成Excel表的实体类】
BeanUtils.copyProperties(dict,dictVo);
dictEeVoList.add(dictVo);
}
//excel写入数据 输出流 excel实体类映射 导出的模板名称
EasyExcel.write(response.getOutputStream(),DictEeVo.class).sheet("数据字典")
//要导出的数据列表
.doWrite(dictEeVoList);

} catch (IOException e) {
e.printStackTrace();
}
}

mapper层的查询数据 自己随便写:导出时的场景

image.png

导入数据

实现读取监听器 DictListener:读取excel文件的数据以后 通过监听器向数据表中一行一行的插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.yygh.cmn.mapper.DictMapper;
import com.atguigu.yygh.model.cmn.Dict;
import com.atguigu.yygh.model.cmn.DictEeVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DictListener extends AnalysisEventListener<DictEeVo> {

@Autowired
private DictMapper dictMapper;

//一行一行的读取数据
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {

//dict对应数据库表的实体类 dictEeVo代表Excel数据映射的实体类
Dict dict = new Dict();
//需要将获取的 Excel实体类的数据 变成数据库表映射的实体类数据 才能插入到数据库
BeanUtils.copyProperties(dictEeVo,dict);
//设置是否删除 当前这一行是我个人的业务
dict.setIsDeleted(0);
//一行一行的插入数据
dictMapper.insert(dict);
}

//导出数据完成调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("导入数据完成");
}
}

service层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码
@Autowired
private DictListener dictListener;


/**
* 字典数据的导入
* @param file
*/
@Override
public void importDictData(MultipartFile file) {
try {
//从excel中读取数据 输入流 excel实体类 监听器将数据插入到表
EasyExcel.read(file.getInputStream(),DictEeVo.class,dictListener).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}

controller层代码

1
2
3
4
5
6
java复制代码@ApiOperation(value = "字典表数据导入")
@PostMapping("importData")
public R importData(MultipartFile file){
dictService.importDictData(file);
return R.ok();
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PDF文件转换为图片(JPG/PNG) PDF文件转换为图片

发表于 2021-09-14

PDF文件转换为图片

安装扩展

imagick

  • 下载: PECL :: Package :: imagick (php.net)
  • 安装: 解压后把 php_imagick.dll 复制到配置的扩展目录中(默认是php根目录下的ext文件夹)
  • 配置: 在 php.ini 文件,增加 extension=php_imagick.dll

ImageMagick

  • 下载: ImageMagick – Download
  • 安装: 执行安装后将安装目录下的 CORE_RL_.dll 这些文件拷贝到php根目录下

Ghostscript

  • 下载: Ghostscript

完成上述扩展后重启

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
php复制代码# PDF转图片
public function pdf2img($pdf_path_folder, $pdf_name, $new_type = 'png')
{
$pdf_path = $pdf_path_folder . $pdf_name;
$file_name_no_suff = str_replace(strrchr($pdf_name, "."),"",$pdf_name); // 文件名(无后缀)
$new_type = $new_type == 'jpg' || $new_type == 'png' ? $new_type : 'jpg';
$img_path = $pdf_path_folder . $file_name_no_suff . '.' .$new_type;
try {
// 识别 PDF 为 前景图片 $img_front;
$img_front = $pdf_path_folder . $file_name_no_suff . '_fimg.' .$new_type;
$im = new \Imagick();
$im -> setResolution(300, 300); // 设置图像的分辨率
$im -> readImage($pdf_path);
$im -> setImageFormat($new_type);
$im -> setImageCompression(\Imagick::COMPRESSION_JPEG);
$im -> setImageCompressionQuality(100);
$im -> writeImage($img_front);
$im -> clear();
$im -> destroy();
// 读取 前景图片 $img_front;
$readImage = new \Imagick();
$readImage -> readImage($img_front);
// 生成背景图片(白底)
$blankPage = new \Imagick();
$blankPage -> newPseudoImage($readImage->getImageWidth(), $readImage->getImageHeight(), "canvas:white");
// 设置合并的位置
$blankPage -> compositeImage($readImage, \Imagick::COMPOSITE_ATOP, 0, 0);
// 合并
$blankPage = $blankPage->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
$blankPage -> writeImage($img_path);
$blankPage -> destroy();
} catch (Exception $e) {
var_dump(iconv("gbk",'utf-8',$e ->getMessage()));
}
return $img_path;
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot异步使用Async原理及线程池配置 前

发表于 2021-09-14

前言

在实际项目开发中很多业务场景需要使用异步去完成,比如消息通知,日志记录,等非常常用的都可以通过异步去执行,提高效率,那么在Spring框架中应该如何去使用异步呢

使用步骤

完成异步操作一般有两种,消息队列MQ,和线程池处理ThreadPoolExecutor

而在Spring4中提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接使用注解启用@Async,这个注解让我们在使用Spring完成异步操作变得非常方便

配置线程池类参数配置

自定义常量类

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class ConstantFiledUtil {
public static final String AUTHORIZATION_TOKEN = "authorizationToken";
/**
* kmall线程池名称
*/
public static final String KMALL_THREAD_POOL = "KmallThreadPool";

/**
* kmall线程名称前缀
*/
public static final String KMALL_THREAD_NAME_PREFIX = "kmall-thread-";
}

配置线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java复制代码@Configuration(proxyBeanMethods = false)
@EnableAsync //开启注解
public class KmallConfig {

@Bean(ConstantFiledUtil.KMALL_THREAD_POOL)
public ThreadPoolTaskExecutor FebsShiroThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(5);
//配置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(200);
//线程池维护线程所允许的空闲时间
executor.setKeepAliveSeconds(30);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix(ConstantFiledUtil.KMALL_THREAD_NAME_PREFIX);
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}

}

注意这里需要通过@EnableAsync开启异步否则无效

自定义线程任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public interface ILogService extends IService<Log> {

/**
* 分页搜索查询日志信息
* @param logParams
* @return
*/
IPage getSearchLogByPage(SearchLogParams logParams);

/**
* 保存日志
*
* @param logSubject
*/
@Async
void saveLog(LogSubject logSubject);
}

在需要异步执行的接口,或者方法上加上@Async注解此方法就是异步方法,在主程序中调用的化,就是异步方式单独线程执行

此注解表明saveLog方法进入的线程池是KmallThreadPool方法创建的。

我们也可以单独指定方法名称@Async("saveLogs")

这样在进行日记记录的时候就是单独线程执行每次请求都快速响应了,而耗时的操作都留给线程池中的线程去异步执行

总结

Spring中用@Async注解标记的方法,称为异步方法。在spring boot应用中使用@Async很简单:

  1. 调用异步方法类上或者启动类加上注解@EnableAsync
  2. 在需要被异步调用的方法外加上@Async
  3. 所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;

注意同一个类里面调用异步方法不生效:原因默认类内的方法调用不会被aop拦截,即调用方和被调用方是在同一个类中,是无法产生切面的,该对象没有被Spring容器管理。即@Async方法不生效

解决办法:

如果要使同一个类中的方法之间调用也被拦截,需要使用spring容器中的实例对象,而不是使用默认的this,因为通过bean实例的调用才会被spring的aop拦截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用这个引用调用本地的方法即可达到被拦截的目的
备注:这种方法只能拦截protected,default,public方法,private方法无法拦截。这个是spring aop的一个机制。

原理刨析

异步方法返回类型只能有两种:

  1. 当返回类型为void的时候,方法调用过程产生的异常不会抛到调用者层面,可以通过注AsyncUncaughtExceptionHandler来捕获此类异常
  2. 当返回类型为Future的时候,方法调用过程产生的异常会抛到调用者层面

注意如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

Spring异步线程池接口 TaskExecutor

看源码可知

1
2
3
4
java复制代码@FunctionalInterface
public interface TaskExecutor extends Executor {
void execute(Runnable var1);
}

它的实先类有很多如下:

  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
  3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
  5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装,
    ————————————————

文章参考

  1. Spring Boot(5) @Async异步线程池详解
  2. segmentfault.com/a/119000004…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

FastAPI官档精编005 - 第一步 第一步

发表于 2021-09-14

呆鸟云:发布本系列旨在推广 FastAPI 以及推进 FastAPI 中文官档翻译,目前,已完成 98% 的中文官档翻译,如果您对 FastAPI 有兴趣,可以为这个很赞的开源项目做些贡献,比如校译、翻译、审阅等。

开源项目的发展离不开大家的支持。当然最简单的就是在 Github 上点 Star 了。

如果觉得 FastAPI 不错,您也可以转发、转载本文,让更多人知道 Python 还有这么简单、快速的后端支持库。

FastAPI 官档地址:fastapi.tiangolo.com/zh/

下面先列出几个需要 Review 的 PR,希望大家多多参与。

  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…
  • github.com/tiangolo/fa…

以下为正文。


本指南将逐步介绍 FastAPI 的绝大部分功能。

本指南的每个章节循序渐进,但又有各自的主题,您可以直接阅读所需章节,解决特定的 API 需求。

本指南还是参考手册,供您随时查阅。

运行代码

本指南中的所有代码都能直接复制使用(实际上,这些代码都是经过测试的 Python 文件)。

要运行示例,只需把代码复制到 main.py,用以下命令启动 uvicorn:

1
2
3
4
5
6
7
console复制代码$ uvicorn main:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.

强烈建议您在本机编辑并运行这些代码。

只在有编辑器中输入代码时,您才能真正感受到 FastAPI 的优势,体验到需要输入的代码到底有多少,还有类型检查、自动补全等功能。


安装 FastAPI

第一步是安装 FastAPI。

学习本教程,需要安装所有可选依赖支持库:

1
console复制代码$ pip install fastapi[all]

……上述命令还安装了运行 FastAPI 应用的服务器 - uvicorn。

!!! note “笔记”

1
2
3
go复制代码您可以单独安装各个支持库。

需要把应用部署到生产环境时,首先要安装 FastAPI:

pip install fastapi

1
2

然后,还要安装服务器 `uvicorn`:

pip install uvicorn[standard]

1
2

按需单独安装其它可选依赖支持库。

高级用户指南

学完用户指南后,您还可以继续学习高级用户指南。

高级用户指南基于本指南,核心概念都一样,但介绍了更多功能。

建议您先阅读用户指南。

学完用户指南就能开发完整的 FastAPI 应用。然后,再使用高级用户指南中的功能扩展应用。

第一步

最简单的 FastAPI 文件所示如下:

1
2
3
4
5
6
7
8
Python复制代码from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
return {"message": "Hello World"}

复制代码到 main.py。

运行实时服务器:

1
2
3
4
5
6
7
console复制代码$ uvicorn main:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.

!!! note “笔记”

1
2
3
4
5
markdown复制代码`uvicorn main:app` 命令说明如下:

* `main`:`main.py` 是 Python **模块**;
* `app`:`main.py` 中 `app = FastAPI()` 创建的对象;
* `--reload`:代码更新后,重启服务器。仅在开发时使用。

输出信息如下:

1
vbnet复制代码INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

这是 FastAPI 应用在本机提供服务的 URL。

查看文档

打开浏览器访问 http://127.0.0.1:8000。

JSON 响应如下:

1
JSON复制代码{"message": "Hello World"}

API 文档

跳转到 http://127.0.0.1:8000/docs。

查看自动生成的(Swagger UI)API 文档:

image.png

备选 API 文档

跳转到 http://127.0.0.1:8000/redoc。

查看自动生成的(ReDoc)备选文档 :

image.png

OpenAPI

FastAPI 使用 OpenAPI (定义 API 的标准 )把所有 API 转换成概图。

概图

概图是对事物的定义与描述,不是实现功能的代码,只是抽象的描述。

API 概图

本指南中,OpenAPI 是定义 API 概图的规范。

这里的概图包括 API 路径、路径参数等。

数据概图

概图这一术语也指 JSON 等数据的结构。

本指南中,数据概图是指 JSON 属性、数据类型等。

OpenAPI 和 JSON Schema

OpenAPI 用于定义 API 概图。该概图包含由 JSON Schema 为 API 发送与接收的数据所做的定义。JSON Schema 是 JSON 数据概图标准。

查看 openapi.json

如果您对 OpenAPI 原始概图感兴趣,FastAPI 自动生成了描述所有 API 的 JSON (概图)。

直接查看:http://127.0.0.1:8000/openapi.json。

JSON 文件的开头如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JSON复制代码{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
...

OpenAPI 是干什么用的

OpenAPI 概图用于驱动 FastAPI 内置的两个 API 文档。

基于 OpenAPI 的备选方案还有很多,为 FastAPI 应用添加其它备选方案很容易。

OpenAPI 还可以用于自动生成和 API 通信的客户端代码。例如前端、移动端、物联网应用等。

分步小结

第一步:导入 FastAPI

1
Python复制代码from fastapi import FastAPI

FastAPI 是为 API 提供所有功能的 Python 类。

!!! note “技术细节”

1
2
3
go复制代码`FastAPI` 是直接继承自 `Starlette` 的类。

`FastAPI` 可以调用 Starlette 的所有功能。

第二步:创建 FastAPI 实例

1
Python复制代码app = FastAPI()

变量 app 是 FastAPI 的类实例。

该实例是创建所有 API 的主要交互对象。

这个 app 就是以下命令中由 uvicorn 引用的变量:

1
2
3
console复制代码$ uvicorn main:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

如果用下面的代码创建应用:

1
Python复制代码my_awesome_api = FastAPI()

把代码存入 main.py,要以如下方式调用 uvicorn:

1
2
3
console复制代码$ uvicorn main:my_awesome_api --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

第三步:创建路径操作

路径

路径是指 URL 的第一个反斜杠(/)及它之后的内容。

下列 URL 中:

1
arduino复制代码https://example.com/items/foo

……路径是:

1
bash复制代码/items/foo

!!! info “说明”

1
markdown复制代码**路径**通常也叫作**端点**或**路由**。

开发 API 时,路径是分离 concerns 和 resources 的主要方式。

操作

操作是指 HTTP 方法。

常用方法如下:

  • POST
  • GET
  • PUT
  • DELETE

罕见方法如下:

  • OPTIONS
  • HEAD
  • PATCH
  • TRACE

HTTP 协议支持使用上述任何一种(或多种)方法与路径通信。


开发 API 时,通常要使用特定 HTTP 方法执行特定操作。

常用方法:

  • POST:创建数据
  • GET:读取数据
  • PUT:更新数据
  • DELETE:删除数据

OpenAPI 把 HTTP 方法称为操作。

我们也称之为操作。

定义路径操作装饰器

1
Python复制代码@app.get("/")

@app.get("/") 告诉 FastAPI 下方函数以如下方式处理访问请求:

  • 请求路径为 /
  • 使用 get 操作

!!! info “@decorator 说明”

1
2
3
4
5
6
7
8
9
markdown复制代码`@something` 语法是 Python **装饰器**。

就像一顶放在函数上面的装饰帽(估计这个术语的命名就是这么来的)。

装饰器接收下方函数,并用它执行一些操作。

本例中,这个装饰器告诉 **FastAPI** 下方函数对应的**路径**是 `/` 及 `get` **操作**。

这就是***路径操作装饰器***。

其它常用操作如下:

  • @app.post()
  • @app.put()
  • @app.delete()

及罕见的操作:

  • @app.options()
  • @app.head()
  • @app.patch()
  • @app.trace()

!!! tip “提示”

1
2
3
4
5
6
7
markdown复制代码您可以随意使用任何操作(HTTP方法)。

**FastAPI** 不向操作强制附加任何特定含义。

本章中的说明仅是指导,不是要求。

例如,使用 GraphQL 时,通常所有操作都只使用 `post` 一种方法。

第四步:定义路径操作函数

路径操作函数由以下几部分组成:

  • 路径: /
  • 操作: get
  • 函数:装饰器下方的函数(位于 @app.get("/") 下方)
1
Python复制代码async def root():

路径操作函数就是 Python 函数。

FastAPI 每次接收使用 GET 方法访问 URL**/**的请求时都会调用这个函数。

本例中的路径操作函数是异步函数(async)。


也可以不使用 async def,把路径操作函数定义为普通函数:

1
Python复制代码def root():

!!! note “笔记”

1
markdown复制代码如果不清楚普通函数与异步函数的区别,请参阅异步:***等不及了?***一节中的内容。

第五步:返回内容

1
Python复制代码return {"message": "Hello World"}

路径操作函数可以返回字典、列表,以及字符串、整数等单值。

还可以返回 Pydantic 模型(稍后介绍)。

还有很多能自动转换为 JSON 的对象与模型(比如 ORM 等)。您可以尝试使用最喜欢的对象,FastAPI 很可能已经为其提供支持了。

小结

  • 导入 FastAPI
  • 创建 app 实例
  • 编写路径操作装饰器(如 @app.get("/"))
  • 编写路径操作函数(如 def root(): ...)
  • 运行开发服务器(如 uvicorn main:app --reload)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…529530531…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%