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

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


  • 首页

  • 归档

  • 搜索

python快速生成restful接口

发表于 2021-07-18

既然rest接口已经是统一规范了,为什么还要重复给每个资源写增删改查的逻辑呢。
于是我做了flask-sqlalchemy-rest,根据数据模型生成rest接口的库。

项目主要依赖了flask和sqlalchemy库,调用一个函数就能够给数据表生成规范的rest接口,简单又快速。

安装

1
ruby复制代码$ pip install flask_sqlalchemy_rest

示例代码,新建main.py,内容如下

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
python复制代码from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy_rest import Rest

# 创建一个flask应用
app = Flask(__name__)

# 配置sqlite数据库路径
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite"

# 创建SQLAlchemy对象
db = SQLAlchemy(app)

# 创建flask_sqlalchemy_rest对象
rest = Rest(app, db)

# 定义一个用户表
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name= db.Column(db.String)
age = db.Column(db.Integer)

# 生成数据库表结构
with app.app_context():
db.create_all()

# 给用户表绑定rest接口
rest.add_model(User)

# 运行flask应用
app.run()

运行代码

1
css复制代码python main.py

自动创建了以下几个接口

请求方法 url 成功返回json 描述
GET http://127.0.0.1:5000/api/user {“code”: 200, “msg”: “OK”, “data”: { list”: [{},{}], “page”: 1,”page_size”: 10,”total”: 2 }} 获取所有用户
GET http://127.0.0.1:5000/api/user/<user_id> {“code”: 200,”msg”: “OK”,”data”: {}} 获取指定id用户
POST http://127.0.0.1:5000/api/user {“code”: 200,”msg”: “OK”,”data”: {“id”:1}} 增加一个用户
PUT http://127.0.0.1:5000/api/user/<user_id> {“code”: 200,”msg”: “OK”,”data”: {“id”:1}} 修改指定id用户
DELETE http://127.0.0.1:5000/api/user/<user_id> {“code”: 200,”msg”: “OK”,”data”: {}} 删除指定id用户

GET请求中可以添加参数进行筛选、分页、排序等
示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码 // 获取姓名为tony、年龄为20的所有用户
[GET] http://127.0.0.1:5000/api/user?name=tony&age=20

// 获取所有用户,每页20个,第2页
[GET] http://127.0.0.1:5000/api/user?_page=2&_page_size=20

// 获取年龄大于20的所有用户
[GET] http://127.0.0.1:5000/api/user?age:gt=20
// 操作符gt(greter than),更多操作符可在github文档中查看

// 获取名字包含ton字符的所有用户
[GET] http://127.0.0.1:5000/api/user?name:ct=ton

// 获取所有用户,按年龄倒序
[GET] http://127.0.0.1:5000/api/user?_sort=age&_desc=1

添加身份验证

1
2
python复制代码# 创建flask_sqlalchemy_rest对象时,添加到auth_decorator参数
rest = Rest(app, db, auth_decorator=yout_auth_func)

更多文档在Github: github.com/qf0129/flas…

本文转载自: 掘金

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

抽象工厂模式(Abstract Factory Patter

发表于 2021-07-17

在设计模式中按照不同的处理方式共包含三大类:创建型模式、结构型模式和行为模式。

创建型模式

  1. 工厂方法模式(Factory Method Pattern)
  2. 抽象工厂模式(Abstract Factory Pattern)
  3. 建造者模式(Builder Pattern)
  4. 原型模式(Prototype Pattern)
  5. 单例模式(Singleton Pattern)

工厂方法模式引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题,但由于工厂方法模式中每个工厂只创建一类具体类的对象,这将会导致系统当中的工厂类过多,这势必会增加系统的开销。此时,我们可以考虑将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产,这就是我们本文要说的“抽象工厂模式”的基本思想。

该模式中包含的角色和职责

抽象工厂(AbstractFactory)角色

抽象工厂,用于声明抽象定义具抽象产品的方法。比如获取电脑的Cpu,Mouse,Keyboard等抽象产品。

具体工厂(ConcreteFactory)角色

定义抽象工厂定义的方法。生成具体的产品。比如DellCpu,DellMouse,DellKeyboard这类具体产品

抽象产品(AbstractProduct)角色

抽象产品,定义一类产品对象的接口。

具体产品(ConcreteProduct)角色

具体的产品实现,比如DellCpu,DellMouse,DellKeyboard这些具体的产品信息定义类。

实例的的UML图

image.png

下面是具体的代码

抽象工厂(AbstractFactory)产品角色

Computer

1
2
3
4
5
6
7
8
9
csharp复制代码/**
* @author yangqiang
* @date 2021-07-16 17:42
*/
public interface Computer {
   Cpu getCpu();
   Keyboard getKeyboard();
   Mouse getMouse();
}

具体工厂(ConcreteFactory)角色

DellFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:22
*/
public class DellFactory  implements Computer {
​
   @Override
   public Cpu getCpu() {
       return new DellCpu();
  }
​
   @Override
   public Keyboard getKeyboard() {
       return new DellKeyboard();
  }
​
   @Override
   public Mouse getMouse() {
       return new DellMouse();
  }
}

抽象产品(AbstractProduct)角色

Cpu

1
2
3
4
5
6
7
csharp复制代码/**
* @author: yangqiang
* @create: 2021-07-16 19:30
*/
public interface Cpu {
   void operation();
}

Mouse

1
2
3
4
5
6
7
csharp复制代码/**
* @author: yangqiang
* @create: 2021-07-16 19:29
*/
public interface Keyboard {
   void operation();
}

Keyboard

1
2
3
4
5
6
7
csharp复制代码/**
* @author yangqiang
* @date 2021-07-16 19:22
*/
public interface Mouse {
   void operation();
}

具体产品(ConcreteProduct)角色

DellCpu

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:34
*/
public class DellCpu implements Cpu {
   @Override
   public void operation() {
       System.out.println("this is DellCpu");
  }
}

DellMouse

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 20:40
*/
public class DellMouse implements Mouse {
   @Override
   public void operation() {
       System.out.println("this is DellMouse");
  }
}

DellKeyboard

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:56
*/
public class DellKeyboard implements Keyboard {
   @Override
   public void operation() {
       System.out.println("this is DellKeyboard");
  }
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码/**
* @author yangqiang
* @date 2021-07-16 17:42
*/
public class ComputerTest {
   public static void main(String[] args) {
       DellFactory dellFactory = new DellFactory();
       dellFactory.getCpu().operation();
       dellFactory.getKeyboard().operation();
       dellFactory.getMouse().operation();
  }
}

扩展

比如这时我们需要增加一个惠普电脑产品只需要新增一个HpFactory

HpFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:22
*/
public class HpFactory implements Computer {
​
   @Override
   public Cpu getCpu() {
       return new DellCpu();
  }
​
   @Override
   public Keyboard getKeyboard() {
       return new DellKeyboard();
  }
​
   @Override
   public Mouse getMouse() {
       return new DellMouse();
  }
}

HpCpu

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:34
*/
public class HpCpu implements Cpu {
   @Override
   public void operation() {
       System.out.println("this is HpCpu");
  }
}

HpMouse

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 20:40
*/
public class HpMouse implements Mouse {
   @Override
   public void operation() {
       System.out.println("this is HpMouse");
  }
}

HpKeyboard

1
2
3
4
5
6
7
8
9
10
typescript复制代码/**
* @author yangqiang
* @date 2021-07-16 19:56
*/
public class HpKeyboard implements Keyboard {
   @Override
   public void operation() {
       System.out.println("this is HpKeyboard");
  }
}

实例的UML图也就变成了这样

image.png

抽象工厂模式是工厂方法模式的进一步延伸, 由于它提供了功能更为强大的工厂类并且具备较好的可扩展性, 在软件开发中得以广泛应用, 尤其是在一些框架和API类库的设计中, 例如在Java语言的AWT( 抽象窗口工具包) 中就使用了抽象工厂模式, 它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。 抽象工厂模式也是在软件开发中最常用的设计模式之一。

抽象工厂模式在Spring中的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package org.springframework.aop.framework;
​
public interface AopProxyFactory {
​
  /**
   * Create an {@link AopProxy} for the given AOP configuration.
   * @param config the AOP configuration in the form of an
   * AdvisedSupport object
   * @return the corresponding AOP proxy
   * @throws AopConfigException if the configuration is invalid
   */
  AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
​
}

AopProxyFactory 是个抽象的生产AopProxy的工厂 AopProxy 可以是由JDK动态代理 也可以是由CGLIB动态代理

所以AopProxy 也是一个抽象的类。具体的代理对象由AopProxyFactory的子类工厂DefaultAopProxyFactory去进行实现生成 当然最好是每个 不同的代理对象有其对应的代理工厂

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
java复制代码​
​
package org.springframework.aop.framework;
​
import java.io.Serializable;
import java.lang.reflect.Proxy;
​
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
import org.springframework.util.ClassUtils;
​
​
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
​
  private static final long serialVersionUID = 7930414337282325166L;
​
​
  @Override
  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
     /*
      * 下面的三个条件简单分析一下:
      *
      *   条件1:config.isOptimize() - 是否需要优化,
      *   条件2:config.isProxyTargetClass() - 检测 proxyTargetClass 的值,
      *         前面的代码会设置这个值
      *   <aop:aspectj-autoproxy proxy-target-class="true"> 通过这种方式设置
      *   条件3:hasNoUserSuppliedProxyInterfaces(config)
      *         - 目标 bean 是否实现了接口
      */
​
     if (!NativeDetector.inNativeImage() &&
          (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
           throw new AopConfigException("TargetSource cannot determine target class: " +
                 "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
           return new JdkDynamicAopProxy(config);
        }
        // 创建 CGLIB 代理,ObjenesisCglibAopProxy 继承自 CglibAopProxy
        return new ObjenesisCglibAopProxy(config);
    }
     else {
        // 创建 JDK 动态代理
        return new JdkDynamicAopProxy(config);
    }
  }
​
  /**
   * Determine whether the supplied {@link AdvisedSupport} has only the
   * {@link org.springframework.aop.SpringProxy} interface specified
   * (or no proxy interfaces specified at all).
   */
  private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
     Class<?>[] ifcs = config.getProxiedInterfaces();
     return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
  }
​
}

