今年的关键字包括看书学习、业务研发、产品思维、交互设计、兴趣爱好等,已浏览相关书籍或专栏 30 篇,平均每周阅读 10~20 篇技术博文。

这些资料的内容我不可能全部消化记住,但可以让我在解决实际问题时,会有个印象,便于给出不同的方案。

维护着的技术仓库
daily
也在继续补充着 2024 年的
面试资料

性能优化

技术工具
等信息。

在公司方面,自从去年裁员之后,团队人员就稳定在 3 人,全年基本都扑在业务研发上,基建工作也只是做些优化迭代,没有大动作。

尽管如此,全年下来在工作上还是有些亮点,团队技术也一直在革新,例如首次进行了 Electron 研发、首次使用 Taro 框架、首次引入 Ant Design Pro 等等。

而自己在完成本职工作后,会腾出更多的精力去涉足更上层的产品、交互等工作,让自己的技能更加复合,也为了能更好的协作与服务。

平时有空也会去数据分析平台看些用户指标变化,或者去浏览下其他组的 OKR,以及了解下各个组的核心指标。

也会去 Wiki 上看看品牌、运营、产品等各个小组记录的各类文档,想了解下他们的工作内容,还有我们服务的是一群什么样的用户。

让自己知道他们都在关心什么,也能更好的理解他们为什么提出这个需求,想要的结果是什么,我除了完成技术支撑,还能为他们提供哪些帮助或建议。

公司去年提出了 4 项企业价值观,我觉得挺好的,蛮正向的,我们团队和个人也是遵循该价值观在慢慢的成长。

  1. 用户导向:以用户的需求和体验为中心,交付让用户满意的产品和服务。
  2. 乐于创造:思考问题本质,打破惯性思维,用创造性方案去解决本质问题。
  3. 积极分享:积极真诚分享用户洞察、工作信息等让团队共同成长。
  4. 使命必达:以结果为导向,全力推进问题解决,促进目标达成。

下面的思维图列举出了今年做的一些比较重要的事情。

一、项目经历

今年总共完成了 7 个版本迭代,大大小小 90 多个需求,这里就列举其中 3 个比较重要的项目,包括杀猪盘、PC 直播助手和社群小程序。

1)杀猪盘

杀猪盘是一个保护用户安全,识别诈骗用户的需求,涉及到多端联调,此项目从 2023 年 6 月份开始,至 2024 年 8 月份才上线,研发周期长达 1 年多。

这里面有客观资源问题,也有人的问题,技术方案改了好几版,计算方式从一端改到另一端,每次的改变都几乎是重写,所以说出现了很大的技术规划问题。

产品组也是奔溃了,不断的投诉另外两个组,我们组维护的部分比较轻量,所以时刻配合的变化。

这个项目暴露了太多的问题,从我的视角看,还是前期的技术准备不够充分,不够重视,把需求也想简单了。

在写代码时,写到某个位置,才发现这里有问题,然后拉上产品,讨论一通,这种情况发生了好几次,换谁都会不爽。

不断的在调整方案,最后将计算的逻辑从一个组调整到另一个组,直接重写,这么拖拖拉拉的,周期肯定会被拉长。

项目上线后,还是有效果的,杀猪盘相关的举报变少了,虽然杀猪盘没变少,但是每天都能封禁很多相关账号,避免发生杀猪盘。

这个项目对我们组也是一个警示,是一个反面教材,让我们在应对复杂需求时要做好万全的准备。

2)PC 直播助手

由于主播反映模拟器操作麻烦、输出效果打折且经常出各类问题,因此就需要一款稳定的 PC 客户端的直播助手软件。

公司直播使用的是声网的服务,声网说有 Electron 的 SDK,于是我们就基于 Electron 来开发这个直播助手,从 2 月份开始,到 4 月份上线第一版。

首次基于 Electron 开发,还是有些陌生的,简单理解地话,这是一个可以改造的浏览器环境,我们的业务开发其实并不需要改造环境。

声网的 SDK 会集成到 Electron 中,然后他们打包给我们,在此基础上开发业务。

直播间的通信基于腾讯 IM,以上麦为例,在上麦时客户端会向 IM 服务器发送上麦消息,服务端在完成上麦处理后,让 IM 服务端发送响应消息给客户端。

核心业务其实就是根据通信收到的 Code 码,完成不同的操作。

但是在业务中会涉及到大量的 Code 码,没有文档,没有统一结构,我们这边等于是边开发,边与客户端核对,非常费时间。

还有些问题是由于客户端和 Electron 环境的差异导致的,例如客户端的主播先以观众身份进入直播间的,但是在 PC 助手中没有这个身份,那样对应的处理逻辑就会不同。

