注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

Demo体验

Demo体验

场景Demo,开箱即用
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

如何努力才能成为核心骨干?

我相信每个人都想成为组里的核心骨干,不用打酱油,不用干杂活,可以选择最有挑战的工作、最有收益的工作,可以把杂活脏活累活甩给其他人。 有更多的锻炼机会,有更好的晋升机会…… 但组里的核心骨干毕竟是少数,就像年终绩效考评S和 A 绩效总是极少数人。大多数人只能拿 ...
继续阅读 »

我相信每个人都想成为组里的核心骨干,不用打酱油,不用干杂活,可以选择最有挑战的工作、最有收益的工作,可以把杂活脏活累活甩给其他人。 有更多的锻炼机会,有更好的晋升机会……


但组里的核心骨干毕竟是少数,就像年终绩效考评S和 A 绩效总是极少数人。大多数人只能拿 B。


我的三份工作都把前辈熬走了


熬走了前辈,我就成为了核心骨干。


在我待过的三家公司来看,第一家是新创项目,我就是元老,虽然刚毕业,但被安排负责了一部分核心工作,然而另一个名校应届生因为来的晚,只能负责监控打点优化超时的一些杂活,我则始终参与核心业务需求和核心框架升级。我觉得我的能力不比他强,只是我来的早一点罢了。


第二份工作,我入职时候表现很差。因为公司不加班,我表现的过于放飞自我,下班较早,被老板认为摆烂没上进心。一直被安排负责边缘业务。但是待了一年半不到,比我入职早的、同时入职的都离职跑路了,看得出领导是捏着鼻子把我当核心骨干用。后来换了领导,新的领导更是把我当骨干用。就这样新的一年我接触核心业务,核心技术改造,进步确实比之前干杂活快的多。


第三份工作是在美团外卖,比较核心的业务,但是一开始也是负责杂活。老员工把他一些不想干的小技术优化分给我,还说"这些容易出成果","呵呵"。这些小活杂活真的干起来很没有意思。但是互联网人员流动快的铁律又出现了,才入职一年,负责核心模块的两个员工相继离职,而我毫无疑问,马上接替他们的工作。之前虽然给他俩打杂,但好歹我平时没少熟悉代码,熟悉业务逻辑,所以最适合接替他俩的自然是我。


总结下来,这三份工作我从打杂到核心骨干,并不是靠卷出来的。靠内卷上位,真的是非常难的。大家都是一个脑袋,凭什么你更强。大家都是 24 小时,凭什么你更强。你作为后来的,很难挤掉其他人上位。


当核心骨干要靠【 熬 】


成核心骨干是适合大多数人的方式。新同学进入到新的环境,想要超越组里的老人,是一件很有挑战的事情。因为大多数工作只要做的久就一定能胜任,系统再复杂再有挑战,组里总会有人熟悉。因为系统代码都是他们写的,你作为一个新人 想追上再超越非常困难。(想要超车,必须要有弯道啊)


就算你工作能力突出,核心工作就会交给你负责吗?不然。组里的老员工负责核心工作干的好好地,为什么主管会让你负责呢?让你负责核心工作,组里的核心老员工去干啥,干杂活吗?从个人感情上,老员工更亲。从工作经验上老员工更多。新人要想超越,需要付出太多。


就我个人经验而言,组里每个人都负责其中一部分工作,在没有特殊情况下,不会出现工作内容的交换。(不排除个别团队会轮换工作内容)


把老员工熬走,你就是老员工。把核心员工熬走,你就是核心员工。”熬“是一种平和的心态,让我们戒骄戒躁,当被安排一些杂活时,能保持平静。只有情绪平和,才能好好地工作。


互联网公司人员流动快,虽说是熬成老员工。但可能只需要熬一年就成老员工。


熬是一种良好心态,让我们脚踏实地,眼望天空


熬着,并不是把上班当煎熬。而是能摆正心态,认识到被分配干杂活是正常的,并不是针对你,每个人都是这么过来的。 熬着是一种耐心,相信早晚有一天轮到自己。


平时要把每一份交给自己的工作做好。否则,等核心员工离职了,领导也不信任你,还继续让你干杂活,这就真是悲剧了。


劳资要和他们斗到底


工作中难免有不顺心的时候,可能是产品经理奇葩需求太多,加班太多。也可能是和上下游团队吵架甩锅。甚至可能是间歇性疲倦。


不顺心,难免会想着离职跳槽,跳槽也许还能涨薪,诱惑很大。


但是成为核心骨干后,做最有挑战的工作才能让人成长最快。如果轻易跳槽,就错失最好最快的成长机会。


当面对工作中的不顺心,要始终保持 一份斗争的心态。告诉自己,谁欺负自己,就和他斗到底。



  1. 产品奇葩需求多,就和他争排期,争需求的不合理,争取领导协调更多人力。

  2. 上下游团队吵架甩锅。就和大家学,如何微笑的吵架,微笑的把锅甩出去。

  3. 加班多,周末就多出去放松、锻炼下身体、吃点好吃的。让自己开心。同时也要向领导寻求帮助,或者传递自己加班太多的委屈。不要让自己的辛苦被领导忽视。


“劳资要和他们斗到底” 这是对”敌人“的宣战,更是对一个懦弱、爱放弃、爱退缩的自己宣战!


好事多磨,机会是熬出来的,优秀的人也是熬出来的。不要轻易跳槽哦~


大家一起共勉。


作者:他是程序员
来源:juejin.cn/post/7281499704220106815
收起阅读 »

父母在家千万注意别打开“共享屏幕”,银行卡里的钱一秒被转走......

打开屏幕共享,差点直接被转账 今天和爸妈聊天端午回家的事情,突然说到最近AI诈骗的事情,千叮咛万嘱咐说要对方说方言才行,让他们充分了解一下现在骗子诈骗的手段,顺便也找了一下骗子还有什么其他的手段,打算一起和他们科普一下,结果就发现下面这一则新闻: 在辽宁大连务...
继续阅读 »

打开屏幕共享,差点直接被转账


今天和爸妈聊天端午回家的事情,突然说到最近AI诈骗的事情,千叮咛万嘱咐说要对方说方言才行,让他们充分了解一下现在骗子诈骗的手段,顺便也找了一下骗子还有什么其他的手段,打算一起和他们科普一下,结果就发现下面这一则新闻:


在辽宁大连务工的耿女士接到一名自称“大连市公安局民警”的电话,称其涉嫌广州一起诈骗案件,让她跟广州警方对接。耿女士在加上所谓的“广州警官”的微信后,这位“警官”便给耿女士发了“通缉令”,并要求耿女士配合调查,否则将给予“强制措施”。随后,对方与耿女士视频,称因办案需要,要求耿女士提供“保证金”,并将所有存款都集中到一张银行卡上,再把钱转到“安全账户”。


图片


期间,通过 “屏幕共享”,对方掌握了耿女士银行卡的账号和密码。耿女士先后跑到多家银行,取出现金,将钱全部存到了一张银行卡上。正当她打算按照对方指示,进行下一步转账时,被民警及时赶到劝阻。在得知耿女士泄露了银行卡号和密码后,银行工作人员立即帮助耿女士修改了密码,幸运的是,银行卡的近6万元钱没有受到损失。


就这手段,我家里的老人根本无法预防,除非把手机从他们手里拿掉,与世隔绝还差不多,所以还是做APP的各大厂商努力一下吧!


希望各大厂商都能看看下面这个防劫持SDK,让出门在外打工的我们安心一点。


防劫持SDK


一、简介


防劫持SDK是具备防劫持兼防截屏功能的SDK,可有效防范恶意程序对应用进行界面劫持与截屏的恶意行为。


二、iOS版本


2.1 环境要求


条目说明
兼容平台iOS 8.0+
开发环境XCode 4.0 +
CPU架构armv7, arm64, i386, x86_64
SDK依赖libz, libresolv, libc++

2.2 SDK接入


2.2.1 DxAntiHijack获取

官网下载SDK获取,下面是SDK的目录结构


1.png


DXhijack_xxx_xxx_xxx_debug.zip 防劫持debug 授权集成库 DXhijack_xxx_xxx_xxx_release.zip 防劫持release 授权集成库




  • 解压DXhijack_xxx_xxx_xxx_xxx.zip 文件,得到以下文件




    • DXhijack 文件夹



      • DXhijack.a 已授权静态库

      • Header/DXhijack.h 头文件

      • dx_auth_license.description 授权描述文件

      • DXhijackiOS.framework 已授权framework 集成库






2.2.2 将SDK接入XCode

2.2.2.1 导入静态库及头文件

将SDK目录(包含静态库及其头文件)直接拖入工程目录中,或者右击总文件夹添加文件。 或者 将DXhijackiOS.framework 拖进framework存放目录


2.2.2.2 添加其他依赖库

在项目中添加 libc++.tbd 库,选择Target -> Build Phases,在Link Binary With Libraries里点击加号,添加libc++.tbd


2.2.2.3 添加Linking配置

在项目中添加Linking配置,选择Target -> Build Settings,在Other Linker Flags里添加-ObjC配置


2.3 DxAntiHijack使用


2.3.1 方法及参数说明

@interface DXhijack : NSObject

+(void)addFuzzy; //后台模糊效果
+(void)removeFuzzy;//后台移除模糊效果
@end

2.3.2 使用示例

在对应的AppDelegate.m 文件中头部插入


#import "DXhijack.h"

//在AppDelegate.m 文件中applicationWillResignActive 方法调用增加
- (void)applicationWillResignActive:(UIApplication *)application {
[DXhijack addFuzzy];
}

//在AppDelegate.m 文件中applicationDidBecomeActive 方法调用移除
- (void)applicationDidBecomeActive:(UIApplication *)application {
[DXhijack removeFuzzy];
}


三、Android版本


3.1 环境要求


条目说明
开发目标Android 4.0+
开发环境Android Studio 3.0.1 或者 Eclipse + ADT
CPU架构ARM 或者 x86
SDK三方依赖

3.2 SDK接入


3.2.1 SDK获取


  1. 访问官网,注册账号

  2. 登录控制台,访问“全流程端防控->安全键盘SDK”模块

  3. 新增App,填写相关信息

  4. 下载对应平台SDK


3.2.2 SDK文件结构



  • SDK目录结构 android-dx-hijack-sdk.png



    • dx-anti-hijack-${version}.jar Android jar包

    • armeabiarmeabi-v7aarm64-v8ax86 4个abi平台的动态库文件




3.2.3 Android Studio 集成

点击下载Demo


3.2.3.1 Android Studio导入jar, so

把dx-anti-hijack-x.x.x.jar, so文件放到相应模块的libs目录下


android-dx-hijack-as.png



  • 在该Module的build.gradle中如下配置:


 android{
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}

repositories{
flatDir{
dirs 'libs'
}
}
}


dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}



3.2.3.2 权限声明

Android 5.0(不包含5.0)以下需要在项目AndroidManifest.xml文件中添加下列权限配置:


<uses-permission android:name="android.permission.GET_TASKS"/>

3.2.3.3 混淆配置

-dontwarn *.com.dingxiang.mobile.**
-dontwarn *.com.mobile.strenc.**
-keep class com.security.inner.**{*;}
-keep class *.com.dingxiang.mobile.**{*;}
-keep class *.com.mobile.strenc.**{*;}
-keep class com.dingxiang.mobile.antihijack.** {*;}

3.3 DxAntiHijack 类使用


3.3.1 方法及参数说明

3.3.1.1 初始化


建议在Application的onCreate下調用


/**
* 使用API前必須先初始化
* @param context
*/

public static void init(Context context);

3.3.1.2 反截屏功能


/**
* 反截屏功能
* @param activity
*/

public static void DGCAntiHijack.antiScreen(Activity activity);

/**
* 反截屏功能
* @param dialog
*/

public static void DGCAntiHijack.antiScreen(Dialog dialog);

3.3.1.3 反劫持检测


/**
* 调用防劫持检测,通常现在activity的onPause和onStop调用
* @return 是否存在被劫持风险
*/

public static boolean DGCAntiHijack.antiHijacking();

3.3.2 使用示例

//使用反劫持方法
@Override
protected void onPause() {
boolean safe = DXAntiHijack.antiHijacking();
if(!safe){
Toast.makeText(getApplicationContext(), "App has entered the background", Toast.LENGTH_LONG).show();
}
super.onPause();
}

@Override
protected void onStop() {
boolean safe = DXAntiHijack.antiHijacking();
if(!safe){
Toast.makeText(getApplicationContext(), "App has entered the background", Toast.LENGTH_LONG).show();
}
super.onStop();
}



//使用反截屏方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DXAntiHijack.antiScreen(MainActivity.this);
}

以上。


结语


这种事情层出不穷,真的不是吾等普通民众能解决的,最好有从上至下的政策让相应的厂商(尤其是银行和会议类的APP)统一做处理,这样我们在外打工的人才能安心呀。


作者:昀和
来源:juejin.cn/post/7242145254057312311
收起阅读 »

我裸辞了,但是没走成!

人在国企,身不由己!公司福利和薪资还可以,但有个难顶的组长就不可以,说走就走!如果把这个组长换了的话就另说了! 1.为什么突然想不干了? 1.奇葩的新组长 我的前组长辞职了,然后被安排到这个自研项目组,而这个新组长我之前得罪过,老天爷真爱开玩笑! 今年过年前,...
继续阅读 »

人在国企,身不由己!公司福利和薪资还可以,但有个难顶的组长就不可以,说走就走!如果把这个组长换了的话就另说了!


1.为什么突然想不干了?


1.奇葩的新组长


我的前组长辞职了,然后被安排到这个自研项目组,而这个新组长我之前得罪过,老天爷真爱开玩笑!


今年过年前,我主开发的平台要嵌入到他负责的项目里面,一切对接都很顺利,然而某天,有bug,我修复了,在群里面发消息让他合并分支更新一下。他可能没看到,然后我下班后一个小时半,我还在公司,在群里问有没有问题,没回应!


然后我就坐车回家,半路,产品经理组长、大组长和前组长一个个轮流call我,让我处理一下bug,我就很无语!然后我就荣获在家远程办公,发现根本没问题!然后发现是对方没更新的问题!后面我修复完直接私聊他merge分支更新,以免又这样大晚上烦人!
而类似的事情接连发生,第三次之后,我忍不住了,直接微信怼了他,他还委屈自己晚上辛苦加班,我就无语大晚有几个人用,晚上更新与第二天早上更新有什么区别?然后就这样彻底闹掰了!


我就觉得这人很奇葩,有什么问题不能直接跟我沟通,一定要找我的上级一个个间接联系我呢?而且,这更新流程就很有问题,我之前在别的组支援修bug,是大早上发布更新,一整天测试,保证不是晚上的时候出现要紧急处理的问题!


然后,我跟这人有矛盾后,我就没继续对接这个项目了,前组长安排了别人代替我!


结果兜兜转转,竟然调到他这里来!作孽啊!


2.项目组乱糟糟


新项目组可以看出新组长管理水平很糟糕!


新组长给自己的定位是什么都管!产品、前后端、测试、业务等,什么都往自己身上揽!他自己觉得很努力,但他不是那部分的专业人员,并不擅长,偏偏还没那个金刚钻揽一堆瓷器活!老爱提建议!


项目组就两个产品,其中一个是UI设计刚转,还没成长为专业的产品经理,而那个主要负责的产品经理根本忙不过来!


然后,他一个人搞不定,就开始了PUA大法,周会的时候就会说:“希望大家要把这个项目当成自己的事业来奋斗,一起想,更好地做这个产品!”


“这个项目集成了那么多的模块功能,如果大家能够做好,对自己有很大的历练和成长!”


“我们项目是团队的重点项目,好多领导都看好,开发不要仅限于开发,要锻炼产品思维……”


……


简而言之就是,除了本职工作也要搞点产品的工作!


然后建模师开始写市场调研文档,前后端开发除了要敲代码,还得疯狂想新功能。


整个组开始陷入搞新东西的奇怪旋涡!


某次需求评审的时候,因为涉及到大量的文件存储,我提出建议,使用minio,fastdfs,这样就不用每次部署的时候,整体文件还要迁移,结果对方一口拒绝,坚决使用本地存储,说什么不要用XX平台的思想来污染他这个项目,他这个项目就要不需要任何中间件都能部署。


就很无语!那个部署包又大又冗余,微服务都不用,必须要整包部署整套系统,只想要某几个功能模块都不行,还坚持说这样可以快速整包部署比较好!


一直搞新功能的问题就是版本更新频繁!一堆新功能都没测清楚就发布,导致产品质量出现严重问题,用户体验极差!终于用户积攒怨气爆发了,在使用群里面@了我们领导,产品质量问题终于被彻底揭开了遮羞布!


领导开始重视这个产品质量的问题,要求立即整改!


然后这个新组长开始新一轮搞事!


“大家保证新功能进度的同时,顺便测试旧功能,尽量不要出bug!”


意思就是你开发进度也要赶,测试也要搞!


就不能来点人间发言吗?


3.工作压力剧增


前组长是前端的,他带我入门3D可视化的,相处还算融洽!然而他辞职了,去当自由职业者了!


新组长是后端的,后端组长问题就是习惯以后端思维评估前端工作,给任务时间很紧。时间紧就算了,还要求多!


因为我之前主开发的项目是可视化平台,对方不太懂,但不妨碍他喜欢异想天开,加个这个,加个那个,他说一句话,就让你自行想象,研究竞品和评估开发时间!没人没资源,空手套白狼,我当时就很想爆他脑袋了!


我花一个星期集成了可视化平台的SDK,连接入文档都写好了,然后他验收的时候提出一堆动态配置的要求,那么大的可视化平台,他根本没考虑项目功能模块关联性和同步异步操作的问题,他只会提出问题,让你想解决方案!


然后上个月让我弄个web版的Excel表格,我看了好多开源项目,也尝试二开,增加几个功能,但效率真的好低!于是我就决定自己开发一个!


我开发了两个星期,他就问我搞定没?我说基本功能可以,复杂功能还在做!


更搞笑的是,我都开发两个星期了,对方突然中午吃饭的时候微信我,怕进度赶不上,建议我还是用开源的进行二开,因为开源有别人帮忙一起搞。


我就很无语,人家搞的功能又不是一定符合你的需求,开源不等于别人给你干活,大家都是各干各的,自己还得花精力查看别人代码,等价于没事找事,给自己增加工作量!别人开发的有隐藏问题,出现bug排查也很难搞,而自己开发的,代码熟悉,即便有问题也能及时处理!


我就说他要是觉得进度赶不上就派个人来帮忙,结果他说要我写方案文档,得到领导许可才能给人。


又要开发赶进度,又要写文档,哪有那么多时间!最终结果就是没资源,没人手,进度依旧要赶!


因为我主开发的那个可视化平台在公司里有点小名气,好多平台想要嵌入,然后,有别的平台找到他要加上这个可视化平台,但问题是我很忙,又要维护又要开发,哪搞得了那么多?还说这个很赶!赶你个头!明知道时间没有,就别答应啊!工作排期啊!


新组长不帮组员解决问题,反而把问题抛给组员,压榨组员就很让人反感!


2.思考逃离


基于以上种种!我觉得这里不是一个长久之地,于是想要逃离这里!


我联系了认识的其他团队的人,别人表示只要领导愿意放人,他们愿意接收我,然后我去咨询一些转团队的流程,那些转团队成功的同事告诉我,转团队最难的是领导放人这关,而且因为今年公司限制招聘,人手短缺,之前有人提出申请,被拒绝了!并且转团队的交接的一两个月内难免要承受一些脸色!有点麻烦!


我思虑再三,我放弃了转团队这条路,因为前组长走了之后,整个团队只剩下我一个搞3D开发的,估计走不掉!


3.提出辞职


忍了两个月,还是没忍住,工作最重要的是开开心心!赚钱是一回事,要是憋出个心理疾病就是大事了!于是我为了自己的身心健康,决定走人!拜拜了喂!老娘不奉陪了!


周一一大早,我就提交了辞职信,大组长表示很震惊,然后下午的时候,领导和大组长一起来跟我谈话,聊聊我为什么离职?问我有没有意愿当个组长之类的,我拒绝了,我只想好好搞技术!当然我不会那么笨去说别人的坏话得罪人!


我拿前组长当挡箭牌,说自己特别不习惯这个新组长的管理方式!前组长帮我扛着沟通之类的问题,我只要专心搞开发就好了!


最终,我意志坚定地挡住了领导和大组长的劝留谈话,并且开始刷面试题,投简历准备寻找新东家!


裸辞后真的很爽,很多同事得知消息都来关心我什么情况,我心里挺感动的!有人说我太冲动了,可以找好下家再走!但我其实想得很清楚,我没可能要求一个组长委屈自己来适应我,他有他的管理方式,我有我的骄傲!我不喜欢麻烦的事,更不喜欢委屈自己,一个月后走人是最快解决的方案!


4. 转机


其实我的离开带来了一点影响,然后加上新组长那个产品质量问题警醒了领导,然后新组长被调去负责别的项目了,换个人来负责现在的项目组,而这个人就是我之前支援过的项目组组长,挺熟悉的!


新新组长管理项目很有条理也很靠谱,之前支援的项目已经处于稳定运行的状态了,于是让他来接手这个项目!他特意找我谈话,劝我留下来,并且承诺以后我专心搞技术,他负责拖住领导催进度等问题!


我本来主要就是因为新组长的问题才走人的,现在换了个不错的组长!可以啊!还能苟苟!


5.反思



  1. 其实整件事情中,我也有错,因为跟对方闹掰了,就拒绝沟通,所以导致很多问题的发生,如果我主动沟通去说明开发难度的问题,并且争取时间,就不至于让自己处于一个精神内耗的不快乐状态。

  2. 发现问题后,我没有尝试去跟大组长反馈,让大组长去治治对方,或者让大组长帮忙处理这个矛盾,我真的太蠢了!

  3. 我性格其实挺暴躁的,看不顺眼就直接怼,讨厌的人就懒得搭理,这样的为人处世挺不讨喜的,得改改这坏脾气!


作者:敲敲敲敲暴你脑袋
来源:juejin.cn/post/7241884241616076858
收起阅读 »

怎么用一句话证明你在游戏公司里的最底层?

引言 今天在知乎看到一个有趣的帖子:如何一句话证明你在公司最底层?我们把范围缩小到游戏公司。 关于这个问题,身边80%的朋友描述了自己在公司底层的难忘回忆,还有几位朋友甚至因为这不堪的回忆破防了。 刚进入游戏公司的新人,迷茫是常态。和大家一样,笔者也曾是公司的...
继续阅读 »

如何一句话证明你在公司最底层?


引言


今天在知乎看到一个有趣的帖子:如何一句话证明你在公司最底层?我们把范围缩小到游戏公司。


关于这个问题,身边80%的朋友描述了自己在公司底层的难忘回忆,还有几位朋友甚至因为这不堪的回忆破防了。


刚进入游戏公司的新人,迷茫是常态。和大家一样,笔者也曾是公司的最底层,总觉得每天一睁眼就是各种困难的事等着我:



担心工作内容不会做,担心与同事沟通不好,担心自己考核不过关......



今天的这篇文章,大家一起来看看一位位于游戏公司底层的游戏开发者的最底层体验。


最底层体验


图片源于网络


1.介绍一下你自己


大家好,我是XXX,来自XXX。虽然我是一个新人,但我对游戏充满了热情,这种热情已经伴随我多年。小时候,我就沉迷于各种游戏,从那时起,我就梦想着有一天能够为创造令人陶醉的游戏世界做出贡献。我加入这个行业的目标是成为一个出色的游戏开发者,并参与创造令人惊叹的游戏体验。我相信,通过与这个行业的优秀人才一起工作,我可以不断成长,并为我们的团队和项目做出贡献。谢谢大家,请多多指教。


此处应有一阵热烈的掌声,那是对一位懵懂的游戏行业新人的勇敢表示敬畏。他或许不知道他的棱角将在这里被磨平。


熟悉又让人崩溃的弹窗


2.熟悉项目,体验游戏。


游戏行业新人刚进到游戏公司,可能第一件事就是登陆公司内部使用的通讯工具。你的直属上司可能早早的在网线那头等候着你的上线。


你好,XXX。你先接收一下这份文档,仔细阅读一下里面的内容。检出一下公司的游戏项目,然后根据文档把游戏跑起来。体验一下游戏,熟悉一下游戏的每个系统。有问题可以请教你旁边的那位大神,他负责带你。


好的,谢谢。由于在来公司之前做足了准备,检出项目、运行项目这种小问题肯定难不倒你。这时候你会惊讶,原来这就是大型的商业化游戏项目,看起来有那么点高大上,但是最多的是还是看不懂。不过这游戏玩着好无聊,不是我喜欢的类型。想到未来的日子里,需要不停地重复地在这个游戏里面遨游,"真的会谢"。



3.分配任务



  • 修改禅道bug序号XXX的问题。

  • 修改活动XXX文本显示异常问题。

  • 修改XXX报错问题,完成禅道单子序号1、2、3、4、5......。


游戏行业新人的入门任务往往就是这些看起来微不足道,但是却非常细节的问题。正所谓不积跬步无以至千里,通过慢慢处理这些小小的bug和显示异常的问题,无疑是熟悉项目的最好方式。虽然这些都是比较基本的内容,修改bug、调整UI、修复报错。但是能够体现一个新人的基本功:阅读问题描述、理解问题描述、定位问题所在系统、定位系统所在代码、读懂代码原有逻辑、修改错误代码、验证问题是否修复、思考会不会对其他内容造成影响。


这对于管理者来说是非常合理的,但对于新人来说,未免太过于简单了。


支线任务


4.支线任务


游戏行业新人入门有可能并不能第一时间接触到游戏项目主分支的代码,往往是参与其他的一些分支版本,例如审核服(专门为了应对平台审核员的审核搭建的游戏服)、版署服(用于申请版号专门搭建的游戏服)、海外服(主要负责多语言版本的语言提取、翻译替换、本地化处理)等等。


安排新人去处理这些支线任务,为的就是让新人从另外一个相对安全的分支去熟悉游戏项目,避免因新人的处理不当造成线上版本出问题,从而造成公司的经济损失。支线任务通常就是枯燥单一的体力劳动,不需要过多的技巧,只需要耐得住寂寞的心。


图片源于网络


5.几点下班


一位有着远大抱负的新人,往往在刚进入公司的日子里,不知道几点下班。领导分配给我的任务,实在太简单了,三两下就完成了,还不到规定的时间。为了能够更加快速地熟悉项目,参与游戏功能的开发,继续研究代码。


HR说19点下班,但是18点的时候大家都跑去吃饭,不解,跟着。等到19点的时候,果然没有人下班。继续奋笔疾书。20点的时候终于有人下班了,可是领导还是没动静,算了,再看看代码吧。21点,领导好像发现了这个新人,让他早点回去休息。(没有人告诉他,这将是常态。) "没事,我再看会代码,马上就回去了。"


手机先吃


6.福利


同事: “公司发月饼了,你没去领吗?”,“不知道啊,没人通知。我刚来几天。”


同事: ”我看大家都去领了,现在。“,”我不知道自己是否算正式员工“


同事: ”你先去看看吧,反正大家都在领。“,兴致冲冲地跑到发月饼的地方。


发月饼的: “叫什么名字?”,”XXX“


发月饼的: ”名单上没这个人,不能领!“


刚加入公司的时候,可能由于没转正或者名字还没有进入公司的名册,往往会导致有些福利不能享受。例如公司发月饼的时候,人人有份,唯独你。又或者公司发奖金,你拿200慰问金。公司发年终奖,你还是拿慰问金。 但是如果你想请假,领导秒批。甚至说你想离职,领导也是轻描淡写,“好的”。没有丝毫的牵挂留恋。这是前所未有的福利。


结语


不管怎样,虽然你是公司的最底层,但你是公司中最坚实的基石,因为你在每一颗砖石上都留下了你的汗水和努力,为了让整座大厦能够稳固地矗立在成功的巅峰。加油,请认真工作,积极向上。


AD:笔者已经上线的小游戏《贪吃蛇掌机经典》《填色之旅》《重力迷宫球》大家可以自行点击搜索体验。


感兴趣的小伙伴记得关注微信公众号"亿元程序员"哦,一位有着8年游戏行业经验的主程。学习游戏开发不迷路。感谢您的关注,希望能给到您帮助, 也希望通过您能帮助到大家。


喜欢的可以点个、点个在看哦!请把该文章分享给你觉得有需要的其他小伙伴。谢谢。


作者:亿元程序员
来源:juejin.cn/post/7281589318329925689
收起阅读 »

uCharts 小程序地图下钻功能

web
uCharts 小程序地图下钻功能 最近在研究小程序图表,其中提到了一个地图下钻的功能,感觉挺有意思的,分享一下共同学习! 项目简介 这个Uni-App项目旨在提供一个可交互的地图,允许用户在中国地图的不同层级之间自由切换。用户可以从国家地图开始,然后深入到各...
继续阅读 »

uCharts 小程序地图下钻功能


最近在研究小程序图表,其中提到了一个地图下钻的功能,感觉挺有意思的,分享一下共同学习!


项目简介


这个Uni-App项目旨在提供一个可交互的地图,允许用户在中国地图的不同层级之间自由切换。用户可以从国家地图开始,然后深入到各省份地图,最终进入城市地图,点击不同区域/县级市查看详细信息。


下面是最终效果图👇👇


1695175245503.png


文档地址



准备工作


在开始之前,请确保你已经安装了Vue.js和Uni-App


并且准备好了模拟地图数据。这些数据将用于绘制地图。


地图数据遵循geoJson地图数据交换格式。如果你不熟悉geoJson,可以参考这里


绘制中国地图


// 首先引入我们的mock数据
import mockData from '../../mock/index'

// onLoad中调用 drawChina 方法来绘制中国地图
drawChina() {
uni.setNavigationBarTitle({
title: '中国地图'
});
setTimeout(() => {
let series = mockData.china.features;
// 这里循环一下series,把需要的数据增加到serie的属性中,fillOpacity是根据数据来显示的颜色层级透明度
for (var i = 0; i < series.length; i++) {
// 这里模拟了随机数据,实际开发中请根据实际情况修改
series[i].value = Math.floor(Math.random() * 1000)
series[i].fillOpacity = series[i].value / 1000
series[i].color = "#0D9FD8"
}
// 这里把series赋值给chartData,这样就可以在页面中渲染出来了
this.chartData = {
series: series
};
}, 100);
}

uCharts组件使用


插件导入后在uni_modules中,命名规则符合easyCom,可以直接在页面中使用


<qiun-data-charts
type="map"
canvas2d=""
:chartData="chartData"
:opts="opts"
:inScrollView="true"
:pageScrollTop="pageScrollTop"
tooltipFormat="mapFormat"
@getIndex="getIndex"
@complete="complete"
/>

注释说明:



  • chartData 包含地图数据

  • opts 是我们在 data 中定义的配置项

  • tooltipFormat 类型为字符串,需要指定为 config-ucharts.jsconfig-echarts.js 中 formatter 下的属性值,
    这里我们使用了 mapFormat,可以在 config-ucharts.js 中查看

  • 在页面中必须传入 pageScrollTop,并将 inScrollView 设置为 true,否则可能导致某些地图事件无法触发


事件说明:



  • @complete 事件是地图绘制完成后触发的事件,我们可以在这个事件中获取地图的实例,
    然后可以调用地图的方法进行进一步操作。

  • @getIndex 事件是地图点击事件,我们可以获取到点击的地图信息,
    根据这个信息来判断是否需要进行下钻操作,如果需要下钻,可以替换 chartData 并重新绘制地图。


下钻操作


  // 点击地图获取点击的索引
getIndex(e) {
console.log('点击地图', e);
if (e.currentIndex > -1) {
switch (this.layout) {
case 'china':
this.layout = 'province';
break;
case 'province':
this.layout = 'city';
break;
case 'city':
this.layout = 'area';
break;
default:
uni.showModal({
title: '提示',
content: '当前已经是最后一级地图,点击空白回到中国地图',
success: () => {

}
});
break;
}

this.drawNext(e.currentIndex);
} else {
this.layout = 'china';
this.drawChina();
}
}

以上代码中,我们通过 currentIndex 来判断当前点击的是哪个地图,然后根据 layout 的值来判断是否需要进行下钻操作。
如果需要下钻,我们就调用 drawNext 方法来绘制下一级地图。


这个demo中,我们只模拟了中国地图、省级地图、市级地图和区县级地图,如果在开发中我们需要根据adcode请求后端接口来获取地图数据


具体代码:git仓库地址


作者:养乐多多多
来源:juejin.cn/post/7278945628905226275
收起阅读 »

因为你没有正确面对问题,所以遍体鳞伤在所难免!

昨天在开发过程中遇到一个问题,排查到晚上12点,今天又排查了一天,到了晚上九点的时候,经过与同事的不断讨论,验证,终于发现了问题! 遇到问题是常态,在每个阶段对问题的思考方式也不一样,面对问题有两种选择,要么撒手不干,要么就硬着头皮去解决,当解决了一个又一个问...
继续阅读 »

昨天在开发过程中遇到一个问题,排查到晚上12点,今天又排查了一天,到了晚上九点的时候,经过与同事的不断讨论,验证,终于发现了问题!


遇到问题是常态,在每个阶段对问题的思考方式也不一样,面对问题有两种选择,要么撒手不干,要么就硬着头皮去解决,当解决了一个又一个问题的时候,你会发现自己成长了好多,技术只是一部分,我觉得只能占20%,而你的思考能力,你的认知,你的心态将会得到很大的提升,这占80%,这个世界上还有比进步更美好的事情吗?


但是解决问题是要有策略的,如果想单靠自己的能力去解决问题,我觉得不大可能,人在不同阶段,不同环境是十分受限的,因此除了打铁还得本身硬之外,我觉得更重要的是要学会“借力”,下面从自己的经历中总结几点!


1.学会不要脸


我觉得问题部分简单与否,傻逼与否,只要你肯问,基本上没人会拒绝回答你,不过得有一个前提,就是自己的确深度去思考了,闷只能让自己陷入深渊!


记得刚毕业进入公司的时候,分配了一个业务很复杂的问题,加上祖传代码,令人痛不欲生,虽然理清了代码,但是业务不太懂,问了同事几次,还是不太清楚,后面脆弱的自尊心作怪,不好意思继续问了,总怕别人觉得自己傻逼,于是就埋头苦干,我还记得下班后回到出租屋,深夜都在研究,但是问题越来越多,后面厚着脸皮去问,才把很多问题解决!


所以其实很多时候问题并不是很难,只要去和别人多沟通,沟通能解决百分之七八十的问题!


2.众里寻它千百度,问题出在自己处


问题有时候就出在眼前,只是自己没去去仔细观察,千里之行始于足下,有时候出现问题,我们总是会说这可能是别的地方有问题从而影响自己这边,但是却很少先从自己下手,一个很简单的逻辑,当自己加入后,出现问题了,那么很大程度是从自己这里开始的!


所以在觉得其他地方不对的时候得先审视自己,将自己的东西详细考虑后,确定没问题后再去怀疑其他地方,大多时候肯定是自己这里出问题,可能是粗心,也可能是基础不扎实,逻辑混乱等!


我们可以相信自己,但是也要学会怀疑自己,只有持怀疑的态度才能进步,如果有人给你说,“你完全不用怀疑我”,那么这人大概率也不怎么样!


3.你今天觉得是问题,过段时间就不是啥问题


我觉得心态是最重要的,很多人需要问题解决不了,就会陷入焦虑,害怕工作受影响,害怕学业受影响,从而睡不好觉!


很多同学找我毕业设计辅导的时候说自己焦虑得睡不着觉,害怕毕不了业,其实大可不必,因为焦虑是一个死循环,会对你的身心产生很大的影响,从而工作和学习的效率就会变得很低,有的时候无所谓一点比较好!


当实在做不了啦,那就别去做了,出去狂欢一下,把烦恼释放出去,心态整理好,然后再回来继续做,有时候我自己在做一些东西的时候,十分痛苦,也会很焦虑,但是当我跑出去看看山水,跑跑步,让自己的大脑和身体放松下来后,我再去解决这个问题,很快就能解决了!


每当遇到问题,我总会给自己说,现在这个问题你觉得是个问题,但是一段时间过后,你会发现这啥也不是,你会觉得为啥自己那时候会那么焦虑呢!


所以天塌下来了,该吃饭就吃饭,该睡觉就睡觉,别让工作和学业绑架自己,因为生活才是最重要的,如果真的实在干不了,觉得自己扛不住,身心受到很大影响,那就暂时撂挑子不干了!


你觉得呢!


今天的分享就到这里,感谢你的观看,下期见!


作者:刘牌
来源:juejin.cn/post/7281113939124453376
收起阅读 »

谈谈我家的奇葩买房经历

我是 2017 年毕业的,18 年买的房。 当时 IT 行业还是如日中天,薪资确实很高,我刚毕业就有接近 40 万。 当时的房价也是一路飙升,一周一个价那种。 我有个同事那年在北京买了房,犹豫了一周,涨了 20 多万。 那年过年回家的时候,我爸问我存了多少钱,...
继续阅读 »

我是 2017 年毕业的,18 年买的房。


当时 IT 行业还是如日中天,薪资确实很高,我刚毕业就有接近 40 万。


当时的房价也是一路飙升,一周一个价那种。


我有个同事那年在北京买了房,犹豫了一周,涨了 20 多万。


那年过年回家的时候,我爸问我存了多少钱,我说没有存多少,不知道钱花在哪里了。


我爸嫌我花的太多了,说要不买个房吧,这样每月还房贷还能存下点。


我说北京的房子需要交 5 年公积金才能买,而且首付二百多万呢,还没那么多钱。况且以后我也不一定留在北京,可能回青岛干。


年后我就回京继续上班了。


我爸在家开了一个门店,每天坐在门口和邻居聊天。


邻居聊起他儿子读完博士在青岛工作了,在黄岛区买了个房子,两周涨了十多万呢。


然后我爸就急了,非让我妈也去买一个,说是现在买还便宜点,就算我以后不回青岛,也可以卖了去北京再买。


我爸和我妈其实关系并不好,几乎是连吵带骂的逼着我妈去买。


为什么他不自己去呢?


因为我爸有严重的晕车,一坐车就吐。


我妈其实也没出过远门,自己一个人坐车从潍坊去青岛买房确实难为她了。


我妈还有点迷信,临走之前找算卦的占了一卦,算出一个方位,说是去青岛的城阳区买。


然后我妈就去了。


我妈啥也不懂,就在一个小区门口转悠。


然后保安过来问她干啥的。


她说想来买房,但是不知道去哪里买。


保安说我给你介绍一个人,可以找他买。


然后就给我妈介绍了一个中介。