优点

  • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。 由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
  • 当一个族中的多个对象被设计成一起工作时, 它能够保证客户端始终只使用同一个族中的对象。
  • 增加新的具体工厂很方便,比如增加一个HpFactory,无须修改已有系统,符合“开闭原则”。
缺点
  • 增加新的抽象产品时会比较麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便。比如我要增加一个电源抽象产品。这时候需要改动的地方有。Computer(抽象工厂),DellFactory(具体工厂),以及新增一个具体产品类(PowerSupply),这种情况不符合开闭原则,对于产品类的扩展不友好。
适用场景
  • 一个系统不应当依赖于具体类实例如何被创建、 组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
  • 系统中有多于一个的实现工厂 比如DellFactory和HpFactory。如果是一个工厂的话可能使用工厂方法模式会比较好用。多于一个时在抽象工厂模式下 扩展会更加方便。
  • 属于同一个工厂下的具体产品一般都会一起使用。比如说DellFactory生成的DellMouse,DellCpu,DellKeyboard。都会在同一个Computer中体现出来。这个约束需要在系统的设计中体现出来
  • 系统结构设计稳定。一旦结构确定后。不会增加新的产品类型。因为抽象工厂模式对于新的产品类型扩展不是很友好。

本文转载自: 掘金

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

【物联网】使用ESP8266(基于官方SDK20)接入阿里

发表于 2021-07-17

提醒:本文写于2018年8月,由于ESP8266的SDK和阿里云平台更新迭代很快,可能有部分信息已经过时,以下方案仅做参考。

引言

作为物联网开发者,ESP8266应该一点都不陌生了。只需十几块钱淘宝一个小开发板,就可以连上Wi-Fi接入互联网,尽情享受从手机端或Web端控制设备的乐趣。ESP8266接入Wi-Fi是没问题,但是用户端不能直接设计成与ESP8266通信,还是需要一个中心服务器作为用户端和设备端的代理。一种方案是自己搭建设计这么一个中心服务器,只是费时费力;另一种方案,就是直接使用大厂提供的物联网平台服务,使设计方案PaaS化。

上网搜寻物联网平台方案,百度、阿里、腾讯早就推出了自己的物联网平台了,同时也收集了一点其他公司的平台。之后我就对这三大平台以及一些常见的平台简单评估了一下。最后还是选择了阿里云物联网平台(以下简称阿里云IoT hub)深入了解。

那么,关于ESP8266和阿里云IoT hub,首先要告诉大家,乐鑫官方github上已经有了「ESP8266 对接阿里云」的repository了,github:github.com/espressif/e…

但是!当我下载下来并经过一阵焦头烂额的编译测试后,依旧没法成功编译!最最最关键的是,不支持安信可ESP8266 IDE,似乎是因为修改了顶层Makfile文件。

一气之下!我根据阿里云IoT hub提供的文档,首先使用Python脚本模拟设备对接阿里云IoT Hub,然后使用ESP8266尝试连接MQTT Broker,最后使用ESP8266基于官方SDK,自己弄了一个esp8266 app,接入了阿里云物联网平台。github:github.com/AngelLiang/…

适合读者

本文适合有ESP8266开发经验的读者阅读,如果熟悉安信可ESP8266 IDE更佳。熟悉阿里云IoT hub和开发环境的读者可以直接跳到「四、下载aliyun_mqtt_app并导入」小节。

一、获取阿里云IoT设备认证三元组

本小节主要讲如何获取阿里云IoT hub设备认证三元组,熟悉的读者可以跳过了。

第一步:开通平台

首先,需要进入阿里云IoT hub控制台进行操作,如果没有开通直接开通即可,免费。控制台连接:www.aliyun.com/product/iot

物联网平台

第二步:创建产品

开通后我们首先需要创建产品,产品名称随便输入即可,其他默认。

创建产品

第三步:创建设备

然后是创建设备,随便输入一个DeviceName即可。

创建设备

第四步:获取认证三元组

最后就得到了设备认证三元组:ProductKey、DeviceName和DeviceSecret。

获取认证三元组

二、安信可ESP8266 IDE

下面是如何搭建安信可ESP8266 IDE环境的文档,熟悉的读者可以直接跳过了:

  • 如何安装安信可一体化开发环境:wiki.ai-thinker.com/ai_ide_inst…
  • 如何使用安信可 ESP 系列一体化开发环境:wiki.ai-thinker.com/ai_ide_use
  • 如何为 ESP 系列模组烧录固件:wiki.ai-thinker.com/esp_downloa…

