首页 文章 精选 留言 我的

精选列表

搜索[项目实战],共10010篇文章
优秀的个人博客,低调大师

从零构建多数据库API开发工具:WorkBuddy项目实战全记录

在数字化转型浪潮中,中小企业面临着一个普遍痛点:如何将现有业务数据库快速转化为对外API接口?传统方案需要专业的后端开发人员编写Controller、Service、DAO层代码,周期长、成本高。本文分享一个基于Node.js的"配置即生成"API开发工具的研发历程,涵盖从需求分析、技术选型、核心功能实现到内网部署的完整实践。

优秀的个人博客,低调大师

每日一博 | 分布式 ID 生成服务的技术原理和项目实战

作者 | 文库App 导读 ID在我们的开发工作和日常生活中使用的非常频繁,几乎只要是在开发就会天天打交道,它的应用场景十分广泛,比如:身份证号,下单生成的订单号,购买的联合会员商品的兑换券码。不同场景对ID生成服务的要求不同,以下我们逐个分析。 全文6863字,预计阅读时间18分钟。 01 什么是分布式ID生成服务 在业务开发中,大量场景需要唯一ID来进行标识:用户独一无二的身份认证、超市售卖的商品、微信的即时消息,它们都需要标识来确定唯一性。需要在特定范围内保证ID具备唯一性,这是ID生成服务最基本的要求。 生成ID的方式多种多样,可以使用Redis键自增,UUID,或者基于雪花算法实现的ID生成服务。最常见的基于数据库ID自增的方式,在业务数据量不大的时候,单库单表可以支撑,数据再大一点搞个MySQL主从同步、读写分离也能对付。但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求。 伴随着业务快速迭代,很多业务都需要生成ID,各自为政会陷入"重复造轮子"的低效劳动中,同时造成服务治理上的混乱,此时一个能够生成全局唯一ID的服务是非常必要的。那么这个全局唯一ID就叫分布式ID生成服务。 02 服务特性 ① 唯一性:生成的ID唯一,特定范围不冲突; ② 有序性:生成的ID按某种规则有序,趋势递增,便于入库和查询,但不严格要求; ③ 高可用、高性能:高并发下的具备高可用,确保任何情况能容灾,稳定提供服务; ④ 自主性:分布式环境下不依赖中心认证即可自行生成ID; ⑤ 安全性:脱敏,不暴露系统和业务的信息,如:订单数,用户数。 03 常见的技术实现方式 04 技术为业务服务 技术归根到底是为业务服务,要在业务中体现技术的价值。 网上绝大多数的分布式id生成服务,一般着重于技术原理剖析,很少见到根据具体的业务场景去选型ID生成服务的文章。 本文结合一些使用场景,进一步探讨业务场景中对ID有哪些具体的要求。 4.1 场景一:订单系统 我们在商场买东西一码付二维码,下单生成的订单号,使用到的优惠券码,联合商品兑换券码,这些是在网上购物经常使用到的单号,那么为什么有些单号那么长,有些只有几位数?有些单号一看就知道年月日的信息,有些却看不出任何意义?下面展开分析下订单系统中不同场景的id服务的具体实现。 1、一码付 我们常见的一码付,指的是一个二维码可以使用支付宝或者微信进行扫码支付。 二维码的本质是一个字符串。聚合码的本质就是一个链接地址。用户使用支付宝微信直接扫一个码付钱,不用担心拿支付宝扫了微信的收款码或者用微信扫了支付宝的收款码,这极大减少了用户扫码支付的时间。 实现原理是当客户用APP扫码后,网站后台就会判断客户的扫码环境。(微信、支付宝、QQ钱包、京东支付、云闪付等)。 判断扫码环境的原理就是根据打开链接浏览器的 HTTP header。任何浏览器打开http链接时,请求的header都会有User-Agent(UA、用户代理)信息。 UA是一个特殊字符串头,服务器依次可以识别出客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等很多信息。 各渠道对应支付产品的名称不一样,一定要仔细看各支付产品的API介绍。 微信支付:JSAPI支付支付 支付宝:手机网站支付 QQ钱包:公众号支付 其本质均为在APP内置浏览器中实现HTML5支付。 文库的研发同学在这个思路上,做了优化迭代。动态生成一码付的二维码预先绑定用户所选的商品信息和价格,根据用户所选的商品动态更新。这样不仅支持一码多平台调起支付,而且不用用户选择商品输入金额,即可完成订单支付的功能,很丝滑。用户在真正扫码后,服务端才通过前端获取用户UID,结合二维码绑定的商品信息,真正的生成订单,发送支付信息到第三方(qq、微信、支付宝),第三方生成支付订单推给用户设备,从而调起支付。 区别于固定的一码付,在文库的应用中,使用到了动态二维码,二维码本质是一个短网址,ID服务提供短网址的唯一标志参数。唯一的短网址映射的ID绑定了商品的订单信息,技术和业务的深度结合,缩短了支付流程,提升用户的支付体验。 2、订单号 订单号在实际的业务过程中作为一个订单的唯一标识码存在,一般实现以下业务场景: 用户订单遇到问题,需要找客服进行协助; 对订单进行操作,如线下收款,订单核销; 下单,改单,成单,退单,售后等系统内部的订单流程处理和跟进。 很多时候搜索订单相关信息的时候都是以订单ID作为唯一标识符,这是由于订单号的生成规则的唯一性决定的。从技术角度看,除了ID服务必要的特性之外,在订单号的设计上需要体现几个特性: (1)信息安全 编号不能透露公司的运营情况,比如日销、公司流水号等信息,以及商业信息和用户手机号,身份证等隐私信息。并且不能有明显的整体规律(可以有局部规律),任意修改一个字符就能查询到另一个订单信息,这也是不允许的。 类比于我们高考时候的考生编号的生成规则,一定不能是连号的,否则只需要根据顺序往下查询就能搜索到别的考生的成绩,这是绝对不可允许。 (2)部分可读 位数要便于操作,因此要求订单号的位数适中,且局部有规律。这样可以方便在订单异常,或者退货时客服查询。 过长的订单号或易读性差的订单号会导致客服输入困难且易错率较高,影响用户体验的售后体验。因此在实际的业务场景中,订单号的设计通常都会适当携带一些允许公开的对使用场景有帮助的信息,如时间,星期,类型等等,这个主要根据所涉及的编号对应的使用场景来。 而且像时间、星期这些自增长的属于作为订单号的设计的一部分元素,有助于解决业务累积而导致的订单号重复的问题。 (3)查询效率 常见的电商平台订单号大多是纯数字组成,兼具可读性的同时,int类型相对varchar类型的查询效率更高,对在线业务更加友好。 3、优惠券和兑换券 优惠券、兑换券是运营推广最常用的促销工具之一,合理使用它们,可以让买家得到实惠,商家提升商品销量。常见场景有: 在文库购买【文库VIP+QQ音乐年卡】联合商品,支付成功后会得到QQ音乐年卡的兑换码,可以去QQ音乐App兑换音乐会员年卡; 疫情期间,部分地方政府发放的消费券; 瓶装饮料经常会出现输入优惠编码兑换奖品。 从技术角度看,有些场景适合ID即时生成,比如电商平台购物领取的优惠券,只需要在用户领取时分配优惠券信息即可。有些线上线下结合的场景,比如疫情优惠券,瓶盖开奖,京东卡,超市卡这种,则需要预先生成,预先生成的券码具备以下特性: 1.预先生成,在活动正式开始前提供出来进行活动预热; 2.优惠券体量大,以万为单位,通常在10万级别以上; 3.不可破解、仿制券码; 4.支持用后核销; 5.优惠券、兑换券属于广撒网的策略,所以利用率低,也就不适合使用数据。 库进行存储(占空间,有效的数据有少) 设计思路上,需要设计一种有效的兑换码生成策略,支持预先生成,支持校验,内容简洁,生成的兑换码都具有唯一性,那么这种策略就是一种特殊的编解码策略,按照约定的编解码规则支撑上述需求。 既然是一种编解码规则,那么需要约定编码空间(也就是用户看到的组成兑换码的字符),编码空间由字符a-z,A-Z,数字0-9组成,为了增强兑换码的可识别度,剔除大写字母O以及I,可用字符如下所示,共60个字符: abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXZY0123456789 之前说过,兑换码要求近可能简洁,那么设计时就需要考虑兑换码的字符数,假设上限为12位,而字符空间有60位,那么可以表示的空间范围为60^12=130606940160000000000000(也就是可以12位的兑换码可以生成天量,应该够运营同学挥霍了),转换成2进制:1001000100000000101110011001101101110011000000000000000000000(61位) 兑换码组成成分分析 兑换码可以预先生成,并且不需要额外的存储空间保存这些信息,每一个优惠方案都有独立的一组兑换码(指运营同学组织的每一场运营活动都有不同的兑换码,不能混合使用, 例如双11兑换码不能使用在双12活动上),每个兑换码有自己的编号,防止重复,为了保证兑换码的有效性,对兑换码的数据需要进行校验,当前兑换码的数据组成如下所示: 优惠方案ID + 兑换码序列号i + 校验码 编码方案 兑换码序列号i,代表当前兑换码是当前活动中第i个兑换码,兑换码序列号的空间范围决定了优惠活动可以发行的兑换码数目,当前采用30位bit位表示,可表示范围:1073741824(10亿个券码)。 优惠方案ID, 代表当前优惠方案的ID号,优惠方案的空间范围决定了可以组织的优惠活动次数,当前采用15位表示,可以表示范围:32768(考虑到运营活动的频率,以及ID的初始值10000,15位足够,365天每天有运营活动,可以使用54年)。 校验码,校验兑换码是否有效,主要为了快捷的校验兑换码信息的是否正确,其次可以起到填充数据的目的,增强数据的散列性,使用13位表示校验位,其中分为两部分,前6位和后7位。 深耕业务还会有区分通用券和单独券的情况,分别具备以下特点,技术实现需要因地制宜地思考。 通用券:多个玩家都可以输入兑换,然后有总量限制,期限限制。 单独券:运营同学可以在后台设置兑换码的奖励物品、期限、个数,然后由后台生成兑换码的列表,兑换之后核销。 4.2 Tracing 1、日志跟踪 在分布式服务架构下,一个 Web 请求从网关流入,有可能会调用多个服务对请求进行处理,拿到最终结果。这个过程中每个服务之间的通信又是单独的网络请求,无论请求经过的哪个服务出了故障或者处理过慢都会对前端造成影响。 处理一个Web请求要调用的多个服务,为了能更方便的查询哪个环节的服务出现了问题,现在常用的解决方案是为整个系统引入分布式链路跟踪。 在分布式链路跟踪中有两个重要的概念:跟踪(trace)和 跨度( span)。trace 是请求在分布式系统中的整个链路视图,span 则代表整个链路中不同服务内部的视图,span 组合在一起就是整个 trace 的视图。 在整个请求的调用链中,请求会一直携带 traceid 往下游服务传递,每个服务内部也会生成自己的 spanid 用于生成自己的内部调用视图,并和traceid一起传递给下游服务。 2、TraceId 生成规则 这种场景下,生成的ID除了要求唯一之外,还要求生成的效率高、吞吐量大。traceid需要具备接入层的服务器实例自主生成的能力,如果每个trace中的ID都需要请求公共的ID服务生成,纯纯的浪费网络带宽资源。且会阻塞用户请求向下游传递,响应耗时上升,增加了没必要的风险。所以需要服务器实例最好可以自行计算tracid,spanid,避免依赖外部服务。 产生规则:服务器 IP + ID 产生的时间 + 自增序列 + 当前进程号 ,比如: 0ad1348f1403169275002100356696 前 8 位 0ad1348f 即产生 TraceId 的机器的 IP,这是一个十六进制的数字,每两位代表 IP 中的一段,我们把这个数字,按每两位转成 10 进制即可得到常见的 IP 地址表示方式 10.209.52.143,您也可以根据这个规律来查找到请求经过的第一个服务器。 后面的 13 位 1403169275002 是产生 TraceId 的时间。之后的 4 位 1003 是一个自增的序列,从 1000 涨到 9000,到达 9000 后回到 1000 再开始往上涨。最后的 5 位 56696 是当前的进程 ID,为了防止单机多进程出现 TraceId 冲突的情况,所以在 TraceId 末尾添加了当前的进程 ID。 3、SpanId 生成规则 span是层的意思,比如在第一个实例算是第一层, 请求代理或者分流到下一个实例处理,就是第二层,以此类推。通过层,SpanId 代表本次调用在整个调用链路树中的位置。 假设一个 服务器实例 A 接收了一次用户请求,代表是整个调用的根节点,那么A 层处理这次请求产生的非服务调用日志记录spanid的值都是0,A层需要通过 RPC 依次调用 B、C、D 三个服务器实例,那么在 A 的日志中,SpanId 分别是 0.1,0.2 和 0.3,在 B、C、D 中,SpanId 也分别是 0.1,0.2 和 0.3;如果 C 系统在处理请求的时候又调用了 E,F 两个服务器实例,那么 C 系统中对应的spanid是 0.2.1 和 0.2.2,E、F 两个系统对应的日志也是 0.2.1 和 0.2.2。 根据上面的描述可以知道,如果把一次调用中所有的 SpanId 收集起来,可以组成一棵完整的链路树。 spanid的生成本质:在跨层传递透传的同时,控制大小版本号的自增来实现的。 4.3 场景三:短网址 短网址主要功能包括网址缩短与还原两大功能。相对于长网址,短网址可以更方便地在电子邮件,社交网络,微博和手机上传播,例如原来很长的网址通过短网址服务即可生成相应的短网址,避免折行或超出字符限制。 常用的ID生成服务比如:mysql ID自增、 redis键自增、号段模式,生成的ID都是一串数字。短网址服务把客户的长网址转换成短网址, 实际是在dwz.cn域名后面拼接新产生的数字类型ID,直接用数字ID,网址长度也有些长,服务可以通过数字ID转更高进制的方式压缩长度。这种算法在短网址的技术实现上越来越多了起来,它可以进一步压缩网址长度。转进制的压缩算法在生活中有广泛的应用场景,举例: 客户的长网址:https://wenku.baidu.com/ndbusiness/browse/wenkuvipcashier?cashier_code=PCoperatebanner ID映射的短网址:https://dwz.cn/2047601319t66 转进制后的短网址:https://dwz.cn/2ezwDJ0 长数字转短字符串的压缩算法,以下为具体实现: /** * 10进制转为62进制 * * @param integer $n 10进制数值 * @return string 62进制 */ function dec62($n) { $base = 62; $index = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $ret = ''; for($t = floor(log10($n) / log10($base)); $t >= 0; $t --) { $a = floor($n / pow($base, $t)); $ret .= substr($index, $a, 1); $n -= $a * pow($base, $t); } return $ret; } 端字符串同样支持反解,将高位进制转为10进制。 /** * 62进制转为10进制 * * @param integer $n 62进制 * @return string 10进制 */ function dec10($s) { $base = 62; $index = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $ret = 0; $len = strlen($s) - 1; for($t = 0; $t <= $len; $t ++) { $ret += strpos($index, substr($s, $t, 1)) * pow($base, $len - $t); } return $ret; } 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 依次表示0-62,大小写表示不同的进制。10进制的数字采取62进位压缩的方式转为字符串,仅需要6位就可以满足500多亿的网址(568 0023 5583),唯一性得到满足的同时保证长度够短,在具体实现中也可随机跳跃生成,防御撞库攻击。 05 总结 在诸多需要ID服务的业务中,联合商品需要做好信息加密,兑换码、优惠券需要支持可反解或者可验证,订单号需要支持部分信息可读,可以轻松获取日期信息。 纯数字组成的用户uid,订单号出于数据安全,要求是非连续的,但是如果是一些需要计数的ID,则要求严格递增。短url服务中的ID对长度有很高要求,traceid则要求ID可反解,且支持本地生成,保证局部唯一就行。 由此可见,ID生成服务除了具备基础特性,部分场景下还需要满足的特定需求。制定合适的技术方案结合场景去实现,发挥技术优势,才可以更好的支撑业务发展。 ———END———