那个中介说现在青岛都限购,需要交 2 年社保,只有即墨不限购,因为它刚撤市划区,划入青岛。


然后我妈找了个出租,并且给了出租的 200 块钱,让他一起去。


之后就到了即墨观澜国际的售楼处,人家介绍说这个房子是楼王,也就是最中间的那栋楼,是最好的,而且只有几套了。


我妈还在纠结,但是那个出租不耐烦了,要走。


然后我妈就定下来了,交了 70 万首付。


之后要办理手续,我从北京回家了一趟,和我爸我妈一起打车去了青岛。


我爸一路吐了有几十次,他说把胆汁都吐出来了。


就这样,我们就在青岛买下了这套房子。


13380 一平,首付 70 万,贷款 100 万,还 15 年,总共还 150 万。


然后我又回北京上班去了,只不过开始了还房贷的日子,一个月 1 万。


之后我爸又给了我 30 万,加上我自己还的,差不多在 2021 年就把 100 多万贷款还完了。因为提前还还的少。


差不多我爸 100 万,我拿了 100 万。


其实我还挺震惊的,我爸这样一个吃喝都那么节俭的人,竟然能拿出 100 万现金来。


后来在 2022 年年中的时候,我爸浑身疼的厉害,在地上打滚那种疼,去医院查出来是淋巴癌晚期。


然后 2023 年也就是今年年初的时候,我爸去世了。


去世前交代了一些事情,这套房子给我的 100 万就是他一辈子的积蓄了。


二手房要满 5 年才能卖,正好今年可以卖了。


但是问了下房价,现在观澜国际的均价是 7000 多,我们 2018 年买的时候是 13380 呢。而且 200 万的房子现在 90 万都不一定卖出去。


那我能咋办?


怪我爸?但我爸已经没了。


怪我妈?我妈也经常犯愁,而且当年是我爸逼她去的。


而且当年那种情况,我爸做的决定并没有错,当年大多数人都会认为房价会一直涨,早上车省很多钱。


我身边有一些朋友也是为了这个刚毕业不久就买房了。


其实住的话倒也没啥问题,关键是我并不去青岛工作,而且即墨那边也找不到前端的工作,互联网公司就集中在那几个城市。


租的话,一年才 1 万 5,而且装修还要投入好几万。


所以只能卖了。


本来是我们打算 5 年后卖了,正好在北京交满了 5 年公积金,然后再去北京买。


现在这情况,估计 200 万可能一分也收不回来。


遇到这事,正常人都会难受吧,我也一样。


那天我公众号发了条卖房消息:



真的是为了卖房么?


肯定不是啊,这样能把房子卖出去就怪了。


我只是想把它讲出来,仅此而已。讲出来之后确实好多了。


这几年我这种情况的全国也有不少:



并不是为了炒房,但确实因为各种原因不去住了。结果再卖的时候腰斩都卖不出去。


后来我也释然了,我本身物欲就很低,一辈子也用不了多少钱。


而且我还年轻,赚的也不少,可以再攒。


更重要的是,我一直觉得人这一生不能只是为了赚钱,要找到自己热爱的事业,在这个方向上持续开拓,创造自己的价值。


所幸我找到了。它才是支撑起我后半生的骨架。


最后,这段经历也不是完全没价值,至少我可以把它写下来,当做故事讲给你们听。


作者:zxg_神说要有光
来源:juejin.cn/post/7281833142104948776
收起阅读 »

因为你没有正确面对问题,所以遍体鳞伤在所难免!

昨天在开发过程中遇到一个问题,排查到晚上12点,今天又排查了一天,到了晚上九点的时候,经过与同事的不断讨论,验证,终于发现了问题! 遇到问题是常态,在每个阶段对问题的思考方式也不一样,面对问题有两种选择,要么撒手不干,要么就硬着头皮去解决,当解决了一个又一个问...
继续阅读 »

昨天在开发过程中遇到一个问题,排查到晚上12点,今天又排查了一天,到了晚上九点的时候,经过与同事的不断讨论,验证,终于发现了问题!


遇到问题是常态,在每个阶段对问题的思考方式也不一样,面对问题有两种选择,要么撒手不干,要么就硬着头皮去解决,当解决了一个又一个问题的时候,你会发现自己成长了好多,技术只是一部分,我觉得只能占20%,而你的思考能力,你的认知,你的心态将会得到很大的提升,这占80%,这个世界上还有比进步更美好的事情吗?


但是解决问题是要有策略的,如果想单靠自己的能力去解决问题,我觉得不大可能,人在不同阶段,不同环境是十分受限的,因此除了打铁还得本身硬之外,我觉得更重要的是要学会“借力”,下面从自己的经历中总结几点!


1.学会不要脸


我觉得问题部分简单与否,傻逼与否,只要你肯问,基本上没人会拒绝回答你,不过得有一个前提,就是自己的确深度去思考了,闷只能让自己陷入深渊!


记得刚毕业进入公司的时候,分配了一个业务很复杂的问题,加上祖传代码,令人痛不欲生,虽然理清了代码,但是业务不太懂,问了同事几次,还是不太清楚,后面脆弱的自尊心作怪,不好意思继续问了,总怕别人觉得自己傻逼,于是就埋头苦干,我还记得下班后回到出租屋,深夜都在研究,但是问题越来越多,后面厚着脸皮去问,才把很多问题解决!


所以其实很多时候问题并不是很难,只要去和别人多沟通,沟通能解决百分之七八十的问题!


2.众里寻它千百度,问题出在自己处


问题有时候就出在眼前,只是自己没去去仔细观察,千里之行始于足下,有时候出现问题,我们总是会说这可能是别的地方有问题从而影响自己这边,但是却很少先从自己下手,一个很简单的逻辑,当自己加入后,出现问题了,那么很大程度是从自己这里开始的!


所以在觉得其他地方不对的时候得先审视自己,将自己的东西详细考虑后,确定没问题后再去怀疑其他地方,大多时候肯定是自己这里出问题,可能是粗心,也可能是基础不扎实,逻辑混乱等!


我们可以相信自己,但是也要学会怀疑自己,只有持怀疑的态度才能进步,如果有人给你说,“你完全不用怀疑我”,那么这人大概率也不怎么样!


3.你今天觉得是问题,过段时间就不是啥问题


我觉得心态是最重要的,很多人需要问题解决不了,就会陷入焦虑,害怕工作受影响,害怕学业受影响,从而睡不好觉!


很多同学找我毕业设计辅导的时候说自己焦虑得睡不着觉,害怕毕不了业,其实大可不必,因为焦虑是一个死循环,会对你的身心产生很大的影响,从而工作和学习的效率就会变得很低,有的时候无所谓一点比较好!


当实在做不了啦,那就别去做了,出去狂欢一下,把烦恼释放出去,心态整理好,然后再回来继续做,有时候我自己在做一些东西的时候,十分痛苦,也会很焦虑,但是当我跑出去看看山水,跑跑步,让自己的大脑和身体放松下来后,我再去解决这个问题,很快就能解决了!


每当遇到问题,我总会给自己说,现在这个问题你觉得是个问题,但是一段时间过后,你会发现这啥也不是,你会觉得为啥自己那时候会那么焦虑呢!


所以天塌下来了,该吃饭就吃饭,该睡觉就睡觉,别让工作和学业绑架自己,因为生活才是最重要的,如果真的实在干不了,觉得自己扛不住,身心受到很大影响,那就暂时撂挑子不干了!


你觉得呢!


今天的分享就到这里,感谢你的观看,下期见!


作者:刘牌
链接:https://juejin.cn/post/7281113939124453376
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Flutter 实现登录 UI

本文,我将解析怎么前构建一个用户交互的登录页面。这里,我使用 TextField 挂件,这方便用户输入用户名和密码。还使用 FlatButton 挂件,来处理一些动作。当然,我还使用了 Image 挂件来设定登录页面的 logo。 效果图如下: 第一步: m...
继续阅读 »

本文,我将解析怎么前构建一个用户交互的登录页面。这里,我使用 TextField 挂件,这方便用户输入用户名和密码。还使用 FlatButton 挂件,来处理一些动作。当然,我还使用了 Image 挂件来设定登录页面的 logo


效果图如下:




第一步: main() 函数

import 'package:flutter/material.dart';void main() {
runApp(MyApp());
}class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginDemo(),
);
}
}

这个 main() 函数也就是应用的入口。MyApp 类中添加了一个 LoginDemo 类作为 home 属性的参数。


第二步:class LoginDemo


  • 设定脚手架的 appBar 属性来作为应用的标题,如下:
appBar: AppBar(
title: Text('Login Page'),
),

  • 在本次的 UI 布局中,所有的挂件都会放在 Column 挂件中,然后存放在脚手架的 body 中。Column 中的第一个是存放 Container 挂件,用来处理 Image 挂件。
Container(
height: 150.0,
width: 190.0,
padding: EdgeInsets.only(top: 40),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(200),
),
child: Center(
child: Image.asset('asset/images/flutter-logo.png'),
),
),

flutter-logo.png 文件存放在 asset/images 文件夹中。我们需要在 pubspec.yaml 文件中配置路径。

# To add assets to your application, add an assets section, like this:
assets:
- asset/images/



添加完资源之后,我们可以运行应用了。


  • 然后,使用 TextField 挂件处理用户名和密码。 TextField 挂件是一个输入挂件,帮助我们处理用户的输入信息。
Padding(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'User Name',
hintText: 'Enter valid mail id as abc@gmail.com'
),
),
),
Padding(
padding: EdgeInsets.all(10),
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
hintText: 'Enter your secure password'
),
),
),

这里的 Padding 挂件能够帮助你设定 TextField 挂件的内边距。



obscureText 属性值为 true 的时候,帮助我们对 TextField 展示特殊的字符,而不是真正的文本。



  • 我们使用 FlatButton 挂件来处理忘记密码
FlatButton(
onPressed: (){
//TODO FORGOT PASSWORD SCREEN GOES HERE
},
child: Text(
'Forgot Password',
style: TextStyle(color: Colors.blue, fontSize: 15),
),
),

onPressed() 这个函数中,我们可以处理页面跳转或者其他的点击逻辑。


  • 对于登录按钮,我们使用 FlatButton 挂件,但是我们得装饰一下,这里我们使用 Container 进行包裹。
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.*blue*, borderRadius: BorderRadius.circular(20),
),
child: FlatButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => HomePage()),
);
},
child: Text(
'Login',
style: TextStyle(color: Colors.*white*, fontSize: 25),
),
),
),

上面我们设定了 Container 挂件的 heightwidth 属性,所以 flatbutton 也会获取到相同的高度和宽度。


decoration 属性允许我们设计按钮,比如颜色 colorColors.blueborderRadiusBorderRadius.circular(20) 属性。


  • 最后指定 Text 挂件以为新用户创建账号

这里我们可以通过 GestureDetector 挂件的 onTap() 功能进行导航操作。或者创建类似忘记密码按钮的 onPressed() 事件。


这里是整个项目的完整代码:

// lib/HomePage.dart

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: Container(
height: 80,
width: 150,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(10)),
child: FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Welcome',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
),
),
);
}
}
// lib/main.dart
import 'package:flutter/material.dart';

import 'HomePage.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginDemo(),
);
}
}

class LoginDemo extends StatefulWidget {
@override
_LoginDemoState createState() => _LoginDemoState();
}

class _LoginDemoState extends State<LoginDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Login Page"),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
child: Container(
width: 200,
height: 150,
/*decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(50.0)),*/
child: Image.asset('asset/images/flutter-logo.png')),
),
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email',
hintText: 'Enter valid email id as abc@gmail.com'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(

obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
hintText: 'Enter secure password'),
),
),
FlatButton(
onPressed: (){
//TODO FORGOT PASSWORD SCREEN GOES HERE
},
child: Text(
'Forgot Password',
style: TextStyle(color: Colors.blue, fontSize: 15),
),
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(20)),
child: FlatButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => HomePage()));
},
child: Text(
'Login',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
),
SizedBox(
height: 130,
),
Text('New User? Create Account')
],
),
),
);
}
}


本文采用意译的方式翻译。原文 levelup.gitconnected.com/login-page-…



推荐阅读

作者:Jimmy
链接:https://juejin.cn/post/7147865530869907487
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

在这个大环境下我是如何找工作的

蛮久没更新了,本次我想聊聊找工作的事情,相信大家都能感受到从去年开始到现在市场是一天比一天差,特别是在我们互联网 IT 行业。 已经过了 18 年之前的高速发展的红利期,能做的互联网应用几乎已经被各大公司做了个遍,现在已经进入稳定的存量市场,所以在这样的大背景...
继续阅读 »

蛮久没更新了,本次我想聊聊找工作的事情,相信大家都能感受到从去年开始到现在市场是一天比一天差,特别是在我们互联网 IT 行业。
已经过了 18 年之前的高速发展的红利期,能做的互联网应用几乎已经被各大公司做了个遍,现在已经进入稳定的存量市场,所以在这样的大背景下再加上全世界范围内的经济不景气我想每个人都能感受到寒意。


我还记得大约在 20 年的时候看到网上经常说的一句话:今年将是未来十年最好的一年。


由于当时我所在的公司业务发展还比较顺利,丝毫没有危机意识,对这种言论总是嗤之以鼻,直到去年国庆节附近。


虽然我们做的是海外业务,但是当时受到各方面的原因公司的业务也极速收缩(被收购,资本不看好),所以公司不得不进行裁员;
其实到我这里的时候前面已经大概有 2~3 波的优化,我们是最后一波,几乎等于是全军覆没,只留下少数的人维护现有系统。


这家公司也是我工作这么多年来少数能感受到人情味的公司,虽有不舍,但现实的残酷并不是由我们个人所决定的。


之后便开始漫长的找工作之旅,到现在也已经入职半年多了;最近看到身边朋友以及网上的一些信息,往往是坏消息多于好消息。


市场经历半年多的时间,裁员的公司反而增多,岗位也越来越少,所以到现在不管是在职还是离职的朋友或多或少都有所焦虑,我也觉得有必要分享一下我的经历。


我的预期目标


下面重点聊聊找工作的事情;其实刚开始得知要找工作的时候我并不是特别慌,因为当时手上有部分积蓄加上公司有 N+1 的赔偿,同时去年 10 月份的时候岗位相对于现在还是要多一些。


所以我当时的目标是花一个月的时间找一个我觉得靠谱的工作,至少能长期稳定的工作 3 年以上。


工作性质可以是纯研发或者是偏管理岗都可以,结合我个人的兴趣纯研发岗的话我希望是可以做纯技术性质的工作,相信大部分做业务研发的朋友都希望能做一些看似“高大上”的内容。
这一点我也不例外,所以中间件就和云相关的内容就是我的目标。


不过这点在重庆这个大洼地中很难找到对口工作,所以我的第二目标是技术 leader,或者说是核心主程之类的,毕竟考虑到 3 年后我也 30+ 了,如果能再积累几年的管理经验后续的路会更好走一些。


当然还有第三个选项就是远程,不过远程的岗位更少,大部分都是和 web3,区块链相关的工作;我对这块一直比较谨慎所以也没深入了解。


找工作流水账


因为我从入职这家公司到现在其实还没出来面试过,也不太知道市场行情,所以我的想法是先找几家自己不是非去不可的公司练练手。



有一个我个人的偏好忘记讲到,因为最近的一段时间写 Go 会多一些,所以我优先看的是 Go 相关的岗位。



第一家


首先第一家是一个 ToB 教育行业的公司,大概的背景是在重庆新成立的研发中心,技术栈也是 Go;


我现在还记得最后一轮我问研发负责人当初为啥选 Go,他的回答是:



Java 那种臃肿的语言我们首先就不考虑,PHP 也日落西山,未来一定会是 Go 的天下。



由于是新成立的团队,对方发现我之前有管理相关的经验,加上面试印象,所以是期望我过去能做重庆研发 Leader。


为此还特地帮我申请了薪资调整,因为我之前干过 ToB 业务,所以我大概清楚其中的流程,这种确实得领导特批,所以最后虽然没成但依然很感谢当时的 HR 帮我去沟通。


第二家


第二家主要是偏年轻人的 C 端产品,技术栈也是 Go;给我印象比较深的是,去到公司怎么按电梯都不知道🤣



他们办公室在我们这里的 CBD,我长期在政府赞助的产业园里工作确实受到了小小的震撼,办公环境比较好。



当然面试过程给我留下的印象依然非常深刻,我现在依然记得我坐下后面试官也就是 CTO 给我说的第一句话:



我看过你的简历后就决定今天咱们不聊技术话题了,直接聊聊公司层面和业务上是否感兴趣,以及解答我的疑虑,因为我已经看过你写的很多博客和 GitHub,技术能力方面比较放心。



之后就是常规流程,聊聊公司情况个人意愿等。


最后我也问了为什么选 Go,这位 CTO 给我的回答和上一家差不多😂


虽然最终也没能去成,但也非常感谢这位 CTO,他是我碰到为数不多会在面试前认真看你的简历,博客和 GitHub 都会真的点进去仔细阅读👍🏼。



其实这两家我都没怎么讲技术细节,因为确实没怎么聊这部分内容;这时就突出维护自己的技术博客和 GitHub 的优势了,技术博客我从 16 年到现在写了大约 170 篇,GitHub 上开源过一些高 star 项目,也参与过一些开源项目,这些都是没有大厂经历的背书,对招聘者来说也是节约他的时间。



 


当然有好处自然也有“坏处”,这个后续会讲到。


第三家


第三家是找朋友推荐的,在业界算是知名的云原生服务提供商,主要做 ToB 业务;因为主要是围绕着 k8s 社区生态做研发,所以就是纯技术的工作,面试的时候也会问一些技术细节。



我还记得有一轮 leader 面,他说你入职后工作内容和之前完全不同,甚至数据库都不需要安装了。



整体大概 5、6 轮,后面两轮都是 BOSS 面,几乎没有问技术问题,主要是聊聊我的个人项目。


我大概记得一些技术问题:


  • k8s 相关的一些组件、Operator
  • Go 相关的放射、接口、如何动态修改类实现等等。
  • Java 相关就是一些常规的,主要是一些常用特性和 Go 做比较,看看对这两门语言的理解。

其实这家公司是比较吸引我的,几乎就是围绕着开源社区做研发,工作中大部分时间也是在做开源项目,所以可以说是把我之前的业余爱好和工作结合起来了。


在贡献开源社区的同时还能收到公司的现金奖励,不可谓是双赢。


对我不太友好的是工作地在成都,入职后得成渝两地跑;而且在最终发 offer 的前两小时,公司突然停止 HC 了,这点确实没想到,所以阴差阳错的我也没有去成。


第四家


第四家也就是我现在入职的公司,当时是我在招聘网站上看到的唯一一家做中间件的岗位,抱着试一试的态度我就投了。
面试过程也比较顺利,一轮同事面,一轮 Leader 面。


技术上也没有聊太多,后来我自己猜测大概率也和我的博客和 Github 有关。




当然整个过程也有不太友好的经历,比如有一家成都的“知名”旅游公司;面试的时候那个面试官给我的感觉是压根没有看我的简历,所有的问题都是在读他的稿子,根本没有上下文联系。


还有一家更离谱,直接在招聘软件上发了一个加密相关的算法,让我解释下;因为当时我在外边逛街,所以没有注意到消息;后来加上微信后说我为什么没有回复,然后整个面试就在微信上打字进行。


其中问了一个很具体的问题,我记得好像是 MD5 的具体实现,说实话我不知道,从字里行间我感觉对方的态度并不友好,也就没有必要再聊下去;最后给我说之所以问这些,是因为看了我的博客后觉得我技术实力不错,所以对我期待较高;我只能是地铁老人看手机。


最终看来八股文确实是绕不开的,我也花了几天时间整理了 Java 和 Go 的相关资料;不过我觉得也有应对的方法。


首先得看你面试的岗位,如果是常见的业务研发,从招聘的 JD 描述其实是可以看出来的,比如有提到什么 Java 并发、锁、Spring等等,大概率是要问八股的;这个没办法,别人都在背你不背就落后一截了。


之后我建议自己平时在博客里多记录八股相关的内容,并且在简历上着重标明博客的地址,尽量让面试官先看到;这样先发制人,你想问的我已经总结好了😂。


但这个的前提是要自己长期记录,不能等到面试的时候才想起去更新,长期维护也能加深自己的印象,按照 “艾宾浩斯遗忘曲线” 进行复习。


选择



这是我当时记录的面试情况,最终根据喜好程度选择了现在这家公司。


不过也有一点我现在觉得但是考虑漏了,那就是行业前景。


现在的 C 端业务真的不好做,相对好做的是一些 B 端,回款周期长,同时不太吃现金流;这样的业务相对来说活的会久一些,我现在所在的公司就是纯做 C 端,在我看来也没有形成自己的护城河,只要有人愿意砸钱随时可以把你干下去。


加上现在的资本也不敢随意投钱,公司哪天不挣钱的话首先就是考虑缩减产研的成本,所以裁员指不定就会在哪一天到来。


现在庆幸的是入职现在这家公司也没有选错,至少短期内看来不会再裁员,同时我做的事情也是比较感兴趣的;和第三家有些许类似,只是做得是内部的基础架构,也需要经常和开源社区交流。


面对裁员能做的事情


说到裁员,这也是我第一次碰上,只能分享为数不多的经验。


避免裁员


当然第一条是尽量避免进入裁员名单,这个我最近在播客 作为曾经的老板,我们眼中的裁员和那些建议 讲到在当下的市场情况下哪些人更容易进入裁员名单:


  • 年纪大的,这类收入不低,同时收益也没年轻人高,确实更容易进入名单。
  • 未婚女性,这点确实有点政治不正确,但确实就是现在的事实,这个需要整个社会,政府来一起解决。
  • 做事本本分分,没有贡献也没出啥事故。
  • 边缘业务,也容易被优化缩减成本。

那如何避免裁员呢,当然首先尽量别和以上特征重合,一些客观情况避免不了,但我们可以在第三点上主动“卷”一下,当然这个的前提是你还想在这家公司干。


还有一个方法是提前向公司告知降薪,这点可能很多人不理解,因为我们大部分人的收入都是随着跳槽越来越高的;但这些好处是否是受到前些年互联网过于热门的影响呢?


当然个人待遇是由市场决定的,现在互联网不可否认的降温了,如果你觉得各方面呆在这家公司都比出去再找一个更好,那这也不失为一个方法;除非你有信心能找到一个更好的,那就另说了。


未来计划


我觉得只要一家公司只要有裁员的风声传出来后,即便是没被裁,你也会处于焦虑之中;要想避免这种焦虑确实也很简单,只要有稳定的被动收入那就无所谓了。


这个确实也是说起来轻松做起来难,我最近也一直在思考能不能在工作之余做一些小的 side project,这话题就大了,只是我觉得我们程序员先天就有自己做一个产品的机会和能力,与其把生杀大权给别人,不如握在自己手里。


当然这里得提醒下,在国内的企业,大部分老板都认为签了合同你的 24 小时都是他的,所以这些业务项目最好是保持低调,同时不能影响到本职工作。


作者:crossoverJie
链接:https://juejin.cn/post/7246570594991718455
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

当史上最强AI,遇上中国2023高考作文

今天是2023年高考第一天,刚过中午,坐在办公室里就收到了关于今年高考作文题的新闻推送。 要知道,高考作文题,历来都是能引发全国人民热议的一个话题,毕竟是写东西嘛,虽然不是所有人都能写好文章,但是绝大部分人多多少少都能写两句,面对高考作文都能发表下自己的见解。...
继续阅读 »

今天是2023年高考第一天,刚过中午,坐在办公室里就收到了关于今年高考作文题的新闻推送。


要知道,高考作文题,历来都是能引发全国人民热议的一个话题,毕竟是写东西嘛,虽然不是所有人都能写好文章,但是绝大部分人多多少少都能写两句,面对高考作文都能发表下自己的见解。


那么,当ChatGPT遇到高考作文,会发生什么呢?它能写出什么样的水平呢?


带着这个疑问,我决定试试。


我选取的是今年上海的作文题(我比较感兴趣),题目如下:




面对这个题目,不知道各位有什么想法么?如果你去考试,你会怎么写?


来,我们看看AI是怎么写的。


以下是GPT-4的作文,大家品鉴一下:




我先说下我的整体感受,然后再具体分析。


我就一个感受:我维持4个月前对于AI能力的基本判断,即「长短板都格外的明显」。




先说优点吧,全文非常顺畅,不仅有条理有举证,更重要的是有层次 当我还在思考,除了好奇心之外,还能从哪些角度去谈这个问题的时候,GPT几秒钟就给了我答案:


「一个人乐意去探索陌生世界,并非仅仅是因为好奇心,还有求知欲实现自我价值,和社会责任感


醍醐灌顶啊朋友们!


不知道你们有没有想到这么多角度,反正我这么短时间是想不出来的,太牛了!


关键是,这4个因素,是层层递进的!


“好奇心”尚且处于动物性 ,处于「本我」阶段,“求知欲”就具备了人性,迈入了「自我」,“实现自我价值”则是更进一步的「自我」,而到了“社会责任感”,就直接达到了「超我」的境界。


厉害了啊!


这就是我说的,它不仅仅是写的有条理,还有层次递进,这个太夸张了,可以说轻松超越99%的普通人。


好啦,彩虹屁吹完了,那么我再来说下缺点或者说劣势在哪。


其实相关观点我在之前的文章里已经表达过一次了,大家可以点这个看下:《不会出错的ChatGPT毫无意义》


还是那句话,GPT是以成年人的数据训练出来的,而成年人是数据具备了极强的逻辑性,也正因此,GPT最擅长逻辑推演,表达也极具逻辑性(所以它能层层递进)。


但是吧,强逻辑的东西自然就缺乏了人文美,缺乏浪漫和非理性想象力


不认可?好,我来给你证明一下。


为了试探GPT是否具备更多可能性,把文章写的更文艺一些,于是我换了个问法:




哦吼,翻车。除了题目稍微变了一下,剩下的内容几乎完全没有变化。


嗯,你可能要说了,是你问的不对。好,我再换个问法:




依然不行,依然是那副强逻辑的修辞和表达,我很失望,它不懂我的文艺。


也许确实是我问法不对,但我仍然坚定的认为,AI在人性的光芒面前,依然只是一台冰冷的机器,至少目前仍然是。


好啦,本文只是一个浅尝辄止的探讨,更多对于AI与人性的探讨,可以读读我之前的文章:


《不会出错的ChatGPT毫无意义》


作者:沐洒
链接:https://juejin.cn/post/7241874482574180411
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

在 iOS 中使用 IdentifyLookup 进行短信过滤

iOS
垃圾短信是一个长期存在、令人困扰的问题。本文将介绍如何阻止这些短信、设备端的检测以及整合动态的服务器检测等。 Apple 在 WWDC 2017(iOS 11) 推出了 IdentityLookup 框架,让开发者可以参与到过滤短信的过程中。在 iOS 14,...
继续阅读 »

垃圾短信是一个长期存在、令人困扰的问题。本文将介绍如何阻止这些短信、设备端的检测以及整合动态的服务器检测等。


Apple 在 WWDC 2017(iOS 11) 推出了 IdentityLookup 框架,让开发者可以参与到过滤短信的过程中。在 iOS 14,Apple 新增了两种过滤类别:交易信息(Promotion)、推广信息(transaction)。在 WWDC 2022(iOS 16),针对这两种类别,Apple 新增了 12 种子类别,推广信息包括 9 种子类别:其他(Others)、财务(Finance)、订单(Orders)、提醒(Reminders)、健康(Health)、天气(Weather)、运营商(Carrier)、奖励(Rewards)、公共服务(PublicServices)。交易信息包括 3 种子类别:其他(Others)、优惠(Offers)、优惠券(Coupons)。




消息过滤流程


消息过滤通过应用程序扩展(App extension)来完成。当用户收到来自未知发件人的消息时,“消息” APP 通过询问 Message Filter Extension,来确定该消息的类别。Message Filter Extension 可以通过使用内置逻辑或推迟到关联服务器的分析来做出此决定。



IdentityLookup 仅适用于来自未知发件人的短信和彩信,它不适用于联系人列表中发件人的消息、不适用任何 iMessage 消息、不适用于回复发件人 3 次及以上的会话。





“消息” APP 使用一个 ILMessageFilterQueryRequest 对象将信息传递给 Message Filter Extension。Message Filter Extension 确定该消息的类别后,将 ILMessageFilterQueryResponse 对象返回给“消息” APP。


如果 App extension 无法自行做出决策,“消息” APP将会把有关信息发送到与 Message Filter Extension 关联的服务器,并将响应传递给 Message Filter Extension。Message Filter Extension 解析服务器的响应并返回最终的 ILMessageFilterQueryResponse 对象,如下图所示。




出于隐私原因,系统会处理与关联的服务器的所有通信;Message Filter Extension 无法直接访问网络,也无法将数据写入应用的共享容器中。


消息过滤实践


为 APP 新增 Message Filter Extension:











我们依次来看 MessageFilterExtension.swift 文件中的代码:

import IdentityLookup
final class MessageFilterExtension: ILMessageFilterExtension {}

ILMessageFilterExtension 是的主要类的抽象基类。在 Info.plist 中被设置 NSExtensionPrincipalClass,将在收到消息时被构造:




ILMessageFilterExtension 类无其他要求或限制:

open class ILMessageFilterExtension : NSObject {
}

MessageFilterExtension 实现了 ILMessageFilterQueryHandlingILMessageFilterCapabilitiesQueryHandling 协议:

extension MessageFilterExtension: ILMessageFilterQueryHandling, ILMessageFilterCapabilitiesQueryHandling {
// ...
}

ILMessageFilterQueryHandling


ILMessageFilterExtension 子类必须符合 ILMessageFilterQueryHandling协议,通过包含短信信息的 queryRequest 、提供请求关联网络服务器能力的 context,来进行短信类别的判断。最终返回提供包含类别信息的 response

@available(iOS 11.0, *)
public protocol ILMessageFilterQueryHandling : NSObjectProtocol {
   // 闭包
   func handle(_ queryRequest: ILMessageFilterQueryRequest,
               context: ILMessageFilterExtensionContext,
               completion: @escaping (ILMessageFilterQueryResponse) -> Void)
// 异步函数
   func handle(_ queryRequest: ILMessageFilterQueryRequest,
               context: ILMessageFilterExtensionContext
   ) async -> ILMessageFilterQueryResponse
}

queryRequest 的信息如下,包括发件人号码 sender、短信内容 messageBodyISO 国家代码 receiverISOCountryCode

@available(iOS 11.0, *)
open class ILMessageFilterQueryRequest : NSObject, NSSecureCoding {
   open var sender: String? { get }
   open var messageBody: String? { get }
   @available(iOS 16.0, *)
   open var receiverISOCountryCode: String? { get }
}

context 提供请求关联网络服务器能力,我们也只能使用该能力访问网络:

@available(iOS 11.0, *)
open class ILMessageFilterExtensionContext : NSExtensionContext {
// 闭包
   open func deferQueryRequestToNetwork(completion: @escaping (ILNetworkResponse?, Error?) -> Void)
// 异步函数
   open func deferQueryRequestToNetwork() async throws -> ILNetworkResponse
}


URL 记录在 Info.plistILMessageFilterExtensionNetworkURL 中,无法进行自定义。





response 定义如下,需要提供对应的类别和子类别:

@available(iOS 11.0, *)
open class ILMessageFilterQueryResponse : NSObject, NSSecureCoding {
   open var action: ILMessageFilterAction
   @available(iOS 16.0, *)
   open var subAction: ILMessageFilterSubAction
}

noneallowjunkpromotiontransaction 类别,noneallow 的行为相同:

@available(iOS 11.0, *)
public enum ILMessageFilterAction : Int, @unchecked Sendable {
   case none = 0
   case allow = 1
   case junk = 2
   @available(iOS 14.0, *)
   case promotion = 3
   @available(iOS 14.0, *)
   case transaction = 4
}

以及文章开头提到的 12 种子类别:

@available(iOS 16.0, *)
public enum ILMessageFilterSubAction : Int, @unchecked Sendable {
   case none = 0
   
   /// TRANSACTIONAL SUB-ACTIONS
   
   case transactionalOthers = 10000
   case transactionalFinance = 10001
   case transactionalOrders = 10002
   case transactionalReminders = 10003
   case transactionalHealth = 10004
   case transactionalWeather = 10005
   case transactionalCarrier = 10006
   case transactionalRewards = 10007
   case transactionalPublicServices = 10008
   
   /// PROMOTIONAL SUB-ACTIONS
   
   case promotionalOffers = 20001
   case promotionalCoupons = 20002
}

因此,整体的过滤代码框架如下,依次进行设备端的检测、服务器检测:

func handle(_ queryRequest: ILMessageFilterQueryRequest,
           context: ILMessageFilterExtensionContext,
           completion: @escaping (ILMessageFilterQueryResponse) -> Void
) {
   // 设备端的检测
   let (offlineAction, offlineSubAction) = self.offlineAction(for: queryRequest)
   switch offlineAction {
   case .allow, .junk, .promotion, .transaction:
       let response = ILMessageFilterQueryResponse()
       response.action = offlineAction
       response.subAction = offlineSubAction
       completion(response)
   case .none:
      // 服务器检测
       context.deferQueryRequestToNetwork() { (networkResponse, error) in
           let response = ILMessageFilterQueryResponse()
           if let networkResponse = networkResponse {
               (response.action, response.subAction) = self.networkAction(for: networkResponse)
           }
           completion(response)
       }
   @unknown default:
       break
   }
}

这里需要注意,Apple 定义了服务器检测网络请求的格式,开发者无法进行自定义:

POST /server-endpoint HTTP/1.1
Accept: */*
Content-Type: application/json; charset=utf-8
Content-Length: 148
{
  "_version": 1,
  "query": {
      "sender": "14085550001",
      "message": {
          "text": "This is a message"
      }
  },
  "app": {
      "version": "1.1"
  }
}

ILMessageFilterCapabilitiesQueryHandling


ILMessageFilterCapabilitiesQueryHandling 协议会更简单些:

@available(iOS 16.0, *)
public protocol ILMessageFilterCapabilitiesQueryHandling : NSObjectProtocol {
 // 闭包
   func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest,
               context: ILMessageFilterExtensionContext,
               completion: @escaping (ILMessageFilterCapabilitiesQueryResponse
   ) -> Void)
// 异步函数
   func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest,
               context: ILMessageFilterExtensionContext
   ) async -> ILMessageFilterCapabilitiesQueryResponse
}

其中,capabilitiesQueryRequest 无实际含义,context 同前文。需要提供的是 ILMessageFilterCapabilitiesQueryResponse:

@available(iOS 16.0, *)
open class ILMessageFilterCapabilitiesQueryResponse : NSObject, NSSecureCoding {
}

@available(iOS 16.0, *)
@available(macOS, unavailable)
extension ILMessageFilterCapabilitiesQueryResponse {

   @nonobjc final public var transactionalSubActions: [ILMessageFilterSubAction]

   final public var promotionalSubActions: [ILMessageFilterSubAction]
}

指定了 Message Filter Extension 可以显示的子类别。我们可以这样展示以显示子类别:

func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest,
           context: ILMessageFilterExtensionContext,
           completion: @escaping (ILMessageFilterCapabilitiesQueryResponse) -> Void
) {
   let response = ILMessageFilterCapabilitiesQueryResponse()
   response.transactionalSubActions = [        .transactionalOthers,        .transactionalFinance,        .transactionalOrders,        .transactionalReminders,        .transactionalHealth,        .transactionalWeather,        .transactionalCarrier,        .transactionalRewards,        .transactionalPublicServices    ]
   response.promotionalSubActions = [                .promotionalOthers,        .promotionalOffers,        .promotionalCoupons,    ]
   completion(response)
}

在 iOS16 设备上,不同配置样式如下:


垃圾短信和垃圾电话上报



此外,我们可以一个 App Extension,让用户将不需要的短信和电话上报为垃圾内容。上报电话需要用户在最近列表中进行左滑后选择报告。对于在消息记录中的短信,用户可以按下报告垃圾信息按钮:












创建 Unwanted Communication Reporting Extension:











我们可以看到模版代码十分简单:

class UnwantedCommunicationReportingExtension: ILClassificationUIExtensionViewController {
   
   override func viewDidAppear(_ animated: Bool) {
       super.viewDidAppear(animated)
       // Notify the system when you have completed gathering information
       // from the user and you are ready with a classification response
       self.extensionContext.isReadyForClassificationResponse = true
   }
   
   // Customize UI based on the classification request before the view is loaded
   override func prepare(for classificationRequest: ILClassificationRequest) {
       // Configure your views for the classification request
   }
   
   // Provide a classification response for the classification request
   override func classificationResponse(for request:ILClassificationRequest) -> ILClassificationResponse {
       return ILClassificationResponse(action: .reportJunk)
   }
}

当用户上报时,系统会启动 App Extension。搜集用户外信息后,进行后续的上报或阻止,如下图所示。




具体来说,系统会依次:


  1. 实例化 App Extension 中的 ILClassificationUIExtensionViewController 子类。
  2. 调用实例的 prepare(for:)`方法并将控制器呈现给用户。
    1. 使用实例从用户那里收集数据,搜集完成 isReadyForClassificationResponse 设置为 true
    2. 如果用户按下取消按钮,系统将关闭 ILClassificationUIExtensionViewController 子类实例。

    1. 如果用户按下完成,系统将调用 classificationResponse(for:) 方法,传入一个 ILClassificationRequest 对象。

    1. 系统根据方法的 ILClassificationResponse 响应采取不同的操作。
@available(iOS 12.0, *)
open class ILClassificationResponse : NSObject, NSSecureCoding {
   open var action: ILClassificationAction { get }
   @available(iOS 12.1, *)
   open var userString: String?
   open var userInfo: [String : Any]?
   public init(action: ILClassificationAction)
}

ILClassificationAction 类型为:

/// Describes various classification actions.
@available(iOS 12.0, *)
public enum ILClassificationAction : Int, @unchecked Sendable {
   /// Indicate that no action is requested.
   case none = 0
   /// Report communication(s) as not junk.
   case reportNotJunk = 1
   /// Report communication(s) as junk.
   case reportJunk = 2
   /// Report communication(s) as junk and block the sender.
   case reportJunkAndBlockSender = 3
}

对于 ILClassificationAction.none,系统会关闭视图控制器,但不会采取任何其他操作。


对于 ILClassificationAction.reportNotJunkILClassificationAction.reportJunk,系统会根据 userInfo 属性生成报告,然后将其发布到扩展程序的 Info.plist 文件中指定的服务端(ILClassificationExtensionNetworkReportDestination)或者使用短信发到对应的号码(ILClassificationExtensionSMSReportDestination)。




对于 ILClassificationAction.reportJunkAndBlockSender,系统的响应就像在 ILClassificationAction.reportJunk 操作中一样。 但是,在报告步骤之后,系统会发出提示,让用户知道该号码将被阻止(拉黑)。


最后,为了保护用户隐私,系统会在 App Extension 终止后删除该容器。有关详细信息,请参阅关于 iOS 文件系统


参考资料




基于短信过滤能力。上线了喵喵消烦员 App:


喵喵消烦员是一款短信过滤工具软件。在如今信息爆炸的时代,您的隐私和安全由喵喵来守护!我们使用 scikit-learn,通过朴素贝叶斯算法对垃圾短信进行识别,通过 Core ML 将模型部署在本地,从而完成离线过滤任务。



  1. 隐私安全:我们不会索要任何位置、相机、通知、无线数据(网络)等权限,用户的数据不会被保存,同时也不可能被上传,所有操作均在本地完成。

  2. 高效精准:通过先进的算法技术,自动识别并过滤掉垃圾短信、诈骗信息、广告推销等不必要打扰的内容。

  3. 自定义设置:支持自定义关键词过滤,方便您针对个人需求进行个性化设置,避免不必要的干扰。

  4. 更新迭代:我们会通过版本升级定期更新过滤模型,确保始终能够准确地识别和拦截最新的垃圾短信和诈骗信息。


模型、App 还在不断调整和优化,欢迎使用和提供建议!




作者:Layer
链接:https://juejin.cn/post/7237697495109845052
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

程序员接外包的三个原则以及有意思的讨论

文章来源网络 原则一:乙方来做决策 最终拍板人是谁?是甲方,如果你非要抢板子,那你以后就没有甲方了但是,如果甲方也觉得“我花钱了,当然要听我的(那些只对上级负责又不能拍板的底层打工人,总是这样认为)”,那这种甲方的项目你就不要接因为在这种甲方眼里,你只是“施工...
继续阅读 »

文章来源网络


原则一:乙方来做决策


  • 最终拍板人是谁?是甲方,如果你非要抢板子,那你以后就没有甲方了
  • 但是,如果甲方也觉得“我花钱了,当然要听我的(那些只对上级负责又不能拍板的底层打工人,总是这样认为)”,那这种甲方的项目你就不要接
  • 因为在这种甲方眼里,你只是“施工方”,他们即不需要你的经验价值,更不会为你的经验买单。所以这种甲方你会做得很累,当他们觉得“你的工作强度不足以匹配付给你的费用时(他们总这样觉得)”,他们就会不停地向你提出新的开发需求
  • 所以,你要尽量找那种尊重你经验价值,总是向你请教,请你帮他做决策的甲方

原则二:为甲方护航


  • 甲方未来会遇到什么问题,你们双方其实都不知道,所以你需要一边开发一边解决甲方实际遇到的问题。
  • 因此,不要为了完成合同上的工作内容而工作,要为甲方遇到的实际问题,为了甲方的利益而开发,要提前做好变通的准备。
  • 永远不要觉得你把合同上的功能列表做完,你就能休息了。你要解决的真正问题是,为甲方护航,直至甲方可以自己航行。

原则三:不做没有用户的项目


  • 如果甲方的项目没有太多用户使用,这种项目就不要接。
  • 除了代码的累计经验,还有一种经验也很重要,那就是“了解用户的市场经验”
  • 只有真正面对有实际用户的项目,你才能有“解决市场提出的问题”的经验,而不是停留在“解决甲方提出的问题”
  • 拥有市场经验,你就会有更高的附加价值,再配上尊重你经验价值的甲方,你就会有更高的收入
  • 永远记住:真正愿意在你身上花钱的甲方,他的目的一定是为了让你帮他赚钱!

以上只是我根据自己经验的一家之言,可能对我有用,不一定对别人也有用。肯定还有很多有价值的原则,希望大家根据自己的经验一起来分享。


下面是一些有意思的讨论


原则 2 、3 都是虚的,就不讨论了。

只说原则一:


一般而言,甲方跟你对接的,一定不是老板。

所以他的核心目的一定是项目实施成功。

但项目是不是真的能给企业带来效益,其实是优先级特别低的一个选项。


拿日常生活举个例子。夫妻、情侣之间,你媳妇儿托你办个事,比如让你买个西瓜。

你明知道冬天的西瓜又贵又不好吃,你会怎么办?


A ,买西瓜,回去一边吃西瓜一起骂水果摊老板没良心。

B ,给你媳妇儿上农业课。然后媳妇儿让你跪搓衣板。

C ,水果摊老板训你一顿,以冬天吃白菜豆腐好为由,非卖你一颗大白菜。你家都不敢回。


这里面,你就是那个甲方对接人。你怎么选?


所以乙方一定不能做决策。乙方做决策的结果,就是甲方对接人被利益集团踹开或者得罪甲方对接人,最终导致项目失败




我也来说三个原则

1.要签合同,合同越细越好;

2.要给订金,订金越多越好;

3.尾款不结不给全部源码。




原则一:外包大部分就是苦力活,核心有价值的部分有自己公司的人干轮不到外包,你不干有的是人干,不会有人尊重你经验价值,甲方说怎么干就怎么干,写到合同里,按合同来,没甲方懂自家业务,别替甲方做决策,万一瞎建议导致项目出现大问题,黄了,外包钱都拿不回来


原则二:给多少钱办多少事,如果甲方给钱痛快,事少,可以看自己良心对甲方多上点心,否则别给自己加戏,不然很可能把自己感动了,甲方却想着好不容易碰上这么个人,白嫖


原则三:不做没有用户的项目,太片面,不是所有外包项目都是面对海量用户,但是做所有外包项目都是为了赚钱,假如有个富二代两三万找你做个毕设,简单钱多不用后续维护,这种接不接?假如某工厂几十万定制内部系统,可能只有几个人用,这种接不接


总之外包就是赚个辛苦钱,别指望这个来提升自己技术和自我价值,外包行业水太深,你这几个原则都太理想化




某富豪要盖一栋私人别墅,招建筑工人,现在缺一名搅拌水泥的工人,

找到了张三,张三说我去过很多工地,啥活儿都干过,经验极其丰富,我可以指导一切事物,我再给你兼职当个总设计师吧,一切事物听我的决策没错。

我每天做完我的本职工作搅拌水泥砂浆,我还能熬夜给建筑设计布局,风水,房间规划,材料采购等等,我啥都会,直接干到建筑完工

富豪很感兴趣,说那你来吧,我盖的是自己住的别墅,张三一听连连摆手:你是盖私人别墅啊?不行不行,我不去了,我以前盖的都是高楼大厦,住户多对我技术水平有严峻的考验,做成了对我有很大提高,私人别墅才几个人用,对我职业生涯一点帮助都没




永远不要接外包

这才是正确的答案

做私活的时间

不如自己休息休息,陪陪家人




屁事真多,有钱就行了,管他项目有没有人,人家产品低能你还得兜底,接外包考虑的是能不能满足需求。啥条件啊还能挑三拣四,给多少钱干多少活。 招投标接的 30 万以上的项目才有可能考虑你说的这些东西。





呵呵 我的意见是:

  1. 给钱就做(前提是合规,不是合理),先给定金,拿到定金开工。
  2. 遇到扯皮,就停止开发。
  3. 要有空闲的时间,偶尔做做(上面说的对:永远不要做外包)。

展开来说,做外包的长期收益很低。就当临时玩一下,所以给钱就做,不管你的需求合理不合理,比如甲方想给智障人士开发一款数独小游戏,好,给钱,签合同,支付定金,开工。


开工了3天,甲方突然说,那个我想加个魔方游戏。不好意思,不行,立即停止开发,开始和甲方掰扯,如果掰扯不明白,就终止合同,如果掰扯明白就继续。


不说了,我要和甲甲甲甲甲方掰扯去了。


作者:Data_Adventure
链接:https://juejin.cn/post/7256590619412676663
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

假如互联网人都很懂冒犯

大家好,我是老三,最近沉迷于听脱口秀,并且疯狂安利同事。 脱口秀演员常常说的一句话是:“脱口秀是冒犯的艺术”。最近我发现,同事们好像有点不一样了。 阳光灿烂的早上,趿拉着我的宝马拖鞋,跨上包浆的小黄车,屁股感受着阳光积累的炙热,往公司飞驰而去。 一步跨进电梯...
继续阅读 »

大家好,我是老三,最近沉迷于听脱口秀,并且疯狂安利同事。


脱口秀演员常常说的一句话是:“脱口秀是冒犯的艺术”。最近我发现,同事们好像有点不一样了。




阳光灿烂的早上,趿拉着我的宝马拖鞋,跨上包浆的小黄车,屁股感受着阳光积累的炙热,往公司飞驰而去。


一步跨进电梯间,我擦汗的动作凝固住了,挂上了矜持的微笑:“老板,早上好。”


老板:“早,你还在呢?又来带薪划水了?”


我:“嗨,我这再努力,最后不也就让你给我们多换几个嫂子嘛。”


老板:“没有哈哈,我开玩笑。”


我:“我也是,哈哈哈。”


今天的电梯似乎比往常慢了很多。


我:“老板最近在忙什么?”


老板:“昨天参加了一个峰会,马xx知道吧?他就坐我前边。”


我:“卧槽,真能装。没有,哈哈。”


老板:“哈哈哈”。


电梯到了,我俩都步履匆匆地进了公司。


小组内每天早上都有一个晨会,汇报工作进度和计划。


开了一会,转着椅子,划着朋友圈的我停了下来——到我了。


我:“昨天主要……今天计划……”


Leader:“你这不能说没有一点产出,也可以说一点产出都没有。其实,我对你是有一些失望的,原本今年绩效考评给你一个……”


我:“影响你合周报了是吗?不是哈哈。”


Leader、小组同事:“哈哈哈“。


Leader:“好了,我们这次顺便来对齐一下双月OKR,你们OKR都写的太保守了,一看就是能完成的,往大里吹啊。开玩笑哈哈。”。


我:”我以前就耕一亩田,现在把整个河北平原都给犁了。不是,哈哈。”


同事:“我要带公司打上月球,把你踢下来,我来当话事人。唉,哈哈”


Leader、同事、我:“哈哈哈“。


晨会开完,开始工作,产品经理拉我和和前端对需求。


产品经理:“你们程序员懂Java语言、Python语言、Go语言,就是不懂汉语言,真不想跟你们对需求。开个玩笑,哈哈。”


我:“没啥,你吹牛皮像狼,催进度像狗,做需求像羊,就这需求文档,还没擦屁股纸字多,没啥好对的。不是哈哈。”


产品经理、前端、我:“哈哈哈”。


产品经理:“那我们就对到这了,你们接着聊技术实现。”


前端:“没啥好聊的,后端大哥看着写吧,反正你们那破接口,套的比裹脚布还厚,没事还老出BUG。没有哈哈。”


我:“还不是为了兼容你们,一点动脑子的逻辑都不写,天天切图当然不出错。不是哈哈。”


前端、我:“哈哈哈”。


经过一番拉扯之后,我终于开始写代码了。


看到一段代码,我皱起了眉头,同事写的,我顺手写下了这样一段注释:

/**
* 写这段代码的人,建议在脑袋开个口,把水倒掉。不是哈哈,开个玩笑。
**/

代码写完了,准备上线,找同事给我Review,同事看了一会,给出了评论。



又在背着我们偷偷写烂代码了,建议改行。没有哈哈。



同事、我:“哈哈哈”。


终于下班了,路过门口,HR小姐姐还在加班。


我:“小姐姐怎么还没下班?别装了,老板都走了。开玩笑哈哈。”


HR小姐姐:“这不是看看怎么优化你们嘛,任务比较重。不是,哈哈。”


HR小姐姐、我:“哈哈哈”。


我感觉到一种不一样的氛围在公司慢慢弥散开来,我不知道怎么形容,但我想到了一句话——


“既分高下,也决生死”。




写这篇的时候,想到两年前,有个叫码农小说家的作者横空出世,写了一些生动活泼、灵气十足的段子,我也跟风写了两篇,这就是“荒腔走板”系列的来源。


后来,他结婚了。


看(抄)不到的我只能自己想,想破头也写不不来像样的段子,这个系列就不了了之,今天又偶尔来了灵感,写下一篇,也顺带缅怀一下光哥带来的快乐。


作者:三分恶
链接:https://juejin.cn/post/7259036373579350077
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

数组去重的多种方式

iOS
前言 从数组中删除重复项是一项常见的任务,在 Swift 中,标准库没有直接提供一个系统函数给我们,必须自己实现这样的方法。 实现数组去重的方法有很多,今天来介绍一些常用的方法。 1、使用 Set 去重 Set 也是一个集合,只是它不包含重复项,利用这个特点,...
继续阅读 »

前言


从数组中删除重复项是一项常见的任务,在 Swift 中,标准库没有直接提供一个系统函数给我们,必须自己实现这样的方法。


实现数组去重的方法有很多,今天来介绍一些常用的方法。


1、使用 Set 去重


Set 也是一个集合,只是它不包含重复项,利用这个特点,我们可以简单的给一个数组去重:

let array: [Int] = [1, 1, 3, 3, 2, 2]
let set: Set<Int> = Set(array)
print(set)


上边的代码会打印去重之后的 [1, 2, 3],但结果也可能是 [3, 1, 2],也可能是 [3, 2, 1],这就涉及到 Set 的原始设计了,它内部的元素是无序的,不能保证固定的顺序,如果你对顺序有要求,就不能用 Set 来实现了。


2、巧用字典去重


我们都知道字典中是无法存储相同 Key 的,也就可以利用 Key 的唯一性来去重:

let array: [Int] = [1, 1, 3, 3, 2, 2]
var dic: [Int: Int] = [:]
array.forEach { dic[$0] = 0 }
print(dic.keys)


先把 array 遍历一遍,所有元素作为字典的 key 存储起来,最后再取 dic.keys 获得去重之后的数组。


但是字典的 key 也一样是无序的,而且使用字典会带来额外的性能开销,因此不推荐这种方式。


上边提到这两种方案都无法保证顺序,如果需要保证去重后的顺序和原数组保持一致,请看下边的几个方案。


3、利用 NSOrderedSet


NSOrderedSet 是 OC 时代的产物,继承自 NSObject,它可以像 Set 一样实现去重,也可以保证顺序:

let array: [Int] = [1, 1, 3, 3, 2, 2]
let orderSet = NSOrderedSet(array: array)
print(orderSet.array)


最终打印 [1, 2, 3],顺序和原数组保持一致,但是需要注意这玩意性能比 Set 差很多。


4、遍历数组去重


这也是最符合直觉的方法,把数组遍历一遍,创建个新数组,如果这个元素没有加入新数组就加进去,如果加过了就抛掉:

let array: [Int] = [1, 1, 3, 3, 2, 2]
var newArray: [Int] = []
array.forEach { item in
    if !newArray.contains(item) {
        newArray.append(item)
    }
}
print(newArray)


最终打印 [1, 3, 2],也是保持顺序的。


为了更方便调用还可以将这个方法写个数组扩展:

extension Array where Element: Hashable {
    var unique: Self {
        var newArray: Self = []
        forEach { ele in
            if !newArray.contains(ele) {
                newArray.append(ele)
            }
        }
        return newArray
    }
}


这样调用的时候就方便了:

let array: [Int] = [1, 1, 3, 3, 2, 2]
print(array.unique) // [1, 3, 2]


5、filter 高阶函数 + Set


Set 在插入元素的时候调用 insert 函数,这个函数返回一个元组,第一个值是一个 Bool 类型代表是否插入成功,如果已经包含了这个元素则不能插入成功,另一个是被插入的这个元素,这在 Set 的函数声明中可以看得出来:

func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element)


我们可以利用这个特性,再加上 filter 这个高阶函数,来给集合 Array 写一个扩展:

extension Array where Element: Hashable {
    var unique: Self {
        var seen: Set<Element> = []
        return filter { seen.insert($0).inserted }
    }
}


调用方法和上边的方式一样。


作者:iOS新知
链接:https://juejin.cn/post/7275943600771399699
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

日志打得好,代码差不了

1. 前言 众所周知,一个完善的日志体系对于开发的顺利进行至关重要。尽管构建这样一个系统可能需要与开发几个功能模块相当的时间,但为了在日常开发中高效地记录日志,我进行了以下尝试。希望这些经验能为后端入门的同学和对日志管理不够熟悉的朋友们提供帮助。 (本文面向后...
继续阅读 »

1. 前言


众所周知,一个完善的日志体系对于开发的顺利进行至关重要。尽管构建这样一个系统可能需要与开发几个功能模块相当的时间,但为了在日常开发中高效地记录日志,我进行了以下尝试。希望这些经验能为后端入门的同学和对日志管理不够熟悉的朋友们提供帮助。


(本文面向后端入门同学或是对日志管理缺乏深入了解的朋友)


2. 注解+手动记录


使用注解@Sl4j来自动创建日志类,再通过log.info()手动记录日志,是一种常见且相对灵活的方法。这样,我们可以在需要的任何地方记录所想要的日志内容。


然而,在实际开发中,这种方法可能会导致大量的日志与业务逻辑代码混杂,增加了代码与日志的耦合度。当需要修改业务逻辑时,往往还需要调整相关的日志,这可能导致重复记录,如请求参数和鉴权信息。


尽管如此,我并不是完全反对这种日志处理方法。其简单性和灵活性是其显著优点。但建议开发者结合其他方法,以减少日志与业务逻辑的耦合。


3. AOP统一处理


首先,我会介绍如何利用AOP自动记录日志以及哪些日志适合用AOP来处理。


3.1 请求详情日志

使用AOP统一处理请求详情日志能够有效地解耦控制层和业务层,这是一个非常高效的策略。以下是一个示例,展示如何使用AOP通知类记录详细的请求信息:


@Slf4j
@Aspect
@Component
public class LoggingAspect {

/* 日志输出被访问的接口url */
@Before("execution(* com.steadon.example.controller.*.*(..))")
public void beforeRequest(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

String httpMethod = request.getMethod();
String remoteAddr = request.getRemoteAddr();
String requestURI = request.getRequestURI();
String queryString = request.getQueryString();
String params = "";

if ("POST".equalsIgnoreCase(httpMethod)) {
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
params = Arrays.toString(args);
}
}

log.info("Request Info: IP [{}] HTTP_METHOD [{}] URL [{}] QUERY_STRING [{}] PARAMS [{}]",
remoteAddr, httpMethod, requestURI, queryString, params);
}
}

通过这个AOP通知类,我们可以轻松地记录详细的请求信息。在遇到问题时,分析此日志通常可以帮助我们迅速找到原因,例如前端参数传递错误或后端参数接收问题。效果图如下:


image.png


3.2 其他

你可以利用AOP来拦截几乎任何切点,无论是消息队列、Mybatis的Mapper操作,还是特定的循环,都可以织入相应的通知。然而,使用AOP时,我们也必须权衡其成本。


AOP的优势并不仅仅在于其性能或易用性,而是它出色的解耦能力。选择是否使用AOP应基于你的日志和业务逻辑之间的耦合程度。只有当耦合度足够高,需要解耦时,AOP才真正显示其价值。


4. Lark机器人 + 全局异常处理


我相信许多读者都已经熟悉飞书机器人(Lark机器人)。通过全局异常捕获并调用飞书机器人进行告警,这是一种极为有效的业务告警通知方式。你可能会想:“哦,这个我知道。” 但是,具体如何实现呢?其实操作起来并不复杂,接下来我会详细介绍:


4.1 创建告警群聊

对于简单的报警,我们通常选择创建群聊机器人,而不是单独的机器人应用。这样做的好处是,我们可以动态管理需要接收告警的开发团队成员。


4.2 创建自定义机器人


  1. 在群聊中,依次选择:设置 -> 群机器人 -> 添加机器人。


image.png



  1. 在机器人详情页面,获取webhook地址。请确保此地址保密。


image.png



  1. 接下来,在代码中集成飞书机器人的告警功能。下面是如何在全局异常处理中集成飞书机器人的示例代码:


/* 飞书机器人告警 */
public String sendLarkNotification(String webhookUrl, String user, String title, String messageBody) throws Exception {
URL url = new URL(webhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
ObjectNode content = root.putObject("content").putObject("post").putObject("zh_cn");
content.put("title", title);

ArrayNode contentArray = content.putArray("content");
ArrayNode atUserArray = contentArray.addArray();
atUserArray.addObject().put("tag", "at").put("user_id", user);

ArrayNode messageArray = contentArray.addArray();
messageArray.addObject().put("tag", "text").put("text", messageBody);

root.put("msg_type", "post");

byte[] input = mapper.writeValueAsBytes(root);

try (OutputStream os = connection.getOutputStream()) {
os.write(input, 0, input.length);
}

try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
return response.toString();
}
}

代码相对简单,主要逻辑是通过HTTP请求向webhook地址发送带有告警信息的POST请求。为了实现这个请求,我们需要在全局异常处理中调用上述方法。为了便于展示,我直接在全局异常处理类中编写了这个方法。但读者可以考虑创建一个专门的工具类来实现这一功能。接下来,我将展示我的全局异常处理类的逻辑:


@ControllerAdvice
public class GlobalExceptionHandler {

@Value("${notifications.larkBotEnabled}")
private boolean larkBotEnabled;

@ExceptionHandler(value = Exception.class)
public CommonResult<String> handleException(Exception e) {
if (!larkBotEnabled) return CommonResult.fail();
// Send notification to Lark
String title = "线上BUG通报";
String user = "all";
String webhookUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/f4150b6c-xxxx-xxxx-xxxx-xxxx-xxxx";

try {
String s = sendLarkNotification(webhookUrl, user, title, e.getMessage());
return CommonResult.fail(s);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

你可能会对larkBotEnabled感到好奇。实际上,全局异常捕获并不会自动区分线上环境与开发环境。为了能够在不同的环境中控制是否发送告警,我们使用了这个变量。通过在线上和本地的配置文件中设置不同的值,我们可以轻松地区分这两种环境。


application.yml


spring:
profiles:
active: dev #决定是否使用本地配置
application:
name: app-name

notifications:
larkBotEnabled: true #自定义字段控制报警行为

application-dev.yml


notifications:
larkBotEnabled: false

这样,我们就可以根据不同的环境来决定是否发送告警。以下是告警效果的示例:


image.png


这种告警方式既简洁又直观,帮助我们迅速定位问题。当然,针对不同的问题,我们还可以进一步对告警进行分级处理。


总结:在大型项目中,日志体系的构建需要大量的时间和资源。虽然在实际业务中,我们可能会使用更复杂的日志系统和告警机制,但本文提供的方法对于日常开发已经足够使用。如有任何建议或纠正,欢迎在评论区提出!


作者:雾山小落
来源:juejin.cn/post/7280864416554500131
收起阅读 »

某律师执业诚信信息公示平台字体加密解决思路

web
本文章只做技术探讨,请勿用于非法用途。 目标网站 为持续加深对律法的学习, 我们需要再来收集一些数据。 本文来解决 credit.acla.org.cn/ 这个网站的字体加密问题。 网站分析 网站反爬 这个网站的各种反爬措施还挺多的, 接口加密啊, 验证码啊...
继续阅读 »

本文章只做技术探讨,请勿用于非法用途。



目标网站


为持续加深对律法的学习, 我们需要再来收集一些数据。


本文来解决 credit.acla.org.cn/ 这个网站的字体加密问题。


网站分析


网站反爬


这个网站的各种反爬措施还挺多的, 接口加密啊, 验证码啊(每个页面都有), 无限 debugger 啊, 什么的, 还是挺烦的, 如果不要求效率的话可以考虑用 selenium 来过掉, 这里重点来解决一下字体加密的问题。


加密分析


首先来看下字体加密什么样子。


image.png


如图, 为律所详情页的截图, 可以看到啊, 这个 标签下的字体为加密字体, 这个网站他大多数数据信息都会像这样来做一个加密。


开整


首先来说下解决的方法。




  1. 找到字体文件。




  2. 确定文件字体与网站字体的映射关系。




  3. 替换网站字体。




字体文件获取


image.png


刷新页面, 勾选字体栏即可看到返回的页面, 直接下载下来即可。



有些网站可能会返回多个字体文件来迷惑你, 这时候可以全局搜索 ttf 等字体文件的关键词, 来读相关代码来找到前端页面解密时用的具体是字体文件。



字体文件解析


字体文件处理可以用 TTFont 工具, 我们先将文件解析成可读的 xml 格式来看下这到底是什么个东西。


image.png


下载字体文件保存为本地 font.ttf, 然后解析为 font.xml。


image.png


可以看到文件里是一些映射关系, 和一些字形的信息, 如果是简单的数字加密或是很少的字体加密的话, 这一步直接拿到映射关系就可以用了, 但是这个网站他每次的字体文件都不一样, 所以这种简单的映射关系不可用。


image.png


在字体编辑软件里也可以看到是对哪些字体进行了修改加密, windows 可以用 font creator , mac 上我用的是 FontForge 来解析的。


映射关系获取


上一步我们拿到了字形信息, 这里来生成提供一个通用的方法来做映射关系。



font.ttf 文件中通过 unicode 码来标记对应的字体的字形信息, 我们也可以用同样的方式, 获取加密字体对应的原字体的字形信息(固定不变的), 以此为 key 来设计映射关系。



image.png


这里定义了一个全局变量 font_map 来存储映射关系, 通过 PIL 的 ImageFont 对象来将字体的字形信息复现出来, 然后通过 ocr 技术得到字的原型, 完成解密。将解密过的字存入 font_map, 随着收录的字越来越多, 解析效率会越来越高。


加密字体替换


image.png


这里没什么难度, 做一个简单的替换就好。


结语


这个也是我首次接触这种麻烦些的字体加密, 就想写出来权当分享, 思路也是借鉴于之前看到的一个帖子(找不见了。。)。 文中有些东西需要自己去调试后可能会理解更深些, 因为写的过程中被其他事情打断了几次, 之前整理的思路乱掉了, 写的可能不太顺畅, 大家哪里不懂的话可以留言讨论吧, 或者有什么更好的思路也欢迎来交流。


作者:Glommer
来源:juejin.cn/post/7272399042091909131
收起阅读 »

关于我的人生(假如我工作13年就能得到3w个花西币)

关于我的人生(假如我工作13年就能得到3w个花西币) 前言 我一直在思考一个问题,我们真的有正视过未来吗? 其实大家可能会遇到一种可能就是平平淡淡的过完一生,什么20岁CEO、年纪轻轻福布斯前几?这种小说中主角的模板可能不一定会出现在我们身上。(这里并不是嘲笑...
继续阅读 »

关于我的人生(假如我工作13年就能得到3w个花西币)


前言


我一直在思考一个问题,我们真的有正视过未来吗?


其实大家可能会遇到一种可能就是平平淡淡的过完一生,什么20岁CEO、年纪轻轻福布斯前几?这种小说中主角的模板可能不一定会出现在我们身上。(这里并不是嘲笑,谁不想拥有爽文男主的人生,笔者还是希望大家能过得很好)


这段时间我正处于离职状态就一直在思考,我究竟是在做什么?


毕业了两年,在一家公司做了两年拿了一笔不算多不算少的薪水,然后每天不知道为何一直忙碌着,这也许就是大部分人的现状。


image.png


image.png



有很多人一生都不知道,自己要做什么?



这几天我重新看到这句话,我就感觉说得真的很对


那么就会有人说,我一生要做的事情就是搞钱,没错很多人都是如此。


image.png


关于我




  • 目前做前端开发,月薪9k,2年工作经验,工作中算不算特别出彩,但是也有一定能力,简单说普通




  • 学历: 本科(非211、985)




  • 性格: 偏内向,朋友较少,交际一般




  • 爱好: 游戏、跑步、健身




  • 不良爱好: 无




预景


目前,25岁,假设工作到38岁10年时间薪资预计如下



  • 3-5年工作经验 11-14k 继续工作3年 一年按照13.2w算 三年39.6万

  • 5-10年工作经验 14-18k 继续工作5年 一年16.w算 5年84万

  • 10-15年工作经验 20-22k 继续工作5年 一年24w 5年 120万


预计会获得243.4w ,13年时间,当前这是目前比较客观并且没有考虑环境通货膨胀等其他元素的情况下,并且涨薪也是比较优异的情况下,不吃不喝工作13年获得的理想薪资。



是不是看着挺多,哇200多万呢,换算成花西币能有3w枚



那么按照我目前工作的地点厦门,我不吃不喝13年,可能连房子都买不起


副业的可能


副业:这个东西不一定大家都适合,其实我感说许多人都想过,但是大家真的挣到钱了吗?


我这里可能可以直接给出答案没有,为什么?



我自己就是一个实例:



上面介绍了我是一个程序员,上班时间是9:00~18:00通常是朝9晚6,如果项目赶还需要加班,那么我的副业,肯定需要一点时间灵活,那么就限制了很多,固定时间工作我都做不了


image.png



那么这样说大家就会想到几个选项




  • 自媒体

  • 滴滴

  • 骑手

  • 接私活

  • 摆摊


这是我目前能想到几个,我先逐个分析



自媒体



目前我有尝试过,那么问题来了我播什么?才艺没有颜值,额虽然是吴彦祖级别,身材是彭于晏,大家v我50,我可以继续吹下去


image-20230917135313799


不过我认为:做自媒体依然是最佳的选择,我现在虽然没有什么可以播的东西,但是我依然在寻找一个自己适合的方向,我个人认为内容应该有具备以下特点



  • 自己一直在坚持做的东西,例如: 我喜欢吃美食、经常会探店,这样你才有持续的内容输出

  • 稀缺的东西,例如:网上很多探店自助、米其林、高端,说白了就是平常少接触的东西

  • 能力主播(搞笑、颜值、特长、跳舞等等),这不得不说颜值也是一种优势,这个大家都懂


暂时就想到这么多,目前虽然做自媒体的人很多,但是依然有很多机会。


我自己也在寻求机会和方向



滴滴、骑手



这个两个我就一起说了仁者见仁,智者见智,首先个人肯定不推荐,你上班一天做了8小时,晚上下班继续跑滴滴、送外卖,说实话特别苦,我之前就跑了一段时间,收入非常惨淡,同时收入不具备发展性


我之前下班6.30左右,吃完饭7点左右去送外卖10点左右回家,可能3小时才30左右,一单4块一般,而且单子少,人家5点多-7点是外卖最多的时候,你这时候也是刚才下班去吃完饭,7-8都是晚吃饭。



时间冲突,并且收入不可观,如果是娱乐逛逛风景可以跑着玩



下面附上我送外卖的收入,可以说一晚上2 3小时最多收入30-40左右一天


image-20230917140243436



摆摊



摆摊:如果是我可能不考虑,周末可以一试,但是周一到周五,可能自己没办法摆摊


原因:


摆摊一般都是卖吃的6-7点下班。


假设材料你都以前准备好了,回家出摊7点左右,这时候饭点都差不多过了,怎么可能卖得出去,就算不是卖吃的下班人都回家,客流量小了,肯定会印象收益



接私活



接私活这一项我自己感觉太难了,说实话,除非是公司不忙的人,可以考虑。


例如: 我上班一天8小时,都在面对电脑,下班了还得继续干,说实话,我很难坚持,也许是我比较弱鸡,我自认为自己毅力还是有的,即使这样我自己也不太会去考虑私活


我认识的有些大佬是真的强,不过也许有些也是上班不忙的时候写,具体还是看自己的情况。


结论


目前环境真的很不好,互联网现在已经开始饱和了,后面失业的人可能会越来越多,同时环境也会越来越差,想到在这环境下面生存真的很不容易。加油!!!都看到这里不妨点个赞!



作者:柒丶月
来源:juejin.cn/post/7280747833384484919
收起阅读 »

Kotlin Flow入门

Flow作为Android开发中的重要的作用。尤其在Jetpack Compose里左一个collect,右一个collect。不交接Flow而开发Android是寸步难行。作为一个入门文章,如果你还不是很了解Flow的话,本文可以带你更进一步的了解Flow。...
继续阅读 »

Flow作为Android开发中的重要的作用。尤其在Jetpack Compose里左一个collect,右一个collect。不交接Flow而开发Android是寸步难行。作为一个入门文章,如果你还不是很了解Flow的话,本文可以带你更进一步的了解Flow。


Flow是一个异步数据流,它会发出数据给收集者,最终带或者不带异常的完成任务。下面我们通过例子来学习。


假设我们正在下载一幅图片。在下载的时候,还要把下载的百分比作为值发出来,比如:1%,2%,3%,等。收集者(collector)会接收到这些值并在界面上以合适的方式显示出来。但是如果出现网络问题,任务也会因此终止。


现在我们来看一下Flow里的几个API:



  • 流构建器(Flow builder)

  • 操作符(Operator)

  • 收集器(Collector)


流构建器


简单来说,它会执行一个任务并把值发出来,有时也会只发出值而不会执行什么任务。比如简单的发出一些数字值。你可以把流构建器当做一个发言人。这个发言人会思考(做任务)和说(发出值).


操作符


操作符可以帮助转化数据。


我们可以把操作符当做是一个翻译。一个发言人说了法语,但是听众(收集器)只能听懂英语。这就需要一个翻译来帮忙了。它可以把法语都翻译成英语让听众理解。


当然,操作符可以做的远不止这些。以上的例子只是帮助理解。


收集器


Flow发出的值经过操作符的处理之后会被收集器收集。


收集器可以当做是收听者。实际上收集器也是一种操作符,它有时被称作终端操作符


第一个例子


flow { 
(0..10).forEach {
emit(it)
}
}.map {
it * it
}.collect {
Log.d(TAG, it.toString())
}

flow {}->流构建器
map {}->操作符
collect {}->收集器

我们来过一下上面的代码:



  • 首先,流构建器会发出从0到10的值

  • 之后,一个map操作符会把每个值计算(it * it)

  • 之后,收集器收集这些发出来的值并打印出来:0,1,4,9,16,25,36,49,64,81,100.


注意:collect方法把流构建器和收集器连到了一起,这个方法调用之后流就开始执行了。


流构建器的不同类型


流构建器有四种:



  1. flowOf():从一个给定的数据集合生成流

  2. asFlow(): 一个扩展方法,可以把某个类型转化成流

  3. flow{}: 我们例子中使用的方法

  4. channelFlow{}:使用构造器自带的send方法发送的元素构建流


例如:


flowOf()


flowOf(4, 2, 5, 1, 7) 
.collect {
Log.d(TAG, it.toString())
}

asFlow()


(1..5).asFlow()
.collect {
Log.d(TAG, it.toString())
}

flow{}


flow {
(0..10).forEach {
emit(it)
}
}
.collect {
Log.d(TAG, it.toString())
}

channelFlow{}


channelFlow {
(0..10).forEach {
send(it)
}
}
.collect {
Log.d(TAG, it.toString())
}

flowOn操作符


flowOn这个操作符可以控制flow任务执行的线程的类型。在Android里一般是在一个后台线程执行任务,之后在界面上更新结果。


下面的例子里加了一个500毫秒的延迟来模拟实际任务。


val flow = flow {
// Run on Background Thread (Dispatchers.Default)
(0..10).forEach {
// emit items with 500 milliseconds delay
delay(500)
emit(it)
}
}
.flowOn(Dispatchers.Default)

CoroutineScope(Dispatchers.Main).launch {
flow.collect {
// Run on Main Thread (Dispatchers.Main)
Log.d(TAG, it.toString())
}
}

本例,流的任务就会在Dispatchers.Default这个“线程”里执行。接下来就是要在UI线程里更新UI了。为了做到这一点就需要在UI线程里collect


flowOn操作符就是用来控制任务执行的线程的。它的作用和RxJava的subscribeOn类似。


Dispatchers主要有这些类型:IODefaultMain。flowOn和CoroutineScope都可以使用Dispatchers来执行任务执行的“线程”(暂且这么理解)。


使用流构造器


我们通过几个例子学习。


移动文件


这里我们用流构造器新建一个流,让流任务在后台线程执行。完成后在UI线程显示状态。


val moveFileflow = flow {
// move file on background thread
FileUtils.move(source, destination)
emit("Done")
}
.flowOn(Dispatchers.IO)

CoroutineScope(Dispatchers.Main).launch {
moveFileflow.collect {
// when it is done
}
}

下载图片


这个例子构造一个流在后台线程下载图片,并且不断的在UI线程更新下载的百分比。


val downloadImageflow = flow {
// start downloading
// send progress
emit(10)
// downloading...
// ......
// send progress
emit(75)
// downloading...
// ......
// send progress
emit(100)
}
.flowOn(Dispatchers.IO)

CoroutineScope(Dispatchers.Main).launch {
downloadImageflow.collect {
// we will get the progress here
}
}

现在你对kotlin的流也有初步的了解了,在项目中可以使用简单的流来处理异步任务。


什么是终端操作符


上文已经提到过collect()方法是一个终端操作符。所谓的终端操作符就是让流跑起来的挂起方法(suspend function)。在以上的例子中,流构造器构造出来的流是不动的,让这个流动起来的操作符就是终端操作符。比如collect


还有:



  • 转化为各种集合的,toList, toSet

  • 获取第一个first,与确保流发射单个值的操作符single

  • 使用reduce, fold这类的把流的值规约到单个值的操作符。


比如:


val sum = (1..5).asFlow()
.map { it * it } // 数字 1 至 5 的平方
.reduce { a, b -> a + b } // 求和(末端操作符)
println(sum)

冷热流


前面的例子里的流都是冷流。我们来对比一下流的不同:


冷流热流
收集器调用的时候开始发出值没有收集器也会发出值
不存储数据可以存储数据
不支持多个收集器可以支持多个收集器

冷流,如果带上了多个收集器,流会每次遇到一个收集器就从头把完整的数据发送一次。


热流遇到多个收集器的时候,流会一直发出数据,收集器开始收集数据的时候遇到的是什么数据就收集什么数据。热流的多个收集器共享一份数据。


冷流是推模式,热流是拉模式。


下面看几个例子:


冷流实例


fun getNumbersColdFlow(): ColdFlow<Int> {
return someColdflow {
(1..5).forEach {
delay(1000)
emit(it)
}
}
}

开始收集


val numbersColdFlow = getNumbersColdFlow()

numbersColdFlow
.collect {
println("1st Collector: $it")
}

delay(2500)

numbersColdFlow
.collect {
println("2nd Collector: $it")
}

输出:


1st Collector: 1
1st Collector: 2
1st Collector: 3
1st Collector: 4
1st Collector: 5

2nd Collector: 1
2nd Collector: 2
2nd Collector: 3
2nd Collector: 4
2nd Collector: 5

两个收集器都从头获取到流的数据,在每次收集的时候都相当于遇到了一个全新的流。


热流实例。本例会设置一个热流每隔一秒发出一个1到5的数值。


fun getNumbersHotFlow(): HotFlow<Int> {
return someHotflow {
(1..5).forEach {
delay(1000)
emit(it)
}
}
}

现在开始收集:


val numbersHotFlow = getNumbersHotFlow()

numbersHotFlow
.collect {
println("1st Collector: $it")
}

delay(2500)

numbersHotFlow
.collect {
println("2nd Collector: $it")
}

输出:


1st Collector: 1
1st Collector: 2
1st Collector: 3
1st Collector: 4
1st Collector: 5

2nd Collector: 3
2nd Collector: 4
2nd Collector: 5

StateFlow


在Android开发中,热流的一个很重要的应用就是StateFlow


StateFlow是一种特殊的热流,它可以允许多个订阅者。如果你使用了jetpack compose来开发app的话,StateFlow可以简单而高效的在app的不同地方享状态(state)。因为热流只发送当前的状态(而不像冷流那样从开始发送值)。


要新建一个StateFlow,可以使用MutableStateFlow,然后给它一个初始值:


val count = MutableStateFlow(0)

在这里新建了一个叫做count的StateFlow,初始值为0。要更新它的值可以使用update方法,或者value属性:


this.count.update { v -> v + 1 }
this.count.value = 10

这时,订阅了count状态的订阅者就可以收到更新之后的值了。要订阅可以这样:


count.collect {
//...
}

在冷热流之外还有两种流:回调流和通道流。这个后面会详细讲到。


SharedFlow


SharedFlow也是一种热流,主要用于事件流。它会对所有的活的收集器发送事件。不同的消费者可以在同一时间收到同一个事件。


可以使用MutableSharedFlow()方法来创建一个SharedFlow对象。可以通过replay参数指明多少个已经发送的事件可以再发送给新的收集器,默认的是0。也即是在默认情况下,收集器只会接收到开始收集之后发送过来的事件。


这个时候可以来一个例子了:


class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0) // 1
    val tickFlow: SharedFlow<Event<String>> = _tickFlow // 2

    init {
        externalScope.launch {
            while(true) {
                _tickFlow.emit(Unit) // 3
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    ...,
    private val tickHandler: TickHandler, // 4
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect { // 5
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { ... }
    ...
}

示例解析:



  1. MutableSharedFlow声明了一个变量_tickFlow

  2. 定义了属性tickFlow

  3. 在初始化的时候使用SharedFlow成员变量_tickFlow每隔一段时间发送一个空事件

  4. NewsRepository类里声明成员变量tickHandler

  5. NewsRepository初始化之后开始收集事件,并在收集到事件之后调用refreshLatestNews方法来更新新闻。


看完这个例子再结合上面的介绍就会更加深入的了解SharedFlow了。


注意



  • 这SharedFlow是用于事件流处理的,可不是用来维护状态(state)的。

  • SharedFlow的另外一个重要的参数是extraBufferCapacity,它决定了流要在缓存里保留多少个发送过的事件。缓存满了之后会把缓存里面的一个值清理掉,并放入新的值。

  • 要处理缓存溢出的问题可以给onBufferOverflow指定一个方法。比如当缓存满了之后,并遇到新的事件的时候清理掉最旧的值或者暂停发送新事件一直到缓存有空余。

  • 可以使用tryEmit方法来检测是否存在一个活的收集器。这样可以避免无效的事件发送。


热流的坑


如果在同一个协成里订阅了多个热流,只有第一个才会被收集。其他的永远不会得到数据。


所以,要在同一个协成里订阅多个热流可以使用combine或者zip操作符把这些热流都合成到同一个流里。或者分别在每个协程订阅一个热流。


例如:


coroutineScope.launch {
hotFlow1.collect { value ->
// 处理收到的数据
}
hotFlow2.collect { value ->
// 永远不会执行到
}
}

在本例中,第二个collect不会收到数据。因为第一个collect会运行一个无限循环。


背压 (Backpressure)


背压,顾名思义,当消费者消费的速度没有生产者生产的速度快了。在Flow遇到这个情况的时候,生产者就会挂起直到消费者可以消费更多的数据。


runBlocking {
getFastFlow().collect { value ->
delay(1000) // simulate a slow collector
process(value)
}
}

在这个例子中,getFastFlow()会生成数据的速度比process(value)的速度快。因为collect是一个挂起函数,在process(value)数据处理不过来的时候getFastFlow()就会自动挂起。这样就防止了没有处理的数据的堆积。


使用缓存处理背压


有的时候,即使消费者处理速度已经慢于生产者产生数据的速度的时候,你还是想让生产者继续生产数据。这时就可以引入缓存了。Flow可以使用buffer操作符。如:


runBlocking {
getFastFlow().buffer().collect { value -> process(value) }
}

这个例子里使用了buffer操作符,这样在process(value)还在处理旧数据的时候getFastFlow()可以接着生产新的数据。


今日份先更新到这了。to be continued...


作者:小红星闪啊闪
来源:juejin.cn/post/7271153372793946168
收起阅读 »

打工:本身就毫无意义

昨天的时候有一个朋友联系我,说:“他被裁员了,一个工作了 11 年的老 JAVA” 其实在这段时间里面,我收到了很多类似的朋友来信:被裁员,焦虑到睡不着觉。 甚至有些公司,明确的表示不会给你 n+1 。 这些事,让我深切的感受到什么叫做:取之尽锱铢,用之...
继续阅读 »

昨天的时候有一个朋友联系我,说:“他被裁员了,一个工作了 11 年的老 JAVA”



其实在这段时间里面,我收到了很多类似的朋友来信:被裁员,焦虑到睡不着觉。



甚至有些公司,明确的表示不会给你 n+1 。



这些事,让我深切的感受到什么叫做:取之尽锱铢,用之如泥沙,弃之是垃圾。


那么对于我们这些普通的打工人而言,如何应对这样的一个时代,如何才能让自己更加具备竞争力,不要陷入毫无意义的打工内卷呢?


这个也就是咱们这次,主要说明的问题。


一共分为三点:



  1. 要工作,不要打工

  2. 如何得分,为自己工作

  3. 什么时候应该选择打工


要工作,不要打工


工作和打工是不一样的。


想要让自己真正的成长,首先我们需要区分出工作和打工的区别。


打工


回忆一下你的打工生涯,你有没有为今天摸鱼一天,但是工资照发而感到高兴。有没有为今天加班到深夜,但是一点加班费都没有,而感到愤怒。


如果你有这样的情绪,那么就是没有搞明白打工的本质的原因。


打工本质上就是一个机器,它有两个口 一个口吸收你的时间,一个口产出对应的金钱。


产出的金钱多少,与你的工作量,并无本质关系。它需要消耗的是你的时间。


现在很多公司都在要求 996、大小周。很多朋友都说:“ 996 有什么用啊,工作效率那么低,我们只需要在规定的时间之内,把事情做完不就可以了吗?不知道哪个王八蛋想出来的这个。公司也是傻X!”


你以为:公 司 不 知 道 9 9 6 效 率 低 吗? 。你可以怀疑某些公司的人品,但不要怀疑他们的智商(注意:我说的是某些公司,我相信大部分公司是好的~~)


那么既然公司知道 996 效率低,为什么还要让你 996 呢?


说白了就是尽量压榨你的时间。让你没有时间再去思考工作真正的意义。


工作


接下来,我们来说工作。


之前在群里看到了一个朋友说的话,我觉得非常准确,在这里分享给大家:



如果把一个公司比喻成一个篮球队。那么在这个球队里面,将会有两个角色,一个叫做 投篮手
,一个叫做 其他


公司的老板担任的就是投篮手的角色,公司的员工担任的就是其他的角色。


其他的角色的主要任务就是帮助投篮手得分。所以,你运球再好,传球再好,都没有关系。


但是,得分必须要投篮手完成,其他是不允许得分的,也不允许练习得分,甚至连得分的想法都不应该有。



打工压榨你的时间,不允许你做私活,不允许你做任何可以自己创造利润的事情。说白了就是让你尽可能的思考如何运球、如何传球,但是不允许思考如何得分。


而工作区别于打工的地方,就是:工作你要思考的问题就是:如何得分!


很多开发者都是 唯技术至上论,我们永远在思考如何利用技术来换取更高的薪酬,但是我们没有思考过如何利用技术来自己得分。


所以说,当我们想要把打工思维,转化为工作思维的时候。首先需要做的就是:如何可以利用自己技术,来创造出直接属于自己的利润。


这个利润一定不是你通过打工机器换来的,而是通过自己得分得来的。


当有一天,你发现:原来我也是可以投篮的。到这个时候,你就算真正的知道:如何工作了。


如何得分,为自己工作


那么到现在咱们知道了工作和打工是不一样,工作更关注如何投篮。


那么接下来,咱们就来说一说,投篮这个事怎么做呢?


所谓投篮,就是:通过自己现有的储备,产生直接价值,利用对应的价值换取收益


其中,最简单的方式就是:利用现有平台,产生延伸价值


利用现有平台,产生延伸价值


打工机器最好的一点就是:他可以让你在对市场一无所知的时候,快速的进入到这个市场中。从而让你可以知道这个市场运行的规律。


只要你有打过工,那么你一定可以接触到公司内部的一些业务。那么好好的利用这个机会,想一下:“这些业务究竟是解决了市场上哪些现有的问题?目前还有哪些不足?我是否做一些事情,弥补这些不足。”


以我为例:


我之前在黑马工作,从而接触到了线下培训的业务。发现了线下培训的三个问题:



  1. 线下培训,费用极高,通常需要在两万元以上。额外学生还需要支付住宿费、生活费等其他费用。六个月下来,每个学员支出需要在 4-5万元 之间。

  2. 老师的水平参差不齐,运气好撞见了一个好老师(比如像我这样的),可以学的比较好。如果点背,碰上了一个没有那么负责的老师,那么学习情况真的堪忧。

  3. 统一授课需要保证统一进度,所以每天要讲的内容都是固定的。你进度慢跟不上这个进度,那么不好意思。这些都是你的问题,你要想办法解决。课程不会等你。


所以,针对这三个问题,我就在想,那么是不是可以做线上的培训?这样有三点好处:



  1. 费用低:因为没有线下教室的成本,所以可以把费用压到很低。

  2. 1V1私教:我全程提供 1V1 的私教,手把手教学、解决问题。可以保证每个同学都可以得到一个好老师。

  3. 定制化教学:不统一授课,每个同学学习进度都可以不一样。根据每个同学的学习情况进行定制化的教学。


以此来解决线下培训中存在的三个问题。


这就属于:“发现现有业务的不足,从而通过一些改变,来弥补这些不足”。从而开始尝试自己投篮。


什么时候应该选择打工


那么说到这里,打工就毫无意义了吗?我们就应该直接开始学习投篮吗?


当然不是!


所以最后,咱们就来说一下,什么时候应该选择打工。


当我们刚刚走出校园,或者虽然已经工作,但是还没有发现现有业务的一些不足时。


那么打工是一个最好的选择。


千万不要贸然的放弃现有的收入!


但是,我们需要知道的是:打工的目的绝不仅仅只是为了通过时间换取金钱。而一定是,利用现有的平台,发现它所存在的问题,然后想办法解决这个问题。


也就是:打工的终极形态,就是可以真正的工作!


而:工作的终极形态,就是发现了一个社会中现有的问题,然后想办法解决它!


作者:程序员Sunday
来源:juejin.cn/post/7280104452399743028
收起阅读 »

各位微信小程序的开发者们,你们还好吗?

web
前言 最近微信小程序隐私指引这波骚操作实在是太好玩了,剧情跌宕起伏,让人不由得直呼周星驰在《唐伯虎点秋香》里的那句名言“人生大起大落得太快,实在是太刺激了”。 我自己因为有几个小程序需要做适配,所以全程体验了整个剧情,到今天感觉这过程实在是有点离谱,所以决定...
继续阅读 »

前言


最近微信小程序隐私指引这波骚操作实在是太好玩了,剧情跌宕起伏,让人不由得直呼周星驰在《唐伯虎点秋香》里的那句名言“人生大起大落得太快,实在是太刺激了”。


3583c74475ac4036932cf1421e1abafd.jpeg


我自己因为有几个小程序需要做适配,所以全程体验了整个剧情,到今天感觉这过程实在是有点离谱,所以决定记录一下。以飨读者。


起因


让我们把时钟回拨到一个月前的8月14日,微信默默的发了一个足以影响所有小程序的公告 《关于小程序隐私保护指引设置的公告》。大概意思就是对于某些隐私接口,如相册,地理位置啥的,要给用户弹个窗,需要用户点同意以后才能调用。公告内容很多,总结起来有两个重点:



  1. 这个弹窗要你们自己做哟。

  2. 9月15号生效呦,在此之前要是没做弹窗,你的小程序就死定了呦。


然后小伙伴们不敢怠慢,纷纷开始研究怎么个改法,社区论坛里乱成了一锅粥。


有看文档看不懂的:


Screen Shot 2023-09-15 at 12.46.41 AM.png


有没看到公告早上上班突然发现接口都出错一脸懵的:


6a5f760e492e69d02606d9339c33ea8a.jpeg


Screen Shot 2023-09-15 at 12.56.09 AM.png


还有先知先觉的:


Screen Shot 2023-09-15 at 1.05.15 AM.png


骂街的我就不贴了,以防不能过审。


发展


由于推出的过于仓促,阻碍了开发调试,引起开发者不满,官方很快又默默回滚了此次更新。并且鉴于大家都看不懂文档,也不知道怎么改代码。在小伙伴们的强烈呼吁下


Screen Shot 2023-09-15 at 1.17.53 AM.png
官方答应尽快推出demo。


终于在8天之后的8月22日,距离9月15日大限还有24天的时候,大家翘首以盼的官方demo终于出现了。而且不是1个,是3个(后来又多了1个,一共4个)。这下一脸懵变成三(四)脸懵了。


3d769c68d1e39eb2cfc670d8f47af595.jpeg


你让我用哪个???


demo都有了,那就开干吧,还有二十几天,来得及。于是天真的小伙伴们开始高ma高ma兴lie兴lie的写bug。这次改动不仅仅涉及代码层面,还需要后台配置,前台配置,更新缓存,uni/taro框架,基础库版本,第三方开发等等各种问题,大家也是磕磕绊绊的慢慢的都摸清楚了该怎么做。


由于此次改动涉及到所有的小程序,影响面太大,随着9月15号大限的临近,更多的问题渐渐浮出水面。


首先就是那些维护着几十上百个小程序的小朋友,改动看似不多,但每一个都要走开发测试发版流程,而且必须在这短短的不到一个月的时间内完成,听起来就头大有木有?改不完,根本改不完,加班加点不睡觉也改不完。


然后就是那些在线上跑了好几年没更新的小程序,有的是源代码都找不到了,有的原来的开发者早就提桶跑路了,直接欲哭无泪。


还有后知后觉的,剩下几天就到大限的时候来社区里问这个隐私协议是个啥?这么重要的事情平台为什么没有早点通知我???


最后,就是审核变的奇慢无比,原来几个小时就有结果,而现在有小程序审核了好几天了还是没有反应。这其实也是可以预见的,这种影响所有小程序的改动必然会导致版本发布量大增,微信这是人为的自己给自己制造了个审核DDOS,但后果都是开发者承担。就问你急不急吧


Screen Shot 2023-09-15 at 2.20.05 AM.png
就剩几天的时候,有小伙伴被逼的没办法了,不得不使用了一年一次平时打死都不敢用的加急审核。


当时间来到今天,也就是9.15大限前最后一天的时候,上了车的暗自庆幸,没上车的放弃治疗,尘埃即将落定,大家各安天命的时候,意想不到的转折又来了。。。


高潮


9月14号晚上20点38分,也就是距离9月15号0点还有3个多小时的时候,微信官方又发布了一条公告:


Screen Shot 2023-09-15 at 2.49.07 AM.png
总结下来就两点:



  1. 原来说大限是还剩3个小时的9月15号,新的大限推迟到10月17号。

  2. 代码不改也没关系,平台自己会弹窗。


啊???????????????


啥???????????????


就剩3个小时了你给我来个180度大转弯???????????


不瞒你们说,我晚上看到这个公告的开始的表情是:


96282a4cc8aa3522a78448bd660be56a.jpeg
然后是


210235eea23d7fcc4867ae9bbccb3df9.jpeg


这下原本上了车的直接被180度逮虾户给甩出去了,合着我吭哧吭哧费劲巴拉的折腾了一个月眼看就剩3个小时了你告诉我都是白干???你平台为什么不一开始就把方案定成你们自己处理,非要折磨我们开发者???我加班都是白加了?我的用掉的加急审核次数能不能退给我??我怎么觉得有一种被人耍了的感觉?


离最后期限3个小时啊,不是3周,也不是3天,是3个小时啊。你们官方人员业余都是玩赛车的还是兼职开渣土车的啊,平时上下班开车是不是都是漂移过弯啊。


未完待续


这事就这么完了吗? no no no,我觉得还没完,接下来这几天社区里估计又要炸锅了,大家喜欢看热闹的没事可以去围观一下。这不又延期了一个月吗?再出什么幺蛾子我是一点都不会感到意外了。


最后,我觉得这件事印证了我之前听过的一句话,“这个世界就是个巨大的草台班子”。


最后的最后,拿社区里最经典的一张动图镇楼


0-2.gif


作者:ad6623
来源:juejin.cn/post/7278517841884266536
收起阅读 »

记录一下27岁的前端,从二本到澳洲🦘的故事

前言 转眼在悉尼已经206天了,也算是跟大家走了一条不太一样的道路,想还是写下一点东西。为自己作记录,也可以让大家在摸鱼之际看看不太一样的故事。 长文预警,或许有点碎碎念,可以根据目录酌情跳转。 大学的故事 我的大学在成都的一个二本院校读的计算机专业,算不上...
继续阅读 »

前言


转眼在悉尼已经206天了,也算是跟大家走了一条不太一样的道路,想还是写下一点东西。为自己作记录,也可以让大家在摸鱼之际看看不太一样的故事。


FF826FEE-B67F-4661-9728-8139C4132183_1_201_a.jpeg


长文预警,或许有点碎碎念,可以根据目录酌情跳转。


大学的故事


我的大学在成都的一个二本院校读的计算机专业,算不上好,但也没那么坏。




  • 通宵跟室友开过黑,后来上了钻石。




  • 学了网球,当过网球社社长并且一直打到了现在。


    8CEBB138-EC6E-4659-90E9-24CB94747566_1_105_c.jpeg




  • 大二暑假去了美国看过外面的世界,回来结果留了一级🙈。


    899BEF1F-CC21-433A-9872-EF8D0AE2C9CE_1_105_c.jpeg




  • 在学校拿过英语演讲比赛第一名,后来代表学校去参赛见到了好多好优秀的同龄人。


    A5ABE88B-DFB0-498F-820F-8726A3606069_1_102_a.jpeg




  • 去星巴克打过工发生了很多有趣的故事。


    773B0525-473A-4B07-8D43-A652CDBE8E02_1_201_a.jpeg




但要说最大的收获,还是认识了我老婆,陪伴着我一直走到了许多至暗时刻,直至现在:


9548EBF0-5F0A-4803-9046-CB00CEFE30CC_1_105_c.jpeg


毕业以后


创业 Team


毕业之后第一家上班的公司是一个小的创业团队,当时算上老板一共才五个人。


但是依然非常感谢那个机会,我还记得入职的第一天,后端小哥让我看一下接口报错信息,我甚至不知道在 devtools 中如何找到 Networks 😄


后来,我在那里的几个月学会了 JavaScript,学到了简单的 Vue,做了一大堆没有成功的小程序。虽然回过头去看技术不那么酷,但自己凭借自己的三脚猫功夫也算入了门。


5295BBF8-FB29-438B-96B3-73E507A62A08_1_105_c.jpeg


本地房产媒体公司


后来因为要买房子,想获取更多的信息,机缘巧合入职了一家本地的房产媒体公司 🏠。主要做小程序和后台的开发,虽然工作强度不大,但在那里略微拓宽了自己的技术。有机会把学到的 React 上生产环境,后台依然用 Vue 也没有落下,还积累了不少的运维经验,有的用到了现在。


485E4E11-0870-4B8E-A03C-0694E860CCCB_1_105_c.jpeg


下班看到的行色匆匆的人们


医疗信息系统


在工作到第二年的时候,发现自己陷入了重复的怪圈当中。因为业务得限制,技术也不需要很大,所以一直没啥进步,于是决定去一下更能提升自己的公司。


在水群的过程中认识了很好的朋友,在他的推荐下进了一家阿里出来的团队。


8893E40B-94EE-48ED-B7EE-FFF1CE3291E1_1_201_a.jpeg


我在那里度过了飞速成长了一年半时光,虽然有被大家诟病的 996 等,但团队氛围非常好,我也承担了更多的责任,反而没觉得那么累。


在那里的时间,我精进了自己的 React 技术,维护开发过内部的组件库,开发过内部的医疗系统的富文本编辑器,给大家培训过算法。可以说是痛并快乐着的日子。


C7B6D439-E4B6-4D24-82B8-CB1137209638_1_105_c.jpeg


萌生出国的念头


其实到了现在,出国的心情已经跟最开始完全不太一样了。


在一切的最开始,是21年中国互联网开始裁员的那一段时间,阿里腾讯等大手笔地裁员甚至一度让未来科技城的房价下跌。彼时的我还在吭哧吭哧地魔改 antd 的代码,水群的时候偶然看到一条被裁员房贷还不下去还因为高龄找不到工作的消息,突然陷入了对自我的反思。开始觉得如果这种事情发生在我的身上,我会不会有什么不一样。


想了很久,答案是似乎没有什么不同。新闻中的老哥工作的我曾经梦想的“大厂”,拿着令人羡慕的工资,拉满了杠杆买房但却无情地被经济周期抛弃。虽然讨论了这么多年的年龄焦虑,曾经我也是“只要技术硬,不怕年纪大”中的一员,但当站在高龄失业的路口的时候,摆在我面前的似乎只有“降薪”和“牺牲家庭更拼命地走上管理岗”这两条路径。但我也见过自己曾经的领导被卡在高层和一线之间左右为难的境地,楼道间抽完一根烟却也只能苦笑着对团队说“我们这个周末加一加班”。


似乎没有办法改变身边的环境,那么唯一的答案就是只能改变自己。


幸运的是我曾经见识过外面的世界,也明白走出国门并不是那么遥不可及,只要一步一步往外迈,总是能够做到的。



写到这里的时候再次感慨自己何其幸运遇到了一个好的对象,支持我做出的如此巨大的决定,并愿意辞去自己白领的工作跟我走出来。



一个有意思的事情,也在跟一些朋友聊过出国这件事情后发现出其地一致。
当一旦你的心中埋下“出国”这颗种子之后,你会发现你身边所有的信息在一点一点的改变,以前自己从来没有接触过的观点和信息会开始进入你的世界,一次一次击碎你的三观,让你不断的反思自己和周围。


出国的准备


当我们两个确定了自己要一起做这件事情以后,也只是迈出了漫漫长征的第一步。


我们需要选定目的地,需要说服各自的父母,需要做好中短期的规划。


选定目的地


在选目的地之前,我们首先列出了自己的条件,排除掉不可能的路径。例如:我们只有很少的积蓄,决定了我们无法一开始通过读书的方式出国;再者,我的技术水平、经验还有学历还没有强到可以 offshore 直接面试上岸让雇主担保的程度。那么在排除掉了“读书”、“雇主担保”两条路径之后,我们开始了解各个国家。


我在这里列出几个当时我们自己的选项,过多的不再展开:



  • 日本 —— 老婆对它没有什么特殊的情结,不值得放弃一切去日本。

  • 美国 —— 最好,但难度太大,花费太高。

  • 欧洲 —— 备选

  • 加拿大 —— 备选

  • 澳洲 —— 备选

  • 新西兰 —— 备选


在划定完范围以后,就开始做签证的攻略了。


咨询加拿大


最开始我们是有做加拿大的攻略的,了解到加拿大政策稳定,对华人也较友好,除了天寒地冻以外,也挑不上什么毛病。于是我们认真的了解了政策,每天中午午休的时候就省去了睡觉的时间了解每一个省的政策,中间还认真花费咨询了持牌律师,然后选定了曼省配偶工签再走省提名的道路。


方法方式敲定了以后,我们又陷入了纠结的境地。当时算下来老婆读书要20W的学费,加上租房生活,还有签证律师费等一系列的费用,要准备4-50W。算出来数字的那一瞬间,我们知道还是跟我们无缘了。


了解 WHV


老婆突然有一天想起 WHV 这个签证类别,跟我说2022年的名额开放了。虽然我们早就知道这个签证类型,但因为它本质上还是给背包客们一个旅行干劳力活儿的临时签证,当时没有想到跟我们任何关系。但我后来认真的研读了它的签证条款后,发现它并不局限于签证持有人的劳动行业,只是不能为同一个雇主工作超过6个月,于是一条似乎可行的路径在我心中萌发出来。



裸辞,拿着WHV入境,落地后在境内找工作,工作一段时间卷出一定的成果后让雇主给我做担保。



并且这个签证的成本非常之低,只需要出450AUD的签证费,考出雅思4.5分或者PTE Academic 30分即可。


2022年澳洲政府将这个签证由之前的先到先得改成提交EOI等待邀请的形式,甚至提交EOI表格的时候不需要任何材料辅助,完全可以受邀再去考英语。


那就没有什么好商量的,我们挑了一个风和日丽的上午,将两个人的EOI都递进了澳洲官网,从此命运的齿轮发生了转动。


与此同时我们同时还花钱准备下来了新西兰的 WHV 签证,但这一部分按下不表。


前后受邀


递交完EOI的日子里,我们的生活在有条不紊地进行。我换到了一家五百强的外企继续做前端,在成都完成了我们的婚礼,也挑了好的时候跟双方父母表明了这个事情。更加幸运的是,我们双方的父母都非常开明支持我们的决定。我们看到过WHV交流群中有的小伙伴父母听到“出国”两个字,甚至以死、断绝关系来要挟孩子不让离开。我们倍感幸运,同时也感慨世界之大。


先受到澳洲政府邀请的是我老婆,但我们没有特别大的波动,如果我最后没拿到的话,依旧是没有意义的事情。


好在,我大概在一个月之后就在邮箱中收到了邀请递签的信。


image.png


于是,身份有了,接下来就是准备出发的事情。


正式出发


我们在双双受到邀请后的一个月递交了自己的签证申请,在等待下签的同时我们也没闲着。


老婆去跟她实习到毕业到最后一天的公司说了再见,整整三年,即是解脱也充满了忧愁。为了让自己有一技之长,她选择辞职以后去报班学了美甲💅,也为我们的生活增添一分可能性。


我也去跟 Team Leader 表明了想法,离开了在很多人眼里在成都非常好的工作。


7F01C619-D970-4F80-BF07-90681031CDE8_1_105_c.jpeg


image.png


我们决定了在过完年后出发,于是开始处理自己在成都几年积累下来东西,把自己结婚买的新车卖掉了;和住了很久的房子说再见,和很多好朋友告别,在2023年元旦过后回到了老家,然后定了2月21号出发的机票。


0A77260A-42FE-492F-95BF-8DB6005FCE2D_1_105_c.jpeg


image.png


在家里无忧无虑的日子还是过的飞快,转眼就到了要出发的日子。那个时候出境还需要48小时核酸,我们因为害怕感染,愣是三天都没有出过家门。


4DE70FCA-3201-498A-BA8E-1F294193216C_1_105_c.jpeg


从重庆 -> 厦门 -> 悉尼


B4B0A5BA-55B0-4A97-A5A3-D3CD0FD661D4_1_105_c.jpeg


2A130C69-71EF-4314-95E3-7C457E9B4476_1_105_c.jpeg


找工作的那些日子


落地之后我们选择住了三天酒店,因为租的房子的起租期在三天之后,然后又因为悉尼的正好在举办 Sydney WorldPride 🌈 节,世界各地的人都涌过来参加活动,酒店超级贵。我们租了一个角落里的房间三天,那是我们俩此生住过最小的房间,2000RMB。


595431C2-A897-49B3-A13A-C819973C1B40_1_105_c.jpeg


A00FEDB6-5855-4201-B0A2-4114EE82DC49_1_105_c.jpeg


落地后发现自己的英文完全属于没法用的阶段,便利店买东西也听不懂,办银行卡也听不懂,但磕磕绊绊两个人也还是一起完成了下来。不敢懈怠,第二天就开始把自己的简历电话号码更新成了当地号码,LinkinIn 状态改成了 open。期待着能够开始有一些面试或者电话,因为每天花澳币几乎是五倍的生活开销,心理压力实在是太大。


3C01548D-63DB-4F6C-BB91-69EBD3347588_1_105_c.jpeg


还记得躺在酒店的第二天晚上,我刷了一晚上小红书,因为发现了送外卖能赚到一些钱而非常激动。第二天早上很兴奋地跟老婆说,“我要去送外卖啦!”


第三天将我们巨多的行李箱搬到了租的一室房子内,房租 1200AUD 每周:


7521FA19-5105-443E-BED8-CFF7E04F1A61_1_105_c.jpeg


折腾了一天之后,看着窗外的夕阳🌇,发了一条朋友圈:


image.png


我戏谑地跟老婆说,我们毕业的第一年住的一室的房子,后来逐渐换得像一个家。现在又回到了一室的房子中,这次不知道再要住多久了。


7E1E2567-7B36-49E5-BEB4-DBA04420D3B6_1_105_c.jpeg


因为澳洲租的房子大多完全不带家具,我们头一个星期完全处于淘家具的过程中,家中不断地受到网上买的便宜家具包裹,然后安装:


4F40CE21-6E89-4356-8B4C-3B764C54F5B3_1_105_c.jpeg


在睡了一个星期地板的,人生第一次自己安装了床架:


277B3E14-1661-4885-A390-18E7FC704A02_1_105_c.jpeg


第一次安装了柜子:


9B2E8FCF-34F0-4EDB-8E5B-995B4A871E94_1_105_c.jpeg


家徒四壁依旧坚持面试:


F75880C1-D99E-456B-A182-88BF70BEF690_1_105_c.jpeg


中途去看了周董疫情后的第一场演唱会:


A1FADDD8-210F-4633-BB85-64E8B7E2F3CA_1_105_c.jpeg


给老婆圆了她一直的梦,用我们卖车的钱买了 Mini Cooper:


F4C5C70D-3C0C-4E2C-A071-3EECF7B3F242_1_105_c.jpeg


买了车之后,就一直在送外卖:


0923ED08-ADAB-49A3-A3F2-A4CA00663E97_1_105_c.jpeg


还记得送外卖的第一天,我们中午从11点跑到了2点,回来休息了2个小时,从4点跑到了8点,赚了100多刀。我们兴奋地去超市里买了很多的水果:


DAB0A1CE-C43F-499D-A99F-714832D3CFEF_1_105_c.jpeg


因为这边东西太贵,并且家里没有冰箱。我们吃了整整一周的速食食品,直到送外卖开始赚生活费:


D2BAFBB5-7BC2-4E4A-AD61-9517C937C097_1_105_c.jpeg


AF07C3C3-B406-4425-8A07-EB1663CEFC5F_1_105_c.jpeg


中途送外卖一度开心到忘记了自己曾经是一个程序员,笑。


9624BF24-2C40-4961-9D7F-3D7B60138020_1_105_c.jpeg


后来很多简历投递到公司邮箱,除了拒信就是没有回复,我变得十分焦虑。跟家里人打电话的时候也在说,只能再给自己两个月的时间了,家里人支持一个月,自己的生活费能再撑一个月,5月还没有找到工作就要回家了 🙈


于是我开始反思自己的方式方法。痛定思痛之后决定主动出击,厚着脸皮开始在 LinkedIn 上私信猎头和工程师,寻找内推的机会。


换了新的方法之后,果然成功率要高了不少,尽管有一半以上的人不会回复我的消息,但剩下的一半也至少让我开始收获一些电话询问我的情况。


image.png


然后陆陆续续地开始接到一些面试,但刚开始大多都还是 recruiter 和猎头了解情况的面试。我深知自己没有任何本地的工作经验,口语也算不得好,因为珍惜每一次能跟别人打电话的机会,尽量表现出热情,结束后也会记录自己的表现:


image.png


在足够多的厚着脸皮尬聊之后,终于迎来了生活的曙光。猎头给我打电话有澳洲最大的保险公司之一在招聘一批 contractor,并迅速地帮我安排了第二天的面试。也许这次运气比较好吧,顺利的通过了面试,拿到了6个月的合同,生活可以稳定一段时间了。


还记得在受到 offer 的那天晚上,我回家的路上,想起了在再次收到拒信的那个晚上看《当幸福来敲门》,一瞬间理解了 Chris 收到了正式 offer从办公室走出来的心情:


image.png


工作的时光


入职的一点小震撼


入职的第一天是我和一个父母中东但在这边出生的小哥一起办理入职,我的 Manager 带我们领了电脑,中午一起吃了饭。但是英语闲聊也太太太难了,我现在都记得第一天完全不知道他们在说什么...


但没过多久澳洲职场就给我这个中国小伙子来了一个大大的震撼:当天下午四点,我还在工位上吭哧吭哧 yarn install。中东小伙过来拍了拍我的肩膀,“你要去火车站吗?要不要一起?”,我看了看表,又看了看他真诚的目光,点了点头。于是我们去跟 Manager 说我们走了, Manager 面不改色,问我们的 VPN 的调通了吗,确保可以在非办公室网络下连上了吗?我说弄好了。她点点头说,那就好,这样你明天就可以在家工作了。


我当时听到并没有当一回事,只当是新人入职的客套。于是平生在4点30分搭上了回家的列车。


image.png


第二天,我高兴地背上电脑和小书包来到 CBD 的办公室,结果空无一人。早会的时候,我说我在办公室,结果大家一脸震惊,说你昨天不是去了吗?为什么今天又去?于是我在第三天顺理成章地居家办公了,直到今天,我还大概保持着一个月去一天的通勤频率。


image.png


技术栈


其实技术方面反而没什么好聊的,跟国内比起来,这边对技术的要求反而没这么高,对候选人的资质也更加宽容。


我们用的是 React + NestJS + AWS 的一揽子全栈方案,没有什么学习成本也就上手了。


在🦘工作和国内的区别


Make sure you are happy and health


在写下这一篇小记的时候,正是澳大利亚的 R U OK Day,每一个 manager 都会买上一些小吃或者礼物,关心自己的员工是否高兴和健康。


例如我们 Manager 最常用来总结早会的一句话就是:“如果你有感觉到任何不舒服或者对项目不那么满意的地方,请不要犹豫地找我聊聊,我的存在就是为了让团队更好的运转。”


9F0DBC26-3B0B-4869-8A02-8316A7CA2C3F.jpeg


大家在早会开始之前,也会毫不忌讳地在群里发出“I'm feeling unwell today. Will taking a day off”,然后潇洒地消失一整天。


3AA7D1D8-68CD-4D32-BCE2-1A709A20B5AB.jpeg


工作与生活的界限


在这边,天大的事情,也几乎不会在你下班之后给你发任何消息。我在下午5点之后收到过几次其他同事或者 Manger 的消息,开头的第一句话必定是道歉。至于晚上和周末,则一次都没有。


235A7542-085B-4817-855D-1A1AD595E70C_1_201_a.jpeg


对职业的容忍度



  1. 我们团队有两个工程师甚至没有上过大学,高中毕业自学编程。他们很大方地在闲聊中承认,团队中的其他人也没有觉得有任何问题。

  2. 有39岁转码带娃的妈妈,她很努力,我们都很喜欢她。

  3. 我们团队的 Senior Engineer 以前是仓库管理员。


后记


澳洲的工作很理想,但这边的生活却和国内大相径庭。在略微稳定下来以后,我们俩又经常思念国内有家人朋友在身边的日子,思念出门就可以吃到美食的日子,思念我们熟悉的文化和乡土。


窗外的明月高挂,不知道下一次回家又是什么时候了。


以此为记。


作者:yuetong3yu
来源:juejin.cn/post/7278929122302132279
收起阅读 »

又一个全新编程语言,诞生了!

最近,编程领域又一个黑马忽然冲进了开发者们的视野并正式开放下载。 它的名字叫Mojo,相信有不少小伙伴最近也看到了。 Mojo是为AI开发者所准备的编程语言,语法有点像Python。 根据Mojo官网的描述,它结合了Python的易用性和C语言的高性能,解...
继续阅读 »

最近,编程领域又一个黑马忽然冲进了开发者们的视野并正式开放下载。


它的名字叫Mojo,相信有不少小伙伴最近也看到了。



Mojo是为AI开发者所准备的编程语言,语法有点像Python。



根据Mojo官网的描述,它结合了Python的易用性和C语言的高性能,解锁了AI硬件的可编程性和AI模型的可扩展性。


Mojo看起来好像挺能打,它到底是哪个公司所推出来的呢?


看了一下才发现Mojo是由人工智能公司Modular所推出的全新编程语言。


而Modular这个公司则是一个非常年轻的新生AI创业公司,于2022年由Chris Lattner和Tim Davis所创立。



提到这两个创始人,相信有些同学也有所了解,都是业内顶级专家。其中Chris Lattner还被称为“LLVM之父”和“Swift之父”,在苹果、谷歌、特斯拉等多家知名科技巨头里曾带领构建了AI和核心系统。


Modular公司的愿景非常宏伟,目标是自下而上重塑AI基础设施。


去年的时候,Modular AI曾获得过3000万美金的融资。而就在前些天,Modular又再次宣布成功融资 1 亿美金,这对于一个刚诞生不久的初创型公司而言可谓是成绩斐然。



另外在公司官网的投资者名单里能看到,不少AI领域的知名投资机构都有参与。



Mojo这个编程语言有几个比较明显的特点。


1、首先是性能方面。


Mojo充分利用硬件的特性和功能,包括多核、矢量单元和加速器单元,以及先进的编译器和异构运行时机制,在不增加复杂性的前提下实现了与C++和CUDA相当的性能。


在并行化这一块,Mojo利用MLIR,使Mojo开发者能够充分利用向量、线程和AI硬件单元。



2、其次是互操作性方面。


大家都知道,发展到今天,Python的生态极其繁荣,各种函数、库、框架、模型、工具等等数不胜数。


而Mojo则可以访问整个Python生态。比如使用Mojo,可以在代码中无缝地接入和混合像Numpy和Matplotlib等库。



3、再者就是可扩展性方面。


可扩展性这块也是Mojo的优势。Mojo可以升级用户模型中的已有操作,以便开发者可以使用预处理、后处理、自定义替换等操作来轻松地扩展用户的模型。


Mojo最初发布于今年的5月初,上线数月以来就已形成基本规模和生态。



前不久,Modular官网宣布Mojo正式开放下载,首先是从Linux系统开始,并在后续的迭代版本中将陆续添加对Mac和Windows的支持。


这也意味着开发者可以通过Mojo SDK进行尝试并编写自己的Mojo代码。



而就在Mojo官宣可以下载后不久,一位名叫Aydyn Tairov开源作者就利用Mojo来做了一个突破性的尝试。


这个作者之前曾将GitHub上火热的由纯C实现的llama2.c项目移植到了基于Python的llama2.py。


而这次Aydyn Tairov又将llama2.py移植到了llama2.mojo,结果非常出乎意料,移植后性能提升了近250倍。



即便如此,作者仍然认为里面还有一些改进的空间。


看到Mojo如此的表现,有不少网友说Python这次可谓是遭遇了一个强大的对手,Mojo甚至有可能在未来会取代Python?


对此,公司CEO Chris Lattner直接回应称:


Mojo并不会对Python造成威胁,相反,还会帮助Python开发者变得更强大。要担心的也不是Python,而是C++们。



文章的最后也附上相关的页面,感兴趣的小伙伴可以尝试一下。



至于这门编程语言在接下来的AI时代会发展如何,我们可以拭目以待。


作者:CodeSheep
来源:juejin.cn/post/7280057907055902760
收起阅读 »

环信web、uniapp、微信小程序sdk报错详解---登录篇

项目场景:记录对接环信sdk时遇到的一系列问题,总结一下避免大家再次踩坑。这里主要针对于web、uniapp、微信小程序在对接环信sdk时遇到的问题。主要针对报错400、404、401、40 (一)登录用户报400原因分析:从console控制台输出...
继续阅读 »

项目场景:
记录对接环信sdk时遇到的一系列问题,总结一下避免大家再次踩坑。这里主要针对于web、uniapp、微信小程序在对接环信sdk时遇到的问题。主要针对报错400、404、401、40

 (一)
登录用户报400



原因分析:
从console控制台输出及network请求返回入手分析
可以看到报错描述invalid password,密码无效,这个时候就需要去排查一下该用户密码填写是否正确


排查思路:
因为环信不保存用户的密码,可以在console后台或者调用修改密码的restapi来修改一下密码再重新登录(修改密码目前只有这两种方式)



(二)
登录用户报404




原因分析:
从console控制台输出及network请求返回入手分析
可以看到报错描述user not found,这个时候就需要去排查一下该用户是否存在于该项目使用的appkey下了

排查思路:
可以看一下console后台拥有这个用户的appkey和自己项目初始化时用的是否是同一个,若在console后台并没有查到该用户,就要注意这个用户是否真的没有注册




(三)
登录用户报40、401




原因分析:
报错40或者401一般都是token的问题,需要排查一下token是否还在有效期,token是否是当前用户的用户token
40的报错还有一种情况,用户名密码登录需要排查用户名及密码传参是否都是string类型


注:此处需要注意用户token和apptoken两种概念
用户token指的是该用户的token,一般只用于该用户在客户端使用环信 token 登录和鉴权
app token指的是管理员权限 token,发送 HTTP 请求时需要携带 app token
token较为私密,一般不要暴露出去

排查思路:
排查用户名及密码传参是否都是string类型,这个可以直接将option传参打印出来取一下数据类型看看是否是string
关于token排查,现在没有合适的办法直接查询token是否还在有效期或者是不是当前用户的token,只能通过api调用看是否报错401,可以在console后台直接获取新的用户token来测试一下


是不是当前用户的token也可以找环信的技术支持帮忙查,但在不在有效期他们也查不了


话外
有人遇到为什么已经open成功了但是还会报错

这里要注意open只能证明获取到了token,证明不了已经建立了websocket连接,只有触发onOpened或者onConnected回调 只有onOpened或者onConnected回调触发,才算真正与环信建立连接。所以也不能在open返回的success或者.then中做任何逻辑处理,此外还要注意监听回调一定要放在调用api之前,在调用任何一个api时都要保证监听挂载完毕,包括open


如何判断自己是否在登录状态

可以用以下三种方法中的一种判断当前用户是否在登录状态~
1、WebIM.conn方法下有一个logOut字段,该字段为true时表明未登录状态,该字段为false时表明登录;
2、WebIM.conn.isOpened () 方法有三个状态,undefined为未登录状态,true为已登录状态,false为未登录状态,可以根据这三个状态去判断是否登录;
3、通过onOpened 这个回调来判断,只要执行了就说明登录成功了,输出的话,输出的是undefined
三者选其一判断登录状态

收起阅读 »

需要具备哪些技能才算中高级前端?

之前有人问过我,“到底什么样才算中高级前端,需要具备哪些技能才算中高级?”他的本意是让我推荐一下前端的学习路线,然后再问了我这个问题,估计是想看看有哪些技术是晋升中高级前端的关键,提前学习吧。 这里不管是前端、终端还是后台,我觉得是可以统一来讨论的。 有什么标...
继续阅读 »

之前有人问过我,“到底什么样才算中高级前端,需要具备哪些技能才算中高级?”他的本意是让我推荐一下前端的学习路线,然后再问了我这个问题,估计是想看看有哪些技术是晋升中高级前端的关键,提前学习吧。


这里不管是前端、终端还是后台,我觉得是可以统一来讨论的。


有什么标志性的技能或者技术是可以作为中级工程师和高级工程师的分水岭的吗?只要学会了这些技术和技能,就一定可以晋升中高级工程师?我想是没有的。


我分享一下我对初中高级工程师的理解,仅供参考。


初级工程师就是应届毕业生,标志是能够熟练支撑中小型业务需求开发。他可能会支撑所有业务模块的开发,或者非核心业务模块的开发,同时也会支撑基础技术项目的开发。所以,如果使用是否参与基础技术项目来作为判断的话,是不对的。


中级工程师的标志是能够独立负责一个核心模块。成为一个模块负责人,这个模块的所有事情,领导都可以放心交给你的时候,你就是中级工程师了。这个负责模块,不是指能够支撑涉及这个模块相关的需求。而是指,你要:


  • 了解它的全部代码、它的设计原理
  • 了解它在整个系统中的位置、它跟其他模块的关联关系
  • 了解它的各种特性、现状、问题、未来的优化、发展方向
  • 维护好它的文档
  • 可以很好地给其他人、你的领导描述清楚,这个模块的所有内容
  • 负责它的一切

高级工程师的标志是能够负责一个系统成为系统负责人,带领项目成员一起,承担这个系统的所有事情。对比中级工程师,负责的内容更大更加复杂了,但本质没变,就是要综合能力。同时,中级工程师还只是单人作战,如果想要成为高级工程师,一定需要了解团队的力量,并学习如何通过合理的项目管理手段,做好一个复杂系统。


这里中级和高级都提到了“负责”这个词,那具体怎样才算负责,是领导指派给你,让你负责一个核心模块,就算负责了吗?不是的。这里的“负责”是指能够完全胜任,做出让领导满意的成果,让领导非常放心


当然,每家公司对不同职级的能力要求是不一样的,你也可以完全按照上面的能力描述来进行有针对性的学习和成长。


以上就是我对于中高级前端开发的理解,希望能够给你带来一些启发。



【讨论问题】


你是如何理解中高级工程师的呢?


欢迎在评论区分享你的想法,一起讨论。



----------------【END】----------------



【往期文章】


给你介绍一个工具,帮你找到未来的努力方向


《程序员职场工具库》高效工作的神器 —— checklist


2023 年上半年最值得看的一篇文章



欢迎加我v【longyiyiyu】,进行无负担沟通,我会


  • 长期职业发展规划指导
  • 近期工作重点交流
  • 职场解惑
  • 面试辅导

也欢迎关注公众号【潜龙在渊灬】,收获程序员职场相关经验、提升工作效率和职场效能、结交更多人脉。


作者:潜龙在渊灬
链接:https://juejin.cn/post/7274902683404206143
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

我又听到有人说:主要原因是人不行

在工作中,我们经常把很多问题的原因都归结为三个字:人不行。 曾经有UI同事指着我的鼻子说,你们没有把设计稿百分百还原,是因为你们人不行。 昨天,我又听到一个研发经理朋友说,唉呀,项目干不好,主要是人不行。 哦,我听到这里,有种似曾相识的感觉,于是我详细问了一...
继续阅读 »

在工作中,我们经常把很多问题的原因都归结为三个字:人不行。



曾经有UI同事指着我的鼻子说,你们没有把设计稿百分百还原,是因为你们人不行


昨天,我又听到一个研发经理朋友说,唉呀,项目干不好,主要是人不行


哦,我听到这里,有种似曾相识的感觉,于是我详细问了一下,你的人哪个地方不行。



朋友说,项目上线那天晚上,他们居然不主动留下来值班,下班就走了,自觉意识太差。代码写的很乱,不自测就发到生产环境,一点行业规范都没有。他们……还……反正就是,能不干就不干,能偷懒就偷懒,人不行!



这个朋友,代码写的很好,人品也很好,刚刚当上管理岗,我也没有劝他,因为我知道,劝他没用,反而会激怒他。


当一个人,代码写得好,人品好,他就会以为别人也和他一样。他的管理方式就会是:大家一定要像我这样自觉,不自觉我就生闷气了!


反而,当一个人代码写得差,自觉性不那么强,如果凑巧还有点自知之明,那么因为他很清楚自己是如何糊弄的,因此他才会考虑如何通过管理的方法去促成目标。


我的这些认知,满是血泪史。因为我就经历过了“好人”变“差人”的过程。


因为代码写得好,几乎在每一个公司,干上一段时间,领导都会让我做管理,这在IT行业,叫:码而优则仕


做管理以后,我就发现,并不是所有人都像我一样,也并不是各个部门都各司其职,所谓课程上学的项目流程,只存在于理想状态下。当然,其中原因非常复杂,并不一定就是人不行,也可能是流程制度有问题。比如我上面的朋友,他就没有安排上线必须留人,留什么人,留到几点,什么时候开始,什么标准算是上线完成,完成之后有什么小奖励,这些他都没有强调和干预。


但是,我们无法活在理想中。不能说产品经理的原型逻辑性差,UI的设计稿歪七扭八,我们就建议老板把公司解散吧,你这个公司不适合做软件产品,那样我们就失业了。


你只能是就目前遇到的问题,结合目前手头的仅有的仨瓜俩枣,想办法去解决。可能有些方案不符合常规的思路,但都是解决实际问题特意设置的。


比如我在项目实践中,经常遇到的一点:



产品经理没有把原型梳理明白,就拿出来给开发人员看,导致浪费大家的时间,同时也打击大家的积极性:这样就开始了,这项目能好的了吗?我们也做不完就交给测试!



这种情况,一般我都会提前和产品经理沟通,我先预审,我这关过了,再交给开发看,起码保证不会离大谱。这里面有一个点,产品没有干好自己的活,人不行?他也只有3天时间设计原型。


还有一个问题也经常出现:



即便是产品原型还算可以,评审也过了。让开发人员看原型,他们没有看的。一直到开发了,自己的模块发现了问题,然后开始吐槽产品经理设计的太烂,流程走不通。



这是开发人不行?他们不仔细看,光想着糊弄。其实是他们没有看的重点,你让我看啥,我就是一个小前端,让我看整个平台吗?让我看整个技术架构?Java该用什么技术栈?看前端,你告诉我前端我做哪一模块的功能?此时,我一般都是先分配任务,然后再进行原型评审。如果先把任务分下去,他知道要做这一块,因为涉及自己的利益,会考虑自己好不好实现,就会认真审视原型,多发现问题。这样会避免做的过程中,再返过头来,说产品经理没设计好。已经进入开发了,再回头说产品问题,其实是开发人员不负责,更确切说是开发领导的责任。


一旦听到“人不行”的时候,我就会想到一位老领导。


他在我心中的是神一般的存在,在我看来,他有着化腐朽为神奇的力量。


有一次,我们给市场人员做了一个开通业务的APP:上面是表单输入,下面是俩按钮,左边是立即开通,右边是暂时保存。后来,市场同事经常找我们:能不能把我已开通的业务,改为暂时保存,我点错了。这点小事还闹到公司大会上讨论,众人把原因归为市场推广的同事人不行:没有上过学?不认识字?开不开通自己分不清吗?


此事持续了很久,闹得不愉快。甚至市场部和研发部出现了对立的局面,市场部说研发部不支持销售,研发部说市场部销售不利乱甩锅。


我老领导知道后,他就去了解,不可能啊,成年人了,按钮老按错,肯定有问题。原来,客户即便是有合作意向,也很少有立即开通的,他们都会调查一下这个公司的背景,然后再联系市场人员开通。两个按钮虽然是左右平分,但是距离很近。于是,他把软件改了,立即开通按钮挪到上边,填完信息后,顺势点击暂时保存,想开通得滑到上面才能点击。此后,出错的人就少了。




后来,行政部又有人抱怨员工人不行。发给员工的表格填的乱七八糟,根本不认真。有一项叫:请确认是否没有错误_____。明明没有错误,但是很多人都填了“否”。尽管反复强调,一天说三遍,依然有人填错,没有基本的职场素质。


老领导,他又去了解。他把表格改了,“是否没有错误”改为“全对”,空格改为打钩。后来,填错的现象明显少了。




很多事情,我们都想以说教来控制形势。比如反复强调,多次要求,我嗓子都喊哑了。因为不管是区分按钮,还是填写表格,你不是个傻子,你的能力是可以做到的,不应该出错,出了错你就是人不行。而老领导总是以人性来控制,知道你是懒散的,肯定不愿意认真付出,因此设置一个流水线,让你随着预设的轨迹被迫走一圈。下线后,居然发现自己合格了,甚至自己都变成人才了。用他的话说就是:流程弥补能力不足。



当归因为人不行时,其实分两种情况:别人不行自己不行


作者:TF男孩
链接:https://juejin.cn/post/7146055238741393415
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

离职交接,心态要好

话说今年经历了几次项目交接?主动和被动的都算! 01 实在是没想到,都到年底快收尾的时候,还要突然接手离职人员的项目; 不断拉扯和管理内心情绪,避免原地裂开; 年度中再次经历突发的交接事宜,并且团队要在极短的时间内完成所有事项的交接流程; 毫无征兆的变动必然...
继续阅读 »

话说今年经历了几次项目交接?主动和被动的都算!




01



实在是没想到,都到年底快收尾的时候,还要突然接手离职人员的项目;


不断拉扯和管理内心情绪,避免原地裂开;


年度中再次经历突发的交接事宜,并且团队要在极短的时间内完成所有事项的交接流程;


毫无征兆的变动必然会引起一系列问题,最直接的就是影响团队现有节奏进度,需要重新调整和规划;


人员的小规模变动,对部门甚至公司产生的影响是显而易见的,道理都懂;


但是从理性上思考,这个问题并非是无解的,是可以在各个团队中,进行内部消化的;


而人力减少带来的成本降低,以及确保公司的可持续,这是极具确定性的,也是核心目的;


所以感性上说,这个梦幻的职场,可能真的是"爱了";



02



如果是常规情况下的离职流程,交接并不是一件复杂的事情,因为有时间有心情来处理这事,好聚好散;


然而最骚的是,奇袭一般的裁员手段,几分钟谈话结束直接走人;


丝毫不顾及由此带来的影响,认定留下的人应该兜底相应的责任,实现无缝接坑;


当然并不是什么公司都有底气这么做的,大部分还是在裁员通知后,留有一定的时间处理交接事项;


对于交的过程是否有质量,完全看接的一方是否聪明;


从感性上分析,都已经被裁了自然要牢牢把握摸鱼的机会,根本不会在意交出的事项谁来维护,不反越防线就不错了;


而压力会直接传送后闪现到接的人正上方;



03



面对被动离职的交接,确实很难妥善处理,情绪化容易导致事情变质,能真正理性对待的并不多;


交接涉及到三方的核心利益:公司、交出人、接手人,不同角度对待这件事件,态度完全不同;


公司,并不关心交接的质量,只要项目有人兜底即可;


交出方,感性上说直接敷衍交接单上的流程即可,并不在意后续的影响;


接手方,项目交接完成后的第一责任人,可能会关心项目的质量状况;


至于说接手的人能否有时间,有能力,有心情接下这种天降大任,可能除了自己以外,不到出问题的时候关注的很少;


因为项目交接过程没有处理好,从而导致后续的事故与甩锅,情绪化的现象并不少见;


如果是在内部矛盾突出的团队中,由此引发的离职效应也并不少见;



04



人的情绪真的是很奇怪,能让复杂的事情变的简单,也能让简单的事情变的离谱;


情绪上头的时候,事情本身是否真的复杂就已经不太重要了;


接手方最大的问题在于吃力不讨好,如果接了一个质量奇差的项目,意味之后很长一段时间内,工作状态都会陷入混乱的节奏中;


对于大部分研发团队来说,都是存在排期规划的,如果被交接的项目横插一脚,重新调规划影响面又偏大;


向上反馈,多半是回答一句:自行消化;


何谓自行消化,就是占用空闲时间处理,比如下班后,比如周末,比如摸鱼,这些都是对工作情绪的持续伤害;


最终兜底的个人或者团队,可能需要带着夜宵去公司搬砖;



05



吐槽归吐槽,裂开归裂开,成熟的搬砖人不该表现出明显的情绪化;


先捋一捋在面对离职交接时的注意事项,虽然说离职后有一个过渡期,但是真正涉及交接的时间通常一周左右;


作为接手一方,自然期待的是各种文档齐全,对于坑坑洼洼的描述足够清楚;


然而对于被离职的交出方,会带着若隐若现的情绪化状态,很难用心处理交接事项,能不挖坑就已经是良心队友了;


接手方作为后续的兜底人员,兜不住就是一地鸡毛;


如果兜住了呢?那是职责所在、理所应当、不要多想、安心搬砖;



06



面对项目交接,这种隔三差五个月就会突发的事,完全可以用一套固定的模式和节奏去执行;


强烈建议:不排斥、不积极、不情绪化;


但是在处理的过程中要理性且严谨,这样可以规避掉许多可能出现的麻烦,毕竟签了交接单,从此该项目问题根本甩不开;


职场几年,在多次"交"与"接"的角色转换过程中,总结以下几点是研发需要注意的;


P1:文档,信息的核心载体;


不管项目涉及多少文档,照单全收;


如果文档严重缺失甚至没有,直接在交接单上写明情况,并且得加粗划重点展示;


文档和项目的维护极有可能是线性不相关,但是手有文档心里不慌,因为方便后续再把项目交接给其他人;


所以,敷衍一时爽,出事火葬场;



07



P2:代码工程,坑与不坑全看此间;


接到手里的项目,是否会导致情绪崩塌,全看项目代码工程的质量,遇上一堆烂摊子,心情会持续的跌跌跌,然后裂开;


直接把人打包送走的情况也并不少见;


如果代码工程质量极高,架构设计稳定,组件集成比较常规,分包井然有序,悬着的情绪可以适当下落;


P3:库表设计,就怕没注释;


对于数据库层面的设计,与代码工程和业务文档三者相辅相成,把握其中的主线逻辑即可;


但前提是表的设计得有清晰的注释,如果是纯中式英文混搭拼音,且缺乏注释,必然会成为解决问题的最佳卡点;


P4:核心接口,应当关注细节;


从项目的核心业务中选出2-3个复杂的接口读一读;需要将注意点放在细节逻辑上,给内心积蓄一丢丢解决问题的底气;


熟悉接口的基本思路:请求从客户端发出,业务服务的处理逻辑,对数据层面的影响,最终响应的主体;



08



P5:遗留问题,考验职场关系的时候到了;


公司一片祥和的时候,员工之间还可以做做样子;


但是已经走到了一别两宽的地步,从感性上来说只要不藏着掖着就行,还想窥探别人安稳摸鱼的秘密,确实想的不错;


老练的开发常干的事,为了解决某个问题临时上线一段代码,处理好后关闭触发的入口,但是会保留代码主体;


这还算常规操作,最骚的是在本地写一段脚本工具解决线上的问题;


这些隐藏的接口和脚本只有开发的人自己清楚,如果不给个说明文档,这不单是挖坑,还顺手倒了一定比例的水进行混合;


P6:结尾事项,寒暄几句还是要的;


安全意识好的公司,会对员工的账号权限做好备份,以便离职时快速处理,不会留下风险隐患;


在所有权限关闭之后,接手人就可以在交接单上完成签字仪式;


交接完成后还是得适当的寒暄几句,万一接了个坑,转头就得再联系也不稀奇,所以职场留一线方便语音再连线;



09



年度收到的离职交接,已经累计好几份,对这种事情彻底麻了;


事来了先兜着,等兜不住的时候自然会有解决办法;


抗拒与烦躁都不会影响流程的持续推进,这种心态需要自己用清醒的意识不断的说服自己;


最后想探讨一个话题,跟项目前负责人联系,用什么话术请教问题,才能显得不卑不亢?



END


作者:知了一笑
链接:https://juejin.cn/post/7157651258046677029
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Swift - 闭包

iOS
定义 闭包是一个自包含的函数代码块,可以在代码中被传递和引用。闭包可以捕获和存储其所在上下文中任意常量和变量的引用**。 闭包的语法有三种形式:全局函数、嵌套函数和闭包表达式。 全局函数是一个有名字但不会捕获任何值的闭包潜逃函数是一个有名字并可以捕获其封闭函数...
继续阅读 »

定义


  • 闭包是一个自包含的函数代码块,可以在代码中被传递和引用
  • 闭包可以捕获和存储其所在上下文中任意常量和变量的引用**。

闭包的语法有三种形式:全局函数、嵌套函数和闭包表达式。


  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 潜逃函数是一个有名字并可以捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

闭包表达式


闭包表达式的一般形式

{ (parameters) -> return type in

statements

}

以数组的sorted(by:)方法为例

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})


写成一行

names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2})

根据上下文推断类型


  • sorted(by:)方法被一个字符串数组调用,Swift 可以推断其参数和返回值的类型,因此其参数必须是 (String, String) -> Bool
  • 这意味着(String, String) 和 Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:
names.sorted(by: { s1, s2 in return s1 > s2})

单表达式闭包的隐式返回


  • 单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果
names.sorted(by: { s1, s2 in s1 > s2})

参数名称缩写


  • Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0$1$2 来顺序调用闭包的参数,以此类推。
  • 闭包接受的参数的数量取决于所使用的缩写参数的最大编号。
  • in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
names.sorted(by: {s1 > s2})

运算符方法


  • Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:
names.sorted(by: >)

尾随闭包


尾随闭包是一种特殊的闭包语法,它可以在函数调用的括号外部以简洁的方式提供闭包作为函数的最后一个参数。
使用尾随闭包的优势在于增加了代码的可读性和简洁性。当闭包作为函数的最后一个参数时,将闭包放在括号外部,可以使函数调用更加清晰,更接近于自然语言的阅读顺序。

func calculate(a: Int, b: Int, closure: (Int, Int) -> Int) {
let result = closure(a, b)
print(result)
}

// 调用函数时使用尾随闭包
calculate(a: 5, b: 3) { (x, y) -> Int in
return x + y
}

// 如果闭包只包含一个表达式,可以省略 return 关键字
calculate(a: 5, b: 3) { (x, y) in
x + y
}

// 省略参数的类型和括号
calculate(a: 5, b: 3) { x, y in
x + y
}

// 使用 $0, $1 等缩写形式代替参数名
calculate(a: 5, b: 3) {
$0 + $1
}


如果一个函数接受多个闭包,需要省略第一个尾随闭包的参数标签,并为其余尾随闭包添加标签。



值捕获


闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。



可以捕获值的闭包最简单的形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。



注意:



如果将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员捕获了该实例,将会造成一个循环引用。



捕获列表


默认情况下,闭包会捕获附近作用域中的常量和变量,并使用强引用指向它们。你可以通过一个捕获列表来显示指定它的捕获行为。


捕获列表在参数列表之前,由中括号括起来,里面是由逗号分隔的一系列表达式。一旦使用了捕获列表,就必须使用in关键字,即使省略了参数名、参数类型和返回类型。


捕获列表中的项会在闭包创建时被初始化。每一项都会用闭包附近作用域中的同名常量或者变量的值初始化。例如下面的代码实例中,捕获列表包含a而不包含b,这将导致这两个变量有不同的行为。

var a = 0
var b = 0
let closure = { [a] in
print(a, b)
}

a = 10
b = 10
closure()
// 打印“0 10”

如果捕获列表中的值是类类型,可以使用weakunowned来修饰它,闭包会分别用弱引用、无主引用来捕获该值:

myFunction { print(self.title) }                    // 隐式强引用捕获
myFunction { [self] in print(self.title) } // 显式强引用捕获
myFunction { [weak self] in print(self!.title) } // 弱引用捕获
myFunction { [unowned self] in print(self.title) } // 无主引用捕获

在捕获列表中,也可以将任意表达式的值绑定到一个常量上。该表达式会在闭包被创建时进行求值,闭包会按照制定的引用类型来捕获表达式的值:

// 以弱引用捕获 self.parent 并赋值给 parent
myFunction { [weak parent = self.parent] in print(parent!.title) }

解决闭包的循环强引用


在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无助引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。


使用规则

  • 在闭包和捕获的实例总是互相引用并且同时销毁时,将闭包内的捕获定义为无主引用

  • 相反,在被捕获的引用可能会变为nil,将闭包内的捕获定义为弱引用,弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在


注意



如果被捕获的实例绝对不会变为nil,应该使用无主引用,而不是弱引用。



闭包是引用类型


无论你将函数和闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用


逃逸闭包


当一个闭包作为参数传到一个函数中,但是这个闭包在函数之后才被执行,称该闭包从函数中逃逸


在参数名之前标注@escaping指明这个闭包是允许逃逸出这个函数。


一种能使闭包"逃逸"出函数的方法是,将这个闭包包存在一个函数外部定义的变量中。例子:很多异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。这种情况下,闭包需要"逃逸"出函数,因为闭包需要在函数返回之后被调用:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}

注意



将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self



自动闭包


自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印“Now serving Ewa!”

总结


Swift 的闭包有以下几个主要的知识点:


  1. 闭包表达式(Closure Expressions):闭包表达式是一种在简短的几行代码中完成自包含的功能代码块。比如数组的排序方法 sorted(by:)
  2. 尾随闭包(Trailing Closures):如果你需要将一个很长的闭包表达式作为一个函数的最后一个参数,使用尾随闭包是很有用的。尾随闭包是一个书写在函数或方法的括号之后的闭包表达式。
  3. 值捕获(Value Capturing):闭包可以在其定义的上下文中捕获和存储任何常量和变量的引用。这就是所谓的闭包的值捕获特性。
  4. 闭包是引用类型(Closures Are Reference Types):无论你将函数/方法或闭包赋值给一个常量还是变量,你实际上都是将引用赋值给了一个常量或变量。如果你对这个引用进行了修改,那么它将影响原始数据。
  5. 逃逸闭包(Escaping Closures):一个闭包可以“逃逸”出被定义的函数并在函数返回后被调用。逃逸闭包通常存储在定义了该闭包的函数的外部。
  6. 自动闭包(Autoclosures):自动闭包能让你延迟处理,因为代码段不会被执行直到你调用这个闭包。自动闭包很有用,用来包装那些需要被延迟执行的代码。

Swift 闭包和OC Block


相似点:


  1. 都是可以捕获和存储其所在上下文的变量和常量的引用的代码块。
  2. 都可以作为参数传递给函数或方法,或者作为函数或方法的返回值。
  3. 都可以在代码块中定义局部变量和常量。
  4. 都可以访问其被创建时所处的上下文环境。

区别:


  1. 语法:Swift 的闭包语法更简洁明了,使用大括号 {} 来定义闭包,而 Objective-C 的 Block 语法相对复杂,使用 ^ 符号和大括号 ^{} 来定义 Block。
  2. 内存管理:Objective-C 的 Block 对捕获的对象默认使用强引用,需要注意避免循环引用;而 Swift 的闭包对捕获的变量默认使用强引用,但通过使用捕获列表(capture list)可以实现对捕获变量的弱引用或无引用。
  3. 类型推断:Swift 的闭包对于参数和返回值的类型具有类型推断的能力,可以省略类型注解;而 Objective-C 的 Block 需要明确指定参数和返回值的类型。
  4. 逃逸闭包:Swift 可以将闭包标记为 @escaping,表示闭包可能会在函数返回之后才被调用;而 Objective-C 的 Block 默认是可以在函数返回后被调用的。

作者:palpitation97
链接:https://juejin.cn/post/7250756790239969340
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

盘点那些国际知名黑客(上篇)

iOS
电影中的黑客仅靠一部电脑就可以窃取别人的信息,利用自己高超的技术让公司甚至国家都胆战心惊。“黑客”原指热心于计算机技术、水平高超的电脑高手,但逐渐区分为黑帽、白帽、灰帽。这些术语源自美国流行文化的老式西部电影,其中主角戴白色或浅色帽子,反派戴黑色帽子。黑帽黑客...
继续阅读 »

电影中的黑客仅靠一部电脑就可以窃取别人的信息,利用自己高超的技术让公司甚至国家都胆战心惊。“黑客”原指热心于计算机技术、水平高超的电脑高手,但逐渐区分为黑帽、白帽、灰帽。这些术语源自美国流行文化的老式西部电影,其中主角戴白色或浅色帽子,反派戴黑色帽子。

  • 黑帽黑客以“利欲”为目标,通过破解、入侵去获取不法利益或发泄负面情绪。
    • 灰帽黑客以“昭告”为目标,透过破解、入侵炫耀自己所拥有的高超技术。
    • 白帽黑客以“改善”为目标,破解某个程序作出修改,透过入侵去提醒设备的系统管理者其安全漏洞,有时甚至主动予以修补。


白帽黑客大多是电脑安全公司的雇员,抑或响应招测单位的悬赏,通常是在合法的情况下攻击某系统,而黑帽黑客同时也被称作“Cracker”(溃客),打着黑客的旗帜做不光彩的事情。接下来我们为大家介绍一下世界上非常厉害的顶级黑客。


“互联网之子”亚伦·斯沃茨 



2013 年 1 月 11 日,年仅26 岁的互联网奇才亚伦斯沃茨自杀身亡。他的一生都在为互联网的信息自由而努力。亚伦·斯沃茨 被称作计算机天才、互联网时代的普罗米修斯。但在这些光环的背后,是美国政府为他定下的 13 项重罪指控和最高 35 年的监禁。



1986年亚伦出生在一个程序员之家。3岁学会编程,12岁创建了一个知识共享网站,叫做 The info,功能和维基百科一样,但比维基百科早了 5 年。15岁参与制订了CC协议。18岁入学斯坦福,20岁辍学创业与Reddit项目的两位创始人合伙开公司,并创建了Reddit网站。 Reddit在当时的影响力不断扩大,成为最受欢迎的网站之一。后来,雅伦卖掉Reddit网站,赚了 100 万美元,在他 20 岁那年成为百万富翁。



亚伦参与构建了RSS,这是博客时代的工具,能让用户订阅自己感兴趣的博客,当订阅更新的时候,用户会收到邮件提醒。彼时的亚伦沉浸在互联网程序世界的理想主义美梦里,他希望自己能像他的偶像万维网的发明人蒂姆·博纳斯·李那样,让互联网回归自由、共享的初心。亚伦对赚钱并不感兴趣,他的梦想是追求一个更宏大的目标——互联网知识的自由和共享。



一次机会,亚纶了解到一个名为PACER的网站,它是一个存放法庭电子记录的系统,每看一页里面的内容,联邦政府需要收取 8 美分的管理费用。这项业务每年能带给政府超过 100亿美元的收入。亚纶认为,这些联邦法庭记录的材料本就属于公众,应当免费向公众开放。于是他编写了一个程序,抓取了超过 2000 万页的PACER资料,并将它们投放到公共资源网上,供大家免费阅读,这一举动相当于直接减少了美国司法系统200万美元的收入,PACER也在巨大的舆论压力下逐渐免费。



亚伦有一个“开放图书馆”的梦想,他认为实体的图书馆限制了知识的传播,而互联网是连接书籍、读者、作者、纸张与思想最好的载体。他在08年发表的《开放获取游击队宣言》中写道:信息就是力量,但就像所有力量一样,有些人只想将其占为己有。世界上大多数的期刊都被类似Elsevier、JSTOR这样的巨头垄断,每阅读一篇文献都需要支付一定数量的费用。亚伦想帮助更多的人平等地享受这些知识。于是他通过自己高超的黑客技术,利用麻省理工学院的校园网络免费端口从JSTOR下载了 480 万篇论文,相当于整个文献数据库的80% 。



亚伦毫无意外地被警察逮捕,但由于并未用论文牟利,JSTOR放弃了对他的指控。但马萨诸塞州检察长坚持起诉雅伦违反了1986年的计算机欺诈与滥用法。若罪名成立,亚伦将面临35年的监禁和100万美元的巨额罚款。亚伦拒绝认罪他选择与美国政府斗争。在这期间,他积极参与到各种推动知识共享的运动中,传播他关于知识共享的理念。



2012 年9月 12 日,联邦检察官提出了一份替换起诉书,增加了电子欺诈、非经授权访问计算机等罪名,从原来的 4 项重罪指控变成了 13 项。2013 年1月,雅伦在布鲁克林的公寓中上吊自杀,结束了自己的生命。这一年,他26岁。他死后,超过5万人在白宫网站上请愿,要是起诉亚伦的检察官辞职,维基百科以黑屏为他悼念。



亚伦认为知识共享能提高全人类的智慧,信息共享、言论自由才是真正的平等。在他死后,黑客入侵了麻省理工官网,抗议这个被视为黑客起源地的学府对于亚伦的无所作为。麻省理工的标题页被改为亚伦在2008 年写下的《开放获取游击队宣言》宣言中鼓励每一个网络用户行动起来,阻止商人与政客将网络私有化。



2013 年3月,亚伦被追授詹姆斯麦迪逊奖,用以表彰他捍卫公众的知情权所作出的贡献。


“世界头号黑客”凯文·米特尼克



凯文·米特尼克曾说:“巡游五角大楼,登录克里姆林宫,进出全球所有计算机系统,摧垮全球金融秩序和重建新的世界格局,谁也阻挡不了我们的进攻,我们才是世界的主宰。”



如果说谁的人生像小说一样精彩,那一定当属凯文·米特尼克。他出生于美国洛杉矶,是第一个被美国联邦调查局通缉的黑客,号称“世界头号黑客”。



20世纪80年代,他因多次入侵美国联邦调查局的中央电脑系统等而被逮捕三次。米特尼克的所作所为与人们所熟知的犯罪不同,他所做的一切似乎都不是为了钱,他曾破坏了40多家的安全系统,只是为了表明他“有能力做到”。



2000年,米特改邪归正,成为了一名白帽黑客,成功创办了米特尼克安全咨询公司,专门世界500强企业做网络咨询工作。2023年7月16日去世,享年59岁。


“C语言之父”丹尼斯·里奇



“丹尼斯·里奇一点也不家喻户晓,但是如果你有一台显微镜,能在电脑里看到他的作品,你会发现里面到处都是他的作品。”



丹尼斯·里奇(Dennis Ritchie)是美国计算机科学家,被称为“C语言之父”“Unix之父”。20世纪60年代,丹尼斯·里奇和肯·汤普逊参与了贝尔实验室Multics系统的开发。在开发期间,肯·汤普逊开发了游戏【空间旅行】,但当时的系统不给力,游戏运行速度很慢。



然而不久之后贝尔实验室撤出了Multics计划,里奇和汤普逊利用一台旧的迷你计算机Digital PDP-7,1969年的圣诞节Unix系统诞生了。最初的Unix内核使用B语言编写,为了更好开发Unix,1973年,里奇以B语言为基础发展出C语言,在它的主体设计完成后,他和汤普森又用它完全重写了Unix。



随着计算机的发展,编程语言层出不穷,但无论如何翻涌,都无法改变C语言在编程界德高望重的地位,C++、Java、C#都是在C语言的基础上衍生出来的。而如今诸多流行的操作系统也是在Unix的基础上开发的,如Linux、MacOS甚至最流行的手机系统Android。


丹尼斯·里奇发明的C语言联合Unix操作系统,构建了当代计算机世界的钢筋水泥。正是因为C语言和Unix系统这两项成就,里奇成为了许多编程爱好者膜拜的对象。


“Linux之父”林纳斯·托瓦兹



“Given enough eyeballs,all bugs are shallow.”【很多双眼睛盯着的代码,bug无处藏身】


1991年Linus开发了Linux操作系统,在最初几年里,Linux并没有得到太多关注。但随着互联网的普及,如今的linux已经成为全球最受欢迎的操作系统之一,被广泛应用于服务器、移动设备、家庭电脑和超级计算机等领域。



Linux的诞生充满了偶然,林纳斯经常用他的终端仿真器去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统。这些在后来成为了Linux第一个内核的雏形,那时的他年仅21岁。



我们能够看到如今日渐壮大的Linux,但也不难发现,在成功的Linux背后,有着几十年如一日的持之以恒,有着对高质量代码的坚持,更是有着合作的。林纳斯没有建立组织,仅仅通过吸引全球数以万计的自由开发者免费贡献就完成了项目。Linux不仅仅是一个代码项目,也是一种互联网出现以后的新的协作方式——开源模式。


写在最后


现在国家很重视网络安全建设,网络安全已经成为了很多高校的一级学科,因此通过正常学习即可进入网络安全行业,大家一定要遵纪守法,效仿黑客们的行为做一些非法的黑客攻击行为,下期我们将继续为大家送上其他几位世界著名黑客的传奇故事,请大家保持关注哦。


作者:禅道程序猿
链接:https://juejin.cn/post/7273125596951478284
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

🔥🔥🔥996已明确违法,从此拒绝精神内耗!

之前一个禅道用户说,他在国外工作时主动加过两次班,然而被上司慰问了。上司特别严肃地跟他说:“请你不要再加班了,这让我很困扰。我们不加班,而且我无法向我的上司解释你为什么要加班,工作做不完可以明天做,工作只是你一天的一部分,利用好这8小时就可以了。” 对内卷严重...
继续阅读 »

之前一个禅道用户说,他在国外工作时主动加过两次班,然而被上司慰问了。上司特别严肃地跟他说:“请你不要再加班了,这让我很困扰。我们不加班,而且我无法向我的上司解释你为什么要加班,工作做不完可以明天做,工作只是你一天的一部分,利用好这8小时就可以了。”


对内卷严重的公司来说:一天干8小时怎么够?全天all in的状态才是我想要的。于是996疯狂盛行。


冷知识:“996”已严重违反法律规定。


早在2021年8月,最高法、人社部就曾联合发布超时加班典型案例,明确“工作时间为早9时至晚9时,每周工作6天”的内容,严重违反法律关于延长工作时间上限的规定,应认定为无效。


最近两会期间,全国政协委员蒋胜男也在提案中表示,应加强劳动法对劳动者的休息权保护。


由此,新的一波讨论已然来袭。


一、“996”带来了什么?



产品没有核心价值,缺乏核心竞争力,害怕落后于竞争激烈的市场……越来越多的管理者选择用加班、拉长工作时间来弥补技术创新的匮乏。


这种高强度的996工作制,侵占了我们的“充电”时间,甚至让我们丧失对新事物的接收力和思考能力;高强度的工作压力+长期的加班、熬夜、不规律饮食,给身体带来了沉重的负担;在忙碌了一周之后,感受到的是前所未有的迷茫与疲倦,精神内耗愈发严重


而对于企业来说,当员工沦为“执行工具”,原本的创新型发展却变成闭门造车,所以只能不停地加班、拉长工作时间,以产出更多的成果。长此以往,就形成了一种恶性循环。


在普遍“苦996久矣”的环境下,“8小时工作制”的推崇便显得尤为可贵。


二、“8小时工作制”从何而来?


8小时工作制,不应成为一个冷知识。《中华人民共和国劳动法》第三十六条规定:国家实行劳动者每日工作时间不超过8小时,平均每周工作时间不超过44小时的工时制度


8小时工作制的提出,要感谢来自英国的Robert Owen。1817年,他提出了“8小时工作制”,也就是将一天分成3等分,8小时工作、8小时娱乐、8小时休息。在当时一周普遍工作时间超过80个小时的情况下,这种要求简直是天方夜谭。


而8小时工作制得到推行,应归功于福特汽车品牌的创始人亨利·福特。1914年1月,福特公司宣布将员工的最低薪资从每天的2.34美元涨到5美元,工作时间减少至每天8小时。这项计划将会使福特公司多支付1000万美元。



在增加了员工薪资后,最直观的是员工流动率的下降。员工的稳定以及对操作的愈发熟练,增加了生产效率,从而降低成本、提高产量。最后,福特公司只用了两年时间,就将利润增加了一倍。


1926年,福特公司又宣布将员工的工作时间改为每周5天、每天8小时。亨利·福特用实际行动证明了增加工作收入、减少工作时间,对公司来说是可以实现正向创收的。


随后,8小时工作制才开始逐渐普及。随着Z时代的到来,更多新型职场状态也已经诞生。


液态职场早已到来,你准备好了吗?


三、液态职场是什么?



1)“3+2”混合办公模式