三、ESP8266官方SDK

最后是如何下载ESP8266官方SDK,可以到乐鑫官网或github或者下载。熟悉的读者也可以直接跳过。本人开发所使用的SDK是目前最新的版本:ESP8266_NONOS_SDK-2.2.1 ,aliyun_mqtt_app理论上支持SDK 2.0+。
(2018-11-02更新:github已经更新到基于SDKv3.0.0版本,SDKv2.0和SDKv3.0不兼容,如果编译报错请参考这篇博客。)

下载官方SDK后,把driver_lib、examples和third_party三个文件夹压缩备份再删除,以免编译的时候出现干扰信息。

工程目录

四、下载aliyun_mqtt_app并导入

下载aliyun_mqtt_app并拷贝aliyun_mqtt_app文件夹过去。

编辑app/include/user_config.h文件,修改下面信息:

1
2
3
4
5
6
C复制代码#define PRODUCT_KEY     "PRODUCT_KEY"
#define DEVICE_NAME "DEVICE_NAME"
#define DEVICE_SECRET "DEVICE_SECRET"

#define WIFI_SSID "WIFI_SSID"
#define WIFI_PASS "WIFI_PASS"

五、接入成功

编译、烧写、重启ESP8266,观察串口打印的信息,感觉没有异常后在看看阿里云IoT Hub控制台,可以看到设备已经接入成功了!

接入成功

然后到设备的topic列表,可以看到 update topic 消息数加1了,那是因为我在代码里面写了只要连接成功就发布一条「hello」消息。

平台从设备接收消息

然后尝试一下对get topic发布消息!

平台下发指令到设备

可以看到串口打印如下:

1
2
kotlin复制代码TCP: data received 45 bytes
Receive topic: /PRODUCT_KEY/esp8266_test/get, data: hello wolrd!

此时说明「设备主动上报数据到平台」和「平台下发指令到设备」这两个功能均测试成功!

六、关于阿里云IoT hub

接入方式

根据文档,阿里云IoT hub至少支持三种设备接入方式:

  1. MQTT方式,包括基于TCP的MQTT和基于WebSocket的MQTT
  2. CoAP
  3. HTTP

其中对于ESP8266来说,最方便的还是基于MQTT-TCP方式。当然,阿里云IoT hub除了支持单一设备接入方式,也支持网关设备的接入。

计费方面

目前开通平台是免费。收费方法是按消息数量收费,不过每月前100万条消息免费,对于我这种物联网爱好者随便玩玩足够了。

基础版和高级版

实际上,阿里云IoT hub分为两个版本:基础版和高级版。高级版在基础版所有功能的基础上,还多了一些实用功能,当然,高级版也是免费开通的,收费方面高级版则多了一个设备日活费用,0.01元/每日活设备/天,目前每个帐号有10个设备的免费额度。

高级版令我关注的有一点:设备的数据存储和查询功能。设备端只要根据阿里云IoT hub高级版的物模型和Alink协议上传数据,阿里云IoT hub就会存储相关数据,同时还可以通过云端API获取历史数据。

相关术语:

  • 物模型:阿里云IoT hub对设备在云端的功能描述,包括设备的属性、服务和事件。
  • Alink协议:阿里云定义的设备与云端之间的通信协议。

阿里云高级版示例

2019年4月1日更新:关于这个温湿度传感器程序太多人问了,这里特别说明一下。这个示例我在电脑上用Python(非MicroPython)写了MQTT客户端代码模拟设备,根据阿里云协议上传相关属性值就可以了。所以根本没有8266温湿度传感器程序!!!
没有8266温湿度传感器程序!!!
没有8266温湿度传感器程序!!!
不过这个回头有机会再更新吧,博主已经很久没有碰8266了。

那么有关阿里云IoT hub的介绍就到这里,下面是简单说下我写的aliyun_mqtt_app。

七、关于aliyun_mqtt_app

自然,我在引言已经提到弄出这么一个app工程的原因了。下面是对这个app简单介绍一下。

由于ESP8266官方SDK中已经有了MQTT示例工程,所以我是在此工程的基础上补充了一点自己的代码。其中最麻烦的是阿里云IoT hub设备认证问题,直接使用MQTT示例工程填写有关MQTT的配置信息也可以,只是mqtt password要先在PC端生成好才行,使用字符串拼接方式静态生成设备认证三元组似乎还做不了。那就干脆一点,让ESP8266能动态生成阿里云的mqtt password就可以了,也就是一个hmacmd5签名。

mqtt password生成的核心代码在 user/aliyun_mqtt.c/gen_mqtt_password() 函数里。

之后,就是加了点辅助功能,比如可以使用smartconfig配置Wi-Fi,这样就可以不用把Wi-Fi信息写死到代码了。同时,阿里云IoT hub大多是使用JSON格式传输数据,给app上cJSON解析的工作以后有空就考虑考虑(ESP8266使用cJSON解析器已经有了,在我的ESP8266工程示例集合仓库里)。

八、结语

自此,洋洋洒洒长篇大论有话没话写了那么多,本文简而言之就介绍了如何使用ESP8266接入阿里云IoT hub,至于能玩出什么花样就要靠开发者的想象力了。话说,某平台是不是应该给我点软文费呢~?

相关资料

  • esp8266 aliyun mqtt app:github.com/AngelLiang/…
  • 阿里云物联网平台文档:help.aliyun.com/product/305…
  • 阿里云IoT hub - MQTT-TCP连接通信:help.aliyun.com/document_de…

本文转载自: 掘金

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

十个形象比喻,助你理解计算机面试必备的知识点

发表于 2021-07-17

前言

大家好,我是捡田螺的小男孩。计算机编程的很多知识点,往往在日常生活中就有类似的例子。最近整理了十个非常形象生动的生活例子,助大家理解这些计算机面试必备的知识点。

  • 公众号:捡田螺的小男孩
  • github地址,感谢每一颗star

1.如何理解HTTP的无状态?

每次HTTP请求都是独立的,无相关的,默认不需要保存上下文信息的。我们来看个便于理解的例子:

有状态:

  • A:今天吃啥子?
  • B:罗非鱼!
  • A:味道怎么样呀?
  • B:还不错,好香。

无状态:

  • A:今天吃啥子?
  • B:罗非鱼!
  • A:味道怎么样呀?
  • B:?啊?啥?什么鬼?什么味道怎么样?

加下cookie这玩意:

  • A:今天吃啥子?
  • B:罗非鱼
  • A:你今天吃的罗非鱼味道怎么样呀?
  • B:还不错,好香。

2. 什么是序列化?什么是反序列化?

  • 序列化:把Java对象转换为字节序列的过程
  • 反序列:把字节序列恢复为Java对象的过程

作为大城市漂泊的码农,搬家是常态。当我们搬书桌时,桌子太大了就通不过比较小的门,因此我们需要把它拆开再搬过去,这个拆桌子的过程就是序列化。 而我们把书桌复原回来(安装)的过程就是反序列化啦。

3. 什么是限流?

我们日常开发中,经常听到接口限流,QPS多少等等这些词。那么,什么是限流呢?在计算机网络中,限流就是控制网络接口发送或接收请求的速率。

举个生活的例子:一些热门的旅游景区,一般会对每日的旅游参观人数有限制的,每天只会卖出固定数目的门票,比如5000张。假设在五一、国庆假期,你去晚了,可能当天的票就已经卖完了,就无法进去游玩了。即使你最后能进去,排队也排到你怀疑人生。