第三方服务商的接口文档不清晰不完整,也会给我们的开发造成些阻碍,有的时候还需要让他们去修改源码。

打包的时候发现公司都是 Mac,而主播大部分是 Windows,还要去找相应的电脑,打包出的客户端还有点问题,都花了时间排查。

总之,这个项目完成下来不容易,但是上线后的反馈也并不好,主要还是功能太少,还无法满足主播的日常使用。

但是现有的资源还不足以完成一套完整的直播服务,因此,这个项目上线第一版后,就被搁置了,非常遗憾。

3)社群小程序

公司开展了一项新业务,就是购买公司的一个商品,可以获得积分,然后用积分可以在社群小程序中兑换徒步活动或者礼品。

小程序就是当前业务中缺失的一环,于是就投入了 1 个产品、1 个UI、7 名技术人员来实现这个需求。

项目周期从 6 月 5 号到 9 月 13 号正式上线,期间也经历了资源问题,导致项目不断的延期。

在这个项目中,首次使用 Ant Design Pro 和 Taro 框架,首次与 Go 的新框架对接,首次部署新框架,首次参与电商需求等。

产品文档更新过 3 版,总共产出 11 份独立文档,12612 个字,总大小 53.34 M。总共 58 个接口,630 条测试用例,测出 268 个 BUG,60 个高等级 BUG。

UI 验收时提出 19 个界面优化,产品验收提出 40 个功能优化点。总之,大家对这个项目都很重视。

在上线后,也陆陆续续迭代了多个版本,运营的意思是两周一个迭代,不过当然,没那么多的资源支持,大家都得妥协。

这个项目让我们体验了好多个新技术,其中 Ant Design Pro 让后台的研发更加的简单化,自己之前也封装过多个后台组件。

我发现我那些组件与成熟的库有着异曲同工之妙,只是开源库的功能更加的完善,可以适应更多的场景。

虽然现在小程序的用户量只有几百人,但是可以参与到一个从 0 到 1 的项目,还是可以学习到很多。

二、工作优化

虽然人员减少了,但是工作上的优化没有停下,都是小步迭代。

其中比较有代表性的就是根据飞书告警优化业务逻辑,完善前端监控系统的功能,各类营收和体验优化等。

1)飞书告警

大部分的飞书告警都不会让页面白屏,但有可能影响页面的性能,今年总共处理了将近 1700 多条的告警(每日新增数)。

例如有些页面每天有 140 多条白屏记录,分析后发现是因为接口响应超过 1 秒导致,主要的时间消耗在传输,因为传输的内容要 1.5M,当网络不好时,就会拉大通信时间。

于是将图被转化成 base64 存储在数据库中,将页面请求转换成 URL 方式,大大降低了页面尺寸,提升了传输速度。

在 7 月份,发现图像告警数量异常,经过排查,确认问题来源于某个特定地区的访问异常。

我将这一结论传达给运维,推动问题得到解决。几天后,另一个业务组也上报了该地区 CDN 异常的问题,进一步验证了我分析的准确性。

在 11 月中旬,通过每日发送的监控记录数量,发现突然暴增将近 68W 条,经过分析是因为打印记录的增多导致的,于是就注释掉几处通用代码中的打印,总量马上就下来了。

2)基建迭代

基建的工作也趋于稳定,榜单活动配置化在今年已经发布了 12 个活动,降本提效的典型案例,并且今年也修复了几个小问题。

静态页面配置是另一个后台可配的网页,是组员在使用中发现问题,而主动做了功能上完善和优化。

前端监控增加了两个监控项目:PC 直播助手和社群小程序,日志搜索增加了操作系统的查询条件,性能监控堆叠面积图和指标增加颜色标识。

自己也去买了几本书、看了几篇专栏去研究可观测性的概念,对于监控有了更深刻的理解。

都是些比较细节的优化,社群小程序是个新项目,包括发布都是新部署的,依照之前的文档,还增加了飞书指令发布的功能。

就是在 IM 界面输入关键字,就能直接发布,省去了进入云效后台界面的步骤。

除了系统优化之外,今年还推动将几个老旧无人维护的 Node 服务接口迁移到了 Go 服务,例如直播状态、赠送物品等。

今年维护和新建了近百份文档,其中包括多份技术分享、Code Review 记录、项目介绍等,组内也紧跟时髦学习了些 AI 技术资料。

3)成本优化

作为支撑部门,我们一般都是消耗公司资源,但有时候也能做些成本优化,最直接是就算减少数据库的存储。

5 月份清理了 MongoDB 中 5 张表,共减少 276G 的容量,删除了 4 张冗余表,定期清理 1 张表。

让整个 MongoDB 维持在 11G,每个月的存储费用可减少 150 不到点。