早在2022年,全国人大代表黄细花提交了建议,呼吁可推广“3+2”混合办公模式,允许员工每周可选择1-2天在家远程办公。黄细花还表示,推广“3+2”混合办公制,提高员工工作效率的同时,减轻年轻群体的生活压力,减少城市通勤压力。对女性员工而言,弹性的办公时间能让她们更好地平衡工作和生活。混合办公制对企业、员工和社会都将产生深远影响。


于是,不少企业开始了行动。携程推出了“3+2”混合办公模式的新政策:从 2022年3月起,允许员工每周三、周五在家远程办公。


2)四天半工作制


乐视也紧随其后,推出“四天半工作制”,每周三弹性工作半天。


3)“上4休3”的工作制


微软日本公司,也早在2019年8月曾宣布,公司开始试运行每周“上4休3”的工作制度,即每周五、六、日休息3天,周五所有办公室全部关闭。


不管是8小时工作制还是上4休3”,其实本质上都一样:都是为了迎合当下的现状,打破固有传统的工作模式,寻找更加多元化的新型职场状态,让员工能够充分休息,提升效率和创造力,也能节省企业开支,最终双方获益。


这世界变化太快了,上一秒还在“996”中疯狂内卷,下一秒就已经有先行者去探索更适合的工作节奏。液态职场时代已经到来,你准备好了吗?


四、提高工作效率,大胆对996说不!


作为打工人,不管是996还是8小时工作制,虽然都不是我们能决定的,但我们可以用法律来维护自己的权利,学会说“不”。利用好这8小时,发挥出自己的价值,提高自身的创新能力和效率,是为了更有底气的说“不”!这样才能保证企业与员工之间形成一个正向循环。如何利用好8小时?给大家分享几个提高工作效率的小技巧:

  1. 保持桌面整洁,减少其他事物对工作专注度的干扰;

  2. 巧用看板,可视化工作任务,便于进行任务管理;

  3. 排列优先级,按照任务的重要紧急程度,尽量避免并行多个任务;

  4. 随时记录工作中的创意和灵感

  5. 将重复、机械的工作自动化,解放双手;

  6. 定期复盘:不断改进与优化;

  7. 培养闭环思维:凡事有交代,件件有着落,事事有回音。


工作本应是我们热爱的样子。当我们还沉浸在无休止的工作与忙碌中,被疲惫、彷徨等负面情绪包围,开始精神内耗时,是时候明确拒绝996了!


作者:禅道程序猿
链接:https://juejin.cn/post/7217616698798096444
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

iOS实现宽度不同无限轮播图

iOS
背景 项目中需要实现一个不同宽度的图片的无限轮播图效果,而且每次滚动,只滚到下一个图片。由于业界实现的轮播图效果都是等宽图片,所以需要重新根据“以假乱真”的原理,设计一款不同宽度的轮播效果; 演示效果 底部是个collectionView,顶部盖了个透明的sc...
继续阅读 »

背景


项目中需要实现一个不同宽度的图片的无限轮播图效果,而且每次滚动,只滚到下一个图片。由于业界实现的轮播图效果都是等宽图片,所以需要重新根据“以假乱真”的原理,设计一款不同宽度的轮播效果;


演示效果


底部是个collectionView,顶部盖了个透明的scrollView,传入的数据源是:

NSArray *imageWidthArray = @[@(200), @(60), @(120)];



实现思路


  1. 传入一个存储图片宽度的数组,计算出屏幕可见的个数,比如下图,假如可见数为3个;

  2. 左、右两侧各有2个灰块,用于实现以假乱真的数据;(两侧各需生成的灰块数=屏幕可见数-1)

    • 比如当前看到123,左滑会滚到231,再左滑会滚到312,此时设置contentOffset,切到前面那个312;
    • 比如当前看到123,右滑会滚到312,再右滑会滚到231,此时设置contentOffset,切到后面那个231;
    1. 为了性能方面的考虑,使用的是collectionView;
    2. 关于每次滚动,只滚到下一个,实现方式则是在collectionView上面盖一个scrollView,设置其isPagingEnabled = YES; scrollView里面的页数和数据源保持一致(方便计算滚到哪个page);





完整的代码实现


Github Demo


ViewController:

#import "ViewController.h"
#import "MyCollectionViewCell.h"

#define padding 10.f
#define margin 16.f
#define scrollViewWidth (self.view.bounds.size.width - 2 * margin)
#define scrollViewHeight 200.f

@interface ViewController ()<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) UIScrollView *topScrollView;
@property (nonatomic, strong) UIPageControl *pageControl;
@property (nonatomic, strong) NSArray *imageWidthArray; // 用户传入,图片宽度数组
@property (nonatomic, assign) NSInteger canSeeViewCount; // 屏幕最多可见几个view
@property (nonatomic, strong) NSMutableArray *imageWidthMuArray;
@property (nonatomic, strong) NSMutableArray *imageContentOffsetXArray;
@property (nonatomic, strong) NSMutableArray *currentPageMuArray;

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupViewWithImageWidthArray:@[@(200), @(60), @(120)]];
//    [self setupViewWithImageWidthArray:@[@(150), @(80),@(60), @(120)]];
}

-(void)setupViewWithImageWidthArray:(NSArray *)imageWidthArray {
    // 根据机型宽度,计算屏幕可见数量
    self.canSeeViewCount = imageWidthArray.count;
    CGFloat checkWidth = 0;
    for (NSInteger i = 0; i < imageWidthArray.count; i ++) {
        checkWidth += [imageWidthArray[i] floatValue];
        if (checkWidth >= scrollViewWidth) {
            self.canSeeViewCount = i + 1;
        }
    }

    self.imageWidthArray = imageWidthArray;
    self.imageContentOffsetXArray = [NSMutableArray arrayWithCapacity:self.imageWidthArray.count];

    // 插入头尾数据(前后插入可见数-1个)、生成currentPageMuArray
    self.imageWidthMuArray = [NSMutableArray array];
    self.currentPageMuArray = [NSMutableArray array];
    for (NSInteger i = self.imageWidthArray.count - (self.canSeeViewCount - 1); i < self.imageWidthArray.count; i ++) {
        [self.imageWidthMuArray addObject:self.imageWidthArray[i]];
        [self.currentPageMuArray addObject:@(i)];
    }
    [self.imageWidthMuArray addObjectsFromArray:self.imageWidthArray];

    for (NSInteger i = 0; i < self.imageWidthArray.count; i ++) {
        [self.currentPageMuArray addObject:@(i)];
    }

    for (NSInteger i = 0; i < (self.canSeeViewCount - 1); i ++) {
        [self.imageWidthMuArray addObject:self.imageWidthArray[i]];
        [self.currentPageMuArray addObject:@(i)];
    }

    CGFloat collectionViewContentSizeWidth = 0;
    for (NSInteger i = 0; i < self.imageWidthMuArray.count; i ++) {
        CGFloat imageWidth = [self.imageWidthMuArray[i] floatValue];
        if ( i > 0) {
            collectionViewContentSizeWidth += padding;
        }
        [self.imageContentOffsetXArray addObject:@(collectionViewContentSizeWidth)];
        collectionViewContentSizeWidth += imageWidth;
    }

    // collectionView
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    flowLayout.minimumInteritemSpacing = padding;
    flowLayout.minimumLineSpacing = padding;

    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(margin, 100, scrollViewWidth, scrollViewHeight) collectionViewLayout:flowLayout];
    [collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:@"MyCollectionViewCell"];
    collectionView.dataSource = self;
    collectionView.delegate = self;
    collectionView.bounces = NO;
    collectionView.showsHorizontalScrollIndicator = NO;
    collectionView.backgroundColor = [UIColor brownColor];
    [self.view addSubview:collectionView];
    collectionView.contentSize = CGSizeMake(collectionViewContentSizeWidth, 0);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [collectionView setContentOffset:CGPointMake([self.imageContentOffsetXArray[self.canSeeViewCount - 1] floatValue], 0)];
    });
    self.collectionView = collectionView;

    // topScrollView
    UIScrollView *topScrollView = [[UIScrollView alloc] initWithFrame:collectionView.frame];
    topScrollView.showsHorizontalScrollIndicator = NO;
    [topScrollView setPagingEnabled:YES];
    topScrollView.backgroundColor = [UIColor clearColor];
    topScrollView.delegate = self;
    topScrollView.bounces = NO;
    [self.view addSubview:topScrollView];
    self.topScrollView = topScrollView;
    topScrollView.contentSize = CGSizeMake(self.imageWidthMuArray.count * scrollViewWidth, 0);
    [topScrollView setContentOffset:CGPointMake((self.canSeeViewCount - 1) * scrollViewWidth, 0)];

    // pageControl
    CGFloat pageControlHeight = 50.f;
    UIPageControl *pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(margin, 100 + scrollViewHeight-pageControlHeight, scrollViewWidth, pageControlHeight)];
    pageControl.numberOfPages = self.imageWidthArray.count;
    pageControl.currentPage = 0;
    [self.view addSubview:pageControl];
    self.pageControl = pageControl;
}

#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.collectionView) {
        return;
    }

    // 页面整数部分
    NSInteger floorPageIndex = floor(scrollView.contentOffset.x / scrollView.frame.size.width);

    // 小数部分
    CGFloat pageRate = scrollView.contentOffset.x / scrollView.frame.size.width - floor(scrollView.contentOffset.x / scrollView.frame.size.width);
    CGFloat imageContentOffsetX = [self.imageContentOffsetXArray[floorPageIndex] floatValue];
    CGFloat imageWidth = [self.imageWidthMuArray[floorPageIndex] floatValue];
    self.collectionView.contentOffset = CGPointMake(imageContentOffsetX + (imageWidth + 10.f) * pageRate, 0);
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSInteger rightIndex = (self.canSeeViewCount - 1) + (self.imageWidthArray.count) - 1;
    NSInteger leftIndex = (self.canSeeViewCount - 1) - 1;

    // 右边卡到尾时
    if (self.collectionView.contentOffset.x == [self.imageContentOffsetXArray[rightIndex] floatValue]) {
        [self.collectionView setContentOffset:CGPointMake([self.imageContentOffsetXArray[leftIndex] floatValue], 0)];
    }

    // 左边卡到头时
    else if (self.collectionView.contentOffset.x == 0) {
        [self.collectionView setContentOffset:CGPointMake([self.imageContentOffsetXArray[self.imageWidthArray.count] floatValue], 0)];
    }

    // 右边卡到尾时
    if (self.topScrollView.contentOffset.x == scrollViewWidth * rightIndex) {
        [self.topScrollView setContentOffset:CGPointMake(scrollViewWidth * leftIndex, 0)];
    }

    // 左边卡到头时
    if (self.topScrollView.contentOffset.x == 0) {
        [self.topScrollView setContentOffset:CGPointMake(scrollViewWidth * self.imageWidthArray.count, 0)];
    }

    // 设置currentPage
    NSInteger floorPageIndex = floor(scrollView.contentOffset.x / scrollView.frame.size.width);
    self.pageControl.currentPage = [self.currentPageMuArray[floorPageIndex] intValue];
}

#pragma mark - UICollectionViewDelegate, UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.imageWidthMuArray.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCollectionViewCell" forIndexPath:indexPath];
    cell.labelText = [NSString stringWithFormat:@"%.0f", [self.imageWidthMuArray[indexPath.item] floatValue]];
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake([self.imageWidthMuArray[indexPath.item] floatValue], scrollViewHeight);
}

@end

MyCollectionViewCell:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyCollectionViewCell : UICollectionViewCell

@property (nonatomic, copy) NSString *labelText;

@end

NS_ASSUME_NONNULL_END
#import "MyCollectionViewCell.h"
#import "Masonry.h"

@interface MyCollectionViewCell()

@property (nonatomic, strong) UILabel *label;

@end

@implementation MyCollectionViewCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupUI];
    }
    return self;
}

- (void)setupUI {
self.backgroundColor = [UIColor grayColor];
    UILabel *label = [[UILabel alloc] init];
    label.textAlignment = NSTextAlignmentCenter;
    label.font = [UIFont boldSystemFontOfSize:18];
    [self.contentView addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.contentView);
    }];
    self.label = label;
}

- (void)setLabelText:(NSString *)labelText {
    _labelText = labelText;
    self.label.text = labelText;
}

-(void)prepareForReuse {
    [super prepareForReuse];
    self.label.text = @"";
}
@end

作者:Wiley_Wan
链接:https://juejin.cn/post/7231443152212312123
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

环信FCM推送详细步骤

集成FCM推推送
准备的地址有 :https://firebase.google.com
1.firebase官网选择我们自己创建的项目

2.点到这个设置按键

3.我们打开到项目设置->常规 拉到最下面有一个“您的应用” 点击下载json文件,json文件的使用是客户端放在安卓项目的app目录下

4.首先环信需要的信息有 项目设置中-> 服务账号 生成新的私钥 生成的文件我们要上传到环信的管理后台证书部分(V1)

5.点击上传证书会选择你下载的文件,注意!! 名称是由你设置的项目名称的json文件 并不是 google-services.json
6.项目名称 是你的发送者ID 这个id 我们在firebase官网中的项目设置-〉常规 -〉您的项目->的项目编号就是您的SenderID 填写到环信官网即可 另外客户端的 google-services.json 这个文件 打开后 project number 也是SenderID

7.将我们下载好的 google-services.json 文件放到app的目录下 (文件获取可以反回步骤3 查看)

8.打开build的根目录添加 :
buildscript {
dependencies {
// classpath 'com.android.tools.build:gradle:7.2.2'
classpath 'com.google.gms:google-services:4.3.8'
}
}

9.build.gradle.app部分添加:
implementation platform('com.google.firebase:firebase-bom:28.4.1')
implementation 'com.google.firebase:firebase-messaging'

10.对应好appkey 以及我们的客户端初始化fcm的senderID

11.在登陆前 初始化以后 添加以下代码:
EMPushHelper.getInstance().setPushListener(new PushListener() {
@Override
public void onError(EMPushType pushType, long errorCode) {
EMLog.e("PushClient", "Push client occur a error: " + pushType + " - " + errorCode);
}

@Override
public boolean isSupportPush(EMPushType pushType, EMPushConfig pushConfig) {
if(pushType==EMPushType.FCM)
{
return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(MainActivity.this)
== ConnectionResult.SUCCESS;
}
return super.isSupportPush(pushType, pushConfig);
}
});

12.登陆成功后的第一个页面添加 :
if(GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(NewAcitivty.this) != ConnectionResult.SUCCESS) {
return;
}
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if (!task.isSuccessful()) {
EMLog.d("PushClient", "Fetching FCM registration token failed:"+task.getException());
return;
}
// 获取新的 FCM 注册 token
String token = task.getResult();
EMClient.getInstance().sendFCMTokenToServer(token);
}
});

13.清单文件注册sevices 主要是为了继承FCM的服务 必要操作!

添加代码: 重写onMessageReceived
收到消息后 就在这个方法中 自己调用 本地通知 因为fCM的推送只有唤醒
public class FireBaseservice extends FirebaseMessagingService {
@Override
public void onMessageReceived(@NonNull RemoteMessage message) {
super.onMessageReceived(message);
if (message.getData().size() > 0) {
String alter = message.getData().get("alter");
Log.d("", "onMessageReceived: " + alter);
}

}
@Override
public void onNewToken(@NonNull String token) {
Log.i("MessagingService", "onNewToken: " + token);
// 若要对该应用实例发送消息或管理服务端的应用订阅,将 FCM 注册 token 发送至你的应用服务器。
if(EMClient.getInstance().isSdkInited()) {
EMClient.getInstance().sendFCMTokenToServer(token);
}
}
}
14.准备测试 这个时候我们就要验证我们的成果了 首先要看自己登录到环信后 是否有绑定证书 借用环信的即时推送功能查看是否有绑定证书
这个时候看到登录了证书还是没有绑定上 那肯定是客户端出现问题了

15.检查错误 看到提示了com.xxxx.play 安装 这个是因为 你的设备没有打开 VPN 或者VPN不稳定,所以你首先要确定VPN打开并且 稳定 然后我们在重新登录测试

16.这个时候我们在借用即时推送查看 看看有没有绑定到环信 看到该字样就证明你的证书已经绑定上了 直接杀掉进程离线 测试离线推送,(一定要在清单文件注册的谷歌服务中 重新的onMessageReceived 中写入本地通知展示 不然fcm的推送只有唤醒)

升级Xcode 15后,出现大量Duplicate symbols问题的解决方案

升级到Xcode 15后,原先Xcode14可以编译的项目出现大量Duplicate symbols,且引用报错指向同一个路径(一般为Framework)下的同一个文件。经过查找相关资料,查到可通过在Xcode -> Target -> Build...
继续阅读 »

升级到Xcode 15后,原先Xcode14可以编译的项目出现大量Duplicate symbols,且引用报错指向同一个路径(一般为Framework)下的同一个文件。经过查找相关资料,查到可通过

在Xcode -> Target -> Build Setting -> Other Linker Flags 添加一行"-ld64"

即可解决该问题

原因是Xcode15采用了新的链接器(Linker),被称作“ld_prime”。新的连接器有诸多好处,尤其是对合并库的支持方面,具体可以查看WWDC 2023 SESSION 10268 Meet mergeable libraries.。然而,链接器的升级可能会出现不兼容老库的情况出现。遇到这种情况,可以通过恢复旧的连接器来解决这个问题。从Other Linker Flags添加"-ld64"后,就会覆盖Xcode编译时选择的链接器,因此可以正常访问。

收起阅读 »

iOS 开发:分享一个可以提高开发效率的技巧

iOS
前言 在日常的开发中,要想提高开发效率,重要的是要集中精力,今天来讲一个我自己日常在用的方法,我认为提高了我的开发效率,大家也可以尝试一下。 我们做开发都很讨厌写代码的过程中被打断,可能你在找一个 bug,或者在做一个很难的需求,好不容易有了思路,结果一被打断...
继续阅读 »

前言


在日常的开发中,要想提高开发效率,重要的是要集中精力,今天来讲一个我自己日常在用的方法,我认为提高了我的开发效率,大家也可以尝试一下。


我们做开发都很讨厌写代码的过程中被打断,可能你在找一个 bug,或者在做一个很难的需求,好不容易有了思路,结果一被打断,思路全忘了。所以在进入开发前,我会尽可能的把可能打断我的的因素屏蔽掉。比如我会关掉社交软件(尤其是微信),关掉软件推送。然后每过两个小时左右上一次社交软件,集中去处理消息,处理完了退掉继续工作


使用 Xcode 的时候我会开启全屏模式,这可以帮助我集中注意力,而不会分散其他应用程序的注意力,接下来讲讲如何把 Xcode 和模拟器同时进入全屏模式。


Xcode 和模拟器并行的全屏模式


最终的全屏模式如图所示,整个屏幕只有左边是 Xcode,右边是模拟器(当然你也可以调整顺序)。



这是一个能让你完全专注的环境,不被顶部的菜单栏和底部的程序坞栏内容分散注意力。


设置全屏只需这样操作:

  1. 打开 Xcode 和模拟器

  2. 点击 Xcode 左上角第三个按钮,开启全屏,或者使用快捷键 control + command + F

  3. 点击快捷键 control + ⬆️上箭头 打开程序控制,或者使用触控板上的四个手指向上滑动。

  4. 然后将你的模拟器拖入到屏幕顶部 Xcode 所在的窗口中,当拖动到窗口左侧或者右侧时,会显示一个加号,放置在上面即可

  5. 最后点击 Xcode 和模拟器所在的窗口就完成了



最后


保持专注是写好代码和提高效率的一种途径,我见过一些程序员一边写代码,一边还在用手机刷剧,这种写出的代码质量不可能很高,一心二用的开发效率也是很低的。


保持专注本身就是一种技能,刚开始你可能会觉得不习惯(没有微信消息、没有热点资讯),但当你适应了之后,你就会发现你的代码质量和效率都有一定提升,而省下来的时间足以做更多的事情了。


而且我发现每两个小时集中处理一次消息的策略还可以让处理信息的质量变高,比如以前在写代码的时候来了一条微信消息,你点开之后发现不是很重要,可以稍后再回,就先去写代码了,但是当你写完代码时可能已经忘记了回微信消息的事情(因为这条消息已经是已读状态了)。而集中处理可以把未读消息集中处理掉,不容易遗漏。


作者:iOS新知
链接:https://juejin.cn/post/7279641901526188086
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

月入五万的西二旗人教你如何活得像月薪五千

在网上看到一篇神奇的文章,确实挺发人深思了的,蜜蜂最为珍惜的是蜂蜜,殊不知自身才是养蜂人的宝贵财产 昨天,有个朋友转给我一篇文章。《月入五万的西二旗人教你如何活得像月薪五千》 这篇文章大概写的是西二旗程序员们吃饭穿衣都会选最便宜的,然后把所有剩余的钱都拿来买房...
继续阅读 »

在网上看到一篇神奇的文章,确实挺发人深思了的,蜜蜂最为珍惜的是蜂蜜,殊不知自身才是养蜂人的宝贵财产


昨天,有个朋友转给我一篇文章。《月入五万的西二旗人教你如何活得像月薪五千》 这篇文章大概写的是西二旗程序员们吃饭穿衣都会选最便宜的,然后把所有剩余的钱都拿来买房。 然后朋友也问我了,说在房价下跌的2023年,你怎么看。


这让我想起了我小时候,家里的阳台上,曾经养过的一箱蜜蜂。因为我家靠近公园,有足够的花可以作为蜜源。所以在我的记忆中,家里从来不缺蜂蜜吃,因为家里人会定期戴着防护面罩开箱,把蜜取走。


「不过我们每次取蜜,都会留下一些蜂蜜给蜜蜂过冬,如果收割得太狠,蜜蜂活不下去,就没有长久的蜂蜜吃了。不能涸泽而渔」。 小学的语文课本里,一直在歌颂蜜蜂的勤劳,可我一直有一个疑问,是不是正因为蜜蜂的勤劳,才使得它们变成了我们的绝佳收割对象。


也许,在 「蜜蜂的认知中,蜂箱蜂巢就是他们最大的资产」,但在更高一维度的养蜂人眼中,把蜂箱视为最大资产的 「蜜蜂,才是养蜂人最大的资产」


所以呢?


生活中极尽节俭,并把房子视为最大资产而背负房贷的西二旗人,会不会是更高维度的操盘手眼中的最大资产呢?


一、操盘手的鬼牌


一个年薪百万的西二旗程序员,交掉社保个税后,到手大概70万,然后公司还要额外缴纳十几万的社保。
也就是,一个西二旗程序员每创造110多万的财富,在第一次分配环节,大概还能剩70万。然后,因为他们吃饭穿衣都极为节省,所以这70万,可能又有五六十万投入了楼市。自己还剩下十几万用于生活。


投入楼市的这五六十万,可能有40多万作为土地出让金交给了操盘手,剩下付给了开发商和上下游供应商。


这么看起来,似乎程序员每赚100块钱,就有80多块以社保个税土地出让金等方式回流到了操盘手的手中。自己只剩下十几块用于生活。


但这里其实是有个问题的,西二旗人可以不买新房的。如果他向老北京人买二手房,他的这笔巨大支出,不就回流到了老北京人手中,而没有回到操盘手的手中吗?


我们来看看这个问题,是通过怎样的步骤,被操盘手解决的。


第一步,零成本选址


操盘手首先要选一块只有很少居民的土地,最好土地上没有老北京人的房子。一块荒地那就是最好了,这样就能实现零拆迁成本征用所有土地。 我们看到,北京西二旗,上海张江唐镇,杭州未科,成都天府新区,几十年前,可能都是比较荒的,也几乎都是这个套路。


第二步,引入科技公司入驻


有花才能引来蜜蜂,有工作机会才能引来年轻人。所以如果能有一些政策优惠啥的,把科技大企业引来入驻,也就等同于,引来了大批期待高薪工作的年轻人。 于是,北京西二旗后厂村路成了程序员宇宙中心,上海张江唐镇成为高科技园区。杭州未科变身未来科技城。。。


第三步,开始售卖科技公司周边的土地


当年轻人开始在科技公司上班,就会就近选择可购买的房子。但附近所有的土地,都在操盘手的手中。所以,操盘手拥有绝对的定价权。于是,年轻人以未来的收入为背书,借债购买房产,支付房产的土地出让金。并开始定期还贷。


所以,当西二旗人把西二旗的房子视为他们最大资产的时候,他们不知道的是,房子并不是最大资产,「背上债务的他们,才是别人眼中的最大资产」


通过债务的跨时间周期交易,他们把每年收入的80%以上,以社保个税土地出让金的形式交了出去。但我始终有个顾忌,80%,这样的上交比例是不是太高了。


如果把80%降为50%,也许他们就不需要996,也可以像欧洲人那样去海边晒太阳,时间多了,生育率也会更高。 目前京沪的总和生育率,已是0.7,不但是全国最低,更是全球最低。


如果涸泽而渔的话,会不会生育率提升不起来呢? 但我似乎又发现了一个隐藏的解法,对于蜜蜂我们不能涸泽而渔,「但对于西二旗和张江程序员,其实是可以涸泽而渔的」


二、谁是蜂王,谁是工蜂


在一个蜂群巢穴,是有着明确的分工的。 一个巢穴的蜜蜂分为三种,蜂王,雄蜂,和工蜂。 工蜂是雌蜂但无生育能力,只负责采蜜工作和照顾小蜜蜂。 蜂王不采蜜,只接受工蜂的养料,专职生小蜜蜂。 雄蜂也不采蜜,唯一的工作就是,和蜂王交配。


也就是说,让每个蜂种,都只从事自己最擅长的工作。 「这似乎给了我一些启示」。 虽然京沪的总和生育率已经降到了0.7,是全球最低。但这并不可怕,其实是有解法的。


我们来做个战棋推演。 一个家庭的分工,夫妻当中赚钱多的那个去赚钱,赚钱少的在家照顾孩子,会让这个家庭的效率最大化。 那么,提升全国的生育率,我们如果仅从效率最大化的角度去考虑,也会有两个方向。



  • 方向一,用最少的钱,激励出最多的生育。


从这个方向看,显然,钱应该花在三四线城市。给一线城市居民补贴50万,可能人家也不愿意生,毕竟房价生活成本高。但如果是四线城市,可能给20万,人家就愿意生了。毕竟养育成本低。 所以,基于花钱花在刀刃上的原则,「补贴三四线城市,其拉动生育效果会明显好于一线城市」。补贴一线城市一个孩子的钱,在四线城市可以补贴好几个孩子了。



  • 方向二,激励同样生育成果的前提下,花费最小的代价。


从这个角度,如果一个985高学历,年薪百万的女性,辞职生二胎照顾孩子,每年会损失百万财富的创造。但如果是一个大专学历,年薪5万的女性辞职生二胎照顾孩子,每年只损失5万财富的创造。 也就是说,达成同样生育数量的情况下,代价是完全不同的。


当然,从人文角度,985女当然和大专女享有同等生育权。从个人角度自主生育的话,那当然都没问题,盈亏反正也是自负。但如果说要操盘手额外花钱激励生育的话,从全国总盘子的效率角度考虑,激励大专女,会代价更小。 那如果操盘手只从效率最大化的角度考虑,很显然,应该让一线城市高学历中产尽可能努力工作,并通过 「税收或买房形成的支付转移」,转移到三四线城市去补贴育龄女性生育。等三四线孩子长大,通过高考选拔后,再进入一线城市,开始下一次循环。 从这个角度出发,很明显, 北京西二旗或上海张江的程序员,贡献蜂蜜,低生育率,是工蜂; 三四线城市多子女家庭,获取转移支付的蜂蜜,高生育率,是蜂王。


蜂王的子女长大后,再去一线城市进入新的一次循环。从而一线城市低生育率问题可解。 而现在的真实情况也确实是如此,比如贵州的总和生育率,就是上海的大约三倍。 有人可能会问,蜂王子女长大后去一线,能那么容易留下来么? 答案是,容易的! 因为今天不容易不代表未来不容易,万物皆周期! 按上海如今0.7的总和生育率,每过一代,就会损失2/3的人口。两代过后,90%的人口就没了。


这时候,是急切需要蜂王的后代,来上海补充年轻劳动力的。 我记得20年前,上海还有一些教上海话的电视节目,而今天几乎绝迹。既然两代之后,上海人口就损失90%,那自然上海也就会变成一个完全的普通话城市。


三、北京西二旗和上海张江男的终极宿命


最后一个问题,西二旗程序员,为啥心甘情愿在吃穿上拼命节省,而把大笔的钱投入楼市呢?


答案是,他们认为房子是核心资产


但问题在于,任何资产,或者说财富,其本质,都是对他人劳动的索取权。也就是说,世间的一切资产,不论是房子,股票,货币,黄金,它最终要能兑换成人的劳动,才有意义。


可问题就在于,2020年之后的生育率断崖式下跌了。未来所有的人,都会盯着这仅有的少数年轻人的劳动价值。 这其中,当然也包括操盘手。毕竟操盘手要负责老人养老金,公务员工资,义务教育等一系列花钱的地方。


现在西二旗人每年收入的80%,切切实实通过各种渠道给出去了,然后换来了一套西二旗的大房子。可30年后,如果西二旗人要用这套房子去换取未来年轻人同样的劳动时,操盘手能让他们得逞吗? 操盘手会不会和今天一样,同样划出一块荒地,然后引入30年后的风口科技公司(不知道会不会是超导,人工智能这些,还是更超前的公司),然后把年轻人引到新的地块呢?毕竟只有这样,才能最大化虹吸未来年轻人的劳动价值。


毕竟蜂巢不是资产,采蜜的蜜蜂,才是操盘手最大的资产。 而30年后,目前人口结构处于青壮年期的西二旗,张江,会不会自然衰老为一个以六七十岁年龄结构为主的老龄化社区呢?


如果一个社区,居民都变成了中老年,即便没有操盘手号召,企业出于自身招聘的考虑,也要搬走了。至于搬去哪里,那自然要看操盘手要把年轻人引向哪里。 如果一个社区,没有企业和工作机会,住的都是中老年,那么必然就不存在接盘力量。


这一点,似乎细思极恐。 原住民年轻时花大力气努力购买的房子,最后会变成一个笑话吗? 如果真是如此,那么该社区原住民的终极悲惨宿命,也就是必然的结局了


四、后记


本文无意得罪张江和西二旗的程序员,因为文中所说的逻辑,其实适用于所有在科技新区安家的一二线城市中产。


但因为我自己是一个前淘宝的程序员。想想还是自嘲下自己这个群体吧。


不过确实能反映当下一些问题引发一些思考,当然还是要保持积乐观的生活态度,想到了学生时代 学习的 普希金的一首诗《假如生活欺骗了你》


「假如生活欺骗了你,」


「不要悲伤,不要心急!」


「忧郁的日子里须要镇静:」


「相信吧,快乐的日子将会来临!」


「心儿永远向往着未来;」


「现在却常是忧郁。」


「一切都是瞬息,一切都将会过去;」


「而那过去了的,就会成为亲切的怀恋。」


作者:Android茶话会
来源:juejin.cn/post/7268975896370937893
收起阅读 »

为什么我们总是被赶着走

最近发生了一些事情,让shigen不禁的思考:为什么我们总是被各种事情赶着走。 一 第一件事情就是工作上的任务,接触的是一个老系统ERP,听说是2018年就在线上运行的,现在出现问题了,需要我去修改一下。在这里,我需要记录一下技术背景: ERP系统背景 后端...
继续阅读 »

最近发生了一些事情,让shigen不禁的思考:为什么我们总是被各种事情赶着走。



第一件事情就是工作上的任务,接触的是一个老系统ERP,听说是2018年就在线上运行的,现在出现问题了,需要我去修改一下。在这里,我需要记录一下技术背景:



ERP系统背景

后端采用的是jfinal框架,让我觉得很奇葩的地方有:



  • 接受前端的参数采用的HashMap封装,意味着前端字段传递的值可以为字符串、数字(float double)

  • 仅仅一个金额,可以有多种形式:1111.001,1,111.001

  • 格式化 1.00000100 小数点保存8位,这样的显示被骂了

  • 数据库采用的是oracle,jfinal的ORM工具可以采取任何的类型存入数据表的字段里,我就遇到了‘1.1111’字符串存入到定义为double的字段中

  • 原来的设计者存储金额、数量全部采用 flaot、double,凭空出现0.0000000000000001的小数,导致数量金额对不上

  • 小数位0.00000000001 会在前端显示成1-e10,直接在sql上格式化

  • sql动辄几百行,上千行,各种连表

  • sql还会连接字典表,显示某个值代表的含义

  • ……


前端不知道啥框架,接近于jquery+原生的js



  • 每改一段代码,都需要重启后端服务

  • 各种代码冗余

  • 后端打包一次40分钟+

  • ……


最关键的是:所有的需求口头说,我也是第一次接触,一次需求没理解,被运维的在办公室大声批评:你让用户怎么想?



后来,需求本来要半个月完成,拖了一个月才勉强结束。一次快下班的时候出现了问题,我没有加班,也因为遇到了问题没人帮忙。第二天问进度,没进展,领导叫去看会,说态度不好。后来换组了……



第二件事情就是我的公众号更新问题,我在八月份的时候个自己定了一个目标:公众号不停更。到最近一段时间发现:很难保持每天更新的需求了。因为我接触到的技巧很少,每篇文章的成本也很大。就拿我的某个需求为例,我需要先把代码写出来,测试完成之后再去写文章,这整个过程最低也需要两个小时的时间。成本很大,所以我有一次很难定顶住这个压力,推荐了往期的文章。


我也经常关注一些技术类的博客,看他们写的文章发现部分的博客都是互相抄袭的,很难保持高质量。更多的是在贩卖焦虑,打广告。


我希望我的每一篇文章都是有意义的,都是原创的、有价值的。所以,我也在陷入了矛盾中,成本这么大,我需要改变一下更新的节奏吗?



最后一件事情就是:我感冒了。


事情是这样的,一连几天没有去跑步了,家里的健腹轮也很少去练了,除了每天骑行了5公里外,我基本没有啥运动量。我以为我吃点维生素B、维生素C我的体质就会好一点,大错特错了。


周一发现嗓子有点干痒疼,晚上还加了班,睡觉的时候已经是凌晨一点了。周二就头很晕、带一点发热的症状,我赶紧下午去医院,在前台测了一下体温,直接烧到了28.4摄氏度。血常规检测发现是病毒性感染,买了两盒药回来了。下午一直在睡觉,睡到了十一点。


也在想:难道我的体质真的这么差吗?如果我坚持那几天戴口罩,坚持运动会不会好一些。我想到了我的拖延症。


我的dock栏永远是满的,各种软件经常打开着,Java、数据库,总是有很多的事情要去做,很忙的样子,最后发现没时间去运动了。一次健腹轮的运动不到十分钟,我都没有去行动。



这次的感冒,让我更加的重视起我的健康了,也让我觉得我丧失了主动性,总是被生活赶着走。


所以,提到了这么多,涉及到了任务的规划、任务中的可变因素……我觉得除了计划之外,更多的是需要保持热爱。不仅仅是热爱生活、热爱运动、热爱事业,更是热爱自己拥有的一切,因为:爱你所爱,即使所爱譬如朝露


作者:shigen01
来源:juejin.cn/post/7280740613891981331
收起阅读 »

智能门锁临时密码的简单实现~~

引子 话说新房子装修,安装了遥遥领先智能门锁PRO,最近到了家具进场的阶段。 某日,接到一通电话:“哥,你现在家里有人吗?你的书桌到了。” 原来是快递小哥,我回复他:“家里没人,但是有智能锁,嗯,因为临时密码有时间限制,等下到了再给我回下电话,我把临时密码给你...
继续阅读 »

引子


话说新房子装修,安装了遥遥领先智能门锁PRO,最近到了家具进场的阶段。


某日,接到一通电话:“哥,你现在家里有人吗?你的书桌到了。”


原来是快递小哥,我回复他:“家里没人,但是有智能锁,嗯,因为临时密码有时间限制,等下到了再给我回下电话,我把临时密码给你。”


“好嘞,那到时候联系”


挂断电话,我随手打开手机上的花粉生活APP,但是感觉有点不对劲,我去,设备咋都离线了(后来发现是网络欠费)?我顿时虎躯一震,脑海中浮现了快递小哥到了后发现自己白跑一趟,带着满头大汗、气喘吁吁并且嘴里一顿C语言输出的尴尬场景...