4. TCP 握手为什么是三次?不能是两次?不能是四次?

TCP握手为什么是三次呢?为了方便理解,我们以谈恋爱为例子:两个人能走到一起,最重要的事情就是相爱,就是我爱你,并且我知道,你也爱我,接下来我们以此来模拟三次握手的过程:

为什么握手不能是两次呢?

如果只有两次握手,女孩子可能就不知道,她的那句我也爱你,男孩子是否收到,恋爱关系就不能愉快展开。

为什么握手不能是四次呢?

因为握手不能是四次呢?因为三次已经够了,三次已经能让双方都知道:你爱我,我也爱你。而四次就多余了。

5. 线程池工作原理

面试官如果要我们讲下线程池工作原理的话,大家讲下以下这个流程图就可以啦:

为了形象描述线程池执行,加深大家的理解,我打个比喻:

  • 核心线程比作公司正式员工
  • 非核心线程比作外包员工
  • 阻塞队列比作需求池
  • 提交任务比作提需求

  • 当产品提个需求,正式员工(核心线程)先接需求(执行任务)
  • 如果正式员工都有需求在做,即核心线程数已满),产品就把需求先放需求池(阻塞队列)。
  • 如果需求池(阻塞队列)也满了,但是这时候产品继续提需求,怎么办呢?那就请外包(非核心线程)来做。
  • 如果所有员工(最大线程数也满了)都有需求在做了,那就执行拒绝策略。
  • 如果外包员工把需求做完了,它经过一段(keepAliveTime)空闲时间,就离开公司了。

6. TCP的流量窗口如何控制流量

我们来看课堂上:这么一个场景,老师讲课,学生做笔记。假设老师念一段话,要求学生孩子们做笔记,记录下来。

第一种模式:

  • 老师说,”从前有个人, 她叫马冬梅. 她喜欢夏洛, 而夏洛却喜欢秋雅.”
  • 学生写道,”从前有…”, “老师你说的太快啦,我跟不上”

于是他们换了模式二

  • 老师说,”从”
  • 学生写,”从”. 学生说”嗯”
  • 老师说,”前”
  • 学生写,”前”. 学生说”嗯”
  • 老师说,”今天我还想早点下班呢…”

于是他们又换了一种模式,模式三

  • 老师说,”从前有个人”
  • 学生写,”从前有个人”. 学生说”嗯”
  • 老师说,”她叫马冬梅”.
  • 学生写,”她叫马…梅”. 学生说”马什么梅?”
  • 老师说,”她叫马冬梅”.
  • 学生写”她叫马冬…”. 学生说”马冬什么?”
  • 老师,”…..”
  • 学生说,”有的时候状态好我能把5个字都记下来, 有的时候状态不好就记不下来. 我状态不好的时候你能不能慢一点呢

于是他们换了模式四:

  • 老师说,”从前有个人”
  • 学生写,”从前有个人”. 学生说”嗯, 再来5个”
  • 老师说,”她叫马冬梅”
  • 学生写,”她叫马..梅”. 学生说,”啥?重来, 来2个”
  • 老师说,”她叫”.学生写,”她叫”.
  • 学生说,”嗯,再来3个”
  • 老师说,”马冬梅”.
  • 学生写,”马冬梅”.
  • 学生说,”嗯, 给我来20个”
  • 老师说,”她喜欢夏洛,而夏洛却喜欢秋雅”
  • 学生写…

因此呢

  • 第一种模式简单粗暴, 发的只管发, 收的可能跟不上.
  • 第二种模式稳定却低效, 每发一个, 必须等到确认才再次发送, 等待时间比较多.
  • 第三种模式提高了效率, 分组进行发送, 但是分组的大小该怎么决定呢?
  • 第四中模式才真正起到了流控的作用, 接收方认为状态好的时候, 让发送方每次多发一点. 接收方认为状态不好的时候(阻塞), 让发送方每次少发送一点。

7. BIO、NIO,AIO的区别

  • 同步阻塞(blocking-IO)简称BIO
  • 同步非阻塞(non-blocking-IO)简称NIO
  • 异步非阻塞(asynchronous-non-blocking-IO)简称AIO

一个生活的例子:

  • 小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)
  • 小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)
  • 小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)

8. 什么死锁?

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

假设你要开车进入一个村子,村口有条非常窄的路,只能容纳一辆车过。这时候,迎面又驶来一辆车,你们都走到一半,谁也不想倒回去,于是各不相让,陷入无尽的等待。

9. TCP为什么需要四次挥手

举个例子吧,假设小明和小红打电话聊天,通话差不多要结束时:

小红说,“我没啥要说的了”。小明回答,“我知道了”。但是小明可能还有要说的话,小红不能要求小明跟着自己的节奏结束通话,于是小明可能又叽叽歪歪说了一通,最后小明说“我说完了”,小红回答“知道了”,这样通话才算结束。

10. select和 epoll的区别

说到select和epoll,相信大家都很熟悉了,它们都使用了IO多路复用机制。可以监视多个描述符的读/写等事件,一旦某个描述符就绪(一般是读或者写事件发生了),就能够将发生的事件通知对应的应用程序去处理该事件。

select 和 epoll 的本质区别在哪里呢?

  • 当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,统统都遍历一遍。
  • epoll使用“事件”的就绪通知方式,给套接字注册某个回调函数,只有活跃可用的FD,自动完成相关操作,避免了轮询,提升了效率。

举个生活类似的例子:

假如时光倒流,我们回到大学读书。你去女生宿舍,找你女朋友。于是你找到了宿管大妈,宿管大妈就会带着你,挨个房间去找,直到找到你女朋友(这就是select版);而epoll版版本呢,你来了,把你女朋友的名字和宿舍房号报给舍管大妈,妈就直接帮你找到你女朋友。

最后

我是捡田螺的小男孩,谢谢大家阅读,希望本文对大家有帮助;有兴趣的小伙伴可以关注我公众号哈:捡田螺的小男孩

本文转载自: 掘金

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

面试高频题:讲讲项目中的技术难点?

发表于 2021-07-17

原创:猿天地(微信公众号 ID:cxytiandi),欢迎分享,转载请保留出处。

相信很多人都有类似的经历,在面试快要结束的时候经常会被问到一个问题:讲讲项目中的技术难点?

这是一个比较开放的问题,首先它没有固定的答案,因为每个人做过的项目不同,使用的框架不同,对应的架构不同,自然遇到的技术难点也不同。

  1. 一定要真实

在回答这个问题的时候,一定要仔细想想之前真实遇到的问题,不要随便编一个,这样很容易出问题,因为面试官会顺着细节一层层的问下去,如果你是编出来的,到最后就圆不回去了。

举个例子:

求职者说我们下单的接口最开始只能支持几百的 TPS,被我优化后 TPS 破万了,只要你说完这句话面试官就开始进入继续追问细节了。

  • 破万具体是多少的 TPS?
  • 有多少台机器?
  • 机器分别是什么配置?
  • 数据库是什么配置?
  • 你们是怎么进行压测的?
  • 下单链路跟多少个服务进行了交互?
  • 每个服务的耗时多久?
  • 如何进行优化的?
  • 如何发现接口中的性能瓶颈?