优秀的个人博客,低调大师

Android项目实战(四十四):Zxing二维码切换横屏扫描

Demo链接 默认是竖屏扫描,但是当我们在清单文件中配置横屏显示的时候: <activity android:name=".CaptureActivity" android:screenOrientation="landscape" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> 这时候扫描,会发现扫描效果变形,方向不对。 可按以下步骤进行修改: 1、修改CameraConfigurationManager.java中的setDesiredCameraParameters函数, camera.setDisplayOrientation(90); ---> camera.setDisplayOrientation(0); 2、修改CameraManager.java的getFramingRectInPreview()函数中修改成如下,即将x改为y , y改为x rect.left = rect.left * cameraResolution.x / screenResolution.x; rect.right = rect.right * cameraResolution.x / screenResolution.x; rect.top = rect.top * cameraResolution.y / screenResolution.y; rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; 3、修改DecodeHandler.java中的decode函数 4、其它可修改部分参照Github Demo 可设置的: 扫描框中的中间线的宽度 扫描框中的中间线的与扫描框左右的间隙 中间那条线每次刷新移动的距离 四个绿色边角对应的长度 、 宽度、颜色 、透明度 中间滑动线的最顶端位置 、 最低端位置 扫描线 是一张图片 遮挡层颜色 , 正方形之外的 区域的颜色 闪点颜色 其他功能: 闪光灯开启 从本地获取图片读取二维码 当前xxx时间内没有扫描 , 关闭当前扫描二维码界面