10 月份每天凌晨 3 点会有数据库的 CPU 告警,分析后发现是一条删除语句造成,在删除语句时增加 limit 限制。

然后分析发现,删除的那张表会将数据迁移到另一个数据库中,但是数据组已经做了同步。

也就是说,我们这边的同步是冗余的,于是就将另一个数据库中的 2 张表直接清空,还有 1 张表做定期清理。

总共减少1TB的数据量,每个月至少节约 1600 元,这比之前的 MongoDB 给力多了。

公司发展了这么多年,肯定有很多地方的技术费用是多付的,就看各个维护人员平时会不会关心这事儿了。

4)体验优化

我们组的用户包括自己、公司同事以及会员,我们要把所有的用户都伺候好了才行。

管理后台暗黑模式是我在浏览开源库的时候,突发奇想,也要给我们的后台整一套。

翻阅 Ant Design 4.X 的文档后,找到了改造流程,修改了代码后,就上线了,然后再慢慢的给个别组件或页面进行适配。

运营看到后台的暗黑模式后,马上去催促产品,给客户端也去整一套,产品一脸无奈,囧。

5 月份为两个对外的项目开启了强缓存,从 3 个小时慢慢加到 3 天,1 秒内白屏占比从 92.04% 提升至 95.32%,1 秒内首屏占比从 79.71% 提升至 88.03%

还有个比较顽疾的匹配问题,一开始也没重视,隔几天会报上来几个,也修复过几次,但还是有问题,最后决定彻底大改下,改完后。

让测试走了遍主流程,就上线了,14点左右上的,观察了一个下午,没啥问题,后面也没人上报了。

管理后台陆陆续续做了些优化,例如完善提示、增加限制、显示有用的数据等。虽然都是小小的改动,但能大大帮助了同事们的日常工作。

伺候好了其他人,也要伺候好我们自己组,Android 6 以后,就不能抓包 HTTPS 的请求,客户端调试很不方便,于是自己去研究了些
WebView的调试方法

iOS 的调试比较简单,可以借助 Safari 浏览器实现;Android 配置调试的过程比较波折,可以借助 Chrome 浏览器实现。

5)绩效考核

年底的时候制订了明年的绩效考核,包括业务指标和重要项目。

业务指标包括交付质量、双月用户满意度、业务支撑;重要项目包括基础建设和社群小程序。

交付质量就是线上急高等级的 BUG 数、SLA 和慢响应占比这些指标需要在一个好的范围内。

双月用户满意度就是我们自己做的一张问卷,每个双月推给各个协作方,让他们打分,满分 5 分。

业务支撑就是完成率和延期率不能超过某一个值。

基础建设就是完成团队和项目的迭代,包括组员能力成长、数据库优化、活动标准化、前端监控平台等。

虽然这一项比较难以量化,但是可以作为一个指导方向,让我们团队可以时刻关注着这块,到年底也能说道说道。

社群小程序是我们所维护的一个持续迭代的重要项目,考核标准是按期上线以及线上急高 BUG 数。

三、日常生活

现在下班后让我再去学技术,已经很难了,回家不是躺着刷手机,就是带小孩去这边那边的玩。

基本都是碎片时间,很少有大段的时间留给自己,一般也就晚上才会有点空闲时间。

小孩幼儿园正式毕业,9 月份入学,成为一年级大朋友了。

学校读书的原因,也在年中搬了次家,正式入住到新装修的房子中,去地铁站的距离加倍了。

1)旅行

今年总共去了 6 个地方旅行,都是带着小孩的,休闲为主,最远的是去了青岛,高铁单程 5 个小时,不远。

7 月中旬去的青岛,4 天 3 夜,天气一点都不热,环境很舒服,吃了点海鲜,有 3 天时间去了沙滩,小孩要挖泥沙。

景点附近容易堵车,不过地铁也很方便,景点都能到。吃的方面,崂山可乐有点喝不惯。

其余的地方就是江浙沪区域,包括常州恐龙园、无锡、同里古镇、西塘古镇、森泊,有的自驾有的高铁,现在去哪都方便。

高中的时候去过一次常州恐龙园,当时觉得是个公园而已,这次去了另一块游乐场区域,颠覆了我之前的认知。

2)兴趣

今年 3 月份看到鸟山明去世的消息后,就萌发了买套龙珠漫画的想法,但是我要买的版本得是童年时候内蒙古出版社的那套。

在闲鱼上看到套买的话比之前贵了几倍,就去散配,但其实也便宜不了,最终搞了一套,放书架。

还买了圣斗士的三套书籍,幽游白书正好看到有正版授权在卖,就也搞了一套,不过目前就 6 本。

5 月份又对手办产生了兴趣,又开始买些眼镜厂、一番赏等系列的手办,买了一堆,都摆到了书架中。