你只有抗住了这一系列的连环炮追问,而且面试官通过你的描述和你说的指标进行对比,如果比较匹配那么你就过关了。如果不匹配,肯定就面失败了。

  1. 技术层面的难点

技术层面的难点可以是做了 GC 的优化,从多少 GC 次优化到多少次,STW 的时间降低了多少,通过哪些手段做的优化。

可以是压测时性能一直上不去,通过什么手段进行了优化,从多少优化到多少。期间有没有加机器,有没有升配服务器,升配数据库等。

可以是项目运行一段时间后就出现假死的情况,处理不了任何请求。然后你是怎么一步步去分析并找到具体原因的,然后又是如何去解决的。

一定要有细节有数据,这样的案例才真实可信。并且面试官会认为你是具备去分析并解决问题的能力。

  1. 不一定是技术层面的难点

虽然问的是技术难点,如果你确实没有遇到过什么技术难点,这个时候可以往其他方面去靠,不要直接回答说:没有遇到过什么难点。我敢保证,你要你这样回答了,面试成功的可能性不大。

可以往业务层面,领导力方面去讲,比如你可以说当时做某个业务的时候,没有这块经验。然后通过查找资料,去咨询有经验的朋友等独立的完成了某个系统的设计。并且在做完后取得了什么样的成绩,这个过程对自己来说是非常具体挑战性的,所以这是在项目中遇到的一个难点。

也可以是自己主动请缨,在领导的支持下主导了老项目的重构,给团队的同学培训了 DDD,并且通过 DDD 成功的将某个业务成功的进行了重构。这样可以体现你的主动性,分享精神,领导力等多方面综合的能力。

最后送给大家的就是:一定要先准备好,想好自己要说什么,临时发挥效果肯定没有事先准备的好。

如果对你有用,来个转发呗!

关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号猿天地发起人。

本文转载自: 掘金

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

在Android Studio中写代码导出aar包,在Uni

发表于 2021-07-17

在Android Studio中写代码导出aar包,在Unity中交互调用(小白完整篇)

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

AndroidStudio中的操作步骤:

首先,打开AndroidStudio新建一个工程,版本不同,所以操作的界面跟步骤可能不太一样,但是核心就是包名罢了,其他的一律默认Next带过就行
在这里插入图片描述
在这里插入图片描述
然后就是等它把工程新建完打开工程后,File->New->New Module,选中Android Library,新建一个Module。我这里新建的Module名字是MyunityLibrary。在这里创建一个EmptyActivity,起名为MainActivity,创建好后是这个样子
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

现在这里有两个AndroidManifest,一个是本工程的,还有一个就是新建的unityLibrary的。这里把app的AndroidManifest中红色背景的部分全部复制到另一个AndroidManifest中。

在这里插入图片描述
报红的都删掉。android:label改成想要的应用名字。并且加上这一句:

1
javascript复制代码<meta-data android:name="unityplayer.UnityActivity" android:value="true"/>

如下图配置所示就行
在这里插入图片描述
删除layout下的activity_main.xml文件,并删除刚刚新建的MainActivity中的setContentView(R.layout.activity_main);

在这里插入图片描述
导入Unity的classes.jar文件,我的路径是在D:\Program D:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes下。复制,粘贴到libs目录下。
在这里插入图片描述

选中class.jar->右键->AddAsLibrary->选中unitylibrary->Ok。
在这里插入图片描述

修改刚才创建的MainActivity:使其继承自UnityPlayerActivity,并加一个方法提供给Unity调用。这里写了一个最简单的相加方法。
UnityPlayer.UnitySendMessage是Android调用Unity的方法,第一个参数为挂载脚本游戏物体名,第二个为方法名,第三个为传递参数。
接下来就是导出aar包然后让unity用了。

在这里插入图片描述
Build->Make Project,等待执行完毕。
选中unitylibrary->Build->Make Module”unitylibrary”,等待执行完毕。

在这里插入图片描述
找到build目录下的AndroidMainfest和unitylibrary.aar包,都复制出来。
在这里插入图片描述

打开复制出来的aar包,将外边这个classes.jar剪切到libs文件夹下替换掉libs中原来的classes.jar包

在这里插入图片描述

修改arr包内的AndroidManifest,将android:label这一行删除。如果不删,则会和外面的AndroidManifest冲突。
在这里插入图片描述
然后修改刚才单独复制出来的AndroidMainfest文问:修改报名packet=,此处报名需和等会unity打包时的报名一致即可。(此处尽量修改一下,不要与原来的package一样,因为我设置一样的时候在unity调用的时候可能会找不到android中的方法类,原因可能是aar包中的AndroidMainfest与aar包外的这个AndroidMainfest的package一样导致unity不知道用哪个出错。这是自己猜想的,大家知道尽量不一样就好)
在这里插入图片描述
到了这一步,AndroidStudio中的步骤就算完成了,然后就得到了一个AndroidMainfest.xml和一个自己命名aar包,接下来就是unity中的操作了。


Unity中的操作步骤

在Project下新建一个Plugins/Android两个文件夹,将得到的.xml和aar包放到这个文件夹目录下。这样就把AndroidStudio中拿到的文件配置好了
在这里插入图片描述
接下来在unity中写一个脚本调用安卓中写的方法进行调试,新建一个Text,将脚本挂在Canvas上,建一个Text,用来显示结果。新建脚本调arr包中写好的add方法,把结果显示在text上。这里用了try cash,如果出错方便从手机上直接看到报错信息,实际操作中不需要。
在这里插入图片描述
调试脚本如下,

1
2
csharp复制代码 AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
1
markdown复制代码    这两行时固定写法,获取安卓相关
1
csharp复制代码 jo.Call<int>("add", 2, 3);

这一行是调用安卓中的add方法

在这里插入图片描述
接下来就是打包这个unity工程,在player Settings找到这个Package Name 改为在AndroidMainfest中设置的Packagename相同
,然后Build打包即可。

在这里插入图片描述
在这里插入图片描述

打出apk包,使用手机安装后打开,显示出数字5,证明unity调用Android成功。文本显示红色,证明Android调用unity成功。

本文转载自: 掘金

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

TX-LCN 集群下分布式事务失效问题及解决方案

发表于 2021-07-17

前言

​ 近期新开发的服务上线后,用户反馈数据更新不成功;但经过本地测试又是正常的;考虑到本地和线上环境的区别是一个单体一个集群。考虑到这个因素,我在本地又起了一个服务,测试结果是大概率的操作失败,事务没有提交成功;由于选择的框架目前已无人维护所以只能开启debug模式来排查问题,经过两天时间的排查终于发现是TM根据模块名称找参与者造成的问题,由于框架的模块名称取值逻辑是采用项目名称,集群下的服务注册到TM时模块名称是一样的,则TM找寻不到具体的参与者,造成了事务提交失败的。

1、版本

WMErKU.png

2、流程分析

依据下图的TC代理控制处理的流程图,从源码层次来分析集群下分布式事务失效的原因。