优秀的个人博客,低调大师

OpenHarmony 项目实战:基于全志 XR806 实现的简易四足行走机器狗

本文整理自:https://bbs.aw-ol.com/topic/932/ 作者 @ 堇花还没开吗 小米在2021年发布的自研四足仿生机器人——CyberDog,使得“仿生机器人”这个话题 再次站在风口之上 ,伴随芯片、舵机、减速器等核心部件的发展,也期待更多更“生动”的“仿生机器人”出现 ,全志XR806是一款支持WiFi和BLE的芯片可满足智能机器人等领域的无线连接需求,今天就先带大家看一款基于全志XR806开源鸿蒙开发板做的四足行走机器狗。 一、机器狗外观图 二、PCA9685开发流程 狗子身上有12个舵机,一条腿接3个舵机,连接到一个pca9685舵机驱动上,舵机驱动通过I2C与XR806通信,XR806有两个I2C接口,我主要是用第1个(B14, B15),通过控制PCA9685上的寄存器来简介控制舵机。 pca9685参数: I2C接口,支持高达16路PWM输出,每路12位分辨率(4096级); 内置25MHz晶振,可不连接外部晶振,也可以连接外部晶振,最大50MHz; 支持2.3V-5.5V电压,最大耐压值5.5V,逻辑电平3.3V; 具有上电复位,以及软件复位等功能。 引脚图示: 1. pca初始化 首先要进行i2c初始化: i2c_init(i2c_id); 导入相关库: #include "driver/chip/hal_i2c.h" #define i2c_id 1 # 使用第1个i2c i2c初始化函数: void i2c_init(unsigned int id){ I2C_InitParam initParam; initParam.addrMode = I2C_ADDR_MODE_7BIT; initParam.clockFreq = 40000; if (HAL_I2C_Init(id, &initParam) != HAL_OK) { printf("i2c init fail!\n"); while(1); } else { printf("i2c init success!\n"); } } 主要是调用HAL_I2C_Init函数对第1个i2c接口进行初始化。 初始化pca9685: pca9685_init(60); pca9685_init函数: void pca9685_init(float hz) { pca_write(pca_mode1, 0x0); pca_setfreq(hz); OS_MSleep(500); } 2. pca操作 1. pca9685写寄存器 void pca_write(uint8_t reg_addr, uint8_t data) { uint8_t buf[1]; buf[0] = data; HAL_I2C_Master_Transmit_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1); } 2. pca9685读寄存器 uint8_t pca_read(uint8_t reg_addr) { uint8_t buf[1]; HAL_I2C_Master_Receive_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1); return buf[0]; } 3. pca9685设置频率 void pca_setfreq(float freq) { uint8_t prescale,old_mode,new_mode; double prescaleval; freq *= 0.92; prescaleval = 25000000; prescaleval /= 4096; prescaleval /= freq; prescaleval -= 1; prescale =(uint8_t)(prescaleval + 0.5f); old_mode = pca_read(pca_mode1); new_mode = (old_mode & 0x7F) | 0x10; pca_write(pca_mode1, new_mode); pca_write(pca_pre, prescale); pca_write(pca_mode1, old_mode); OS_MSleep(2); pca_write(pca_mode1, old_mode | 0xa1); } 这里的代码不像读函数和写函数一样好理解,主要是一般情况下,在用pca9685内置晶振,为25MHZ,通过配置PRE_SCALE寄存器进行配置。如果在舵机控制中,采用内置晶振,取osc_clock=25000000,update_rate=50(舵机控制频率50Hz)。 而且在写PRESCALE寄存器的时候,要先设置为Sleep模式,也就是将mode1寄存器的SLEEP标志位设置为1,具体可参考我上面写的代码。 3. pca设置pwm void pca_setpwm(uint8_t num, uint32_t on, uint32_t off) { pca_write(LED0_ON_L+4*num,on); pca_write(LED0_ON_H+4*num,on>>8); pca_write(LED0_OFF_L+4*num,off); pca_write(LED0_OFF_H+4*num,off>>8); } pwm通道寄存器如下图: 由图可知,对于每一个通道,有4个寄存器,在设置PWM占空比的时候,首先配置舵机的示例如下图所示(ON < OFF的情况): 狗子运行图: 核心代码: #include <stdio.h> #include "ohos_init.h" #include "kernel/os/os.h" // #include "iot_i2c.h" #include "driver/chip/hal_i2c.h" #include "iot_errno.h" #include "math.h" #define i2c_id 1 #define pca_adrr 0x40 // pca9685设备地址 #define pca_mode1 0x0 #define pca_pre 0xFE #define LED0_ON_L 0x6 #define LED0_ON_H 0x7 #define LED0_OFF_L 0x8 #define LED0_OFF_H 0x9 static OS_Thread_t g_main_thread; void pca_write(uint8_t reg_addr, uint8_t data) { uint8_t buf[1]; buf[0] = data; HAL_I2C_Master_Transmit_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1); } uint8_t pca_read(uint8_t reg_addr) { uint8_t buf[1]; HAL_I2C_Master_Receive_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1); return buf[0]; } void pca_setfreq(float freq) { uint8_t prescale,old_mode,new_mode; double prescaleval; freq *= 0.92; prescaleval = 25000000; prescaleval /= 4096; prescaleval /= freq; prescaleval -= 1; prescale =(uint8_t)(prescaleval + 0.5f); old_mode = pca_read(pca_mode1); new_mode = (old_mode & 0x7F) | 0x10; pca_write(pca_mode1, new_mode); pca_write(pca_pre, prescale); pca_write(pca_mode1, old_mode); OS_MSleep(2); pca_write(pca_mode1, old_mode | 0xa1); } void pca_setpwm(uint8_t num, uint32_t on, uint32_t off) { pca_write(LED0_ON_L+4*num,on); pca_write(LED0_ON_H+4*num,on>>8); pca_write(LED0_OFF_L+4*num,off); pca_write(LED0_OFF_H+4*num,off>>8); } void pca9685_init(float hz) { pca_write(pca_mode1, 0x0); pca_setfreq(hz); OS_MSleep(500); } void pca_rotate(uint8_t num,uint8_t angle) { uint32_t off=0; off=floor(angle * 2 + angle / 5 + 158); pca_setpwm(num, 0, off); } void i2c_init(unsigned int id){ I2C_InitParam initParam; initParam.addrMode = I2C_ADDR_MODE_7BIT; initParam.clockFreq = 40000; if (HAL_I2C_Init(id, &initParam) != HAL_OK) { printf("i2c init fail!\n"); while(1); } else { printf("i2c init success!\n"); } } static void MainThread(void *arg) { uint8_t i = 0; i2c_init(i2c_id); pca9685_init(60); printf("i2c and pca9685 init done.\n"); while(1){ pca_rotate(0, 0); OS_MSleep(1000); pca_rotate(0, 180); OS_MSleep(1000); } } void PCAMain(void) { printf("PCA9685 Motor Start.\n"); if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL, OS_THREAD_PRIO_APP, 10 * 1024) != OS_OK) { printf("[ERR] Create MainThread Failed\n"); } } SYS_RUN(PCAMain); 狗子的相关步态算法还在调试,使用的舵机是MG90S,走得还不是很流畅,狗腿子会抖,后面需要继续调试,而且供电也是个大问题,不过已经焊了个供电模块稍微解决了,后面还要给狗子加个壳。

优秀的个人博客,低调大师

Android项目实战(四十一):游戏和视频类型应用 状态栏沉浸式效果

需求: 手机app ,当打游戏或者全屏看视频的时候会发现这时候手机顶部的状态栏是不显示的,当我们从手机顶端向下进行滑动或手机底端向上滑动的时候,状态栏会显示出来,如果短暂的几秒时间没有操作的话,状态栏会再次隐藏。 实现代码: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus && Build.VERSION.SDK_INT >= 19) { // 如果有焦点,说明当前与用户交互 并且SDK_INT>=19 只有Android4.4+才支持沉浸式效果 View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } } 效果: 代码解释: 1、onWindowFocusChanged(boolean hasFocus) 方法 Activity生命周期,可以理解为走到这个方法的时候,activity才是真正意义上的显示/消失在交互层。 当activity显示在栈顶,和用户交互的时候,是有焦点的,hasFocus 为 true 当activity退出栈顶,不和用户交互的时候,是没有焦点的,hasFocus 为 false 2、getWindow.getDecorView()方法 , 获取Window界面最顶层View 一、DecorView为整个Window界面的最顶层View。 二、DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。 三、LinearLayout里有两个FrameLayout子元素。 (20)为标题栏显示界面。只有一个TextView显示应用的名称。也可以自定义标题栏,载入后的自定义标题栏View将加入FrameLayout中。 (21)为内容栏显示界面。就是setContentView()方法载入的布局界面,加入其中。 注: 和Activity全屏显示的区别在于,如果手机有虚拟导航栏(即虚拟后退、home键),则全屏会一直显示,而上述方法,导航栏和状态栏同步显示,上述需求则用全屏实现效果不对。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册