但是我惊喜的发现,门锁的卡片虽然离线但还可以正常进入,我抱着试一试的心态点进去,临时密码竟然可以正常生成,真牛!


于是我点击了生成临时密码...


电话又响起:“哥我到了,把密码给我吧”


我将临时密码给小哥开了门,一切顺利...




实现


这是前段时间亲身经历的一件事,原本以为智能门锁临时密码的功能需要网络支持,服务器生成临时密码给用户,同时下发到门锁里面。现在发现,并不需要门锁联网也可以执行密码验证的操作。
脑海中思考了下,临时密码离线验证这个功能可能是类似这样实现的:



  • 门锁端和服务器端采用相同的规则生成临时密码,并且密码生成规则里面包含了时间这个因素

  • 用户请求临时密码,服务端按照规则生成临时密码返回给用户

  • 用户输入临时密码解锁,门锁按照同样的规则进行校验
    以上实现是一个直觉性的思考,实际编码落地根据不同的需求会有更多的考虑,以我在使用的遥遥领先牌智能门锁Pro为例,下面来做一个简单的实现...


首先,让来看看这款门锁的临时密码有哪些限制条件:


limit12.png


lim22.png


限制条件有:



  • 单个密码有效期为30分钟

  • 有效期内只能使用一次

  • 一分钟内只能添加一个临时密码


根据这些限制条件和前面的思考,密码生成规则可以这样设置:



  • 拼接产品序列号+当前时间字符串,获取拼接后字符串的hashcode,然后对1000000(百万)取余,得到6位数字作为临时密码。并且时间字符串按照yyyy-MM-dd HH:mm 格式,精确到分钟

  • 加入产品序列号的原因是为了让不同门锁在相同时间产生不同的密码,如果只以时间为变量肯定是不安全的

  • 由于门锁生成的限制条件里面约定了一分钟只能添加一个临时密码,因此时间变量也精确到分钟,保证每分钟的临时密码不同,分钟内相同。


然后是实现思路:



  • 用户请求服务端,服务端根据密码生成规则返回一个临时密码

  • 快递小哥拿着临时密码在门锁现场输入

  • 门锁按照临时密码输入的时间点,计算时间点前30分内每一分钟对应的密码,30分钟对应30个临时密码。为什么是30分钟?因为密码30分钟内有效

  • 门锁将快递小哥输入的密码与生成的30个密码进行一一比对,如果有匹配的密码,说明临时密码有效

  • 将输入的临时密码缓存,每次输入密码时都要去缓存里面判断临时密码是否在30分钟内使用过,如果使用过就不能开锁。为什么要判断是否30分钟内使用过?因为有效期内只能使用一次




有了以上思路,下面代码的编写工作就比较简单了,开整...


首先创建三个类:OtherTerminal、SmartLock、PasswordUtils 分别,表示其他可获取密码的终端、门锁以及跟密码相关的工具类


首先是OtherTerminal类,相当于可获取密码的终端,例如我们的手机或者平板,主要功能是调用PasswordUtils工具类根据门锁的序列号和当前时间来获取有效临时密码。



public class OtherTerminal {
private final static String serialNumber = "XiaoHuaSmartLock001";
public static void main(String[] args) {
System.out.println("当前开锁密码:"+PasswordUtils.generate(serialNumber, PasswordUtils.localDateTimeToStr(LocalDateTime.now())));
}
}


接着是SmartLock类


SmartLock的main方法里面等待控制台的输入,并对输入的密码进行验证。验证调用了verify方法。


verify方法的执行逻辑:调用PasswordUtils工具类,获取过去30分钟内每分钟对应的临时密码,判断输入的密码是否在这些临时密码当中。如果存在说明临时密码有效,还需对当前密码在过去30分钟内是否使用进行判断,保证密码只能使用一次。这个判断是通过调用PasswordUtils工具类的getAndSet方法实现的。


如果认证成功,则开锁。否则开锁失败。


// 智能门锁
public class SmartLock {

private final static String serialNumber = "XiaoHuaSmartLock001";
private final static Integer expirationTime = 30;


public static void main(String[] args) {
// 步骤:首先生成过去30分钟内的所有数字

Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int password = scanner.nextInt();
if (verify(password)) {
System.out.println("开锁成功,当前时间:" + LocalDateTime.now());
} else {
System.out.println("开锁失败,当前时间:" + LocalDateTime.now());
}
}
scanner.close();

}

private static boolean verify(Integer inputPassword) {
// 获取当前时间点以前30分钟内的所有密码
LocalDateTime now = LocalDateTime.now();
LocalDateTime validityPeriod = now.minusMinutes(expirationTime);
List<Integer> validityPeriodPasswords = new ArrayList<>();

while (validityPeriod.isBefore(now.plusMinutes(1L))) {
validityPeriodPasswords.add(PasswordUtils.generate(serialNumber, PasswordUtils.localDateTimeToStr(validityPeriod)));
validityPeriod = validityPeriod.plusMinutes(1L);
}
System.out.println(validityPeriodPasswords);
return validityPeriodPasswords.contains(inputPassword) && PasswordUtils.getAndSet(inputPassword);
}
}

再来看下PasswordUtils工具类,这个类内容较多,分步解释:
首先是生成6位临时密码的generate方法,比较简单。但是这样生成的密码不能以0开头,是缺点!


/**
* 生成一个密码
*
* @return 返回一个六位正整数
*/

public static Integer generate(String serialNumber, String time) {
String toHash = time + serialNumber;
return Math.abs(toHash.hashCode() % 1000000);
}

接着是一个格式化时间的方法,将时间格式化为:yyyy-MM-dd HH:mm。精确到分钟,generate方法的第二个参数time需要调用此方法来保证时间以分钟为单位,这样分钟内生成的密码都是相同的


public static String localDateTimeToStr(LocalDateTime localDateTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return formatter.format(localDateTime);
}

最后是门锁对临时密码的管理:



  • 临时密码存储在一个map对象中:usedPasswordMap

  • 有一个标记对象clearTag用于标记是否应当对usedPasswordMap进行清理操作,用于清理已过期的临时密码

  • 临时密码存在时间大于30分钟,判断为已过期


下面是临时密码过期判断和过期清理的方法


/**
* @param current 当前时间
* @param compare 比较时间
* @return 是否过期
*/

private static boolean expired(long current, long compare) {
Instant endInstant = Instant.ofEpochMilli(current);
LocalDateTime end = LocalDateTime.ofInstant(endInstant, ZoneId.systemDefault());
Instant beginInstant = Instant.ofEpochMilli(compare);
LocalDateTime begin = LocalDateTime.ofInstant(beginInstant, ZoneId.systemDefault());

Duration duration = Duration.between(begin, end);
long actualInterval = switch (PasswordUtils.expirationUnit) {
case SECONDS -> duration.toSeconds();
case MINUTES -> duration.toMinutes();
case HOURS -> duration.toHours();
case DAYS -> duration.toDays();
default -> throw new IllegalArgumentException("输入时间类型不支持");
};
return actualInterval >= (long) PasswordUtils.expirationTime;
}

/**
* 清理过期的密码
*/

private static void clearExpired() {
Iterator<Map.Entry<Integer, Long>> iterator = usedPasswordMap.entrySet().iterator();
Long currentTimestamp = System.currentTimeMillis();
while (iterator.hasNext()) {
Map.Entry<Integer, Long> item = iterator.next();
if (expired(currentTimestamp, item.getValue())) {
iterator.remove();
}
}
}

getAndSet方法:



  • 首先判断是否达到了清理阈值,从而执行是否清理的操作,用于节省资源消耗

  • 从usedPasswordMap中获取当前输入密码是否存在,如果不存在说明密码未使用过,则将当前密码设置到map里面并返回true,否则还要进行进一步的判断,因为可能存在历史密码但是已过期和当前密码重复的情况

  • 若usedPasswordMap中存在当前密码,调用expired方法,如果历史密码过期了说明当前密码有效,并刷新时间戳,否则说明有效期内当前密码已经使用过一次


/**
*
* @param password
* @return false说明密码已经使用过,true则表示密码可以使用
*/

public static boolean getAndSet(Integer password) {
// usedPasswordMap存储的过期密码可能会越来越多,需要定期清理
if (clearTag > clearThreshold) {
if (!usedPasswordMap.isEmpty()) {
clearExpired();
}
clearTag = 0;
}
clearTag++;
Long usedPasswordTimestamp = usedPasswordMap.get(password);
Long currentTimestamp = System.currentTimeMillis();
if (ObjectUtils.isEmpty(usedPasswordTimestamp)) {
usedPasswordMap.put(password, currentTimestamp);
return true;
}
// 到了这里说明密码已经使用过(有效期内,或之前),若使用时间距今在有效期内,说明当期已经使用过,否则是以前使用的
if (expired(currentTimestamp, usedPasswordTimestamp)) {
usedPasswordMap.put(password, currentTimestamp);
System.out.println("密码虽然已使用,但为历史使用,因此当前密码有效");
return true;
}
System.out.println("密码有效期内已使用一次");
return false;
}



验证


我将门锁程序部署到我的服务器上面,并运行。随便输入一个数字,例如123456,返回开锁失败。


image.png


然后本地运行OtherTerminal类获取临时密码:974971


image.png
再去门锁上验证试试:开锁成功!


image.png


最后完整的PasswordUtil工具类的代码贴在这里:


// 密码工具类

public class PasswordUtils {
private static Map<Integer, Long> usedPasswordMap = new HashMap<>();
private final static Integer expirationTime = 30;
private final static TimeUnit expirationUnit = TimeUnit.MINUTES;
private final static Integer clearThreshold = 30;
private static Integer clearTag = 0;

/**
* 获取code状态,并设置到使用code里面
*
* @param password
* @return false说明密码已经使用过,true则表示密码可以使用
*/

public static boolean getAndSet(Integer password) {
// usedPasswordMap存储的过期密码可能会越来越多,需要定期清理
if (clearTag > clearThreshold) {
if (!usedPasswordMap.isEmpty()) {
clearExpired();
}
clearTag = 0;
}
clearTag++;
Long usedPasswordTimestamp = usedPasswordMap.get(password);
Long currentTimestamp = System.currentTimeMillis();
if (ObjectUtils.isEmpty(usedPasswordTimestamp)) {
usedPasswordMap.put(password, currentTimestamp);
return true;
}
// 到了这里说明密码已经使用过(有效期内,或之前),若使用时间距今在有效期内,说明当期已经使用过,否则是以前使用的
if (expired(currentTimestamp, usedPasswordTimestamp)) {
usedPasswordMap.put(password, currentTimestamp);
System.out.println("密码虽然已使用,但为历史使用,因此当前密码有效");
return true;
}
System.out.println("密码有效期内已使用一次");
return false;
}


/**
* @param current 当前时间
* @param compare 比较时间
* @return 是否过期
*/

private static boolean expired(long current, long compare) {
Instant endInstant = Instant.ofEpochMilli(current);
LocalDateTime end = LocalDateTime.ofInstant(endInstant, ZoneId.systemDefault());
Instant beginInstant = Instant.ofEpochMilli(compare);
LocalDateTime begin = LocalDateTime.ofInstant(beginInstant, ZoneId.systemDefault());

Duration duration = Duration.between(begin, end);
long actualInterval;
switch (PasswordUtils.expirationUnit) {
case SECONDS:
actualInterval = duration.toSeconds();
break;
case MINUTES:
actualInterval = duration.toMinutes();
break;
case HOURS:
actualInterval = duration.toHours();
break;
case DAYS:
actualInterval = duration.toDays();
break;
default:
throw new IllegalArgumentException("输入时间类型不支持");
}
return actualInterval >= (long) PasswordUtils.expirationTime;
}

/**
* 清理过期的密码
*/

private static void clearExpired() {
Iterator<Map.Entry<Integer, Long>> iterator = usedPasswordMap.entrySet().iterator();
Long currentTimestamp = System.currentTimeMillis();
while (iterator.hasNext()) {
Map.Entry<Integer, Long> item = iterator.next();
if (expired(currentTimestamp, item.getValue())) {
iterator.remove();
}
}
}

/**
* 生成一个密码
*
* @return 返回一个六位正整数
*/

public static Integer generate(String serialNumber, String time) {
String toHash = time + serialNumber;
return Math.abs(toHash.hashCode() % 1000000);
}

public static String localDateTimeToStr(LocalDateTime localDateTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return formatter.format(localDateTime);
}

}

最后的最后,这种方法生成的密码有个bug,就是30分钟内生成的30个密码里面会有重复的可能性,不过想来发生概率很低,看后续如何优化了。


作者:持剑的青年
来源:juejin.cn/post/7280459667129188387
收起阅读 »

中秋节,我只想回家😭

web
前言 中秋节马上就要到啦,中秋节作为刻入我们DNA里面的团圆佳节,我想大家关心的肯定是团圆了吧 对于普通人回家跟家人团圆可能也就是一张车票而已 但是今年~我就想知道28 29号的票都被谁买去了,我现在连30号的都没买到!!! 这让我怎么团圆啊!!! 忆中秋 还...
继续阅读 »

前言


中秋节马上就要到啦,中秋节作为刻入我们DNA里面的团圆佳节,我想大家关心的肯定是团圆了吧


对于普通人回家跟家人团圆可能也就是一张车票而已


但是今年~我就想知道28 29号的票都被谁买去了,我现在连30号的都没买到!!!


这让我怎么团圆啊!!!


忆中秋


还记得小时候过中秋,就跟过年一样。记忆中的中秋,各家各户外出的人都会从天南地北赶回来,一家人团聚在一起;记忆中的月亮,又圆又亮,坐在门前的小院子里,一起吃着月饼赏着月,已经很开心了。


小时候
听着嫦娥的故事
心里却惦记着月饼

长大了
手里捧着月饼
心里却想着嫦娥

中秋节到了
愿你重拾童年的快乐
点缀幸福的生活

程序员怎么过中秋呢?


当然是以代码来庆祝一下中秋啦,正好还可以参加下中秋创意大赛!但是很难有一些新奇的创意了,那就做一个猜灯谜的小游戏供大家消遣吧哈哈哈。话不多说,开始今天的主题,制作灯谜小游戏,码上掘金会有源码哦,很基础的~


1、游戏简介



  • 玩家在提交答案后,游戏将根据玩家的回答情况给予相应的提示信息。如果答案正确,将显示回答正确的提示,并增加相应的得分;如果答案错误,将显示回答错误的提示,并扣除相应的分数。同时,游戏会记录玩家的最高分数,以便玩家挑战自己的最好成绩。

  • 玩家可以选择继续猜下一道题目,直到回答完所有题目或不再继续。游戏结束后,将显示玩家的得分和最高分数,并提供重新开始和退出游戏的选项。


2、游戏规则



游戏包括多道灯谜题目,每个题目都有一个对应的答案。


玩家需要在输入框中输入自己的答案,并点击提交按钮进行确认。


如果答案正确,将显示相应的提示信息,表示回答正确;如果答案错误,将显示错误提示信息并扣除相应分数。


游戏根据玩家的回答情况给予评分,并记录最高分数。


玩家可以选择重置游戏重新开始,或者退出游戏。



3、游戏设计



  • 定义题目和答案数组,每个元素包含一个题目和对应的答案。

  • 初始化游戏数据,包括当前题目索引、得分和最高分数。

  • 显示当前题目,将题目显示在页面上供用户查看。

  • 用户输入答案后,点击提交按钮。

  • 检查用户答案是否正确,如果正确则增加得分,显示回答正确的提示;如果错误则显示回答错误的提示。

  • 更新最高分数,如果当前得分超过最高分数,则更新最高分数。

  • 显示当前得分和最高分数。

  • 清空输入框,准备接受下一题答案。

  • 判断是否回答完所有题目,若回答完所有题目则显示游戏结束的提示信息,并禁用提交按钮;若未完成则显示下一题。

  • 提供重新开始游戏的功能,重置游戏数据并重新显示第一题。

  • 提供退出游戏的功能,显示退出游戏的提示信息。


4、功能实现


题目和答案的存储



题目和答案的存储可以使用数组来实现,每个元素表示一道题目和对应的答案。例如:



// 定义题目和答案
const questions = [

{ question: '中秋佳节结良缘 (打一城市名)', answer: '重庆' },
{ question: '中秋鼓励消费 (打一成语)', answer: '月下花前' },
{ question: '中秋遥知兄弟赏光处 (打一唐诗目)', answer: '望月怀远' },
{ question: '木兰迷恋中秋夜 (打一成语)', answer: '花好月圆' },
{ question: '中秋渡蜜月 (打一成语)', answer: '喜出望外' }
];

每个元素都是一个对象,包含两个属性:question表示题目,answer表示答案。可以根据实际需要修改题目和答案的内容和数量。


5、游戏展示


话不多说直接上效果 !


作者:优秀稳妥的Zn
来源:juejin.cn/post/7280747221510733878
收起阅读 »

外甥女问我什么是代码洁癖,我是这么回答的...

1. 引言 哈喽,大家好,我是小 ❤,一个在二进制世界起舞的探险家,幻想有一天可以将代码作诗的后台开发。 今天,我要和大家聊聊程序员的神秘技能——重构!别担心,我会用通俗易懂的语言和一些趣味对话来帮助你理解和掌握这个技能,我 8 岁的外甥女听了都说懂。 1.1...
继续阅读 »

1. 引言


哈喽,大家好,我是小 ❤,一个在二进制世界起舞的探险家,幻想有一天可以将代码作诗的后台开发。


今天,我要和大家聊聊程序员的神秘技能——重构!别担心,我会用通俗易懂的语言和一些趣味对话来帮助你理解和掌握这个技能,我 8 岁的外甥女听了都说懂。


1.1 背景


代码开发:



一个月后:



后面有时间了改一改吧(放心,不会有时间的,有时间了也不会改)。


六个月后:



如上,是任何一个开发者都会经历的场景:早期的代码根本不能回顾,不然一定会陷入深深的怀疑,这么烂的代码真是出自自己的手吗?


更何况,目前大部分系统都是协同开发,每个程序员的命名规范、编码习惯都不尽相同,就导致了一个系统代码,多个味道的情况。


重构是什么


妍妍:嘿,舅舅,听说你要分享重构,这又是什么新鲜事?



❤:嗨,妍妍!重构就是改进既有代码的设计,让它更好懂、更容易维护,而不改变它的功能。想象一下,它就像是给代码来了个变美的化妆术,但内在还是那个代码,不会变成"不认识的人"。


为什么要重构


露露:哇,听起来好厉害,那为什么我们要重构呢?



❤:哈哈,好问题,露露!因为代码是活的,一天天在变大,当代码变得难以理解、难以修改时,它就像是一头头重的大象,拖慢了我们前进的步伐。重构就像是给大象减肥,使它更轻盈、更灵活,开发速度也能提升不少!


这和你们有小洁癖,爱收拾房间一样,有代码洁癖的程序员也会经常重构 Ta 们的代码呢!


什么时候要重构


妍妍:听起来有道理,但什么时候才应该使用重构呢?



❤:好问题,妍妍!有以下几种情况:




  • 当你看到代码中有好几处长得一模一样的代码,这时候可以考虑把它们合并成一个,减少冗余。




  • 当你的函数或方法看上去比词典还厚重时,可以把它拆成一些小的部分,更好地理解。




  • 当你要修复一个 bug,但却发现原来的代码结构太复杂,修复变得像解迷一样难时,先重构再修复就是个好主意。




  • 当你要添加新功能,但代码不让你轻松扩展时,也可以先重构,然后再扩展。




重构的步骤


露露:明白了舅舅,那重构的具体步骤是什么呢?



❤:问得好,露露,看来你有认真在思考!接下来让我给你介绍一下重构的基本步骤吧!


2. 如何重构


重构之前,我们需要识别出代码里面的坏味道代码。


所谓坏味道,就是指代码的表面的混乱,和深层次的腐化现象。简单来说,就是感觉不太对劲的代码。


2.1 坏味道代码



在《重构-改善既有代码的设计》一书中,讲述了这二十多种坏味道情况,我们下面将挑选最常见的几种来介绍。


1)方法过长


方法过长是指在一个方法里面做了太多的工作,常常伴随着方法中的语句不在同一个抽象层级,比如 dto 和 service 层代码混合在一起,即逻辑分散。


除此之外,方法过长还容易带来一些额外的问题。


问题1:过多的注释


方法太长会导致逻辑难以理解,需要大量的注释,如果 10 行代码需要 20 行注释,代码很难阅读。特别是读代码的时候,常常需要记住大量的上下文。


问题2:面向过程


面向过程的问题在于当逻辑复杂以后,代码会很难维护。


相反地,我们在代码开发时常常用面向对象的设计思想,即把事物抽象成具有共同特征的对象。


解决思路


解决方法过长时,我们遵循这样一条原则:每当感觉要写注释来说明代码时,就把这部分代码写进一个独立的方法里,并根据这段代码的意图来命名。



方法命名原则:可以概括要做的事,而非怎么做。



2)过大的类


一个类做了太多的事情,比如一个类的实现既包含商品逻辑,又包含订单逻辑。在创建时就会出现太多的实例变量和方法,难以管理。


除此之外,过大的类还容易带来两个问题。


问题1:冗余重复


当一个类里面包含两个模块的逻辑时,两个模块容易产生依赖。这在代码编写的过程中,很容易发生 “你带着我,我看着你” 的问题。


即在两个模块中,都看到了和另一个模块相关的程序结构或相同意图的方法。


问题2:耦合结构不良


当类的命名不足以描述所做的事情时,大概率产生了耦合结构不良的问题,这和我们想要编写 “高内聚,低耦合” 的代码目标相悖而行了。


解决思路


将大类根据业务逻辑拆分成小类,如果两个类之间有依赖,则通过外键等方式关联。当出现重复代码时,尽量合并提出来,程序会变得更简洁可维护。


3)逻辑分散


逻辑分散是由于代码架构层次或者对象层次上有不合理的依赖,通常会导致两个问题:


发散式变化


某个类经常因为不同的原因,在不同的方向上修改。


散弹式修改


发生某种变化时,需要多个类中修改。


4)其它坏味道


数据泥团


数据泥团是指很多数据项混乱地融合在一起,不易复用和扩展。


当许多数据项总是一起出现,并且一起出现时更容易分类。我们就可以考虑将数据按业务封装成数据对象。反例如下:


func AddUser(age int, gender, firstName, lastName string) {}

重构之后:


type AddUserRequest struct {
   Age int
   Gender string
   FirstName string
   LastName string
}
func AddUser(req AddUserRequest) {}

基本类型偏执


在大多数高级编程语言里面,都有基本类型和结构类型。在 Go 语言里面,基本类型就是 int、string、bool 等。


基本类型偏执是指我们在定义对象的变量时,常常不考虑变量的实际业务含义,直接使用基本类型。


反例如下:


type QueryMessage struct {
Role        int         `json:"role"`
Content  string    `json:"content"`
}

重构之后:


// 定义对话角色类型
type MessageRole int

const (
HUMAN     MessageRole = 0
ASSISTANT MessageRole = 1
)

type QueryMessage struct {
Role        MessageRole   `json:"role"`
Content  string               `json:"content"`
}

这是 ChatGPT 问答时的请求字段,我们可以看到对话角色为 int 类型,且 0 表示人类,1 表示聊天助手。


当直接使用 int 来表示对话 Role 时,没办法直接从定义里知道更多信息。


但是用 type MessageRole int 定义后,我们就可以根据常量值很清晰地看出对话角色分为两种:HUMAN & ASSISTANT.


混乱的代码层次调用


我们一般的系统都会根据业务 service、中转控制 controller 和数据库访问 dao 等进行分层。一般 controller 调用 service,service 调用 dao。


如果我们在 controller 直接调用 dao,或者 dao 调用 controller,就会出现层次混乱的问题,就可以进行优化了。


5)坏味道带来的问题


妍妍:舅舅,这些坏味道都需要解决吗,你说的这些坏味道代码会带来什么样的影响呢?


❤:是的,代码里如果坏味道代码太多,会带来四个 “难以”



  • 难以理解:新来的开发同学压根看不懂看人的代码,一个模块看了两个周还不知道啥意思。或许不是开发者的水平不够,可能是代码写的太一言难尽。



  • 难以复用:要么是读都读不懂,或者勉强读懂了却不敢用,担心有什么暗坑。或者系统耦合性严重,难以分离可重用部分。



  • 难以变化:牵一发而动全身,即散弹式修改。动了一处代码,整个模块都快没了。




  • 难以测试:改了不好测,难以进行功能验证。命名杂乱,结构混乱,在测试时可能测出新的问题。




3. 重构技巧


露露:哦,原来是这样啊,那我们可以去除它们吗?


❤:当然可以了!就像你们爱收拾房间一样,每一个有责任心(代码洁癖)的程序员,都会考虑代码重构。


而对于重构问题,业界已经有比较好的思路:通过持续不断地重构将代码中的 "坏味道" 清除掉。


1)命名规范


一个好的命名规范应该符合:



  • 精准描述所做的事情

  • 格式符合通用惯例


约定俗成的惯例


我们拿华为公司内部的 Go 语言的开发规范来举例:


场景约束示例
项目名全部小写,多个单词时用中划线 '-' 分隔user-order
包名全部小写,多个单词时用中划线 '-' 分隔config-sit
结构体名首字母大写Student
接口采用 Restful API 的命名方式,路径最后一部分是资源名词如 [get] api/v1/student
常量名首字母大写,驼峰命名CacheExpiredTime
变量名首字母小写,驼峰命名userName,password

2)重构手法


妍妍:哇,这么多成熟的规范可以用啊!那除了规范,我们还需要注意什么吗?


❤:好问题妍妍!接下来我还会介绍一些常见的重构手法:




  • 提取函数:将一个长长的函数分成小块,更容易理解和复用。




  • 改名字:给变量、函数、类等改个名字,更有意义。




  • 消除冗余:找到相似的代码块,合并它们,减少重复。




  • 搬家:把函数或字段移到更合适的地方,让代码更井然有序。




  • 抽象通用类:把通用功能抽出来,变成一个类,增加代码的可重用性。




  • 引入参数对象:当变量过多时,传入对象,消除数据泥团。




  • 使用卫语句:减少 else 的使用,让代码结构更加清晰。




4. 小结


露露:舅舅,你讲得太有趣了,我感觉我也会重构了!


❤:露露真棒,我相信你!重构的思想无处不在,就像生活中都应该留白一样,你们的人生也会非常精彩的。在编程里,重构可以让代码更美观、更容易读懂,提高开发效率,是程序员都应该掌握的技能。


妍妍:我也会了,我也会了!以后我也要写代码,做代码重构,我还要给舅舅的文章点赞。



❤:哈哈哈,好哒,你们都很棒!就像你们喜欢打扫卫生,爱好画画读诗一样,如果以后你们想写代码,它们也会十分的干净整洁,充满诗情画意。



最后,如果你觉得有所收获,别忘了点赞和在看,让更多的人了解重构的神奇之处,一起进步,一起写出更好的代码!


希望这篇文章对你有所帮助,也希望你能在编程的路上越走越远。感谢大家的支持,我们下次再见!🚀✨


最后


妍妍说:看完的你还不赶紧分享、点赞、加入在看吗?



作者:xin猿意码
来源:juejin.cn/post/7277836718760771636
收起阅读 »

前端监控究竟有多重要?

web
为什么要有前端监控? 一个很现实的原因是bug是不可能被全部测试出来的,由于成本和上线档期的考虑,测试无法做到“面面俱到”,即使时间充裕也总会有这样或那样的bug埋藏在某个角落。 所以一个可靠的前端监控系统可以帮助我们化被动为主动,不再被动的等待客服来找,而是...
继续阅读 »

为什么要有前端监控?


一个很现实的原因是bug是不可能被全部测试出来的,由于成本和上线档期的考虑,测试无法做到“面面俱到”,即使时间充裕也总会有这样或那样的bug埋藏在某个角落。


所以一个可靠的前端监控系统可以帮助我们化被动为主动,不再被动的等待客服来找,而是在问题出现时开发人员可以第一时间知道并解决。并且我们还可以通过监控系统获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化的方向。


常见的前端监控


前端监控系统大体可以分为四部分



  • 异常监控

  • 用户数据监控

  • 性能监控

  • 异常报警


用户数据监控



数据监控,就是监听用户的行为,可以帮助我们评估和改进用户在使用网站时的体验:




  • PV:PV(page view):即用户访问特定页面的次数,也可以说是页面的浏览量或点击量,

  • UV:访问网站的不同个体或设备数量,而不是页面访问次数

  • 新独立访客:当日的独立访客中,历史上首次访问网站的访客为新独立访客。

  • 跳出次数:跳出指仅浏览了1个页面就离开网站的访问(会话)行为。跳出次数越多则访客对网站兴趣越低或站内入口质量越差。

  • 来访次数:由该来源进入网站的访问(会话)次数。

  • 用户在每一个页面的停留时间

  • 用户通过什么入口来访问该网页

  • 用户在相应的页面中触发的行为

  • 网站的转化率

  • 导航路径分析


统计这些数据是有意义的,我们可以清晰展示前端性能的表现,并依据这些监控结果来进一步优化前端性能。例如,我们可以改善动画效果以在低版本浏览器上兼容,或者采取措施加快首屏加载时间等。这些优化措施不仅可以提高转化率,因为快速加载的网站通常具有更高的转化率,还可以确保我们的网站在多种设备和浏览器上都表现一致,以满足不同用户的需求。最终达到,改善用户体验,提供更快的页面加载时间和更高的性能,增强用户满意度,降低跳出率的目的。


性能监控



性能监控是一种用于追踪和评估网站和性能的方法。它专注于用户在浏览器中与网站互时的性能体验




  • 首次绘制(FP): 全称 First Paint,标记浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点

  • 首次内容绘制(FCP):全称 First Contentful Paint,标记的是浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至 <canvas> 元素。

  • 首次有效绘制(FMP):全称 First Meaningful Paint,标记的是页面主要内容绘制的时间点,例如视频应用的视频组件、天气应用的天气信息、新闻应用中的新闻条目。

  • 最大内容绘制(LCP):全称 Largest Contentful Paint,标记在可视区“内容”最大的可见元素开始绘制在屏幕上的时间点。

  • 白屏时间

  • http 等请求的响应时间

  • 静态资源整体下载时间

  • 页面渲染时间

  • 页面交互动画完成时间


异常监控



由于产品的前端代码在客户端的执行过程中也会发生异常,因此需要引入异常监控。及时的上报异常情况,这样可以避免线上故障的发生。虽然大部分异常可以通过 try catch 的方式捕获,但是比如内存泄漏以及其他偶现的异常难以捕获。



常见的需要监控的异常包括:



  • Javascript 的异常监控:捕获并报告JavaScript代码中的错误,如未定义的变量、空指针引用、语法错误等

  • 数据请求异常监控:监控Ajax请求和其他网络请求,以便识别网络问题、服务器错误和超时等。

  • 资源加载错误:捕获CSS、JavaScript、图像和其他资源加载失败的情况,以减少页面加载问题。

  • 跨域问题:识别跨域请求导致的问题,如CORS(跨源资源共享)错误。

  • 用户界面问题:监控用户界面交互时的错误,如用户界面组件的不正常行为或交互问题


通过捕获和报告异常,开发团队可以快速响应问题,提供更好的用户体验,减少客户端问题对业务的不利影响


异常报警



前端异常报警是指在网站中检测和捕获异常、错误以及问题,并通过各种通知方式通知开发人员或团队,以便他们能够快速诊断、分析和解决问题。



常见的异常报警方式




  • 邮件通知:通过邮件将异常信息发送给相关人员,通常用于低优先级的问题。




  • 短信或电话通知:通过短信或电话自动通知相关人员,通常用于紧急问题或需要立即处理的问题。




  • 即时消息:使用即时通讯工具如企业微信 飞书或钉钉发送异常通知,以便团队及时协作。




  • 日志和事件记录:将异常信息记录到中央日志,或者监控中台系统,以供后续分析和审计。




报警级别和策略:


异常报警通常有不同的级别和策略,根据问题的紧急性和重要性来确定通知的方式和频率。例如,可以定义以下报警级别:




  • 紧急报警:用于严重的问题,需要立即处理,通常通过短信或电话通知。




  • 警告报警:用于中等级别的问题,需要在短时间内处理,可以通过即时消息或邮件通知。




  • 信息报警:用于一般信息和低优先级问题,通过邮件或即时消息通知。




  • 静默报警:用于临时性问题或不需要立即处理的问题,可以记录到日志而不发送通知。




异常报警是确保系统稳定性和可用性的重要机制。它能够帮助组织及时发现和解决问题,减少停机时间,提高系统的可靠性和性能,从而支持业务运营。异常报警有助于快速识别和响应问题,减少停机时间,提高系统的可用性和性能


介绍完了前端监控的四大部分,现在就来聊聊前端监控常见的几种监控方式。


SDK设计(埋点方案)


前端埋点是一种用于收集和监控网站数据的常见方法


image.png


手动埋点:


手动埋点也称为代码埋点,是通过手动在代码中插入埋点代码(SDK 的函数)的方式来实现数据收集。像腾讯分析(Tencent Analytics)、百度统计(Baidu Tongji)、诸葛IO(ZhugeIO)等第三方数据统计服务商大都采用这种方案,这种方法的优点是:



  • 灵活:开发人员可以根据需要自定义属性和事件,以捕获特定的用户行为和数据。

  • 精确:可以精确控制埋点位置,以确保收集到关键数据。


然而,手动埋点的缺点包括:



  • 工作量大:需要在代码中多次插入埋点代码,工程量较大。

  • 沟通成本高:需要开发、产品和运营之间的频繁沟通,容易导致误差和延迟。

  • 更新迭代成本高:每次有埋点更新或漏埋点都需要重新发布应用程序,成本较高。


可视化埋点:


可视化埋点通过提供可视化界面,允许用户在不编写代码的情况下进行添加埋点。这种方法的优点是:



  • 简单方便:非技术人员也可以使用可视化工具添加埋点,减少了对技术团队的依赖。

  • 实时更新:可以实时更新埋点配置,无需重新上传网站。


然而,可视化埋点的缺点包括:



  • 可定制性受限:可视化工具通常只支持有限的埋点事件和属性,无法满足所有需求。

  • 对控件有限制:可视化埋点通常只适用于特定的UI控件和事件类型。


无埋点:


无埋点是一种自动收集所有用户行为和事件的方法,然后通过后端过滤和分析以提取有用的数据。这种方法的优点是:



  • 全自动:无需手动埋点,数据自动收集,降低了工程量,而且不会出现漏埋和误埋等现象。

  • 全面性:捕获了所有用户行为,提供了完整的数据集。


然而,无埋点的缺点包括:



  • 数据量大:数据量庞大,需要后端过滤和处理,可能增加服务器性能压力。

  • 数据处理复杂:需要处理大量原始数据,提取有用的信息可能需要复杂的算法和逻辑。


作者:zayyo
来源:juejin.cn/post/7280430881964638262
收起阅读 »

闲来无事,拜拜电子财神

web
最近在刷抖音的时候,经常能刷到类似下面这种手机桌面,通过手机小组件功能,搭了一个电子供台。。。    由于最近闲来无事儿,就在想可不可以制作一个类似的网页,功能点有以下这些: 1.类似手机小组件一样的布局 2.点击木鱼一次,可以显示功德加一并且带音效 3.随着...
继续阅读 »

最近在刷抖音的时候,经常能刷到类似下面这种手机桌面,通过手机小组件功能,搭了一个电子供台。。。


  


由于最近闲来无事儿,就在想可不可以制作一个类似的网页,功能点有以下这些:


1.类似手机小组件一样的布局


2.点击木鱼一次,可以显示功德加一并且带音效


3.随着功德点击,香炉上方会有烟雾飘散的效果


4.统计不同省份的功德数据


5.心愿墙功能,


于是说干就干,就开始了开发工作;


经过了 2 个下午的忙碌,完成了前三个功能,有了大概的雏形,就是下面这个样子



开发的过程中也遇到了一些问题


1.在手机上连续点击木鱼时,会导致网页放大


在网上找了一些解决办法,设置 meta 属性


无效,在 ios 的浏览器上没有效果


这个方法类似于写个节流函数,不过这样做就没有连续敲击木鱼的快感了,所以也不行。


最后让我找到了一个插件 fastClick.js,完美解决了问题。只要正常引入,然后加入以下代码即可。


if ("addEventListener" in document) {            document.addEventListener(                "DOMContentLoaded",                function () {                    FastClick.attach(document.body);                },                false            );        }

2.播放木鱼音效延迟问题


通过document.createElement('audio')方式创建 audio 组件,代码如下


var audio = document.createElement('audio') //生成一个audio元素
audio.controls = true //这样控件才能显示出来
audio.src = 'xxxxx' //音乐的路径
document.body.appendChild(audio) //把它添加到页面中
audio.play()

声音是能播放出来了,但是延迟很高,点一下木鱼,过几秒钟后才有音效,所以这个方式 pass 了。还有说可以通过AudioContext API 来播放音效,但是看了一下,感觉写起来有些复杂,也 pass 掉了,最后也是找到了一款合适的插件解决了这个问题。



使用方式也是异常简单


var sound = new Howl({
src: ['sound.mp3']
});

sound.play();

由于有个功能是敲击木鱼后,页面香炉的位置会生成烟雾,自己不太会写,于是又找到了可以一个模拟烟雾的插件,可以在页面任意位置生成烟雾动画


使用时先创建一个 canvas 标签


<canvas id="smoke"></canvas>

然后初始化


let canvas = document.getElementById("smoke");let ctx = canvas.getContext("2d");canvas.width = window.innerWidth;canvas.height = window.innerHeight;party = SmokeMachine(ctx, [230, 230, 230]); // 数组里是颜色 rgb 值

点击木鱼一次,创建一次播放动画