[WMEsrF.png

从上图可知,事务的提交最终交由TM来控制,因此TM最终会通知参与方来响应事务;但在集群的环境下真正的参与方大概率没有接受到TM传来的消息;所以我们的切入点自然是TM的通知信息发送这个过程。

2.1 TM的NotifyGroup的源码分析

TM通知TC的过程

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
java复制代码private void notifyTransaction(DTXContext dtxContext, int transactionState) throws TransactionException {
List<TransactionUnit> transactionUnits = dtxContext.transactionUnits();//@@1
log.debug("group[{}]'s transaction units: {}", dtxContext.getGroupId(), transactionUnits);
for (TransactionUnit transUnit : transactionUnits) {
NotifyUnitParams notifyUnitParams = new NotifyUnitParams();
notifyUnitParams.setGroupId(dtxContext.getGroupId());
notifyUnitParams.setUnitId(transUnit.getUnitId());
notifyUnitParams.setUnitType(transUnit.getUnitType());
notifyUnitParams.setState(transactionState);
txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify {}'s unit: {}",
transUnit.getModId(), transUnit.getUnitId());
try {
List<String> modChannelKeys = rpcClient.remoteKeys(transUnit.getModId());//@@2
if (modChannelKeys.isEmpty()) {
// record exception
throw new RpcException("offline mod.");
}
MessageDto respMsg =
rpcClient.request(modChannelKeys.get(0), MessageCreator.notifyUnit(notifyUnitParams));//@@3
if (!MessageUtils.statusOk(respMsg)) {
// 提交/回滚失败的消息处理
List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
rpcExceptionHandler.handleNotifyUnitBusinessException(params, respMsg.loadBean(Throwable.class));
}
} catch (RpcException e) {
// 提交/回滚通讯失败
List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
rpcExceptionHandler.handleNotifyUnitMessageException(params, e);
} finally {
txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify unit over");
}
}
}
  • @@1 获取该事务组的所有参与方
  • @@2 根据参与方标识获取连接
  • @@3 向第一个连接发送消息

从@@2和@@3 可知,如果集群下是不是modId一样造成了TM找不到准确的TC,带着这个问题我们看看@@2 的处理逻辑:

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
java复制代码 
public List<String> remoteKeys(String moduleName) {
return SocketManager.getInstance().remoteKeys(moduleName);// @@1
}
/*
*/
public List<String> remoteKeys(String moduleName) {
List<String> allKeys = new ArrayList<>();
for (Channel channel : channels) {
if (moduleName.equals(getModuleName(channel))) {//@@2
allKeys.add(channel.remoteAddress().toString());
}
}
return allKeys;
}
/*
*/
public String getModuleName(Channel channel) {//@@3
String key = channel.remoteAddress().toString();
return getModuleName(key);
}
/*
*/
public String getModuleName(String remoteKey) {//@@4
AppInfo appInfo = appNames.get(remoteKey);
return appInfo == null ? null : appInfo.getAppName();
}
  • @@1 依据moduleName获取TC地址
  • @@2 通过遍历所有与TM建立的连接,依据moduleName查找符合条件的连接
  • @@3 依据channel得到其ModuleName
  • @@4 依据远程地址得到ModuleName,其中AppInfo的结构是CurrentHashMap

从上述过程可知transUnit.getModId()和AppInfo 是我们排查的重点;通过其相互的调用关系,最终确定了两者初始化的地方。

AppInfo的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码//TM端的InitClientService
public void bindModuleName(String remoteKey, String appName,String labelName) throws RpcException{
AppInfo appInfo = new AppInfo();
appInfo.setAppName(appName);
appInfo.setLabelName(labelName);
appInfo.setCreateTime(new Date());
if(containsLabelName(labelName)){
throw new RpcException("labelName:"+labelName+" has exist.");
}
appNames.put(remoteKey, appInfo);
}
// appName=applicationName;
String appName = environment.getProperty("spring.application.name");
this.applicationName = StringUtils.hasText(appName) ? appName : "application";
// labelName=modIdProvider.getModId
public static String modId(ConfigurableEnvironment environment, ServerProperties serverProperties) {
String applicationName = environment.getProperty("spring.application.name");
applicationName = StringUtils.hasText(applicationName) ? applicationName : "application";
return applicationName + ":" + serverPort(serverProperties);
}

TransactionUnit的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码//JoinGroupExecuteService 
transactionManager.join(dtxContext, joinGroupParams.getUnitId(), joinGroupParams.getUnitType(),
rpcClient.getAppName(transactionCmd.getRemoteKey()), joinGroupParams.getTransactionState());
//NettyRpcClient
public String getAppName(String remoteKey) {
return SocketManager.getInstance().getModuleName(remoteKey);
}
//SocketManager
public String getModuleName(String remoteKey) {
AppInfo appInfo = appNames.get(remoteKey);
return appInfo == null ? null : appInfo.getAppName();
}
//SimpleTransactionManager
public void join(DTXContext dtxContext, String unitId, String unitType, String modId, int userState) throws TransactionException {
//手动回滚时设置状态为回滚状态 0
if (userState == 0) {
dtxContext.resetTransactionState(0);
}
TransactionUnit transactionUnit = new TransactionUnit();
transactionUnit.setModId(modId);
transactionUnit.setUnitId(unitId);
transactionUnit.setUnitType(unitType);
dtxContext.join(transactionUnit);
}

从上述源码可知TransactionUnit的ModId 最终传的AppName,而AppName则是environment.getProperty(“spring.application.name”)得来的。从中可知,集群下同一服务的spring.application.name是相同的。

3、结论

集群下由于应用名称是一样的,则造成了TM端发送通知信息时找不到准确的参与方,进而导致了事务提交失败;鉴于此种情况一种就是改应用服务名,启动一次服务改一次服务名,此种方法繁琐不切实际故舍去;第二种就是修改源码改变查找逻辑,将TransactionUnit的ModId改为labelName,其中LableName 的命名为服务名+ip+端口号,故需重写相关方法。

4、 解决方案

修改源码两种方式:

  • 直接下载源码在源码中修改,修改完成后打包使用
  • 在项目下创建同目录同文件的类,依据编译文件的优先级来达到修改源码的效果。

本次解决方案会采用第二种方式,第一种方式耦合度高,且不宜框架版本升级。

4.1、重写modId的方法

将该文件放在项目的公共模块类中

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
java复制代码@Configuration
public class LcnConfig {
@Bean
@ConditionalOnMissingBean
@Primary
public ModIdProvider modIdProvider(ConfigurableEnvironment environment,
@Autowired(required = false) ServerProperties serverProperties) {
return () -> modId(environment, serverProperties);
}

private String modId(ConfigurableEnvironment environment, ServerProperties serverProperties) {

String applicationName = environment.getProperty("spring.application.name");
applicationName = StringUtils.hasText(applicationName) ? applicationName : "application";
return applicationName + ":" + serverPort(serverProperties);
}

/**
* 此方法如果获取的不准确的,请从配置文件中获取地址。
* IP:PORT
*
* @param serverProperties serverProperties
* @return int
*/
private String serverPort(ServerProperties serverProperties) {

return Objects.isNull(serverProperties) ? "127.0.0.1:8080" :
(Objects.isNull(serverProperties.getPort()) && Objects.isNull(serverProperties.getAddress().getHostAddress()) ? "127.0.0.1:8080" :
serverProperties.getAddress().getHostAddress() + ":" + serverProperties.getPort());
}
}

4.2 重写SocketManager类

注:在项目的公共包中建立同目录同类的文件

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
java复制代码/*
* Copyright 2017-2019 CodingApi .
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.codingapi.txlcn.txmsg.netty.bean;


import com.codingapi.txlcn.txmsg.RpcConfig;
import com.codingapi.txlcn.txmsg.dto.AppInfo;
import com.codingapi.txlcn.txmsg.dto.MessageDto;
import com.codingapi.txlcn.txmsg.dto.RpcCmd;
import com.codingapi.txlcn.txmsg.dto.RpcResponseState;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.*;

/**
* Created by lorne on 2017/6/30.
*/
@Slf4j
public class SocketManager {

private Map<String, AppInfo> appNames;

private ScheduledExecutorService executorService;

private ChannelGroup channels;

private static SocketManager manager = null;

private long attrDelayTime = 1000 * 60;

private SocketManager() {
channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
appNames = new ConcurrentHashMap<>();
executorService = Executors.newSingleThreadScheduledExecutor();

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException ignored) {
}
}));
}


public static SocketManager getInstance() {
if (manager == null) {
synchronized (SocketManager.class) {
if (manager == null) {
manager = new SocketManager();
}
}
}
return manager;
}


public void addChannel(Channel channel) {
channels.add(channel);
}

public void removeChannel(Channel channel) {
channels.remove(channel);
String key = channel.remoteAddress().toString();

// 未设置过期时间,立即过期
if (attrDelayTime < 0) {
appNames.remove(key);
return;
}

// 设置了过期时间,到时间后清除
try {
executorService.schedule(() -> {
appNames.remove(key);
}, attrDelayTime, TimeUnit.MILLISECONDS);
} catch (RejectedExecutionException ignored) {
// caused down server.
}
}


private Channel getChannel(String key) throws RpcException {
for (Channel channel : channels) {
String val = channel.remoteAddress().toString();
if (key.equals(val)) {
return channel;
}
}
throw new RpcException("channel not online.");
}


public RpcResponseState send(String key, RpcCmd cmd) throws RpcException {
Channel channel = getChannel(key);
ChannelFuture future = channel.writeAndFlush(cmd).syncUninterruptibly();
return future.isSuccess() ? RpcResponseState.success : RpcResponseState.fail;
}

public MessageDto request(String key, RpcCmd cmd, long timeout) throws RpcException {
NettyRpcCmd nettyRpcCmd = (NettyRpcCmd) cmd;
log.debug("get channel, key:{}", key);
Channel channel = getChannel(key);
channel.writeAndFlush(nettyRpcCmd);
log.debug("await response");
if (timeout < 1) {
nettyRpcCmd.await();
} else {
nettyRpcCmd.await(timeout);
}
MessageDto res = cmd.loadResult();
log.debug("response is: {}", res);
nettyRpcCmd.loadRpcContent().clear();
return res;
}

public MessageDto request(String key, RpcCmd cmd) throws RpcException {
return request(key, cmd, -1);
}


public List<String> loadAllRemoteKey() {
List<String> allKeys = new ArrayList<>();
for (Channel channel : channels) {
allKeys.add(channel.remoteAddress().toString());
}
return allKeys;
}

public ChannelGroup getChannels() {
return channels;
}

public int currentSize() {
return channels.size();
}


public boolean noConnect(SocketAddress socketAddress) {
for (Channel channel : channels) {
if (channel.remoteAddress().toString().equals(socketAddress.toString())) {
return false;
}
}
return true;
}

/**
* 获取模块的远程标识keys
*
* @param moduleName 模块名称
* @return remoteKeys
*/
public List<String> remoteKeys(String moduleName) {
List<String> allKeys = new ArrayList<>();
for (Channel channel : channels) {
if (moduleName.equals(getModuleName(channel))) {
allKeys.add(channel.remoteAddress().toString());
}
}
return allKeys;
}


/**
* 绑定连接数据
*
* @param remoteKey 远程标识
* @param appName 模块名称
* @param labelName TC标识名称
*/
public void bindModuleName(String remoteKey, String appName, String labelName) throws RpcException {
AppInfo appInfo = new AppInfo();
appInfo.setAppName(appName);
appInfo.setLabelName(labelName);
appInfo.setCreateTime(new Date());
if (containsLabelName(labelName)) {
throw new RpcException("labelName:" + labelName + " has exist.");
}
appNames.put(remoteKey, appInfo);
}

public boolean containsLabelName(String moduleName) {
Set<String> keys = appNames.keySet();
for (String key : keys) {
AppInfo appInfo = appNames.get(key);
if (moduleName.equals(appInfo.getLabelName())) {
return true;
}
}
return false;
}

public void setRpcConfig(RpcConfig rpcConfig) {
attrDelayTime = rpcConfig.getAttrDelayTime();
}

/**
* 获取模块名称
*
* @param channel 管道信息
* @return 模块名称
*/
public String getModuleName(Channel channel) {
String key = channel.remoteAddress().toString();
return getModuleName(key);
}

/**
* 获取模块名称
*
* @param remoteKey 远程唯一标识
* @return 模块名称
*/
public String getModuleName(String remoteKey) {
AppInfo appInfo = appNames.get(remoteKey);
return appInfo == null ? null : appInfo.getLabelName();
}

public List<AppInfo> appInfos() {
return new ArrayList<>(appNames.values());
}
}

5、总结

本人写作能力水平有限,文中相关的讲解有误请帮忙指出,谢谢!下一步的计划分析tx-lcn的框架,了解其内部原理,从中加强对分布式事务的理解。

本文转载自: 掘金

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

【微软算法面试高频题】二叉搜索树的最近公共祖先 1题目描述

发表于 2021-07-17

微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播

1.题目描述

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

1
2
3
4
5
6
7
8
9
10
makefile复制代码示例 1:
​
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
​
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。
  1. 解析

要利用二叉搜索树的性质

如果p和q都大于node值,则说明p和q在node的右子树中 如果p和q都小于node值,则说明p和q在node的左子树中 如果p小于node,q大于node值,则说明node即为p和q在二叉搜索树上的分叉口 也即最近公共祖先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kotlin复制代码/**
* Definition for a binary tree node.
* public class TreeNode {
*     int val;
*     TreeNode left;
*     TreeNode right;
*     TreeNode(int x) { val = x; }
* }
*/
​
class Solution {
   public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
       while(true){
           if(root.val <= Math.max(p.val, q.val) && root.val >= Math.min(p.val, q.val)){
               break;
          }else if(root.val > Math.max(p.val, q.val)){
               root = root.left;
          }else{
               root = root.right;
          }
      }
       return root;
  }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。分析思路与方法一相同。
  • 空间复杂度:O(1)。

微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播

本文转载自: 掘金

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

对象映射框架MapStruct对比orika

发表于 2021-07-17

1、各大对象映射框架性能对比

工具 实现方式 缺点 说明
mapstruct getter/setter方法 需要了解注解和配置项语法 JSR269注解处理器在编译期自动生成Java Bean转换代码,支持可配置化,扩展性强
orika 动态生成字节码 首次调用耗时较久,性能适中 采用javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件
Spring BeanUtils 反射机制 不支持名称相同但类型不同的属性转换
Apache BeanUtils 反射机制 需要处理编译期异常,性能最差
dozer 反射机制 性能差 使用reflect包下Field类的set(Object obj, Object value)方法进行属性赋值
BeanCopier 反射机制 \1. BeanCopier只拷贝名称和类型都相同的属性。即便基本类型与其对应的包装类型也不能相互转换; 使用ASM的MethodVisitor直接编写各属性的get/set方法

就性能而言:mapstruct性能无疑是是最高的,接下来依次是Spring BeanUtils>orika>BeanCopier>dozer>apache BeanUtils