就摆在视野最好的那两层,也留了几个放在公司办公桌上当摆件,后面觉得占地方,收手了。

7 月份在青岛玩的时候,买了本介绍水浒卡的电子书,水浒卡就是 1999 年的时候流行的小浣熊水浒卡。

看了书之后,才了解到原来水浒卡版本有那么多,还分普卡、碎闪、闪卡等等。

从青岛回来后,就开始慢慢的在闲鱼上买卡,套买、散配都有,还自己的童年债。

我玩的比较花,打算把 34 号解珍这个人物的各个版本都搜集到。

如果每个版本都搜集 108 张,既费钱也费精力,几乎很难完成,并且卡太多了,都会来不及欣赏。

水浒闪卡还分软闪、硬面、奖闪,这部分比较费钱,花的大头都是在这块,其价格从当初翻了几十倍。

今年是怀旧的一年,买了很多以前就喜欢的东西,把私房钱挥霍的也差不多了。

随着人脸识别技术在金融、医疗等多个领域的加速落地,网络安全、信息泄露等问题愈为突出,用户对应用稳定性和安全性的要求也更为严格。

HarmonyOS SDK
场景化视觉服务
(Vision Kit)提供
人脸动作活体检测
能力,增强对于非活体攻击的防御能力和活体通过率。在投资理财、在线支付等高风险金融服务场景中,通过检测用户的组合动作等来验证用户为真实活体操作,抵御攻击,提高安全性,降低业务风险,全方位保障用户体验及信息安全。

动作活体检测支持实时捕捉人脸,采用指令动作配合的方式进行活体检测,开发者可以自定义检测的动作数量(最多为4个),系统会从6种预设动作(眨眼、张嘴、左摇头、右摇头、注视、点头)中随机生成所填数量的动作,用户按指令完成动作就可以判断是真实活体,还是非活体攻击(比如:人脸翻拍图片、人脸面具等)。

能力优势

端侧闭环
:人脸活体检测作为纯端侧能力,数据不保存,杜绝篡改和泄漏,确保数据安全和用户隐私。

空间小,响应快
:实现了高频攻击增强防护,同时保持ROM空间占用小,响应时间毫秒级,提供流畅的用户体验。

防护升级
: Vision Kit的防护系统通过自研算法升级,能够有效抵御高精视频和高精面具攻击。

开发简单
:支持UI定制,以满足个性化需求。经过两年以上的持续演进,积累了亿级数据和千万级的API调用量,成熟稳定。

功能演示

image

开发步骤

1.将实现人脸活体检测相关的类添加至工程。

import { interactiveLiveness } from '@kit.VisionKit';

2.在module.json5文件中添加CAMERA权限,其中reason,abilities标签必填,配置方式参见
requestPermissions标签说明

"requestPermissions":[
  {
    "name": "ohos.permission.CAMERA",
    "reason": "$string:camera_desc",
    "usedScene": {"abilities": []}
  }
]

3.简单配置页面的布局,选择人脸活体检测验证完后的跳转模式。如果使用back跳转模式,表示的是在检测结束后使用router.back()返回。如果使用replace跳转模式,表示的是检测结束后使用router.replaceUrl()去跳转相应页面。默认选择的是replace跳转模式。

Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
  Text("验证完的跳转模式:")
    .fontSize(18)
    .width("25%")
  Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
    Row() {
      Radio({ value: "replace", group: "routeMode" }).checked(true)
        .height(24)
        .width(24)
        .onChange((isChecked: boolean) => {
          this.routeMode = "replace"
        })
      Text("replace")
        .fontSize(16)
    }
    .margin({ right: 15 })

    Row() {
      Radio({ value: "back", group: "routeMode" }).checked(false)
        .height(24)
        .width(24)
        .onChange((isChecked: boolean) => {
          this.routeMode = "back";
        })
      Text("back")
        .fontSize(16)
    }
  }
  .width("75%")
}

4.如果选择动作活体模式,可填写验证的动作个数。

Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
  Text("动作数量:")
    .fontSize(18)
    .width("25%")
  TextInput({
    placeholder: this.actionsNum != 0 ? this.actionsNum.toString() : "动作数量最多4个"
  })
    .type(InputType.Number)
    .placeholderFont({
      size: 18,
      weight: FontWeight.Normal,
      family: "HarmonyHeiTi",
      style: FontStyle.Normal
    })
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .fontFamily("HarmonyHeiTi")
    .fontStyle(FontStyle.Normal)
    .width("65%")
    .onChange((value: string) => {
      this.actionsNum = Number(value) as interactiveLiveness.ActionsNumber;
    })
}

5.点击"开始检测"按钮,触发点击事件。

Button("开始检测", { type: ButtonType.Normal, stateEffect: true })
  .width(192)
  .height(40)
  .fontSize(16)
  .backgroundColor(0x317aff)
  .borderRadius(20)
  .margin({
    bottom: 56
  })
  .onClick(() => {
    this.privateStartDetection();
  })

6.触发CAMERA权限校验。

// 校验CAMERA权限
private privateStartDetection() {
  abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, this.array).then((res) => {
    for (let i = 0; i < res.permissions.length; i++) {
      if (res.permissions[i] === "ohos.permission.CAMERA" && res.authResults[i] === 0) {
        this.privateRouterLibrary();
      }
    }
  }).catch((err: BusinessError) => {
    hilog.error(0x0001, "LivenessCollectionIndex", `Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
  })
}

7.配置人脸活体检测控件的配置项InteractiveLivenessConfig,用于跳转到人脸活体检测控件。配置中具体的参数可参考
API文档

let routerOptions: interactiveLiveness.InteractiveLivenessConfig = {
  isSilentMode: this.isSilentMode as interactiveLiveness.DetectionMode,
  routeMode: this.routeMode as interactiveLiveness.RouteRedirectionMode,
  actionsNum: this.actionsNum
};

8.调用interactiveLiveness的startLivenessDetection接口,判断跳转到人脸活体检测控件是否成功。

// 跳转到人脸活体检测控件
private privateRouterLibrary() {
  if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
    interactiveLiveness.startLivenessDetection(routerOptions).then((DetectState: boolean) => {
      hilog.info(0x0001, "LivenessCollectionIndex", `Succeeded in jumping.`);
    }).catch((err: BusinessError) => {
      hilog.error(0x0001, "LivenessCollectionIndex", `Failed to jump. Code:${err.code},message:${err.message}`);
    })
  } else {
    hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
  }
}

9.检测结束后回到当前界面,可调用interactiveLiveness的getInteractiveLivenessResult接口,验证人脸活体检测的结果。

// 获取验证结果
private getDetectionResultInfo() {
  // getInteractiveLivenessResult接口调用完会释放资源
  if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
    let resultInfo = interactiveLiveness.getInteractiveLivenessResult();
    resultInfo.then(data => {
      this.resultInfo = data;
    }).catch((err: BusinessError) => {
      this.failResult = {
        "code": err.code,
        "message": err.message
      }
    })
  } else {
    hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
  }
}

了解更多详情>>

访问
场景化视觉服务联盟官网

获取
人脸活体检测开发指导文档

大家好,我是编程乐趣。

自从大模型爆火以来,.Net不管是官方、还是社区开源项目,都陆续推出很多重磅的项目。

在AI领域,对话即平台(CaaP)是未来的发展方向。

下面介绍一个开源项目,面向AI Bot平台构建者的开源机器学习框架:BotSharp,它已经为.NET开发人员提供了使用BotSharp AI BOT platform Builder构建CaaP的整套工具包,非常完美的,可以让我们开箱即用快速集成到应用系统中。

01 项目简介

BotSharp涉及自然语言理解、计算机视觉和音频处理技术,帮助开发者在信息系统中的开发和应用智能机器人助手。

兼容多种 AI 平台,如 ChatGPT 3.5/4.0、Gemini 2、LLaMA 3、Claude Sonnet 3.5、HuggingFace 等。

采用C#开发语言、支持跨平台,采用插件和流水线流执行设计,使插件完全解耦。因此,您可以选择不同的UI/UX,或选择不同的LLM提供商。

它们都是基于统一接口进行模块化的。BotSharp提供了一个高级代理抽象层,可以有效地管理企业中的复杂应用场景,使企业开发人员能够有效地将人工智能集成到业务系统中。

02 核心功能

多代理与对话管理:
内置支持多代理系统,能够处理复杂的对话场景,包括状态管理功能。

任务规划:
支持多种大型语言模型(LLM)规划方法,能够处理从简单到复杂的不同任务。

知识检索:
内置与 RAG(Retrieval-Augmented Generation)相关的接口,支持基于记忆的向量搜索。

多平台支持:
兼容多种 AI 平台,如 ChatGPT 3.5/4.0、Gemini 2、LLaMA 3、Claude Sonnet 3.5、HuggingFace 等。

协同工作:
允许多个具有不同职责的代理协同工作,以完成复杂的任务。

一站式开发:
在同一个平台上构建、测试、评估和审计你的 LLM 代理。

用户界面:
内置的 BotSharp UI 使用 SvelteKit 编写,提供友好的用户界面。

内容集成:
抽象化的标准富内容数据结构,可以与 Facebook Messenger、Slack 和 Telegram 等流行的消息渠道集成。

03 使用方法与界面截图

1、下载源码并启动项目

PS D:\> git clone https://github.com/SciSharp/BotSharp-UI
PS D:\> cd BotSharp-UI
PS D:\> npm install
PS D:\> npm run dev

默认就会在浏览器打开:
http://localhost:5015/

2、部分界面截图

2.1 登录界面

图片

2.2 后台首页

图片

2.3 路由

图片

2.4 代理列表

图片

2.5 会话列表

图片

04 技术优势

跨平台:
使用 C# 编写,运行在 .NET Core 上,支持跨平台部署。

模块化设计:
采用插件和管道流执行设计,完全解耦插件,便于开发者根据需求选择不同的 UI/UX 或 LLM 提供商。

使用 C# 机器学习算法:
直接在 C# 中采用机器学习算法,利用 C# 的类型语言特性,系统级重构代码时更加方便。

05 插件系统

采用组件化设计,使得核心功能保持精简,业务功能通过外部组件实现。

以下是部分内置插件:

数据存储:
支持多种数据存储方式,如 MongoDB、LiteDB、腾讯云对象存储等。

LLM 插件:
与不同 AI 平台的接口插件,如 Azure OpenAI、OpenAI、Anthropic AI、Google AI、Meta AI、HuggingFace 等。

消息/渠道:
支持多种消息渠道,如 ChatHub、Meta Messenger、Twilio、Telegram、微信等。

知识检索:
提供知识库和向量数据库插件。

视觉处理:
如 PaddleSharp 插件。

其他工具:
包括仪表板、路由加速器、音频处理、邮件处理、文件处理、HTTP 处理、SQL 驱动、WebDriver、Python 解释器等。

用户界面:
如 Chatbot UI 插件。

06 项目地址

https://github.com/SciSharp/BotSharp

- End -

有关更多AI,欢迎加入一起交流

更多开源项目:
https://github.com/bianchenglequ/NetCodeTop

元旦之后,我们和国内领先的开源软件公司
飞致云
达成了重要合作,合作分两部分,一是推广飞致云旗下的免费开源软件,一是双方合作推出联合会员。

飞致云旗下有多款免费开源软件,1月6日上线了第一个文字链广告,推广的是是飞致云旗下永久免费的开源 BI 工具 ——
DataEase

人人可用的 BI 工具,更符合国人使用习惯的数据可视化分析工具,数据大屏开发神器!

官网地址:
https://dataease.cn/

GitHub 仓库地址:
https://github.com/dataease/dataease/
(目前star:18.8k)

产品介绍 PPT:
https://dataease.io/download/introduce-dataease_202501.pdf

欢迎下载体验 DataEase 桌面版,支持 Windows, macOS, Linux,详见
安装和使用指南

欢迎了解 GitHub 上的
开源代码
,技术栈用的是 Java + Vue,欢迎提交 issue 或者 PR。

接下来,我们即将和飞致云合作推出另一款开源产品
1Panel
的联合会员,敬请期待。

和优秀的开源软件公司合作是园子今年商业化拓展的一个重要方向,1月就有了一个好的开始。

1.简介

本来按照计划这一系列的文章应该介绍Context和Page两个内容的,但是宏哥看了官方文档和查找资料发现其实和宏哥在Python+Playwright系列文章中的大同小异,差不了多少,再在这一个系列介绍就有点画蛇添足,索性就不介绍和讲解了,有兴趣的自己可以看宏哥之前写的,或者自己查找资料和官方文档进行了解和学习。今天讲解和分享的标签操作其实也是基于浏览器上下文(
BrowserContext
)进行操作的,而且宏哥在之前的BrowserContext也有提到过,但是有的童鞋或者小伙伴还是不清楚怎么操作,或者思路有点模糊,因此今天单独来对其进行讲解和分享一下,希望您有所帮助。

2.什么是tab标签页

Tabs 标签页又称选项卡(以下简称标签页),它是一种高效的屏幕空间利用手段,映射非常接近卡片的目录索引,用户可以基于索引标签,快速定位到目标中内容中去,这也是大多数用户来自现实世界的经验。

在 Web 页面中,它的使用场景也较为简单,当页面的内容信息量较多,用标签页可以对其分类,一方面可以提升查找信息的效率,另一方面可以精简用户单次获取到的信息量,用户更能够专注于当前已显示的内容。

‌标签页(Tab)是一种用户界面元素,用于组织和管理网页或应用程序中的内容,允许用户在不同的视图或数据集之间轻松切换。‌

标签页的设计灵感来源于现实生活中文件夹上的标签,通过隐喻的方式,设计师希望用户能够通过直观的方式理解和使用这一交互形式。

3.处理新标签页

浏览器上下文中的事件page可用于获取在上下文中创建的新页面。这可用于处理通过target="_blank"链接打开的新页面。

# Get page after a specific action (e.g. clicking a link)
with context.expect_page() as new_page_info:
    page.get_by_text("open new tab").click() # Opens a new tab
new_page = new_page_info.value

new_page.wait_for_load_state()
print(new_page.title())

实战举例:打开百度页面的-新闻链接,会出现一个新标签页,如下图所示:

3.1代码设计

按照上边的步骤进行代码设计,如下图所示:

3.2参考代码

packagecom.bjhg.playwright;

importcom.microsoft.playwright.Browser;
importcom.microsoft.playwright.BrowserContext;
importcom.microsoft.playwright.BrowserType;
importcom.microsoft.playwright.Locator;
importcom.microsoft.playwright.Page;
importcom.microsoft.playwright.Playwright;
importcom.microsoft.playwright.options.AriaRole;

/**
* @author 北京-宏哥
*
* @公众号:北京宏哥(微信搜索,关注宏哥,提前解锁更多测试干货)
*
* 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-10- 标签页(tab)操作 (详细教程)
*
* 2024年8月26日
*/
public classTest_Tab {

public static voidmain(String[] args) {
try (Playwright playwright =Playwright.create()) {
//1.使用chromium浏览器,# 浏览器配置,设置以GUI模式启动Chrome浏览器(要查看浏览器UI,在启动浏览器时传递 headless=false 标志。您还可以使用 slowMo 来减慢执行速度。
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false).setSlowMo(500));
//2.设置浏览器窗口大小
BrowserContext context =browser.newContext();
//创建page
Page page =context.newPage();

//3.浏览器打开百度
page.navigate("https://www.baidu.com/");
System.out.println(page.title());

// Get page after a specific action (e.g. clicking a link)
Page newPage = context.waitForPage(() ->{
page.getByText("网盘").click(); // Opens a new tab
});

System.out.println(newPage.title());

//关闭page
page.close();
//关闭browser
browser.close();
}
}

}

3.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的浏览器的动作。如下图所示:

如果触发新页面的操作未知,可以使用以下模式。

// Get all new pages (including popups) in the context
context.onPage(page ->{
page.waitForLoadState();
System.out.println(page.title());
});

4.处理弹出窗口

如果页面打开一个弹出窗口(例如通过链接打开的页面),您可以通过监听页面上的事件target="_blank"来获取对它的引用。popup
除了browserContext.on('page')事件之外还会发出此事件,但仅针对与此页面相关的弹出窗口。

// Get popup after a specific action (e.g., click)
Page popup = page.waitForPopup(() ->{
page.getByText("open the popup").click();
});
// Interact with the popup normally
popup.getByRole(AriaRole.BUTTON).click();
System.out.println(popup.title());

如果触发弹出窗口的操作未知,则可以使用以下模式。

// Get all popups when they open
page.onPopup(popup ->{
popup.waitForLoadState();
System.out.println(popup.title());
});

宏哥找了半天也没有找到类似的例子,索性自己动手弄一个弹出窗口的例子。

4.1demo源代码

1.HTML源代码,如下所示:

<!DOCTYPE html>
<html>
<head>
  <metacharset="UTF-8">
  <title> 北京-宏哥的弹出窗口demo</title>
   <style>body{background-color:tomato;font-family:sans-serif;padding:5em;
        }h1, p{text-align:center;
        }.cta{background-color:white;border:1px solid lightgrey;border-radius:6px;color:tomato;display:inline-block;font-family:"Open Sans", sans-serif;font-size:1em;font-weight:700;margin-bottom:1em;padding:0.3em 1em;text-decoration:none;
        }.cta:hover{background-color:lightgrey;color:slategrey;text-decoration:none;
        }.modaloverlay{background:rgba(0, 0, 0, 0.8);bottom:0;left:0;opacity:0;pointer-events:none;position:fixed;right:0;top:0;-webkit-transition:opacity 400ms ease-in;-moz-transition:opacity 400ms ease-in;transition:opacity 400ms ease-in;z-index:-1;display:none;
        }.modaloverlay:target{display:block;opacity:1;pointer-events:auto;z-index:99999;
        }.modaloverlay .modal{background-color:white;height:100%;position:relative;margin:0 auto;padding:3em;overflow-y:scroll;-webkit-overflow-scrolling:touch;
        }@media (min-width: 60em){.modaloverlay .modal {
height
:75%;margin:5% auto;max-height:57em;max-width:66em;width:85%; }}
.modaloverlay .modal > iframe, .modaloverlay .modal > div
{border:none;width:100%;height:100%; }.modaloverlay .close{background-color:turquoise;color:white;font-size:24px;padding:8px 12px;position:absolute;right:0;text-align:center;text-decoration:none;top:0;z-index:1; } </style> </head> <bodytranslate="no" > <p>This is a example of a <aclass="cta"href="#google"target="_blank">popup modal</a></p> <divid="google"class="modaloverlay"> <divclass="modal"> <ahref="#close"class="close">&times;</a> <div> <h1>这是一些窗口里的内容</h1> <p>如果页面打开一个弹出窗口(例如通过链接打开的页面),您可以通过监听页面上的事件target="_blank"来获取对它的引用。popup除了browserContext.on('page')事件之外还会发出此事件,但仅针对与此页面相关的弹出窗口。</p> </div> </div> </div> </body> </html>

2.浏览器预览demo的点击弹窗效果图,如下图所示:

4.2代码设计

4.3参考代码

packagecom.bjhg.playwright;importcom.microsoft.playwright.Browser;importcom.microsoft.playwright.BrowserContext;importcom.microsoft.playwright.BrowserType;importcom.microsoft.playwright.Locator;importcom.microsoft.playwright.Page;importcom.microsoft.playwright.Playwright;importcom.microsoft.playwright.options.AriaRole;/***@author北京-宏哥
*
* @公众号:北京宏哥(微信搜索,关注宏哥,提前解锁更多测试干货)
*
* 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-10- 标签页(tab)操作 (详细教程)
*
* 2024年8月26日
*/ public classTest_Tab {public static voidmain(String[] args) {try (Playwright playwright =Playwright.create()) {//1.使用chromium浏览器,# 浏览器配置,设置以GUI模式启动Chrome浏览器(要查看浏览器UI,在启动浏览器时传递 headless=false 标志。您还可以使用 slowMo 来减慢执行速度。 Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false).setSlowMo(500));//2.设置浏览器窗口大小 BrowserContext context =browser.newContext();//创建page Page page =context.newPage();//3.浏览器打开百度 page.navigate("file:///C:/Users/Administrator/Desktop/66.html");
System.out.println(page.title());
//Get popup after a specific action (e.g., click) Page popup = page.waitForPopup(() ->{
page.getByText(
"popup modal").click();
});

System.out.println(popup.title());
//关闭page page.close();//关闭browser browser.close();
}
}

}

4.4运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的浏览器的动作。如下图所示:

5.小结

在这进行实践操作过程中,宏哥发现了一个问题,宏哥记得在Python系列中的playwright处理新标签的时候,定位新闻的时候可以成功,但是Java系列中的playwright处理新标签的时候,定位新闻的时候却定位到多个。因此没有办法了,宏哥就定位了网盘。宏哥觉得不对啊,都是按官方文档操作的怎么可能一个成功,一个失败了,难道是官方的bug,仔细一想不可能啊,这么简单的问题怎么可能有bug,毕竟都是微软的骨灰级的大佬。然后宏哥返回查了一下Python的,发现没有按官方文档写,而是自己写的一个定位。造成这个现象的主要是因为这两个方法。

page.getByText("新闻").click(); //定位到多个新闻
page.click("text=新闻");   //定位到新闻点击

‌page.getByText("新闻").click(); 和 page.click("text=新闻"); 之间的区别主要在于定位策略和执行方式。‌

page.getByText("新闻").click();‌ 使用了Playwright的getByText方法,这个方法允许你通过文本内容来定位页面元素。首先,它会找到文本内容为"新闻"的元素,然后执行点击操作。这种方法提供了更灵活的定位方式,因为它直接定位到具有特定文本内容的元素,而不需要预先知道元素的ID或其他属性。

‌page.click("text=新闻");‌ 直接使用了click方法,并通过text=新闻作为参数,这表明它可能使用了某种内部机制来识别和定位文本为"新闻"的元素。这种方法虽然简洁,但在某些情况下可能不如getByText方法灵活,因为它依赖于Playwright内部的处理逻辑来定位元素。

总的来说,page.getByText("新闻").click();提供了更明确的定位策略,允许你首先获取到元素,然后对元素执行操作,而page.click("text=新闻");则更侧重于直接执行点击操作,可能依赖于Playwright的内部机制来处理元素的定位。在实际应用中,如果需要更精确地控制元素的获取和操作,推荐使用page.getByText("新闻").click();的方式‌。

综上所述:宏哥的理解就是‌page.getByText("新闻").click(); 先定位然后再去点击,定位到三个新闻,点击的时候,不知道点击那个新闻就报错了,而page.click("text=新闻");先点击,然后再找新闻的文本传给点击。定位到第一个新闻传过去就点击结束了,没有报错。

好了,关于标签页(tab)的相关操作非常简单,时间不早了今天就分享到这里,感谢你耐心地阅读!