iOS开发之蓝牙通讯

iOS开发之蓝牙通讯一、引言蓝牙是设备近距离通信的一种方便手段,在iPhone引入蓝牙4.0后,设备之间的通讯变得更加简单。相关的蓝牙操作由专门的CoreBluetooth.framework进行统一

大家好,又见面了,我是全栈君,今天给大家准备了Idea注册码。

全栈程序员社区此处内容已经被作者隐藏,请输入验证码查看内容
验证码:
请关注本站微信公众号,回复“验证码”,获取验证码。在微信里搜索“全栈程序员社区”或者“www_javaforall_cn”或者微信扫描右侧二维码都可以关注本站微信公众号。

iOS开发之蓝牙通讯

一、引言

        蓝牙是设备近距离通信的一种方便手段,在iPhone引入蓝牙4.0后,设备之间的通讯变得更加简单。相关的蓝牙操作由专门的CoreBluetooth.framework进行统一管理。通过蓝牙进行通讯交互分为两方,一方为中心设备central,一方为外设peripheral,外设通过广播的方式向外发送信息,中心设备检索到外设发的广播信息,可以进行配对连接,进而进行数据交互。

二、中心设备CBCentralManager

        CBCentralManager是管理中心设备的管理类,其中重要方法如下:

//设置中心设备代理
@property(assign, nonatomic, nullable) id<CBCentralManagerDelegate> delegate;
//中心设备当前状态
@property(readonly) CBCentralManagerState state;
//中心设备是否正在扫描
@property(readonly) BOOL isScanning NS_AVAILABLE(NA, 9_0);

其中state是一个枚举,有关蓝牙是否可用的状态如下:

typedef NS_ENUM(NSInteger, CBCentralManagerState) {
        //状态未知
    CBCentralManagerStateUnknown = 0,
    //连接断开 即将重置
    CBCentralManagerStateResetting,
    //该平台不支持蓝牙
    CBCentralManagerStateUnsupported,
    //未授权蓝牙使用 hovertree.com
    CBCentralManagerStateUnauthorized,
    //蓝牙关闭
    CBCentralManagerStatePoweredOff,
    //蓝牙正常开启
    CBCentralManagerStatePoweredOn,
};

下面这些方法用于初始化管理中心:

//初始化方法
//设置的代理需要遵守CBCentralManagerDelegate协议
//queue可以设置蓝牙扫描的线程 传入nil则为在主线程中进行
- (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                           queue:(nullable dispatch_queue_t)queue;
//此方法同上 在options字典中用于进行一些管理中心的初始化属性设置
//字典中支持的键值如下 http://www.cnblogs.com/roucheng/
/*
NSString * const CBCentralManagerOptionShowPowerAlertKey 对应一个NSNumber类型的bool值,用于设置是否在关闭蓝牙时弹出用户提示
NSString * const CBCentralManagerOptionRestoreIdentifierKey 对应一个NSString对象,设置管理中心的标识符ID
*/
- (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                           queue:(nullable dispatch_queue_t)queue
                         options:(nullable NSDictionary<NSString *, id> *)options;
//根据获取所有已知设备
- (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers;
//根据服务id获取所有连接的设备 hovertree.com
- (NSArray<CBPeripheral *> *)retrieveConnectedPeripheralsWithServices:(NSArray<CBUUID *> *)serviceUUIDs;

在初始化管理中心完成后,会回调代理中的如下方法,我们必须实现如下方法:

//这个方法中可以获取到管理中心的状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

如果上面方法中管理中心状态为蓝牙可用,可以通过下面方法开启扫描外设:

//serviceUUIDs用于扫描一个特点ID的外设 options用于设置一些扫描属性 键值如下
/*
//是否允许重复扫描 对应NSNumber的bool值,默认为NO,会自动去重
NSString *const CBCentralManagerScanOptionAllowDuplicatesKey;
//要扫描的设备UUID 数组 对应NSArray hovertree.com
NSString *const CBCentralManagerScanOptionSolicitedServiceUUIDsKey;
*/
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
//停止扫描外设
- (void)stopScan;

扫描的结果会在如下代理方法中回掉:

//peripheral 扫描到的外设
//advertisementData是外设发送的广播数据
//RSSI 是信号强度 http://www.cnblogs.com/roucheng/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;

扫描到外设后,通过下面方法可以连接一个外设:

/*
options中可以设置一些连接设备的初始属性键值如下
//对应NSNumber的bool值,设置当外设连接后是否弹出一个警告
NSString *const CBConnectPeripheralOptionNotifyOnConnectionKey;
//对应NSNumber的bool值,设置当外设断开连接后是否弹出一个警告
NSString *const CBConnectPeripheralOptionNotifyOnDisconnectionKey;
//对应NSNumber的bool值,设置当外设暂停连接后是否弹出一个警告 http://www.cnblogs.com/roucheng/
NSString *const CBConnectPeripheralOptionNotifyOnNotificationKey;
*/
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
//取消一个外设的连接
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;

调用过连接外设的方法后,会回掉如下代理方法:

//连接外设成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
//连接外设失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
//断开外设连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

当管理中心恢复时会调用如下代理:

 1 //dict中会传入如下键值对 hovertree.com
 2 /*
 3 //恢复连接的外设数组
 4 NSString *const CBCentralManagerRestoredStatePeripheralsKey;
 5 //恢复连接的服务UUID数组
 6 NSString *const CBCentralManagerRestoredStateScanServicesKey;
 7 //恢复连接的外设扫描属性字典数组
 8 NSString *const CBCentralManagerRestoredStateScanOptionsKey;
 9 */
10 - (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;

三、外设CBPeripheralManager

        从上面我们知道,中心设备是用来扫描周围的外设,两台设备的通讯中,必须有一个充当中心设备,一个充当外设,外设是由CBPeripheralManager进行管理,主要方法如下:

 1 //设置外设管理中心代理
 2 @property(assign, nonatomic, nullable) id<CBPeripheralManagerDelegate> delegate;
 3 //外设状态 枚举如中心设备
 4 @property(readonly) CBPeripheralManagerState state;
 5 //是否正在发送广播
 6 @property(readonly) BOOL isAdvertising;
 7 //用户的授权状态
 8 + (CBPeripheralManagerAuthorizationStatus)authorizationStatus;
 9 //初始化并设置代理 参数的具体含义与中心设备管理中心
10 - (instancetype)initWithDelegate:(nullable id<CBPeripheralManagerDelegate>)delegate
11                            queue:(nullable dispatch_queue_t);
12 - (instancetype)initWithDelegate:(nullable id<CBPeripheralManagerDelegate>)delegate
13                            queue:(nullable dispatch_queue_t)queue
14                          options:(nullable NSDictionary<NSString *, id> *)options;
15 //开始发送广播 hovertree.com  何问起
16 //advertisementData中可以发送的数据有约定 如下
17 /*
18 对应设置NSString类型的广播名
19 NSString *const CBAdvertisementDataLocalNameKey;
20 外设制造商的NSData数据
21 NSString *const CBAdvertisementDataManufacturerDataKey;
22 外设制造商的CBUUID数据
23 NSString *const CBAdvertisementDataServiceDataKey;
24 服务的UUID与其对应的服务数据字典数组
25 NSString *const CBAdvertisementDataServiceUUIDsKey;
26 附加服务的UUID数组
27 NSString *const CBAdvertisementDataOverflowServiceUUIDsKey;
28 外设的发送功率 NSNumber类型
29 NSString *const CBAdvertisementDataTxPowerLevelKey;
30 外设是否可以连接
31 NSString *const CBAdvertisementDataIsConnectable;
32 服务的UUID数组
33 NSString *const CBAdvertisementDataSolicitedServiceUUIDsKey;
34 */
35 - (void)startAdvertising:(nullable NSDictionary<NSString *, id> *)advertisementData;
36 //停止发送广播
37 - (void)stopAdvertising;
38 //设置一个连接的具体central设备的延时 枚举如下
39 /*
40 typedef NS_ENUM(NSInteger, CBPeripheralManagerConnectionLatency) {
41     CBPeripheralManagerConnectionLatencyLow = 0,
42     CBPeripheralManagerConnectionLatencyMedium,
43     CBPeripheralManagerConnectionLatencyHigh
44 } NS_ENUM_AVAILABLE(NA, 6_0);
45 */
46 - (void)setDesiredConnectionLatency:(CBPeripheralManagerConnectionLatency)latency forCentral:(CBCentral *)central;
47 //添加一个服务 http://www.cnblogs.com/roucheng/
48 - (void)addService:(CBMutableService *)service;
49 //移除一个服务
50 - (void)removeService:(CBMutableService *)service;
51 //移除所有服务
52 - (void)removeAllServices;
53 //响应中心设备的读写请求
54 - (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result;
55 //更新一个连接中心设备的订阅特征值
56 - (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(nullable NSArray<CBCentral *> *)centrals;

外设代理的相关方法如下:

 1 //这个方法是必须实现的 状态可用后可以发送广播
 2 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
 3 //连接回复时调用的方法 和centralManager类似
 4 - (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary<NSString *, id> *)dict;
 5 //开始发送广播时调用的方法
 6 - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error;
 7 //添加服务调用的回调
 8 - (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(nullable NSError *)error;
 9 //当一个central设备订阅一个特征值时调用的方法
10 - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
11 //取消订阅一个特征值时调用的方法
12 - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
13 //收到读请求时触发的方法 何问起 hovertree.com
14 - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;
15 //收到写请求时触发的方法
16 - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;
17 //外设准备更新特征值时调用的方法
18 - (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;

四、中心设备与外设对象CBCentral与CBPeripheral

        上面介绍了中心设备管理类与外设管理类,这些类用于将设备连接建立起来,器具的数据交换的服务和一些信息则是在对应的设备对象中。

1、中心设备 CBCentral属性与方法

//设备UUID
@property(readonly, nonatomic) NSUUID *identifier;
//中心设备最大接收的数据长度
@property(readonly, nonatomic) NSUInteger maximumUpdateValueLength;

2、外设CAPeripheral属性与方法

        外设对象要比中心对象复杂的多,当centralManager连接到外设后,需要通过外设对象的代理方法进行数据交互,其中主要方法属性如下:

iOS开发之蓝牙通讯
iOS开发之蓝牙通讯

 1 //设置代理
 2 @property(assign, nonatomic, nullable) id<CBPeripheralDelegate> delegate;
 3 //外设name
 4 @property(retain, readonly, nullable) NSString *name;
 5 //信号强度 http://www.cnblogs.com/roucheng/
 6 @property(retain, readonly, nullable) NSNumber *RSSI NS_DEPRECATED(NA, NA, 5_0, 8_0);
 7 //外设状态
 8 /*
 9 typedef NS_ENUM(NSInteger, CBPeripheralState) {
10     CBPeripheralStateDisconnected = 0,//未连接
11     CBPeripheralStateConnecting,//正在链接
12     CBPeripheralStateConnected,//已经连接
13     CBPeripheralStateDisconnecting NS_AVAILABLE(NA, 9_0),//正在断开连接
14 } NS_AVAILABLE(NA, 7_0);
15 */
16 @property(readonly) CBPeripheralState state;
17 //所有的服务数组
18 @property(retain, readonly, nullable) NSArray<CBService *> *services;
19 //获取当前信号强度
20 - (void)readRSSI;
21 //根据服务UUID寻找服务对象
22 - (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
23 //在服务对象UUID数组中寻找特定服务
24 - (void)discoverIncludedServices:(nullable NSArray<CBUUID *> *)includedServiceUUIDs forService:(CBService *)service;
25 //在一个服务中寻找特征值
26 - (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
27 //从一个特征中读取数据
28 - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
29 //写数据的最大长度 hovertree.com  何问起
30 //type枚举如下
31 /*
32 typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) {
33     CBCharacteristicWriteWithResponse = 0,//写数据并且接收成功与否回执
34     CBCharacteristicWriteWithoutResponse,//写数据不接收回执
35 };
36 */
37 - (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type NS_AVAILABLE(NA, 9_0);
38 //向某个特征中写数据
39 - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
40 //为制定的特征值设置监听通知
41 - (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;
42 //寻找特征值的描述
43 - (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;
44 //读取特征的描述值
45 - (void)readValueForDescriptor:(CBDescriptor *)descriptor;
46 //写特征的描述值
47 - (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor;

View Code

外设的代理方法如下:

iOS开发之蓝牙通讯
iOS开发之蓝牙通讯

 1 //外设名称更改时回调的方法
 2 - (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(NA, 6_0);
 3 //外设服务变化时回调的方法
 4 - (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray<CBService *> *)invalidatedServices NS_AVAILABLE(NA, 7_0);
 5 //信号强度改变时调用的方法
 6 - (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(nullable NSError *)error NS_DEPRECATED(NA, NA, 5_0, 8_0);
 7 //读取信号强度回调的方法 柯乐义 keleyi.com
 8 - (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error NS_AVAILABLE(NA, 8_0);
 9 //发现服务时调用的方法
10 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
11 //在服务中发现子服务回调的方法
12 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(nullable NSError *)error;
13 //发现服务的特征值后回调的方法
14 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
15 //特征值更新时回调的方法
16 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
17 //向特征值写数据时回调的方法
18  - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
19  //特征值的通知设置改变时触发的方法
20  - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
21  //发现特征值的描述信息触发的方法
22  - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
23  //特征的描述值更新时触发的方法
24  - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;
25  //写描述信息时触发的方法
26  - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;

View Code

五、服务对象CBService

    服务对象是用来管理外设提供的一些数据服务的,其中属性如下:

//对应的外设
@property(assign, readonly, nonatomic) CBPeripheral *peripheral;
//是否是初等服务
@property(readonly, nonatomic) BOOL isPrimary;
//包含的自服务 http://www.cnblogs.com/roucheng/
@property(retain, readonly, nullable) NSArray<CBService *> *includedServices;
//服务中的特征值
@property(retain, readonly, nullable) NSArray<CBCharacteristic *> *characteristics;

六、服务的特征值CBCharacteristic

        通过绑定服务中的特征值来进行数据的读写操作,其中属性如下:

//对应的服务对象
@property(assign, readonly, nonatomic) CBService *service;
//特征值的属性 枚举如下
/*
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast,//允许广播特征
    CBCharacteristicPropertyRead,//可读属性
    CBCharacteristicPropertyWriteWithoutResponse,//可写并且接收回执
    CBCharacteristicPropertyWrite,//可写属性
    CBCharacteristicPropertyNotify,//可通知属性
    CBCharacteristicPropertyIndicate,//可展现的特征值
    CBCharacteristicPropertyAuthenticatedSignedWrites,//允许签名的特征值写入
    CBCharacteristicPropertyExtendedProperties,
    CBCharacteristicPropertyNotifyEncryptionRequired,
    CBCharacteristicPropertyIndicateEncryptionRequired
};
*/
@property(readonly, nonatomic) CBCharacteristicProperties properties;
//特征值的数据 http://www.cnblogs.com/roucheng/
@property(retain, readonly, nullable) NSData *value;
//特征值的描述
@property(retain, readonly, nullable) NSArray<CBDescriptor *> *descriptors;
//是否是当前广播的特征
@property(readonly) BOOL isBroadcasted;
//是否是正在通知的特征
@property(readonly) BOOL isNotifying;

七、读写请求对象CBATTRequest

        服务对象是外设向中心设备提供的相关数据服务,获取到相应服务后,中心设备可以进行读写请求,读写对象属性如下:

//对应的中心设备
@property(readonly, nonatomic) CBCentral *central;
//对应的特征值
@property(readonly, nonatomic) CBCharacteristic *characteristic;
//读写数据值
@property(readwrite, copy, nullable) NSData *value;

http://www.cnblogs.com/roucheng/p/texiao.html

本文小结:

  • iOS开发之蓝牙通讯
  • 一、引言
  • 二、中心设备CBCentralManager
  • 三、外设CBPeripheralManager
  • 四、中心设备与外设对象CBCentral与CBPeripheral
  • 1、中心设备 CBCentral属性与方法
  • 2、外设CAPeripheral属性与方法
  • 五、服务对象CBService
  • 六、服务的特征值CBCharacteristic
  • 七、读写请求对象CBATTRequest
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/120681.html原文链接:https://javaforall.net

(0)
上一篇 2021年12月21日 下午5:00
下一篇 2021年12月21日 下午5:00


相关推荐

  • sql中declare的用法_sql局部变量

    sql中declare的用法_sql局部变量换工作了,以后主要和SqlServer打交道了,仿佛回到了大学,不知道学校的饭还是那么好吃又便宜吗?北京的饭好贵;不知道门口哪家板面的生意是不是还是那么红火,好想再去吃一碗。。。咳咳,不多说了,直接进入主题declare这个类型,其实可以理解为Java里面的public类型变量,全局有效,当然非要较真的话,我觉得归到protected类也可以(不理解的话不要看后半段,只是为了严谨)Java修饰符 public:对所有类可见。使用对象:类、接口、变量、方法 protect..

    2022年8月20日
    10
  • voliate关键字的作用[通俗易懂]

    voliate关键字的作用[通俗易懂]一、内存可见性基于缓存一致性协议,当用voliate关键字修饰的变量改动时,cpu会通知其他线程,缓存已被修改,需要更新缓存。这样每个线程都能获取到最新的变量值。二、基于内存屏障的防止指令重排用voliate修饰的变量,可以防止cpu指令重排序。底层的实现方式是基于4种内存屏障:读读、读写、写读、读读屏障。…

    2022年5月25日
    79
  • 扣子智能体能否直接调用即梦API实现图像生成?

    扣子智能体能否直接调用即梦API实现图像生成?

    2026年3月13日
    1
  • LARGE_INTEGER类型和LONGLONG类型以及QueryPerformanceFrequency函数

    LARGE_INTEGER类型和LONGLONG类型以及QueryPerformanceFrequency函数LARGE_INTEGERLARGE_INTEGER是union,表示64位有符号整数值。其定义如下:    typedefunion_LARGE_INTEGER{     struct{                 DWORDLowPart;                 LONGHighPart;               };

    2022年7月16日
    27
  • jsessionid java_jsessionid 对JAVA WEB jsessionid的剖析

    jsessionid java_jsessionid 对JAVA WEB jsessionid的剖析在服务器端 我们用惯了 session setAttribute userInfo 这样的一行代码 估计你很少想到 服务器与浏览器之间是如何保持会话状态的 好了 先引用一些文章的精彩片段 http www xxx com xxx app jsessionid xxxxxxxxxx a x amp b x 这跟一般的 url 基本一样 只有一个地方有区别 那就是 jessionid xxxxx

    2026年3月16日
    1
  • 高通8953 Android 9.0 在user模式下使用fastboot烧录

    高通8953 Android 9.0 在user模式下使用fastboot烧录高通 8953Android9 0 在 user 模式下使用 fastboot 烧录高通 8953Android9 0 在 user 模式下 默认是无法使用 fastboot 烧录的 这个非常影响我们的调试 bootable bootloader lk makefile 可以屏蔽掉 DDISABLE FASTBOOT CMDS 即可 原来的命令中会去对 TARGET BUILD VARIANT 进行判定 如果是 user 模式就会 DDISABLE FASTBOOT CMDS 置位 从而禁止 fastboot 使用 因此只需要屏蔽掉 D

    2026年3月26日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号