2、MapStruct的使用

2.1、引入pom

1
2
3
4
5
6
xml复制代码<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.3.0.Final</version>
    <scope>provided</scope>
</dependency>

2.2 、单个bean映射

1
2
3
4
5
6
7
8
9
10
11
less复制代码import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface OrderConvert {

    @Mapping(source = "id", target = "orderId")
    @Mapping(source = "createTime", target = "orderTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    OrderDTO from(Order order);

}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scss复制代码@Test
public void test() {
    Order order = Order.builder()
        .id(123L)
        .buyerPhone("13707318123")
        .buyerAddress("中电软件园")
        .amount(10000L)
        .payStatus(1)
        .createTime(LocalDateTime.now())
        .build();

    OrderConvert orderConvert = Mappers.getMapper(OrderConvert.class);
    OrderDTO orderDTO = orderConvert.from(order);

    System.out.println("order:    " + order);
    System.out.println("orderDTO: " + orderDTO);
}

2.3、多个bean的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
less复制代码@Mapper(componentModel = "spring")
public interface GoodInfoConvert {

    /** Long => String 隐式类型转换 */
    @Mapping(source = "good.id", target = "goodId")
    /** 属性名不同, */
    @Mapping(source = "type.name", target = "typeName")
    /** 属性名不同 */
    @Mapping(source = "good.title", target = "goodName")
    /** 属性名不同 */
    @Mapping(source = "good.price", target = "goodPrice")
    GoodInfoDTO from(GoodInfo good, GoodType type);

}

2.4、参数的含义映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Mapper(imports = {CustomMapping.class})
public interface StudentConvert {

    @Mapping(source = "id", target = "studentId")
    @Mapping(source = "name", target = "studentName")
    @Mapping(source = "age", target = "age")
    @Mapping(target = "ageLevel", expression = "java(CustomMapping.ageLevel(student.getAge()))")
    @Mapping(target = "sexName", expression = "java(CustomMapping.sexName(student.getSex()))")
    @Mapping(source = "admissionTime", target = "admissionDate", dateFormat = "yyyy-MM-dd")
    StudentDTO from(Student student);

    default LocalDate map(LocalDateTime time) {
        return time.toLocalDate();
    }

}

自定义的映射类

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
typescript复制代码public class CustomMapping {

    static final String[] SEX = {"女", "男", "未知"};

    public static String sexName(Integer sex) {

        if (sex < 0 && sex > 2){
            throw new IllegalArgumentException("invalid sex: " + sex);
        }
        return SEX[sex];
    }

    public static String ageLevel(Integer age) {
        if (age < 18) {
            return "少年";
        } else if (age >= 18 && age < 30) {
            return "青年";
        } else if (age >= 30 && age < 60) {
            return "中年";
        } else {
            return "老年";
        }
    }

}

3、orika的使用

3.1 引入pom

1
2
3
4
5
xml复制代码<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version>
</dependency>

3.2、初始化实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kotlin复制代码package tech.chenxing.deploy.configuration;

import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MapperConfig {
    @Bean
    public MapperFactory mapperFactory() {
        return new DefaultMapperFactory.Builder().build();
    }
}

3.3、配置映射对象

定义转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
less复制代码public class FlowDocDOConverter extends CustomMapper<FlowDoc, FlowDocBean> {
    @Autowired private DocInfoMapperExt docInfoMapperExt;

    @Override
    public void mapAtoB(FlowDoc flowDocDO, FlowDocBean flowDocBean, MappingContext context) {
        if (StringUtils.isNotBlank(flowDocDO.getDocUuid())) {

            DocInfo docInfoDO = docInfoMapperExt.getByUUID(flowDocDO.getDocUuid());
            ValidationUtil.assertNull(
                    docInfoDO,
                    new BizFlowManagerException(
                            BizFlowManagerBaseResultCodeEnum.FLOW_NOT_EXISTS,
                            flowDocDO.getDocUuid()));
            flowDocBean.setId(docInfoDO.getId());
            flowDocBean.setFileId(docInfoDO.getFileId());
            flowDocBean.setName(docInfoDO.getName());
            flowDocBean.setDocPassword(docInfoDO.getDocPassword());
            flowDocBean.setDocSource(docInfoDO.getDocSource());
            flowDocBean.setEncryption(docInfoDO.getEncryption());
            flowDocBean.setGmtCreate(flowDocDO.getGmtCreate());
            flowDocBean.setGmtModified(flowDocDO.getGmtModified());
        }
    }
}

注入转换器

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
kotlin复制代码package tech.chenxing.deploy.configuration;

import ma.glasnost.orika.MapperFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;


@Component
public class MapperInit {
    @Autowired private MapperFactory mapperFactory;
    @Autowired private FlowDocDOConverter flowDocDOConverter;


    @PostConstruct
    public void init() {
        // 注册文档映射方式
        mapperFactory
                .classMap(User.class, UserDTO.class)
                .field("docUuid", "docId")
                .byDefault() // 剩余的字段映射
                .customize(flowDocDOConverter)
                .register();
    }
}

代码使用

1
2
3
4
5
6
7
ini复制代码public xxx queryuser() {
        List<User> userList =
                userMapperExt.queryUser();
        List<UserDTO> userDTOList =
                mapperFactory.getMapperFacade().mapAsList(userList, UserDTO.class);
        QueryFlowDocResult queryFlowDocResult = new QueryFlowDocResult();
    }

版权声明:本文为人工博客的原创文章,遵循 CC 4.0 BY+SA 版权协议,转载请附上原文出处链接及本声明。

本文链接:www.gzcx.net/article/179…

本文转载自: 掘金

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

Java官方正式宣布AWT、2D、Swing等项目解散

发表于 2021-07-17

7月15日OpenJDK管理委员会全票通过批准成立由Phil Race担任初始负责人的 Client Libraries Group(客户端类库工作组)。

新的工作组将继续赞助OpenJFX和Lanai两个项目。同时批准 AWT, 2D, Swing,和Sound这几个项目解散。另外Harfbuzz、Framebuffer Toolkit和XRender Pipeline 这几个项目因失去赞助而解散。

图片

OpenJDK官方邮件

Java桌面端的收缩

AWT, 2D, Swing这几个是我们熟知的Java桌面端解决方案。这次Java社区正式宣布项目解散,标志着Java正式不再为这几个项目提供技术迭代支持。Java桌面端仅存OpenJFX一个项目,而这个项目一直不温不火。可以说目前Java在桌面端的地位已经几乎丧失。

扩展阅读

OpenJFX 项目

OpenJFX 是一个开源的下一代客户端应用程序平台,是 JavaFX 的开源项目。由 OpenJDK 开源社区领导。适用于与 JDK 一起使用的桌面和嵌入式系统。其目标是为开发富客户端应用程序开发一个现代、高效且功能齐全的工具包。

OpenJDK管理委员会

该管理委员会负责OpenJDK社区的架构和运作。它负责修订OpenJDK社区的章程以完善现有流程、定义新流程并处理不再需要的流程,类似一个立法机构。

由5名贡献者组成:

  • 主席,由Oracle任命。
  • 副主席,由IBM任命,
  • OpenJDK Lead,由Oracle任命。
  • 两名普通成员,由提名选举产生。

关注公众号:Felordcn获取更多资讯

个人博客:https://felord.cn

本文转载自: 掘金

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

1…603604605…956

开发者博客

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