party.start();party.addSmoke(    window.innerWidth / 2,    //烟雾生成的位置,x    window.innerHeight * 0.4, //烟雾生成的位置,y    10 //烟雾大小);

至此烟雾效果就完美实现了。


体验url:财神爷.我爱你


没错,是纯中文域名,中国的神仙就要用中文域名。


未完待续......


作者:yibeicha
来源:juejin.cn/post/7280435142245285946
收起阅读 »

前端又出新框架了,你还学得动吗?

web
最近前端又出来一个新框架/库,名为nue.js。一周前的9.13号提交了第一个commit,到今天已超过2000个star。 翻译一下: Nue 是一个强大的 React、Vue、Next.js、Vite 和 Astro 替代品。它可能会改变您的web开发...
继续阅读 »

最近前端又出来一个新框架/库,名为nue.js。一周前的9.13号提交了第一个commit,到今天已超过2000个star。


官网首页截图


翻译一下:



Nue 是一个强大的 React、Vue、Next.js、Vite 和 Astro 替代品。它可能会改变您的web开发方式。



What is Nue JS?


Nue JS 是一个非常小的(压缩后 2.3kb)JavaScript 库,用于构建 Web 界面。 它是即将推出的 Nue 生态系统的核心。 它就像 Vue.js、React.js 或 Svelte,但没有hooks, effects, props, portals, watchers, provides, injects, suspension 这些抽象概念。了解 HTML、CSS 和 JavaScript 的基础知识,就可以开始了。


用更少的代码构建用户界面


它表示,Nue 最大的好处是你需要更少的代码来完成同样的事情:


同样一个listBox组件,react需要2537行,vue需要1913行,svelte需要1286行,Nue只需要208行,比react小10倍。





仅仅是HTML


Nue 使用基于 HTML 的模板语法:


<div @name="media-object" class="{ type }">
<img src="{ img }">
<aside>
<h3>{ title }</h3>
<p :if="desc">{ desc }</p>
<slot/>
</aside>
</div>

React 和 JSX 声称是“Just JavaScript”,但 Nue 可以被认为是“Just HTML”


按比例构建


Nue 具有出色扩展性的三个原因:



  1. 关注点分离,易于理解的代码比“意大利面条代码”更容易扩展

  2. 极简主义,一百行代码比一千行代码更容易扩展

  3. 人才分离,当 UX 开发人员专注于前端,而 JS/TS 开发人员专注于前端后端时,团队技能就会达到最佳平衡:



解耦样式


Nue不提倡使用 Scoped CSS、样式属性、Tailwind 或其他 CSS-in-JS 体操:



  1. 更多可重用代码:当样式未硬编码到组件时,同一组件可能会根据页面或上下文而看起来有所不同。

  2. 没有意大利面条式代码:纯 HTML 或纯 CSS 比混合意大利面条式代码更容易阅读

  3. 更快的页面加载:通过解耦样式,可以更轻松地从辅助 CSS 中提取主 CSS,并将 HTML 页面保持在关键的14kb 限制以下。


反应式和同构


Nue拥有丰富的组件模型,它允许您使用不同类型的组件创建各种应用程序:



  1. 服务器组件在服务器上呈现。它们可以帮助您构建以内容为中心的网站,无需 JavaScript 即可加载速度更快,并且可以被搜索引擎抓取。

  2. 反应式组件在客户端上呈现。它们帮助您构建动态岛或单页应用程序。

  3. 混合组件部分在服务器端呈现,部分在客户端呈现。这些组件可帮助您构建响应式、SEO 友好的组件,例如视频标签或图片库。

  4. 通用组件在服务器端和客户端上使用相同的方式。


UI库文件


Nue允许您在单个文件上定义多个组件。这是将相关组件组合在一起并简化依赖关系管理的好方法。


<!-- shared variables and methods -->
<script>
import { someMethod } from './util.js'
</script>

<!-- first component -->
<article @name="todo">
...
</article>

<!-- second component -->
<div @name="todo-item">
...
</div>

<!-- third component -->
<time @name="cute-date">
...
</time>

使用库文件,您的文件系统层次结构看起来更干净,并且您需要更少的样板代码将连接的部分连接在一起。他们帮助为其他人打包库。


更简单的工具


Nue JS带有一个简单的render服务器端渲染功能和一个compile为浏览器生成组件的功能。不需要 WebpackVite 等复杂的捆绑程序来控制您的开发环境。只需将 Nue 导入到项目中即可。


如果应用程序因大量依赖项而变得更加复杂,可以在业务模型上使用打包器。Bunesbuild是很棒的高性能选择。


用例


Nue JS是一款多功能工具,支持服务器端和客户端渲染,可帮助您构建以内容为中心的网站和反应式单页应用程序。



  1. UI 库开发:为反应式前端或服务器生成的内容创建可重用组件。

  2. 渐进式增强:Nue JS 是一个完美的微型库,可通过动态组件或“岛”增强以内容为中心的网站

  3. 静态网站生成器:只需将其导入您的项目即可准备渲染。不需要捆绑器。

  4. 单页应用程序:与即将推出的Nue MVC项目一起构建更简单、更具可扩展性的应用程序。

  5. Template Nue:是一个用于生成网站和 HTML 电子邮件的通用工具。


本文参考资料



作者:xintianyou
来源:juejin.cn/post/7280747833371705405
收起阅读 »

iOS小技能:Xcode13的使用技巧

iOS
引言 Xcode13新建项目不显示Products目录的解决方案Xcode13新建的工程恢复从前的Info.plist同步机制的方法自动管理签名证书时拉取更新设备描述文件的方法。 I 显示Products目录的解决方案 问题:Xcode13 新建的项目不显示P...
继续阅读 »

引言


  1. Xcode13新建项目不显示Products目录的解决方案
  2. Xcode13新建的工程恢复从前的Info.plist同步机制的方法
  3. 自动管理签名证书时拉取更新设备描述文件的方法。

I 显示Products目录的解决方案


问题:Xcode13 新建的项目不显示Products目录


解决方式: 修改project.pbxproj 文件的productRefGroup配置信息


效果:

应用场景:Products目录的app包用于快速打测试包。


1.1 从Xcodeeproj 打开project.pbxproj



1.2 修改productRefGroup 的值


将mainGroup 对应的值复制给productRefGroup 的值,按command+s保存project.pbxproj文件,Xcode将自动刷新,Products目录显示出来了。



1.3 应用场景


通过Products目录快速定位获取真机调试包路径,使用脚本快速打包。


打包脚本核心逻辑:在含有真机包路径下拷贝.app 到新建的Payload目录,zip压缩Payload目录并根据当前时间来命名为xxx.ipa。

#!/bin/bash
echo "==================(create ipa file...)=================="
# cd `dirname $0`;
rm -rf ./Target.ipa;
rm -rf ./Payload;
mkdir Payload;
APP=$(find . -type d | grep ".app$" | head -n 1)
cp -rf "$APP" ./Payload;
data="`date +%F-%T-%N`"
postName="$data"-".ipa"
zip -r -q "$postName" ./Payload;
rm -rf ./Payload;
open .
# 移动ipa包到特定目录
mkdir -p ~/Downloads/knPayload
cp -a "$postName" ~/Downloads/knPayload
open ~/Downloads/knPayload
echo "==================(done)=================="
exit;






II 关闭打包合并Info.plist功能


Xcode13之前Custom iOS Target Properties面板和Info.plist的配置信息会自动同步。


Xcode13新建的工程默认开启打包合并Info.plist功能,不再使用配置文件(Info.plist、entitlements),如果需要修改配置,直接在Xcode面板target - Info - Custom iOS Target Propertiesbuild settings中设置。




Projects created from several templates no longer require configuration files such as entitlements and Info.plist files. Configure common fields in the target’s Info tab, and build settings in the project editor.



2.1 设置Info.plist为主配置文件


由于GUI配置面板没有配置文件plist的灵活,不支持查看源代码。所以我们可以在BuildSetting Generate Info.plist File设置为NO,来关闭打包合并功能。



关闭打包合并功能,重启Xcode使配置生效,Custom iOS Target Properties面板的信息以info.plist的内容为准。



每次修改info.plist都要重启Xcode,info.plist的信息才会同步到Custom iOS Target Properties面板。 



2.2 注意事项


注意: 关闭打包合并Info.plist功能 之前记得先手动同步Custom iOS Target Properties面板的信息到Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>iOS逆向</string>
<key>CFBundleIdentifier</key>
<string>blog.csdn.net.z929118967</string>
<key>CFBundleName</key>
<string>YourAppName</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict>
<key>UILaunchScreen</key>
<dict/>
</dict>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~iphone</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>


III 自动管理签名证书时如何拉取最新设备描述文件?


方法:根据描述文件的创建时间来删除旧的自动管理证书的描述文件



 



原理:在~/Library/MobileDevice/Provisioning\ Profiles 文件夹中删除之前的描述文件,然后系统检测到没有描述文件则会自动生成一个新的


see also


iOS第三方库管理规范:以Cocoapods为案例



kunnan.blog.csdn.net/article/det…



iOS接入腾讯优量汇开屏广告教程



kunnan.blog.csdn.net/article/det…


作者:公众号iOS逆向
链接:https://juejin.cn/post/7137938695616741407
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

开发没切图怎么办?矢量图标(iconFont)上手指南

iOS
需求: 有时候我们自己想独立开发一些App,但苦恼没有设计给icon切图? 这可怎么办? 今天我们来介绍一种比较高效且高质量的替代方案:使用矢量图标 —— iconFont。 一、iconFont简介 iconFont:是阿里巴巴提供的一个矢量图标库。简单...
继续阅读 »

需求:

有时候我们自己想独立开发一些App,但苦恼没有设计给icon切图?

这可怎么办?

今天我们来介绍一种比较高效且高质量的替代方案:使用矢量图标 —— iconFont



一、iconFont简介



iconFont:是阿里巴巴提供的一个矢量图标库。简单来说,就是可以把icon转换成font,再通过文本展示出来。官网链接

支持:WebiOSAndroid平台使用。



二、iOS端简单使用指南


第一步:


登录iconFont,挑选你需要的icon,并把它们加入购物车,下载代码。

  • 挑选统一风格的icon

    • 全局搜索想要的icon

    • 将需要使用的icon加入到购物车

    • 下载代码




第二步:


解压下载的压缩包,注意demo_index.htmliconFont.ttf文件。打开工程将ttf导入到项目中,并在info.plist中配置。


  • 压缩文件,找到demo_index.htmliconFont.ttf



  • iconFont.ttf文件导入项目:



第三步:


打开demo_index.html预览iconFont所对应的Unicode编码。并在项目中应用。


  • 打开demo_index.html文件


  • swift使用方法如下,用格式\u{编码}使用Unicode编码
//...
label.font = UIFont.init(name: "iconFont", size: 26.0)
label.text = "\u{e658}"
//...

  • Objective-C使用方法如下,用格式\U0000编码使用Unicode编码
//...
label.font = [UIFont fontWithName:@"uxIconFont" size: 34];;
label.text = @"\U0000e658";
//...

这样,在没有设计提供切图的情况下,就可以用LabeliconFont字体代替切图达成ImageView的效果了。


三、iconFont原理


先把icon通过像素点描述成自定义字体(svg格式字体),然后打包成ttf格式的文件,再通过对应的unicode对应到相关的icon


四、可能遇到的一些问题


  • ttf文件导入冲突问题:

由于从iconFont上打包生成的ttf文件,字体名均为“iconFont”,因此从官网上下载的ttf文件,字体名均为“iconFont”。因此多ttf文件引入时,会有冲突。


解决方案:用一些工具修改字体名,再导入多个ttf文件。(记得在info.plist文件里配置)


  • Unicode变化问题:

尽量使用一个账号下载ttf资源,不同的环境下可能会导致生成的Unicode不同。从而给项目替换icon带来成本。


  • 版权问题:

iconFont目前应该不支持商用,除非有特别的许可。
自己独立写一些小项目的时候可以使用。


作者:齐舞647
链接:https://juejin.cn/post/7254107670012543013
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

全方位对比 Postgres 和 MySQL (2023 版)

根据 2023 年 Stack Overflow 调研,Postgres 已经取代 MySQL 成为最受敬仰和渴望的数据库。 随着 Postgres 的发展势头愈发强劲,在 Postgres 和 MySQL 之间做选择变得更难了。 如果看安装数量,MySQL...
继续阅读 »

根据 2023 年 Stack Overflow 调研,Postgres 已经取代 MySQL 成为最受敬仰和渴望的数据库。




随着 Postgres 的发展势头愈发强劲,在 Postgres 和 MySQL 之间做选择变得更难了。


如果看安装数量,MySQL 可能仍是全球最大的开源数据库。




Postgres 则自诩为全球最先进的开源关系型数据库。




因为需要与各种数据库及其衍生产品集成,Bytebase 和各种数据库密切合作,而托管 MySQL 和 Postgres 最大的云服务之一 Google Cloud SQL 也是 Bytebase 创始人的杰作之一。


我们对 Postgres 和 MySQL 在以下几个维度进行了比较:


  • 许可证 License
  • 性能 Performance
  • 功能 Features
  • 可扩展性 Extensibility
  • 易用性 Usability
  • 连接模型 Connection Model
  • 生态 Ecosystem
  • 可运维性 Operability



除非另有说明,下文基于最新的主要版本 Postgres 15 和 MySQL 8.0 (使用 InnoDB)。在文章中,我们使用 Postgres 而不是 PostgreSQL,尽管 PostgreSQL 才是官方名称,但被认为是一个错误的决定




许可证 License


  • MySQL 社区版采用 GPL 许可证。
  • Postgres 发布在 PostgreSQL 许可下,是一种类似于 BSD 或 MIT 的自由开源许可。

即便 MySQL 采用了 GPL,仍有人担心 MySQL 归 Oracle 所有,这也是为什么 MariaDB 从 MySQL 分叉出来。


性能 Performance


对于大多数工作负载来说,Postgres 和 MySQL 的性能相当,最多只有 30% 的差异。无论选择哪个数据库,如果查询缺少索引,则可能导致 x10 ~ x1000 的降级。
话虽如此,在极端的写入密集型工作负载方面,MySQL 确实比 Postgres 更具优势。可以参考下文了解更多:



除非你的业务达到了 Uber 的规模,否则纯粹的数据库性能不是决定因素。像 Instagram, Notion 这样的公司也能够在超大规模下使用 Postgres。


功能 Features


对象层次结构


MySQL 采用了 4 级结构:


  1. 实例
  2. 数据库

Postgres 采用了 5 级结构:


  • 实例(也称为集群)
  • 数据库
  • 模式 Schema

ACID 事务


两个数据库都支持 ACID 事务,Postgres 提供更强大的事务支持。




安全性


Postgres 和 MySQL 都支持 RBAC。


Postgres 支持开箱即用的附加行级安全 (RLS),而 MySQL 需要创建额外的视图来模拟此行为。


查询优化器


Postgres 的查询优化器更优秀,详情参考此吐槽


复制


Postgres 的标准复制使用 WAL 进行物理复制。MySQL 的标准复制使用 binlog 进行逻辑复制。


Postgres 也支持通过其发布/订阅模式进行逻辑复制。


JSON


Postgres 和 MySQL 都支持 JSON。 Postgres 支持的功能更多:


  • 更多操作符来访问 JSON 功能。
  • 允许在 JSON 字段上创建索引。

CTE (Common Table Expression)


Postgres 对 CTE 的支持更全面:


  • 在 CTE 内进行 SELECT, UPDATE, INSERT, DELETE 操作
  • 在 CTE 之后进行 SELECT, UPDATE, INSERT, DELETE 操作

MySQL 支持:


  • 在 CTE 内进行 SELECT 操作
  • 在 CTE 之后进行 SELECT, UPDATE, DELETE 操作

窗口函数 (Window Functions)


窗口帧类型:MySQL 仅支持 Row Frame 类型,允许定义由固定数量行组成的帧;而 Postgres 同时支持 Row Frame 和范围帧类型。


范围单位:MySQL 仅支持 UNBOUNDED PRECEDING 和 CURRENT ROW 这两种范围单位;而 Postgres 支持更多范围单位,包括 UNBOUNDED FOLLOWING 和 BETWEEN 等。


性能:一般来说,Postgres 实现的 Window Functions 比 MySQL 实现更高效且性能更好。


高级函数:Postgres 还支持更多高级 Window Functions,例如 LAG(), LEAD(), FIRST_VALUE(), and LAST_VALUE()。


可扩展性 Extensibility


Postgres 支持多种扩展。最出色的是 PostGIS,它为 Postgres 带来了地理空间能力。此外,还有 Foreign Data Wrapper (FDW),支持查询其他数据系统,pg_stat_statements 用于跟踪规划和执行统计信息,pgvector 用于进行 AI 应用的向量搜索。


MySQL 具有可插拔的存储引擎架构,并诞生了 InnoDB。但如今,在 MySQL 中,InnoDB 已成为主导存储引擎,因此可插拔架构只作为 API 边界使用,而不是用于扩展目的。


在认证方面,Postgres 和 MySQL 都支持可插拔认证模块 (PAM)。


易用性 Usability


Postgres 更加严格,而 MySQL 更加宽容:


  • MySQL 允许在使用 GROUP BY 子句的 SELECT 语句中包含非聚合列;而 Postgres 则不允许。
  • MySQL 默认情况下是大小写不敏感的;而 Postgres 默认情况下是大小写敏感的。
  • MySQL 允许 JOIN 来自不同数据库的表;而 Postgres 只能连接单个数据库内部的表,除非使用 FDW 扩展。

连接模型 Connection Model


Postgres 采用在每个连接上生成一个新进程的方式工作。而 MySQL 则在每个连接上生成一个新线程。因此,Postgres 提供了更好的隔离性,例如,一个无效的内存访问错误只会导致单个进程崩溃,而不是整个数据库服务器。另一方面,进程模型消耗更多资源。因此,在部署 Postgres 时建议通过连接池(如 PgBouncer 或 pgcat)代理连接。


生态 Ecosystem


常见的 SQL 工具都能很好地支持 Postgres 和 MySQL。由于 Postgres 的可扩展架构,并且仍被社区拥有,近年来 Postgres 生态系统更加繁荣。对于提供托管数据库服务的应用平台,每个都选择了 Postgres。从早期的 Heroku 到更新的 Supabase, render 和 Fly.io。


可运维性 Operability


由于底层存储引擎设计问题,在高负载下,Postgres 存在臭名昭著的 XID wraparound 问题。


对于 MySQL,在 Google Cloud 运营大规模 MySQL 集群时,我们遇到过一些复制错误。


这些问题只会在极端负载下发生。对于正常工作负载而言,无论是 Postgres 还是 MySQL 都是成熟且可靠的。数据库托管平台也提供集成备份/恢复和监控功能。


Postgres 还是 MySQL


2023 年了,在 Postgres 和 MySQL 之间做选择仍然很困难,并且经常引起激烈讨论




总的来说,Postgres 有更多功能、更繁荣的社区和生态;而 MySQL 则更易学习并且拥有庞大的用户群体。
我们观察到与 Stack Overflow 结果相同的行业趋势,即 Postgres 在开发者中变得越来越受欢迎。但根据我们的实际体验,精密的 Postgres 牺牲了一些便利性。如果你对 Postgres 不太熟悉,最好从云服务提供商那里启动一个实例,并运行几个查询来上手。有时候,这些额外好处可能并不值得,选择 MySQL 会更容易一些。


同时,在一个组织内部共存 Postgres 和 MySQL 也是很常见的情况。如果需要同时管理 Postgres 和 MySQL 的开发生命周期,可以来了解一下 Bytebase。






💡 你可以访问官网,免费注册云账号,立即体验 Bytebase。


作者:Bytebase
链接:https://juejin.cn/post/7254944931386228796
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

少一点功利主义,多一点傻逼似的坚持

感谢你观看本文,希望在未来的时光中,我们都能找到真正的自己,做真正的自己 坚持只需要一个理由,而放弃则有无数个接口,坚持很难,而放弃就是一刹那的时间,作为普通人的我们,其实只要能坚持做一件事,那么其实是很了不起的,可能它暂时不能给你带来经济价值,但是经过时间的...
继续阅读 »

感谢你观看本文,希望在未来的时光中,我们都能找到真正的自己,做真正的自己


坚持只需要一个理由,而放弃则有无数个接口,坚持很难,而放弃就是一刹那的时间,作为普通人的我们,其实只要能坚持做一件事,那么其实是很了不起的,可能它暂时不能给你带来经济价值,但是经过时间的酝酿,它会迸发处惊人的力量!


不过有一关是很难过的,这一关基本上可以刷掉百分之九十五的人,那就是否有长期主义,是否能够忍受“没有回报”,因为人的本性就是贪婪,而我们从小受到的教育就是“付出就有收获”,所以我们在做每一件事的时候,心里第一反应是我做这件事能给我带来多少收获。


比如读一本书,其实很多时候我们都是带有目的性的,比如觉得事业不顺,人生失意,或者想赚快钱,那么这时候就会去快速翻阅一些诸如《快速致富》的书籍,然后加满鸡血后,第二天依旧是十二点起,起来又卷入精神内耗中,反反复复,最终宝贵是时光!


又比如你看到别人赚到了钱,于是眼睛一红,就问他怎么赚的,别人稍微指点后,你就暗下决心要搞钱,前几天到几个月期间赚了几块钱,你就失落了,你在想,这条路子行不通,于是就放弃了,又去折腾其它的了。


上述的例子是百分之九十的人的真实写照,那么我觉得可以总结为两点:


1.只要没有得到应有的回报,就觉得是损失


2.极强的功利主义


首先对于这一点,我觉得是我们最容易犯的错,比如当一个人说你去坚持做这件事情,一个月会有一千的附加收入,你去做了,而实际上只拿到了50元的收入,这时候你就会极度的不平衡,感到愤怒,你会觉得花了这么多时间才得到50元,老子不干了,实际上你在这个过程中学到的东西远比1000块多,不过你不会觉得,这时候你宁愿去刷短视频,追剧,你也不会去做这件事了。


所以当你心中满是“付出多少就应该得到多少回报”的时候,你不可能做好事,也不会得到更好的回报,因为你心中总是在想“会不会0回报”,“这玩意究竟靠谱不靠谱”,克服这种心态是一件十分难的事情!


第二点,我觉得我们应该少一点功利主义,多一点傻逼似的坚持,这说得有点理想主义了,人本质就是贪婪的,如果赚不到钱,我就不做,对我没好处,我也不会做,我有写文章的习惯其实从大学就开始了,以前没发公众号,之前朋友经常说我,你写的有什么卵用?能赚钱吗?有人看吗?


一开始我还会在乎,在问自己,你干嘛写这些,因为写个人的感悟和生活这种文章确实会有一定的心里压力,朋友说:”你自己都是这个鸟样,有什么资格去给别人说教“,不过随着时间的推移,我不再去在乎这些了。


就单拿写文章这件事来说,虽然没赚到钱,不过在这个过程中,我逐渐不再浮躁,能静下心来写,也结实了朋友,这是一种对自己的总结,对技术的总结,也是一种锻炼,虽然现在文笔依然很差,不过我依然会像一个傻逼一样去坚持。


时间是最奇妙的东西,你的一些坚持一定会在相应的时间点迸发处惊人的力量!


回头想一下,你没写文章,没看书,没学习,没出去看世界,而是拿着个手机躺在床上刷短视频,像个清朝抽鸦片的人一样,那么你又收获了多少呢?


作者:刘牌
链接:https://juejin.cn/post/7278245506719825955
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

Vision pro,当一切“眼见为实”

iOS
关于 Vision pro,留存一点感想,或许十年后再来回顾。缺点肯定不少,但是这个产品带来了很有趣的新维度 WWDC直播时,最大的疑问是眼动追踪交互足够准确吗?能即时反馈吗?看过各位媒体的文字或口述体验之后,才知道苹果竟然将这种交互方式做得像来自未来一样,...
继续阅读 »

关于 Vision pro,留存一点感想,或许十年后再来回顾。缺点肯定不少,但是这个产品带来了很有趣的新维度




WWDC直播时,最大的疑问是眼动追踪交互足够准确吗?能即时反馈吗?看过各位媒体的文字或口述体验之后,才知道苹果竟然将这种交互方式做得像来自未来一样,通过你的目光,精准得可以定位到一个小小的字母,随时随地随心而动,简直不可思议。


直播中所展示的三维交互效果,让我想起人类对信息的记录和显示方式。文字、图画刻在石头上,刻在竹简上,纸墨印刷成书册;照片、视频,呈现在手机或者电脑的二维屏幕上。而三维信息,可能从未被如此精确真实地呈现在我们的面前。诚然 3D 电影和许多别的 VR、AR 生产厂商也做了诸多努力和探索,但是那些效果都还不足以 “以假乱真”。从现在开始,消费级的信息记录方式,或许又能上升一个维度。


而直播中最大的震撼其实是迪士尼宣传片的一连串 What if...What if all the things we thought impossible were suddenly possible🤯一连串电影和想象中的角色和景观展现在我的眼前,和现实仿若融为一体。这让我感受到 Vision pro 或许能用一种全新的交互体验,让观众真正地身临其境,沉浸其中,甚至忘记真实和虚幻的界限。


直播接近尾声时,BGM 反复唱着 Be a Dreamer,苹果是个实干的梦想家,他们有足够的底气和积淀去梦想,更用努力和科技,将不可能变成可能,将许许多多科幻片中的梦想带来到 2023 年,用 Vision pro 为所有人铺就了无限大的画布,哦,不是二维的画布,是无限大的梦想空间!


当然,这个空间,目前似乎还只有基础的系统应用,像个刚通水电的毛坯房。他究竟能有怎样的表现,还是得看这些内容生产者开发出怎样的内容。有人诟病苹果在六月份拿出来这样的宣传,却要在明年年初才能售卖。可我相信,过去 APP Store 的成功很可能会在 Vision OS 中再现。WWDC,是苹果开发者大会,即主要面向开发者等专业人士的会议。Apple 召集起这些媒体,摄影师,导演,应用和游戏开发者率先开始了解 Vision pro。这些内容生产者,有他们,就有了 dream maker,造梦人,为普通用户编织光怪陆离的绚烂梦境。


看完各位博主的真机体验,Vision pro 并不是一个取代现有的手机、电脑的产品,这是一个全新的,开创新的体验维度,开创人类新需求的产品。


作为多年的哈迷,感觉现在 Vision pro 的语音输入,手势识别和 3D 交互完全可以让我们拿着魔杖释放咒语,让我们和神奇动物面对面,让我们就像骑着飞天扫帚一样去追踪金色飞贼。因为有了如此先进的科技,魔法世界不再是幻想🥺🤩


更可以想象,无论是工业设计,照片、视频、电视电影还是游戏,都可能会因为这种全新的沉浸式的三维交互体验,而被改写。


你可以和同事一起在虚拟空间中建造模型,模拟生产制造流程。


你可以把与亲朋好友、猫猫狗狗共度的美好时光定格在一片似真似幻的空间。无论何时再回首,他们好像永远在你身边,永不褪色。


电影制作人未来可以使用专门的摄像机制作沉浸式三维电影,在家就能有 100 英尺,接近 30 米的巨幕享受。 篮球比赛你可以选择不同的机位跟踪你喜欢的球星和精彩瞬间。 演唱会你可以在任何地方躺下享受最佳视角和空间音效。


而游戏,新增的交互体验更是给了游戏制作人们无限的想象空间。 操控赛车从北极的冰川到热带的雨林;在枪林弹雨中和队友并肩对抗敌人;在球赛场上面对面激情碰撞。配合上 AI 和语言模型,喜欢的二次元角色仿佛搭着你的肩膀和你耳语;所有的一切,开始“眼见为实”。


Vision pro 的眼部追踪、手势交互和 3D 显示混合现实的完成度,带来了像当年 iPhone 实现多点触控的革命性质变。从技术的进步来说,我个人认为这次的质变可能更加惊艳,更加了不起。但是 3499 美元,加上税可能 3 万人民币。说实话,这不是一个大众消费者能够接受的价格。即使有 air 版本,我感觉可能也需要上万人民币。所以,个人估计,它受欢迎的程度应该会大于等于 mac 小于 iPhone 和 AirPods。


当年 Apple Macintosh 开创了精美的高完成度的计算机图形界面 GUI,让电脑走入消费者群体,可价格太贵,真正让个人电脑普及的是微软;现在,iOS 将手机变成了一个功能强大的多媒体设备,可价格不便宜,真正让千家万户享受到智能手机的是 Android。未来,相比 Vision pro,或许有其他品牌的廉价替代品,更开放的开源混合现实系统,Vision 系列或许都不能获得最大的市场份额。可是由 Vision 所真正掀开的新维度不会被关闭,这个被精心打造出来的梦想空间只会无限延伸,满载着人类的创意和梦想…最后的最后,正如 WWDC 中 Apple 所言:


Be a dreamer. This is just the START.


作者:VickyLu
链接:https://juejin.cn/post/7242186721785708604
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

SwiftUI 入门教程 - 基础控件

iOS
SwiftUI 是 Apple 新推出的一款能快速搭建页面的 framework。它采用的是声明式语法,简洁明了。 而且它是所见即所得的,你写的代码都能通过 Preview 实时的看到效果,这可以很大的节省开发者开发时间。当你开发一个复杂的项目,需要等待几分钟...
继续阅读 »

SwiftUI 是 Apple 新推出的一款能快速搭建页面的 framework。它采用的是声明式语法,简洁明了。


而且它是所见即所得的,你写的代码都能通过 Preview 实时的看到效果,这可以很大的节省开发者开发时间。当你开发一个复杂的项目,需要等待几分钟的时间去编译运行代码,只为了看一个 UILabel 字体大小或者颜色是否改变时,你就能体会到所见即所得的快乐了。


基础控件


当我们新建一个项目,选择 Interface 选择 SwiftUI 时,建好的项目会自带一个 ContentView,这是下面的默认代码:

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

ContentView 是需要我们根据需求修改代码的部分,下面的 ContentView_Previews 则是为了实时预览的。


Tips:如果注释ContentView_Previews,你会发现预览页面也会消失。


ContentView 代码说明


首先,可以看到 ContentView 有一个 body 的计算属性,该属性代表当前视图的内容。当你实现一个自定义 view 的时候,必须要实现该属性,否则代码会报错。


VStack 代表的是一个垂直布局。里面包含 Image 和 Text,两个控件垂直布局。padding 则代表当前视图外边距的间距。


Text 对应 UILabel


在 SwiftUI 中,用 Text 控件来展示静态文本。下面是它的代码示例:

Text("我是一个文本")
.font(.title)
.foregroundColor(.red)
.frame(width: 100, alignment: .center)
.lineLimit(1)
.background(.yellow)

常用的属性基本就这几个:


  • font:字体。如果想更加细致化的指定字体,可以用 system,.font(.system(size: 16, weight: .light))
  • foregroundColor:字体颜色。
  • frame:控制文本的大小和对齐位置。这个不写的话默认是自适应宽高。如果仅指定宽度就是高度自适应,仅指定高度就是宽度自适应。
  • lineLimit:指定行数,默认为 0,不限制行数。
  • background:用来设置背景。比如背景形状、背景颜色等等。

Tips:SwiftUI 的布局简化了自动布局和弱化了 frame 指定具体数值的布局方式。默认都是自适应的,这一点和 Flutter 类似,大大提高了开发效率。


Image 对应 UIImageView


在 SwiftUI 中,Image 用来展示图像资源。下面是它的示例代码:

Image(systemName: "globe")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.accentColor)
.background(.red)

常用属性:


  • resizable:可调整大小以适应当前布局。
  • aspectRatio:调整缩放比。
  • foregroundColor、background:参见 Text。

Button 对应 UIButton


在 SwiftUI 中,用 Button 来表示一个按钮。下面是它的示例代码:

Button {
print("点击了按钮")
} label: {
Text("按钮文本")
Image(systemName: "globe")
}
.cornerRadius(10)
.background(.red)
.font(.body)
.border(.black, width: 2)

常用属性:


  • font、foregroundColor、background 等属性与 Text 使用一致。
  • label:用来自定义按钮的文本和图标。
  • cornerRadius:设置圆角。
  • border:设置边框。

总结


本文主要讲解了 SwiftUI 的三个基本控件 Text:用来展示静态文本;Image:用来加载图像资源;Button:用来展示按钮。以及三个控件的基本使用。希望通过此文大家可以对 SwiftUI 的语法有个基本的了解。


作者:冯志浩
链接:https://juejin.cn/post/7239178749153525816
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

中国未来楼市,程序员的小窝购买指南

中国楼市持续火爆,未来趋势如何?中国楼市在过去的几年里一直保持着火爆的态势,无论是房价还是成交量都不断创下新高。本文将从中国楼市的背景介绍、市场分析、原因分析和未来展望等方面进行分析。一、背景介绍中国楼市的发展可以追溯到上世纪80年代,当时城市土地开始实行私有...
继续阅读 »

中国楼市持续火爆,未来趋势如何?

中国楼市在过去的几年里一直保持着火爆的态势,无论是房价还是成交量都不断创下新高。本文将从中国楼市的背景介绍、市场分析、原因分析和未来展望等方面进行分析。

一、背景介绍

中国楼市的发展可以追溯到上世纪80年代,当时城市土地开始实行私有化改革,房地产市场逐渐形成。随着经济的快速发展和城市化进程的加速,中国楼市也迎来了飞速发展的时期。特别是2000年以来,房地产市场逐渐成为国民经济的重要支柱产业,政府也出台了一系列扶持政策,如住房制度改革、住房公积金制度等。

二、市场分析

  1. 投资者热衷于购房

随着人们生活水平的提高和购房政策的宽松,越来越多的投资者热衷于购房。他们将购房视为一种投资手段,认为房价会持续上涨,从而获得更多的收益。这种投资需求的增加也推高了中国楼市的房价。

  1. 房贷违约案例逐渐增多

随着楼市的火爆,越来越多的人选择贷款购房。然而,近年来房贷违约案例逐渐增多,给银行和房地产市场带来了不小的风险。部分购房者由于收入不稳定、贷款利率上升等原因,无法按时偿还房贷,导致违约。

三、原因分析

  1. 政策调控

中国政府对房地产市场的调控政策对市场的影响非常大。例如,政府出台的“国八条”、“限购令”等政策,对楼市进行了严格的调控,使得市场逐渐回归理性。

  1. 利率变化

利率的变化也是影响楼市的重要因素之一。在利率较低的时候,购房者可以获得更低的贷款利率,从而降低了购房成本,提高了购房需求。而在利率较高的时候,购房者的负担加重,购房需求相应减少。

  1. 人口因素

中国拥有庞大的人口基数,这也为房地产市场提供了广阔的需求空间。特别是在城市化进程加速的情况下,大量人口涌入城市,使得城市房屋需求不断增长。

四、未来展望

  1. 政策调整

未来中国政府可能会对楼市政策进行适当调整。一方面,政府将继续加强对房地产市场的监管,抑制房价过快上涨;另一方面,政府可能会出台更加优惠的购房政策,鼓励刚需和改善型购房者购房。

  1. 经济环境的变化

中国经济的发展也可能会对中国楼市产生影响。未来中国经济可能会逐渐转型,从传统的制造业向服务业和高科技产业转型。这种转型可能会导致人们对住房的需求发生变化,对楼市产生一定的影响。

综上所述,中国楼市在经历了一段飞速发展的时期后,目前仍处于较为火热的态势。然而,受到政策调控、利率变化、人口因素等多种因素的影响,楼市也面临一定的挑战。未来,中国楼市将如何在政策调整和经济环境的变化中寻找新的发展方向,值得我们进一步关注和研究。

收起阅读 »

iOS 电商倒计时

iOS
背景 最近项目中,需要做一个如图所示的倒计时控件,上网搜了一圈,发现大家的方法大同小异,都是把倒计时的秒,转换成时分秒然后拼接字符串,见下图 网上大部分采用的方法 juejin.cn/post/684490…  在我的项目中,期望这个倒计时控件的f...
继续阅读 »

背景


最近项目中,需要做一个如图所示的倒计时控件,上网搜了一圈,发现大家的方法大同小异,都是把倒计时的秒,转换成时分秒然后拼接字符串,见下图




网上大部分采用的方法
juejin.cn/post/684490… 



在我的项目中,期望这个倒计时控件的format是可以自定义的,所以计算时分秒这样的方式,对于我的需求是不太灵活的


既然format需要自定义,那么很容易想到一个时间格式处理的类:DateFormatter


思路


后端返回的字段

init_time // 需要倒计时的时长,单位ms
format // 展示的倒计时格式

我们的需求其实非常明确,就是完成一个可以自定义format的倒计时label


那我们拆解一下整个需求:

  • 自定formatlabel
    • Date自定义format显示
    • 指定Date自定义format显示
  • 可以进行倒计时功能
  • 那么我们怎么才能把要倒计时的时长,转换为时分秒呢?

    • 直接计算后端给的init_time,算出是多少小时,多少分钟,多少秒
    • 如果我从每天的零点开始计时,然后把init_time作为偏移量不就是我要倒计时的时间吗,而且这个可以完美解决需要自定义format的问题,Date可以直接通过 DateFormatter转化成字符串 



Date自定义format显示

let df = DateFormatter()

df.dateFormat = "hh:mm:ss"

print("🍀", df.string(from: Date()), "🍀\n\n")

输出:🍀 03:56:28 🍀

指定Date自定义format显示

let df = DateFormatter()

var calendar = Calendar(identifier: .gregorian)

let startOfDate = calendar.startOfDay(for: Date())

df.dateFormat = "hh:mm:ss"

print("🍀", df.string(from: startOfDate), "🍀\n\n")

输出:🍀 12:00:00 🍀

完整功能

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        initCountdownTimer()

        return true

    }

private var timer: DispatchSourceTimer?

private var second = 0

// 单位ms
var delayTime = 0

// 单位ms
var interval = 1000
var initSecound = 10

var format = "hh:mm:ss"

private lazy var startDate: Date = {
var calendar = Calendar(identifier: .gregorian)
        let startOfDate = calendar.startOfDay(for: Date())
        return Date(timeInterval: TimeInterval(initSecound), since: startOfDate)
  }()

  private lazy var df: DateFormatter = {
let df = DateFormatter()
        df.dateFormat = format
        return df
  }()

  func initCountdownTimer() {
        timer = DispatchSource.makeTimerSource(queue: .main)
        timer?.schedule(deadline: .now() + .milliseconds(delayTime), repeating: .milliseconds(interval), leeway: .milliseconds(1))
        timer?.setEventHandler { [weak self] in
            self?.updateText()
            self?.second += 1
        }

        timer?.resume()
    }

    func deinitTimer() {
        timer?.cancel()
        timer = nil
    }

    func updateText() {
        if second == initSecound && second != 0 {
            deinitTimer()
        }
        if second == initSecound {
            return
        }
        let date = Date(timeInterval: -TimeInterval(second + 1), since: startDate)
        let text = df.string(from: date)

        print(text)
    }

输出:
12:00:09
12:00:08
12:00:07
12:00:06
12:00:05
12:00:04
12:00:03
12:00:02
12:00:01
12:00:00

以上整个功能基本完成,但是细心的同学肯定发现了,按道理小时部分应该是00,但是实际是12,这是为什么呢,为什么呢?


我在这里研究了好久,上网查了很多资料


最后去研究了foramt每个字母的意思才知道:

  • h 代表 12小时制

  • H 代表 24小时制,如果想要显示00,把"hh:mm:ss"改成"HH:mm:ss"即可


时间格式符号字段详见


作者:xiAo_Ju
链接:https://juejin.cn/post/7240303252930609207
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »