注册
环信即时通讯云

环信即时通讯云

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

环信开发文档

Demo体验

Demo体验

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

RTE开发者社区

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

技术讨论区

技术交流、答疑
资源下载

资源下载

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

iOS Library

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

Android Library

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

Vue和React权限控制的那些事

web
自我介绍 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出吧。 前言 无论是后台管理系统,还是面向C端的产品,权限控制都是日常工作中常见的需求。在此梳理一下权限控制的那些逻辑,以及在Vue/React框架下是有什么样的解决方案。 ...
继续阅读 »

自我介绍


看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出吧。


前言


无论是后台管理系统,还是面向C端的产品,权限控制都是日常工作中常见的需求。在此梳理一下权限控制的那些逻辑,以及在Vue/React框架下是有什么样的解决方案。


什么是权限控制?


现在基本上都是基于RBAC权限模型来做权限控制


一般来说权限控制就是三种




  • 页面权限:说白了部分页面是具备权限的,没权限的无法访问




  • 操作权限:增删改查的操作会有权限的控制




  • 数据权限:不同用户看到的、数据是不一样的,比如一个列表,不同权限的查看这部分数据,可能有些字段是**脱敏的,有些条目无法查看详情,甚至部分条目是无法查看




那么对应到前端的维度,常见的就4种




  • 权限失效(无效)(token过期/尚未登录)




  • 页面路由控制,以路由为控制单位




  • 页面上的操作按钮、组件等的权限控制,以组件/按钮为最小控制单位




  • 动态权限控制,比如1个列表,部分数据可以编辑,部分部分不可编辑




image.png


⚠️注意: 本文一些方案 基于 React18 React-Router V6 以及 Vue3 Vue-Router V4


⚠️Umi Max 这种具备权限控制系统的框架暂时不在讨论范围内~~


前置条件


由于市面上各家实现细节不一样,这里只讨论核心逻辑思路,不考虑细节实现


无论框架如何,后端根据RABC角色权限这套逻辑下来的,会有如下类似的权限标识信息,可以通过专门的接口获取,或者跟登录接口放在一起。


image.png


然后根据这些数据,去跟路由,按钮/组件等,比对产生真正的权限


像这种权限标识一般都存在内存当中(即便存在本地存储也需要加密,不过其实真正的权限控制还是需要后端来控),一般都是全局维护的状态,配合全局状态管理库使用。


权限失效(无效)


image.png


这种场景一般是在发送某些请求,返回过期状态


或者跟后端约定一个过期时间(这种比较不靠谱)


通常是在 全局请求拦截 下做,整理一下逻辑


路由级别权限控制


通常前端配好的路由可以分为 2 种:


一种是静态路由:即无论什么权限都会有的,比如登录页、404页这些


另一种是动态路由:虽然叫动态路由,其实也是在前端当中定义好了的。说它是动态的原因是根据后端的权限列表,要去做动态控制的


vue实现


在vue体系下,可以通过路由守卫以及动态添加路由来实现


动态路由


先配置静态路由表 , 不在路由表内的路由重定向到指定页(比如404)


在异步获取到权限列表之后,对动态部分的路由进行过滤之后得到有权限的那部分路由,再通过router.addRoute()添加到路由实例当中。


流程为:


(初始化时) 添加静态路由 --> 校验登录态(比如是否有token之类的) --> 获取权限列表(存到vuex / pinia) --> 动态添加路由(在路由守卫处添加)



rightsRoutesList // 来自后端的当前用户的权限列表,可以考虑存在全局状态库
dynamicRoutes // 动态部分路由,在前端已经定义好, 直接引入

// 对动态路由进行过滤,这里仅用path来比较
// 目的是添加有权限的那部分路由,具体实现方案自定。
const generateRoute = (rightsRoutesList)=>{
//ps: 这里需要注意下(如果有)嵌套路由的处理
return dynamicRoutes.filter(i=>
rightsRoutesList.some(path=>path === i.path)
)
}

// 拿到后端返回的权限列表
const getRightsRoutesList = ()=>{
return new Promise(resolve=>{
const store = userStore()
if(store.rightsRoutesList){
resolve(store.rightsRoutesList)
}else{
// 这里用 pinia 封装的函数去获取 后端权限列表
const rightsRoutesList = await store.fetchRightsRoutesList()
resolve(rightsRoutesList)
}
}
}

let hasAddedDynamicRoute = false
router.beforeEach(async (to, from) => {
if(hasAddedDynamicRoute){
// 获取
const rightsRoutesList = await getRightsRoutesList()

// 添加到路由示例当中
const routes = generateRoute(rightsRoutesList)
routes.forEach(route=>router.addRoute(route))
// 对于部分嵌套路由的子路由才是动态路由的,可以
router.addRoute('fatherName',route)
hasAddedDynamicRoute = true
}
// 其他逻辑。。。略


next({...to})
}


踩坑

通过动态addRoute去添加的路由,如果你F5刷新进入这部分路由,会有白屏现象。


image.png


因为刷新进入的过程经历了 异步获取权限列表 --> addRoute注册 的过程,此时跳转的目标路由就和你新增的路由相匹配了,需要去手动导航。


因此你需要在路由守卫那边next放行,等下次再进去匹配到当前路由


你可以这么写


router.beforeEach( (to,from,next) => {
// ...其他逻辑

// 关键代码
next({...to})
})


路由守卫


一次性添加所有的路由,包括静态和动态。每次导航的时候,去对那些即将进入的路由,如果即将进入的路由是在动态路由里,进行权限匹配。


可以利用全局的路由守卫


router.beforeEach( (to,from,next) => {
// 没有访问权限,则重定向到404
if(!hasAuthorization(to)){
// 重定向
return '/404'
}
})

也可以使用路由独享守卫,给 权限路由 添加


    // 路由表
const routes = [
//其他路由。。。略

// 权限路由
{
path: '/users/:id',
component: UserDetails,
// 定义独享路由守卫
beforeEnter: (to, from) => {
// 如果没有许可,则
if(!hasAuthorization(to)){
// 重定向到其他位置
return '/404'
}
},
},
]


react实现


在react当中,一般先将所有路由添加好,再通过路由守卫来做权限校验


局部守卫loader


React-router 当中没有路由守卫功能,可以利用v6版本的新特性loader来做,给权限路由都加上对应的控制loader


import { redirect, createBrowserRouter, RouterProvider } from 'react-router-dom'


const router = createBrowserRouter([
{
// it renders this element
element: <Team />,

// when the URL matches this segment
path: "teams/:teamId",

// with this data loaded before rendering
loader: async ({ request, params }) => {
// 拿到权限
const permission = await getPermission("teams/:teamId")
// 没有权限则跳到404
if(!permission){
return redirect('/404')
}
return null
},

// and renders this element in case something went wrong
errorElement: <ErrorBoundary />,
},
]);

// 使用
function RouterView (){
return (
<RouterProvider router={router}/>
)
}



包装路由(相当于路由守卫)


配置路由组件的时候,先渲染包装的路由组件


image.png


在包装的组件里做权限判断


function RouteElementWrapper({children, path, ...props }: any) {
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(()=>{
// 判断登录态之类的逻辑

// 如果要获取权限,则需要setIsLoading,保持加载状态

// 这里判断权限
if(!hasAccess(path)){
navigate('/404')
}
},[])
// 渲染routes里定义好的路由
return isLoading ? <Locading/> : children
}

按钮(组件)级别权限控制


组件级别的权限控制,核心思路就是 将判断权限的逻辑抽离出来,方便复用。


vue 实现思路


在vue当中可以利用指令系统,以及hook来实现


自定义指令


指令可以这么去使用


<template>
<button v-auth='/site/config.btn'> 编辑 </button>
</template>

指令内部可以操作该组件dom和vNode,因此可以控制显隐、样式等。


hook


同样的利用hook 配合v-if 等指令 也可以实现组件级颗粒度的权限控制


<template>
<button v-if='editAuth'> 权限编辑 </button>
<div v-else>
无权限时做些什么
</div>

<button v-if='saveAuth'> 权限保存 </button>
<button v-if='removeAuth'> 权限删除 </button>
</template>
<script setup>
import useAuth from '@/hooks/useAuth'
// 传入权限
const [editAuth,saveAuth,removeAuth] = useAuth(['edit','save','remove'])
</script>


hook里的实现思路: 从pinia获取权限列表,hook里监听这个列表,并且匹配对应的权限,同时修改响应式数据。


react 实现思路


在React当中可以用高阶组件和hook的方式来实现


hook


定义一个useAuth的hook


主要逻辑是: 取出权限,然后通过关联响应式,暴露出以及authKeys ,hasAuth函数


export function useAuth(){
// 取出权限 ps: 这里从redux当中取
const authData = useSelector((state:any)=>state.login)
// 取出权限keys
const authKeys = useMemo(()=>authData.auth.components ?? [],[authData])
// 是否拥有权限
const hasAuth = useCallback(
(auths:string[]|string)=>(
turnIntoList(auths).every(auth=>authKeys.includes(auth))
),
[authKeys]
)
const ret:[typeof authKeys,typeof hasAuth] = [authKeys,hasAuth]
return ret
}

使用


const ProductList: React.FC = () => {
// 引入
const [, hasAuth] = useAuth();
// 计算是否有权限
const authorized = useMemo(() => hasAuth("edit"), [hasAuth]);

// ...略
return (
<>
{ authorized ? <button> 编辑按钮(权限)</button> : null}
</>

)
};


权限包裹组件


可以跟进一步,依据这个权限hook,封装一层包裹组件


const AuthWrapper:React.FC<{auth:string|string[],children:JSX.Element}> = ({auth, children})=>{
const [, hasAuth] = useAuth();
// 计算是否有权限
const authorized = useMemo(() => hasAuth(auth), [hasAuth]);
// 控制显隐
return authorized ? children : null
}

使用


<AuthWrapper auth='edit'>
<button> 编辑按钮(AuthWrapper) </button>
</AuthWrapper>

还可以利用renderProps特性


const AuthWrapper:React.FC<{auth:string|string[],children:JSX.Element}> = ({auth, children})=>{
const [, hasAuth] = useAuth();
// 计算是否有权限
const authorized = useMemo(() => hasAuth(auth), [hasAuth]);
+ if(typeof children === 'function'){
+ return children(authorized)
+ }
// 控制显隐
return authorized ? children : null
}

<AuthWrapper auth='edit'>
{
(authorized:boolean)=> authorized ? <button> 编辑按钮(rederProps) </button> : null
}
</AuthWrapper>

动态权限控制


这种主要是通过动态获取到的权限标识,来控制显隐、样式等。可以根据特定场景做特定的封装优化。主要逻辑其实是在后端处理。


结尾


可以看到在两大框架下实现权限控制时,思路和细节上还是稍稍有点不一样的,React给人的感觉是手上的积木更加零碎的一点,有些功能需要自己搭起来。相反Vue给人的感觉是面面俱到,用起来下限会更高。


最后


如果大家有什么想法和思考,欢迎在评论区留言~~。


另外:本人经验有限,

作者:JetTsang
来源:juejin.cn/post/7242677017034915899
如果有错误欢迎指正。

收起阅读 »

环信十周年趴——我的程序人生

        2011年,那时候刚上大学,计算机科学与技术专业,大家都是很迷茫的。尤其是大一的时候,面对十几门专业课程(《C语言程序设计》、《C++程序设计》、《微机原理》、《单片机原理》、《算法导论》、《数据结构》、...
继续阅读 »

        2011年,那时候刚上大学,计算机科学与技术专业,大家都是很迷茫的。尤其是大一的时候,面对十几门专业课程(《C语言程序设计》、《C++程序设计》、《微机原理》、《单片机原理》、《算法导论》、《数据结构》、《计算机原理》、《逻辑与数字电路》、《高数》、《线性代数》等)的时候,实在是没有一点儿想法。也不知道该学哪个,该丢哪个。但是要想不挂科,还是得雨露均沾,学习时间平均分配。印象最深的课程就是《C语言程序设计》,刚开始学真是一脸懵,这是啥,这玩意儿有啥用?用来干啥的?然后迫于对知识的渴求,努力学了,也是大学课程里面学得最好的专业,这也是后来为啥成为了iOS开发。

        我是一名曾经从事iOS开发工作的程序员,在这个行业中度过了多年的光阴。但是,在某个时刻,我的程序人生彻底转向了一个不同的方向,那就是我遭遇了落魄的iOS开发之路。

        落魄经历

        在我的iOS开发过程中,我也遇到了很多挑战和困难。其中最重要的一点是,随着竞争日益激烈,市场需求变得更加严格和复杂,我的职业前景开始黯淡下来。

        从那之后,我开始频繁地跳槽,但是并没有找到一个令我满意的岗位。在移动应用开发的生态系统中,iOS领域的变化是惊人的。新的技术和框架不断驱动和推动着市场和用户需求的变化,而这个速度比任何其他应用领域都要快。但我并没有保持这个变化的步伐,慢慢地,我的技能逐渐落后,导致我的职业发展受到了影响。

        在这个过程中,我开始感到自己正在与市场和软件发展的步伐背道而驰。我不再能够适应市场需求和客户的期望,我甚至感觉到自己的运气都已经耗尽了。

        逆境中的人生反思

        在落魄的状态下,我开始经历了一段自我反思的旅程。我开始回顾自己的职业生涯,思考我所从事的工作和做出的决策是否真的为我带来了成就感和满足感。我也开始考虑其他领域和技能的发展可能性。

        这段旅程让我意识到,“业内良心”(本意是良心味道的事物)这个说法是存在的,它深刻地体现了我在这个行业中的经历。与此同时,我也意识到,真正的成功无法用市场或行业发展的脉搏来衡量,而是要经由自己的内心感觉。

        在自我反省的过程中,我也发现了自己职业规划和发展的不足之处。我没有及时了解新技术和框架的发展趋势,并没有花费足够的精力和时间来提高自己的职业素养和思考能力。这让我在竞争激烈的市场中不断失利。

         今天,虽然我没有从事iOS开发了,但我始终没有忘记自己所学到的知识和经验。落魄的经历让我成为一个更好的Programmer,坚持自己的初心且不断进取。不管你们做着什么,无论遇到什么困境,都请不要磨灭自己的热情和信念。这就是我从我的落魄经历中得到的宝贵经验。


本文参与环信十周年活动 ,活动链接:https://www.imgeek.net/question/474026

收起阅读 »

Vue3项目实现图片实现响应式布局和懒加载

web
Element实现响应式布局 分享一下,在Vue3项目中实现响应式布局(一行显示7列)。在这个例子中,我参考了Element官方的Layout布局,使用el-card来放置图片。 利用分栏布局,el-row行上设置每列的间隔gutter,el-col上设置响应...
继续阅读 »

Element实现响应式布局


分享一下,在Vue3项目中实现响应式布局(一行显示7列)。在这个例子中,我参考了Element官方的Layout布局,使用el-card来放置图片。
利用分栏布局,el-row行上设置每列的间隔gutter,el-col上设置响应式的栅格布局,Element官方预设了5个响应式尺寸,官方给出了详细的属性解释。这个例子中我设置了4个尺寸。
在这里插入图片描述
栅格默认的占据的列数是24,设置24就是一列,设置12就显示两列,设置8就显示3列,设置6就显示4列,设置4显示6列......可以根据自己的场景需求来进行布局。这个例子中我设置的响应式布局如下:



:xs="12" 当浏览器宽度<768px时,一行展示2列

:sm="8" 当浏览器宽度>=768px时,一行展示3列

:md="6" 当浏览器宽度>=992px时,一行展示4列

:lg="{ span: '7' }" 当浏览器宽度>=1200px时,一行展示7列 这个需要在css样式中设置一下。



这里例子中的图片都是放在el-card中的,并且图片都是一样大小的。修改图片可以利用图片处理工具,分享一个自己常用的工具:轻量级图片处理工具photopea
Element的Card组件由 header 和 body 组成。 header 是可选的,卡片可以只有内容区域。可以配置 body-style 属性来自定义body部分的style。
:body-style="{padding:10px}" ,这个其实是对el-card头部自动生成的横线下方的body进行边距设置。也就是除了el-card的header部分,其余都是body部分了。
这里例子中没有头部,就是给卡片的body部分设置内边距。
在这里插入图片描述
具体代码如下所示:
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
图片效果如下所示:
当浏览器宽度>=1200px时,一行展示7列:
图片14.png


当浏览器宽度>=992px时,一行展示4列:


图片15.png
当浏览器宽度>=768px时,一行展示3列:


图片16.png
当浏览器宽度<768px时,一行展示2列:


图片17.png
接下来,优化一下页面,对图片进行懒加载处理。


图片懒加载


看下上面没有用于懒加载方式的情况,F12---NetWork---Img可以看到页面加载就会显示这个页面用到的所有图片。


图片18.png
可以利用vue-lazyload,它是一个Vue.js 图片懒加载插件,可以在页面滚动至图片加载区域内时按需加载图片,进一步优化页面加载速度和性能。采用懒加载技术,可以仅在需要加载的情况下再进行加载,从而减少资源的消耗。也就是在页面加载时仅加载可视区域内的图片,而对于网页下方的图片,我们滑动到该图片时才会进行加载。


下载、引入vue-lazyload


npm install vue-lazyload --save


在package.json中查看:


图片19.png
在main.ts中引入:


图片20.png


使用vue-lazyload


在需要使用懒加载的图片中使用v-lazy指令替换src属性。


图片21.png
也可以是下面的写法:


图片22.png
这样,就实现图片的懒加载了。
验证一下懒加载是否生效,F12---NetWork---Img,可以看到图片的加载情况。


一开始没有滑动到图片区域,就不会加载图片,可以在Img中看到loding占位图片在显示。


图片23.png
滑动到了对应的照片才会显示对应的图片信息。


图片24.png


图片25.png


图片26.png


作者:YM13140912
来源:juejin.cn/post/7242516121769033787
>这就实现了懒加载。

收起阅读 »

Android-apk动态加载研究

前言 近期工作中遇到两个问题。 换应用皮肤 加载插件apk中的view Android 换肤技术一文中已经详细说明了如何进行应用换肤。而加载插件apk中的view,利用前文提到的换肤技术,居然无法实现!仔细重新研究Android apk动态加载机制,有了新...
继续阅读 »

前言


近期工作中遇到两个问题。



  • 换应用皮肤

  • 加载插件apk中的view


Android 换肤技术一文中已经详细说明了如何进行应用换肤。而加载插件apk中的view,利用前文提到的换肤技术,居然无法实现!仔细重新研究Android apk动态加载机制,有了新的发现,而且还可以提高插件加载效率。


布局加载


Android 换肤技术文中提到的加载插件图片方法,无法加载插件的布局文件。怎么尝试都是失败。布局文件是资源中最复杂的,需要解析xml中的其它元素,虽然布局文件的id可以获取,但xml中其它元素的id或者其它关联性的东西仍然无法获取,这应该就是加载插件布局文件失败的原因。


宿主应用无法直播加载插件中的xml布局文件,换一个思路,插件将xml解析成view,将view传递给宿主应用使用。


插件apk需要使用插件context才能正确加载view,宿主如何生成插件context呢?

  public abstract Context createPackageContext(String packageName,
@CreatePackageOptions int flags)

context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY |
Context.CONTEXT_INCLUDE_CODE);

使用上述方法可正确创建插件context。


除此之外还有一种方法(本人没有验证过),activity的工作主要是由ContextImpl来完成的, 它在activity中是一个叫做mBase的成员变量。注意到Context中有如下两个抽象方法,看起来是和资源有关的,实际上context就是通过它们来获取资源的,这两个抽象方法的真正实现在ContextImpl中。也即是说,只要我们自己实现这两个方法,就可以解决资源问题了。我们在代码中可以创建activity继承类,重写对应方法即可。具体可参考 下文

/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();

动态加载方式


目前动态加载方式均是使用DexClassLoader方式获取对应的class实例,再使用反射调用对应接口,代码如下:

  DexClassLoader loader = new DexClassLoader(mPluginDir, getActivity().getApplicationInfo().dataDir, null, getClass().getClassLoader());
String dex = getActivity().getDir("dex", 0).getAbsolutePath();
String data = getActivity().getApplicationInfo().dataDir;
Class<?> clazz = loader.loadClass("com.okunu.demoplugin.TailImpl");
Constructor<?> constructor = clazz.getConstructor(new Class[] {});

这种方式存在一个问题,较为耗时,如果宿主管理着许多插件,这种加载方式就有问题,使用下面这种方式可加快插件的加载。

public void getTail2(Context pluginContext){
try {
Class clazz = pluginContext.getClassLoader().loadClass("com.okunu.demoplugin.TailImpl");
Constructor<?> localConstructor = clazz.getConstructor(new Class[] {});
Object obj = localConstructor.newInstance(new Object[] {});
mTail = new IPluginProxy(clazz, obj);
} catch (Exception e) {
Log.i("okunu", "ee", e);
e.printStackTrace();
}
}

注意,一定要使用插件的context为参数,它和插件的其它类使用同一个classloader,既然能获取插件classloader,则可以获取插件中的其它类。如果不使用插件context为参数,则上述方法一定会报错。


总结


针对插件资源加载,其实分为两种形式。



  • 宿主直接使用插件资源,比如使用插件图片、字符串等

  • 宿主间接使用插件资源,比如在宿主中启动插件activity或者显示插件的view


第1种形式,可以在宿主应用中构造AssetManager,添加插件的资源路径。


第2种形式,宿主创建插件context并传递给插件,插件使用自己的context则可自由调用自己的资源了,如何创建插件context前文详述了两种方法。


注意一点,宿主中肯定无法直接调用插件的R文件的。


动态加载apk,也有两种方式。



  • 使用DexClassLoader加载插件路径,获取插件的classLoader。

  • 使用已经创建好的插件context,获取插件的classLoader,效果和第1种一样,但速度要更快


动态加载apk机制还有很多东西可以深入研究,比如说插件activity的生命周期等等,这些内容后续补充。


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

Android屏幕刷新机制

基础知识 CPU、GPU CPU:中央处理器,主要负责计算数据,在Android中主要用于三大绘制流程中Surface的计算过程。 GPU:图像处理器,主要负责对图形数据进行渲染,在Android中主要用于将CPU计算好的Surface数据合成后放到buff...
继续阅读 »

基础知识


CPU、GPU



  • CPU:中央处理器,主要负责计算数据,在Android中主要用于三大绘制流程中Surface的计算过程。

  • GPU:图像处理器,主要负责对图形数据进行渲染,在Android中主要用于将CPU计算好的Surface数据合成后放到buffer中,让显示器进行读取呈现到屏幕上。


逐行扫描


屏幕在刷新buffer的时候,并不是一次性扫描完成,而是从左到右,从上到下的一个读取过程,顺序显示一屏的每个像素点,按60HZ的屏幕刷新率来算,这个过程只有16.66666...ms。



  • 从初始位置(第一行左上角)开始扫描,从左到右,进行水平扫描(Horizontal Scanning)

  • 每一行扫描完成,扫描线会切换到下一行起点,这个切换过程叫做水平消隐,简称 hblank(horizontal blank interval),并发送水平同步信号(horizontal synchronization,又称行同步)

  • 依次类推,整个屏幕(一个垂直周期)扫描完成后,显示器就可以呈现一帧的画面

  • 屏幕最后一行(一个垂直周期)扫描完成后,需要重返左上角初始位置,这个过程叫垂直消隐,简称 vblank(vertical blank interval)

  • 扫描线回到初始位置之后,准备扫描下一帧,同时发出垂直同步信号(vertical synchronization,又称场同步)。


image.png


显卡帧率


表示GPU在1s中内可以渲染多少帧到buffer中,单位是fps,这里要理解的是帧率是一个动态的,比如我们平时说的60fps,只是1s内最多可以渲染60帧,假如我们屏幕是静止的,则GPU此时就没有任何操作,帧率就为0.


屏幕刷新频率


屏幕刷新频率:屏幕在1s内去buffer中取数据的次数,单位为HZ,常见屏幕刷新率为60HZ。屏幕刷新率是一个固定值和硬件参数有关。也就是以这个频率发出 垂直同步信号,告诉 GPU 可以往 buffer 里写数据了,即渲染下一帧。


屏幕刷新机制演变过程


单buffer


GPU和显示器共用一块buffer


screen tearing 屏幕撕裂、画面撕裂


当只有一个buffer时,GPU 向 buffer 中写入数据,屏幕从 buffer 中取图像数据、刷新后显示,理想的情况是显卡帧率和屏幕刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有同步机制,很容易出现问题。

当显卡帧率大于屏幕刷新频率,屏幕准备刷新第2帧的时候,GPU 已经在生成第3帧了,就会覆盖第2帧的部分数据。

当屏幕开始刷新第2帧的时候,缓冲区中的数据一部分是第3帧数据,一部分是第2帧的数据,显示出来的图像就会出现明显的偏差,称为屏幕撕裂,其本质是显卡帧率和屏幕刷新频率不一致所导致。


双buffer


安卓4.1之前

基本原理就是采用两块buffer。

GPU写入的缓存为:Back Buffer

屏幕刷新使用的缓存为:Frame Buffer

因为使用双buffer,屏幕刷新时,frame buffer不会发生变化,通过交换buffer来实现帧数据切换。
什么时候就行buffer交换呢,当设备屏幕刷新完毕后到下一帧刷新前,因为没有屏幕刷新,所以这段时间就是缓存交换的最佳时间。

此时硬件屏幕会发出一个脉冲信号,告知GPU和CPU可以交换了,这个就是Vsync信号,垂直同步信号。
不可否认,双缓冲可以在很大程度上降低screen tearing错误,但是呢,还是会出现一些其他问题。


Jank 掉帧


如果在Vsync到来时back buffer并没有准备好,就不会进行缓存的交换,屏幕显示的还是前一帧画面,即两个刷新周期显示的是同一帧数据,称为Jank掉帧。


image.png
发生jank的原因是:在第2帧CPU处理数据的时候太晚了,GPU没有及时将数据写入到buffer中,导致jank的发生。

CPU处理数据和GPU写入buffer的时机比较随意。


Project Butter 黄油工程


安卓4.1
系统在收到VSync信号之后,马上进行CPU的绘制以及GPU的buffer写入。最大限度的减少jank的发生。


image.png
如果显卡帧率大于屏幕刷新频率,也就是屏幕在刷新一帧的时间内,CPU和GPU可以充分利用刷新一帧的时间处理完数据并写入buffer中,那么这个方案是完美的,显示效果将很好。


image.png
由于主线程做了一些相对复杂耗时逻辑,导致CPU和GPU的处理时间超过屏幕刷新一帧的时间,由于此时back buffer写入的是B帧数据,在交换buffer前不能被覆盖,而frame buffer被Display用来做刷新用,所以在B帧写入back buffer完成到下一个VSync信号到来之前两个buffer都被占用了,CPU无法继续绘制,这段时间就会被空着,于是又出现了三缓存。


三buffer


image.png
最大程度避免CPU空闲的情况。


Choreographer


系统在收到VSync信号之后,会马上进行CPU的绘制以及GPU的buffer写入。在安卓系统中由Choreographer实现。



  • 在Choreographer的构造函数中会创建一个FrameDisplayEventReceiver类对象,这个对象实现了onVSync方法,用于VSync信号回调。

  • FrameDisplayEventReceiver这个对象的父类构造方法中会调用nativeInit方法将当前FrameDisplayEventReceiver对象传递给native层,native层返回一个地址mReceiverPtr给上层。

  • 主线程在scheduleVsync方法中调用nativeScheduleVsync,并传入2中返回的mReceiverPtr,这样就在native层就正式注册了一个FrameDisplayEventReceiver对象。

  • native层在GPU的驱使下会定时回调FrameDisplayEventReceiver的onVSync方法,从而实现了:在VSync信号到来时,立即执行doFrame方法。

  • doFrame方法中会执行输入事件,动画事件,layout/measure/draw流程并提交数据给GPU。

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

Android边框裁切的正确姿势

前言 今天写什么呢,没有太好的思路,就随便写一些细节的点吧。 平时我们都会接触到的一个东西就是设置view的边缘为圆角,因为默认的直角比较难看,这个是涉及比较多的场景,其它当然也有一些场景需用到非正常边框的情况,也需要裁切。 1. 设置圆角边框 一般我们怎么设...
继续阅读 »

前言


今天写什么呢,没有太好的思路,就随便写一些细节的点吧。

平时我们都会接触到的一个东西就是设置view的边缘为圆角,因为默认的直角比较难看,这个是涉及比较多的场景,其它当然也有一些场景需用到非正常边框的情况,也需要裁切。


1. 设置圆角边框


一般我们怎么设置圆角边框的

<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<stroke
android:width="0.8dp"
android:color="#ffffff" />

<corners android:radius="10dp" />
</shape>

这是我们比较常做的设置边框圆角的操作,有没有过这样去设置会不会出问题?其实这样的操作只不过是改变背景而已,它可能会出现内部内容穿透的效果。


2. 使用ClipToOutline进行裁切


这个是android 5.0之后提出的方法,具体的操作是这样

public static void setRoundRect(View view) {
try {
view.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), 10);
}
});
view.setClipToOutline(true);
} catch (Exception e) {
e.printStackTrace();
}
}

可以看出就是调用了view的setOutlineProvider方法和setClipToOutline方法。看这个ViewOutlineProvider,它的注释是

Interface by which a View builds its Outline, used for shadow casting and clipping.


能明显看出它就是为了处理阴影和裁切的。其中我们要设置的话,主要是设置Outline outline这个对象,我们可以看看它所提供的方法


setRect


先随便拿一张图片表示原本的显示效果来做对比


lQDPJxak951EiLzNArPNBBuwlGgcKIdKCsUD55urdoAVAA_1051_691.jpg_720x720q90g.jpg


调用setRect给原图进行边缘裁切

outline.setRect(view.getWidth()/4, view.getWidth()/4, view.getWidth()/4 *3, view.getHeight()/4 * 3);

得到这样的效果,注意,我的原效果是贴边的,这些裁切之后发现是不贴边的


lQDPJxbScR-Lx7zNAtzNBDiwSuSMvAe6JokD55uq3UAVAA_1080_732.jpg_720x720q90g.jpg


setRoundRect的效果和setRect一样,就是多了一个参数用来设置圆角。这里就不演示了


setOval
调用setOval,它的传参和setRect一样

outline.setOval(view.getWidth()/4, view.getWidth()/4, view.getWidth()/4 *3, view.getHeight()/4 * 3);

可以看到效果


lQDPJw5Lp5oLqLzNAqnNBDiwjzj9J14HHxID55ur-8AVAA_1080_681.jpg_720x720q90g.jpg


发现再裁切尺寸的同时并且把图片切成圆形,我记得很早之前,还没毕业时做圆形头像的时候还需要引用别人的第三方,现在5.0之后直接调这个就行,多方便。当然现在头像都是用Glide来做。


setAlpha和setConvexPath也一样,etAlpha是设置透明度,setConvexPath是设置路径,路径和自定义view一样用Path,我这边就不演示了


3.总结


Outline相对于shape来说,是真正的实现边缘裁切的,shape其实只是设置背景而已,它的view的范围还是那个正方形的范围。最明显的表现于,shape如果内容填满布局,会看到内容超出圆角,而Outline不会。当然如果你shape配合padding的话肯定也不会出现这种情况。


使用Outline也需要注意,一般的机子会在当范围超过圆之后,会一直显示圆。比如你设置radius为50是圆角的效果,但是甚至成100已经是整个边是半圆,这时你设200会发现还是半圆,但是在某些机子上200会变成圆锥,所以如果要做半圆的效果也需要去计算好radius


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

Android与JavaScript通信(相互回调)

简述      在移动应用开发中,Android和JavaScript是两个常用的技术栈。Android作为主流的移动操作系统,而JavaScript则是用于网页前端和跨平台开发的脚本语言。为了实现更好的用户体...
继续阅读 »

简述


     在移动应用开发中,Android和JavaScript是两个常用的技术栈。Android作为主流的移动操作系统,而JavaScript则是用于网页前端和跨平台开发的脚本语言。为了实现更好的用户体验和功能扩展,Android与JavaScript之间的通信变得至关重要。本文将介绍Android与JavaScript之间的回调通信技巧


通信基础




  1. 通过 WebView 进行通信


    Android 的 WebView 组件提供了 evaluateJavascript 方法,该方法可以执行 JavaScript 代码并获取返回结果。我们可以利用这一特性实现 Android 和 JavaScript 之间的通信。具体实现步骤如下:



    1. 在 Android 代码中,通过 evaluateJavascript 方法执行 JavaScript 代码。




  2. 使用 JavaScriptInterface 实现通信


    我们可以使用 JavascriptInterface 接口实现JavaScript 与 Android 的通信。具体实现步骤如下:



    1. 在 Android 代码中创建一个类,实现 JavascriptInterface 接口。

    2. 在该类中定义需要供 JavaScript 调用的方法,并添加 @JavascriptInterface 注解。

    3. 在 JavaScript 中通过 window.AndroidFunction 对象调用 Android 代码中的方法,实现通信,其中AndroidFunction是注册JavascriptInterface时指定的, 如下所示:
      webView.addJavascriptInterface(new Object() {
      @JavascriptInterface
      public void jsCallback(String message) {
      // ...
      }
      }, "AndroidFunction");





Android调用JavaScript函数




  1. 忽略返回值

        val webView = findViewById<WebView>(R.id.webView)

    webView.evaluateJavascript("jsFunction('message')", null)




  2. 获取返回值

        val webView = findViewById<WebView>(R.id.webView)

    webView.evaluateJavascript("jsFunction('message')") { result ->
    Log.e("TAG", result)
    }




JavaScript调用Android函数

// 在JavaScript中调用Android函数,并传递参数
function callAndroidFunctionWithParameter() {
var message = "Hello from JavaScript!";
AndroidFunction.jsCallback(message);
}

在上述示例中,JavaScript函数callAndroidFunctionWithParameter()将参数message传递给Android函数jsCallback()


双向回调通信




  1. Javascript传递回调给Android


    上述的 AndroidFunction.jsCallback(message)方式目前只能传递字符串,如果不做特殊处理,是无法执行回调函数的, 执行 AndroidFunction.jsCallback(message)时,在Android中获取到的是字符串,这时将回调函数转换成回调令牌,然后通过令牌执行相应的回调函数,步骤如下:




    1. 在js中将回调函数转换成唯一令牌,然后使用map以令牌为key存储回调函数,以便让Android端根据令牌来执行回调函数

      const recordCallMap = new Map<string, Function>();

      // Android端调用
      window.jsbridgeApiCallBack = function (callUUID: string, callbackParamsData: any) {
      // 通过ID获取对应的回调函数
      const fun = recordCallMap.get(callUUID);
      // 执行回调 `callbackParamsData`回调函数的参数 可有可无
      fun && fun(callbackParamsData);
      // 执行完毕后释放资源
      recordCallMap.delete(callUUID);

      }

      function getUuid() {
      // 生成唯一ID
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/\[xy]/g, function (c) {
      var r = (Math.random() \* 16) | 0,
      v = c == 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
      });
      }

      // 统一处理调用Android的方法
      export function androidCall(funname: string, funData: string, fun: Function) {

      if (!AndroidFunction) {
      return;
      }

      const dataObj = {
      funName: funname,
      funData: funData
      }

      if (typeof fun === "function") {
      const funId = getUuid();
      Object.assign(dataObj, { funId });
      recordCallMap.set(funId, fun);
      }

      AndroidFunction.jsCall(JSON.stringify(dataObj))

      }



    2. 在Android端注册JavascriptInterface统一让js调用

      class JsCallbackModel {
      lateinit var funData: String

      lateinit var funId: String

      lateinit var funName: String
      }

      abstract class JsFunctionCallBack(var funId: String) {
      abstract fun callback(respData: String?)
      abstract fun callback()
      abstract fun callback(respData: Boolean)
      }

      class JavaScriptCall(private val webView: WebView) {
      private fun jsCall(funName: String, funData: String, jsFunction: JsFunctionCallBack) {
      when(funName) {
      "screenCapture" -> {
      screenshot(funData.toInt(), jsFunction)
      }
      }
      }

      @JavascriptInterface
      fun jsCall(data: String) {
      // 将json字符串解析成kotlin对象
      val gson = GsonBuilder().create()
      val model = gson.fromJson(data, JsCallbackModel::class.java)

      // 如果不存在函数名称 则忽略
      if (model.funName == "") {
      return
      }

      val jsFunction: JsFunctionCallBack = object : JsFunctionCallBack(model.funId) {
      override fun callback(respData: String?) {
      if (webView == null) {
      return
      }
      if (funId.isEmpty()) {
      return
      }

      content.webView.post {
      webView.evaluateJavascript("jsBridgeApiCallBack('$funId', "$respData")", null)
      }
      }

      override fun callback() {
      if (funId.isEmpty()) {
      return
      }

      content.webView.post {
      webView.evaluateJavascript("jsBridgeApiCallBack('$funId')", null)
      }
      }

      override fun callback(respData: Boolean) {
      if (funId.isEmpty()) {
      return
      }

      content.webView.post {
      webView.evaluateJavascript("jsBridgeApiCallBack('$funId', '$respData')", null)
      }
      }
      }

      jsCall(model.funName, model.funData, jsFunction);
      }

      private fun screenshot(quality: Int, jsFunction: JsFunctionCallBack) {
      // 执行逻辑...
      // 执行js回调
      jsFunction("base64...")
      }
      }




    3. 在js中传递回调函数调用Android截图

      function screenshot(): Promise<string> {
      return new Promise(resolve => {
      androidCall("screenshot", (base64: string) => resolve(base64))
      })
      }
      screenshot().then(base64 => {
      })





  2. Android传递回调给Javascript


    原理跟Javascript传递回调给Android是一样的,具体实现如下:




    1. Android端

      class JavaScriptCall(private val webView: WebView) {
      companion object {
      val dataF: MutableMap<String, (callData: String) -> Unit> = mutableMapOf()

      fun jsCall(webView: WebView, funName: String, funData: String, funHandler: (callData: String) -> Unit) {
      val ctx: MutableMap<String, String> = mutableMapOf()

      ctx["funName"] = funName
      ctx["funData"] = funData
      val uuid = UUID.randomUUID().toString()
      ctx["funId"] = uuid
      dataF[uuid] = funHandler

      webView.post {
      val gson = GsonBuilder().create()
      val json = gson.toJson(ctx)
      webView.evaluateJavascript("jsCall('$json')", null)
      }
      }
      }

      @JavascriptInterface
      fun androidsBridgeApiCallBack(callUUID: String, callData: String) {
      val funHandler = dataF[callUUID];

      if (funHandler != null) {
      funHandler(callData)
      dataF.remove(callUUID)
      }
      }
      }



    2. Js端

      function doSome(funId, data, callback) {
      // 执行逻辑...
      // 执行Android的回调函数
      callback(funId, data)
      }

      function androidFunction(funId, respData: any?) {
      AndroidFunction.androidsBridgeApiCallBack(funId, respData)
      }

      function androidCallback(funId, funName, funData, fun) {
      switch (funName) {
      case 'doSome': {
      doSome(funId, funData, fun)
      }
      }
      }

      window.jsCall = function (json: string) {
      const obj = JSON.parse(json)
      const funName = obj['funName']
      const funData = obj['funData']
      const funId = obj['funId']

      if (!funName) {
      return
      }

      androidCallback(funId, funName, funData, androidFunction)
      }



    3. 在Android中传递回调函数调用js的doSome

      JavaScriptCall.jsCall(webView, "doSome", "data") { callParam ->
      Log.e("tAG", "回调参数: $callParam")
      }

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

🚗我毕业/转行了,怎么适应我的第一份开发工作?

🚗我毕业/转行了,怎么适应我的第一份开发工作? 嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️ 最近一直在回顾自己的职业生涯,思考自己在这几年里做了什么、成为了什么,实现了什么,失去了什么。虽然一路上充满了挫折和困难,但我其实非常感恩最近几年自己的成长和突破。 在...
继续阅读 »

🚗我毕业/转行了,怎么适应我的第一份开发工作?


嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️


最近一直在回顾自己的职业生涯,思考自己在这几年里做了什么、成为了什么,实现了什么,失去了什么。虽然一路上充满了挫折和困难,但我其实非常感恩最近几年自己的成长和突破。


在这几天的对职业生涯的思考中,我查阅了很多资料和观点,才有了之前那篇《🎖️怎么知道我的能力处于什么水平?我该往哪里努力?》的文章。


在那篇文章中,我从整个职业生涯的角度定义了开发人员可能会经历的各个阶段。


今天,我们来好好聊一聊,当我们因为各种原因,成为一名新晋专业开发者时,如何尽快适应这种变化。


对于那些从学生身份转变为专业开发者,或者是面临职业转行的人来说,希望我的文章可以给你们提供帮助和一些建议。


此外,如果你个人在职业生涯中已经超越了这个阶段(我相信在掘金的大部分人都是大佬了),请不要嫌弃我这篇文章初级,希望回顾一下仍然可能对你有所帮助。


当然,如果我的文章可以对你或其他处于类似情况的开发人员提供指导和帮助,那我会因为能够帮助到你和你的团队更加喜出望外!😄🚀


🤔转型成“初级开发工程师”,会遇到什么挑战?



这些观点源于:《🎖️怎么知道我的能力处于什么水平?我该往哪里努力?》




  1. 以前大部分时间可能是一个人写demo学习,但是现在意味着你需要适应团队协作,和小伙伴们一起完成任务。💪

  2. 以前一个人就能负责完一个不大的项目建设,但是现在项目的体量已经变得超级庞大,一个人根本搞不定!😱

  3. 现在你要学会进行跨领域沟通,就像是翻越一座高山,不仅要搞清楚别人的问题,还要让别人明白你的问题!💡

  4. 怎么在职场中向更高一级进发?就像是玩游戏一样,如何不断升级自己的技能,向着更高的目标冲刺!🚀


基于这些问题,接下来我们将逐一回答它们。


🤔我如何适应可能让我不开心的团队工作呢?


没错!在团队里合作肯定会有些不愉快的事情发生。想想看,世界上可不可能每个人都喜欢你呢?哈哈,当然不可能啦!你也不可能喜欢世界上各种各样的人。


所以,当你加入一个团队时,真的是进入了一个全新的领域!以前,你只需要和自己相处融洽,做错了就怪自己,做不到也只能责怪自己。但是现在,你需要接受别人的不完美,甚至要接受你自己可能把整个团队搞砸的事实。相信我,这真的是一件让人感到尴尬的事情。


我想,面对这些问题,我会鼓励你有意识地培养下面这两个方面的能力:😉💪


1. 学会有效沟通


当你参加各种开发流程中的会议时,一定要意识到会议的重点是什么。要不然这些会议就成了浪费你宝贵时间的活动,还不如在会议期间多写几行代码,多看几篇文章来得实际。


举个例子,每日晨会是每个敏捷团队都有的例会。在这个时候,如果你需要告诉你的领导你今天在做什么,请千万不要深入研究你正在做的事情的细节。(比如,这个模块有个bug一直调不通,你试了很多种方法在会上详细说明)


相反地,因为每日站会的作用是团队之间了解进度和提出问题的会议,你只需要简单说明你在做什么任务,需要什么帮助即可:



“我要修复移动端APP水印失效的问题,但是这个功能我不是很懂,找不到关键代码段,需要有人能帮我梳理一下。”



我在晨会时真的遇到过很多开发人员控制不住自己的发散思维,会讲到他们正在开发的技术细节。如果没有一个有权威的人及时中止这些无意义的展开,真的会浪费大家的时间和精力!


所以如果有可能的话,请花时间多了解对方的需求,让每次沟通都变得简单快速。这样做会让你看起来很干练且专业。😄👍


2. 克制情绪,有意识地锻炼自己的情商


我们都会有克制不住自己情绪的时候,特别是当生活不太顺心时,比如游戏五连败之后,第二天还要上班的情况下。


尤其是当你运气不好,恰好触碰到某人的敏感点,或者开个小小的玩笑,这就有可能无意中挑起双方的争端。


这时候,就是考验一个人情商的时候了。我建议你能尽力地鼓励你的同事,你的团队成员。当他们表现出不好的情绪时,有意识地用理解和支持的态度去面对。当你真的能做到这一点,你就为未来承担更大管理职责做好了最重要的情商储备。


如果你和你的同事确实遇到问题,请从问题本身开始分析,一个一个地解决事情而不是与人对立。要清楚你解决的是问题,而不是与你有冲突的那个人。



当然,如果你真的遇到了难以沟通的团队成员,请顺其自然,让时间或者等待Leader来解决这个问题。我并不倡导无休止的退让。



如果因为这些不可避免的摩擦影响了整个团队的氛围很长一段时间。你只能祈祷你的Leader可以很好的解决这个问题。😄


🤔我怎么在一个我完全看不懂的项目中显得专业?


现在咱们聊聊专业领域的事情吧,我想也是你最关心的问题!


当你加入一个团队时,最大的挑战可能不是适应团队,而是面对一堆看不懂的代码!


刚开始的时候,你可能会觉得自己还行,毕竟学过编程语言,不太可能完全看不懂。


但很快你就会发现处理项目代码跟写小小的Demo程序很不一样!有时候逻辑跳转起来像个迷宫,过几年再回头看,还是一头雾水啊!😵


在团队里,你会面对一个庞大的代码库,可不是一两年就能完全掌握的。又能能用两年时间彻底精通你公司的项目吗?有这种人吗?🤔


所以,当你接到第一个任务,投入开发的时候,别担心,你不是一个人陷入迷茫,我也是一样的。我们都曾经历过那个阶段,只需要时间去适应,一定不要觉得自己不行!


如果你想问有什么可以实践的方法论,我想我能给你的建议是:在进行需求评审的时候,写下所有你可能不理解的内容!


比如:




  • 哪些数据库需要我特别关注?🔍

  • 我需要关注哪些代码文件啊?📂

  • 项目里有没有类似的代码实践可以帮我解决这个需求?🤔

  • 需求里有没有没有说清楚的问题或者一些不够明确的要求呢?🌪️



当你问这些问题的时候,你的小伙伴一定会对你刮目相看哦!相信我,这可是难得一见的专业和细心的表现。(可不是每个人都能做到的!)


这些你整理出来的内容,在你整个开发过程中会给你巨大的支持。当别人想不起来一年都做了些什么事情的时候,这些记录可以让你在年终总结的时候,脱颖而出,变成你宝贵的项目经验! 📚


💪写一份成就清单,为未来赋能


如果可以的话,我建议你保留一份完成的需求任务日记或电子表格。而且,一定一定要记录下你取得的每一个新成就!


把成长当成一个游戏的过程!看着经验值一点点涨上去,发现世界里的新奇事物。


当我这样做的时候,每次一个新成就,都会我的幸福感简直爆棚!我会迫不及待地想要完成下一个新成就。



  • “我设计和开发了一个超棒的用户成就组件库!它提供了一致的界面风格和交互效果,减少了80%的代码冗余!” 🎉

  • “我成功实现了功能X,移动端新用户流入增长了整整120%!” 📈

  • “我掌握了localStorage,并巧妙地用它给功能X实现了用户本地数据缓存!” 💾


有时候,在过程中你可能觉得自己有些傻,但是等有一天你想回顾过去的1年或3年经历,或者当你又被互联网世界”卷“到,对自己失望的时候,你会发现原来那看似平凡无奇的职场生活中,你一直在默默成长。


当然,最重要的是,当你迎接人生中新的阶段,需要换工作或重新制作简历时,这些记录将带来巨大的帮助。你会惊讶地发现,你的项目经历比你想象的要丰富得多! 🌟📝


🥨学习如何提升自己的level



“Never Memorize Something That You Can Look Up” – Albert Einstein


”永远不要记住你可以查到的东西“ - 阿尔伯特·爱因斯坦



大部分的小伙伴们都很热衷于收藏那些像是"100个超牛JavaScript函数"或者"Vue3实用API大集合"这样的文章。


嗯,这真的很棒!我觉得这是个很好的习惯。比起死记硬背那些八股文,这样做要强太多了。因为只要你在需要的时候能找到它们,那它们就是你的宝藏了。


我是绝对反对八股文的开发者,但有时候,面对大环境,我们可能不得不做些妥协。为了面试,我们得背诵各种JavaScript高级函数和Vue生命周期都有什么用。


不过,如果你有时间想要提升自己,有空闲去思考进步的话,我建议你加强阅读。很多很多的阅读!


试试去阅读一些超出你舒适区和当前理解范围的书籍和课程吧!比如计算机组成原理、设计模式,或者现在非常热门的人工智能领域的基础书籍。


这样做可以拓宽你的思维,让你的知识领域更广阔。最终,你会逐渐掌握阅读的技巧,面对这些全新的知识领域时,能更快、更准确地找到重点并掌握它们。


这个过程会很痛苦,因为可能400个字的内容你都需要花一周的时间去消化。


但是只要你坚持下去,未来的你一定会与普通程序员拉开差距。


因为让你有价值的不是那些沉闷的八股文,而是你脑海中关于各个领域的认知和解决方案。


如果你能迅速解决别人不知道怎么办的问题,那你就是人群中那个最了不起的人,很多人会跟着指示做很多的需求,但是他们并不能形成解决方案。


解决方案,才是真正证明你实力的硬通货! 💪


🥩如果有机会,积极加入开发者社区


如果在我刚毕业的时候有人提醒我这个事情,我一定会非常感激他。


回顾我的职业生涯,我最后悔的一件事就是没有早点参与到开发者社区,无论是GitHub还是现在的掘金社区。


当你真正活跃在社区中,试图融入他们,你会结识新朋友,找到可以指导你的导师,让你能够突破当前的认知。你的未来将逐步变得清晰起来。


在你喜欢的领域中,找出谁适合成为你在特定领域发展的导师型开发者朋友。然后关注他们,开始阅读和评论他们的文章和作品,与他们展开讨论,加入他们目前的方向和事业!


最终,借助这个社区,你完全有可能进入职业生涯中一个全新的维度:




  • 你可以为一个开源项目做出属于自己的贡献(甚至是文档方面) 🚩

  • 与社区的开发者合作,发起一个全新的开源项目

  • 你将拥有自己高质量的小圈子💒



这样做会让你收获很多。你不仅能够积累宝贵的经验,还能与行业内优秀的开发者们互动,共同进步!🚀😄




🎉 你觉得怎么样?这篇文章可以给你带来帮助吗?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨


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

谈知识付费与专家建议

我最近可能是病了,或者是心态出了问题。 我经常会听一些付费类的课程,也会在微信上阅读互联网大牛发表的文章。 以前听,还觉得挺有道理的。我甚至绘声绘色地转述给别人。可是后来,怎么听,怎么想怼他们。 可能是我的觉悟降低了,眼界不够。也可能是他们为了证明自己的观点,...
继续阅读 »

我最近可能是病了,或者是心态出了问题。


我经常会听一些付费类的课程,也会在微信上阅读互联网大牛发表的文章。


以前听,还觉得挺有道理的。我甚至绘声绘色地转述给别人。可是后来,怎么听,怎么想怼他们。


可能是我的觉悟降低了,眼界不够。也可能是他们为了证明自己的观点,刻意扭曲一些东西


所以,我打算摆出来,也让读者们帮我分析一下。


首先是我在听罗胖的跨年演讲时,他讲述疫情让大家挣不到钱,他鼓励大家改行,并讲了很多改行成功的故事。


他说南京有一位胡先生,是天文学博士。原本在大学教书,后来他转行去干装修了。还是那种带着劳保手套,搬砖动瓦的那种装修工作。


罗胖说,有人会觉得他得从头开始学吧?实际上不会。


他14年的天文学积累,形成的方法论,可以转到装修上。甚至还可以吊打普通装修工人,这叫降维打击。


首先,他作为天文学家,具有还原宇宙的能力。那么,他可以还原毛坯房的效果图。


这时候,我还能听得进去,觉得有点道理。


随后他说,作为科学家,还具有研究能力。两种建筑材料能不能混合,普通的装修工人靠得是经验。但是科学家,可以分析成分,采用科技手段进行判断。


听到这里,我有点坐不住了。他不是个天文学家吗?怎么又成了材料学家了?两个学科之间没有壁垒吗?


另外,关于乳胶漆和腻子粉混合的后果,科学实验室的数据,同装修工人传下来的经验,效果到底有多大差别?成本又有多大差别?


接下来,罗胖说的,把我的这种情绪推上顶峰。


罗胖说,天文学家转行装修工人,还解决了装修行业的一个痛点。



传统的装修流程,设计、采购、施工是分开的,出了问题会相互扯皮。



但是,天文学家与普通人相比,具有高度的统筹全局的能力、高效的沟通能力,他能把这三个流程都打通,马上就出类拔萃了。反而干得更好。


因此,他给大家的启发是:重要的不是身份,而是内核


我没有干过装修,但我找人装修过我家房子。因此经验很浅。就我个人了解,装修的采购是讲渠道的。有些行业老手拿货很便宜,比我自己去买要便宜很多。因此才有各司其职的划分,不是打不通,而是各自更专业。


他说身份不重要,但是在我的生活中,我感觉身份还是很重要的。甚至自考本科和统招本科,区别都很大。可能还是我境界太低了。


我听完上面的故事,没有再继续往下听。


我感觉可能是自己出问题了。我是无名小卒。人家的课程可是千万用户呢?其中,还不乏好多商界大老板。老板肯定是比我聪明的。


今天,我又从微信公众号看到一篇文章。


写这篇文章的是IT行业的大佬。我不认识。但是他的简介很厉害:国内知名IT管理专家、某甲创始人、某乙创始人,畅销书《xx之道》作者,曾担任大厂的各种总、各种O。


他写的体裁是关于人工智能方面的,题目大意是ChatGPT骗了全世界、你们都被ChatGPT骗了。


因为我就写人工智能代码,也写过关于AIGC原理解析的文章


我就点进去看,一看我就想怼。


他列举了很多条关于人工智能的谎言,然后自己再说出了真相。他揭穿谎言,警醒世人。


他指出现在流传的一个谎言:人工智能会代替人类的工作


他给出的真相是:抢饭碗的永远不是AI,而是会用AI的人


他也讲了一个故事。



原来有很多电话客服人员,负责推销、查询等工作。但是,现在90%的这类工作,已经被AI机器给替代了。那你说原来的客服都失业了吗?没有!你们不知道的是,他们有的转行做了AI训练师。也就是教AI怎么打电话。


你看,客服掌握了AI,重新上岗了。


而你,不愿意学习AI,只能被淘汰。



我想,这不还是人类的工作被替代了吗?只是因为AI的出现,衍生了一些周边岗位。


原来停车场收钱抬杆的大爷,因为车牌识别外加移动支付,他们下岗了。高速口的收费员,因为ETC的出现,也下岗了。


这确实是科技代替了人工。没见哪个大爷跑去教AI如何抬杆儿,就算有,能用得了那么多大爷吗?


AI客服出现的目的,就是提高效率,节省成本。花钱搞了AI客服,那些人工客服还会保留吗?要留也只能留少数一部分人。而大多数人会因为AI的出现,不得不重新选择工作。人工智代替人类的一些工作,就是时代的发展。


这位大佬,最后总结:淘汰你的永远不是这个时代,是你自己。不管时代如何,你不努力,被淘汰很正常


我的思想又活跃了,很想怼。


我感觉,我们的生活深受时代的影响。就像是蚂蚁的生活,也会受大雨、野火、巨兽的踩踏影响一样。


蚂蚁努力积累食物,突然有一天,某个小孩朝着洞穴撒了一泡尿,或者扔了一个鞭炮,蚂蚁的努力就白费了。


这种破坏和是否努力是没有关系


我估计,专家也会怼我:



你早应该有预判,提早发现熊孩子的破坏行为。


从他今天在家多喝了水,还买了鞭炮开始,就该猜到他的这类行为。


这叫见微知著,未雨绸缪。其实,归根到底,还是你不够努力!



其实,他们说的对,没有错。错在哪里?难道不应该努力吗?但是这些话,对你用处不大。


我发现,引发我想怼欲望的事情。大多都是在宣传一件事,那就是人的力量是无限大的。


关系不重要,环境不重要,行业不重要,你的努力最重要!


早年听陈安之的成功学,他说成为马拉松冠军是世上最简单的事情。只需要一句口诀,那就是当你跑不动、心肺难以支撑的时候,大声喊出:我没有心脏,我没有心脏!


我当时想,没有心脏不就倒下了吗?


这是正能量。正能量永远没有错。


你想赚大钱,请了一个大师。


大师传授你三个独家秘诀:



第一,要有强烈赚钱欲望;


第二,要对赚钱抱有持之以恒的行动;


第三,赚到钱时一定不要骄傲,继续赚。



你感觉很有道理,但是收入依然没有什么改变。可能是因为你不够努力。


你想学舞蹈,找了一个大师,大师说,想成为舞蹈高手,首先要有强烈跳舞的欲望,第二要持之以恒的行动,第三要戒骄戒躁。


还是一样的话。


请问,他们错了吗?没错。你成功了吗?没有。


听过马三立的一个相声段子。



说有个穷人,穷的揭不锅了。实在没办法,就去摆摊。他摆了一个算命的摊子,也兼职修鞋。


来算命的人少,挣钱多。来修鞋的人多,挣钱少。两者互补。


有一个人来算命,想发财。这个算命的就指点他去东北,那里能发财。


然后,又跟他说,去东北得走不少路,加固下鞋子才能成功抵达。



我想,现在很多课程可能也是这样。


他们一方面宣传你的遭遇跟环境无关,另一方面强调是你不够努力,让你焦虑。同时,他还有课程,你买了课程就算是努力了


至于最后的结果,他们是不关心的。不成功,可能是你还不够努力。


你自己的事情,需要自己去思考。每个人的境遇和你不一样。


那些行业大佬们,可能没有去小摊上吃过油条,也没有逛过装修市场去买瓷砖。甚至他们的父辈也没有过类似经历。他们更精于顶层建筑。但是有时候,他必须要写相关的文章。于是,他们举的例子,更多的是一种导向,是劝人向善。


而我们,也不要过于盲从他们的指导。他们的意见参考一下就好了。


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

程序员你为什么迷茫

今天在知乎收到一个问题:「程序员你为什么迷茫?」,看到这问题时突然间「福灵心至」想写点什么,大概是好久没写这种「焦虑文」,想想确实挺久没更新《程序员杂谈》 这个专栏了,那就写点「找骂」的东西吧。 其实也不算「贩卖焦虑」,主要是我一直不卖课也不做广告,就是纯...
继续阅读 »

今天在知乎收到一个问题:「程序员你为什么迷茫?」,看到这问题时突然间「福灵心至」想写点什么,大概是好久没写这种「焦虑文」,想想确实挺久没更新《程序员杂谈》 这个专栏了,那就写点「找骂」的东西吧。




其实也不算「贩卖焦虑」,主要是我一直不卖课也不做广告,就是纯粹想着写点东西,不过如果你看了之后觉得「焦虑」了,那也没有「售后服务」。



当我看到「程序员你为什么迷茫?」这个问题的时候,我第一想法居然是:大概是因为预期和结果不一致


现在想想确实好像是这样,在步入职场之前,你一直以为程序员是一个技术岗,是用一双手和一台电脑,就可以改变世界的科技岗位,是靠脑吃饭,至少在社会地位上应该是个白领。


但是入职后你又发现,明明是个技术岗位,干的却是体力活,别人在工地三班倒,而你是在电脑前 996,唯一庆幸的是你可以吹着空调,目前的收入还挺可观。



但是现在看来,程序员的职业生涯好像很短,农民工少说可以干到 40 多,为什么程序员 35 就需要毕业再就业?明明一心一意搬砖,说好奋斗改变人生,最后老板却说公司只养有价值的人,而有价值的人就是廉价而又精力充沛的年轻人


那时候你也开始明白,好像努力工作 ≠ 改变人生,因为你努力改变的是老板的人生,工作带来的自我提升是短暂的,就像工地搬砖,在你掌握搬砖技巧之后,剩下的都只是机械性的重复劳动,机械劳动的勤奋只会带来精神上的奋斗感,并不致富,而对比工地,通过电脑搬砖需要的起点更高,但是这个门槛随着技术成熟越来越低,因为搜索引擎上的资源越来越多,框架和社区越来约成熟,所以🧱越来越好拿,工价也就上不去了,甚至已经开始叫嚣用 AI 自动化来代替人工。



其实对于「老人」来说,这个行业一开始不是这样,刚入行的时候到处在抢人,他们说这是红利期,那时候简历一放出来隔天就可以上岗,那时候的老板每天都强调狼性,而狼需要服从头领,只有听话的狼才有肉吃,所以年轻时总是充满希望,期待未来可以成为头狼,也能吃上肉住炕头。


虽然期间你也和老板说过升职加薪,但是老板总是语重心长告诉大家:



年轻人不好太浮躁,你还需要沉淀,公司这是在培养你,所以你也要劳其筋骨,饿其体肤,才能所有成就,路要一步一走,饭要一步一吃,总会有的。



当然你可以看到了一些人吃到了肉,所以你也相信自己可以吃到肉,因为大家都是狼,而吃到肉的狼也在不停向你传递吃肉经验:



你只需要不停在电梯里做俯卧撑,就可以让电梯快一点到顶楼,从而占据更好的吃肉位置,现在电梯人太多了,你没空间做俯卧撑,那就多坐下蹲起立,这样也是一种努力。




直到有一天,公司和你突然和你说:



你已经跟不上公司的节奏了,一个月就请了三次病假,而且工作也经常出错,所以现在需要你签了这份自愿离职协议书,看在你这么多年的劳苦功高,公司就不对你做出开除证明,到时候给其他人也是说明你是有更好机会才离职的,这样也可以保全你的脸面。



而直到脱离狼群之后你才明白,原来沉淀出来的是杂质,最终是会被过滤掉,电梯空间就那么点,超重了就动不了,所以总有人需要下来


所以你回顾生涯产生了疑惑和迷茫:程序员不是技术岗位吗?作为技术开发不应该是越老越值钱吗?为什么经验在 3-5 年之后好像就开始可有可无,而 35 岁的你更是被称为依附在企业的蛀虫,需要给年轻人让路。


回想刚入行那会,你天天在想着学习技术,无时无刻都在想着如何精通和深入,你有着自己的骄傲,想着在自己的领域内不断探索,在一亩三分地里也算如鱼得水,但是如今好像深入了,为什么就开始不被需要了


回过头来,你发现以前深入研究的框架已经被替代,而当年一直让他不要朝三暮四嚼多不烂的前辈,在已经转岗到云深不知处,抱着一技之长总不致于饿死是你一直以来的想法,但是如今一技之长好像补不上年龄的短板。



如今你有了家庭,背负贷款,而立之年的时候,你原以为生涯还只是开始,没想到早已过了巅峰,曾经的你以为自己吃的技术饭,做的是脑力活,壮志踌躇对其他事务不屑一顾,回过头来,如今却是无从下手,除了在电脑对着你熟悉的代码,你还能做什么?放下曾经的骄傲,放下以往的收入去吃另一份体力活?



但是不做又能怎样?提前透支的未来时刻推着你在前行,哪怕前面是万丈深渊。



所以以前你认为技术很重要,做好技术就行了,但是现在看,也许「技术」也只是奇技淫巧之一,以前你觉得生育必须怀胎十月,但是现在互联网可以让十个孕妇怀胎一月就早产,这时候你才发现,你也没自己想象中的重要。


所以你为什么迷茫?因为到了年龄之后,好像做什么都是错的,你发现其实你并没有那么热爱你的职业,你只想养家糊口,而破局什么的早就无所谓了,只要还能挣扎就行。



所以我写这些有什么用?没什么用,只是有感而发,大部分时候我们觉得自己是一个技术人才,但是现在看来技术的门槛好像又不是那么的高,当技术的门槛没那么高之后,你我就不过是搬砖的人而已,既然是搬砖,那肯定是年轻的更有滋味


所以,也许,大概,是不是我们应该关心下技术之外的东西?是不是可以考虑不做程序员还能做什么?35 岁的人只会越来越多,而入行的年轻人也会越来越多,但是近两年互联网的发展方向看起来是在走「降本增效」,所以这时候你难道不该迷茫一下?


程序员是迷茫或者正是如此,我们都以为自己是技术人,吃的是脑力活,走的是人才路,但是经过努力才知道,也许你的技术,并没有那么技术。


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

思考 | 闲话工作

工作五年有余,似有感悟,可是每每提笔时,脑袋又一片混沌。然而胸中总有些东西不吐不快,如鲠在喉,如芒在背。尤其去年年末和一位好友聊至深夜,席间的一番话令我思绪万千,怀念起曾经那个稚嫩青涩的我。 我想是时候该记录一些东西,哪怕这些东西是混乱的,潦草的。这些内容看似...
继续阅读 »

工作五年有余,似有感悟,可是每每提笔时,脑袋又一片混沌。然而胸中总有些东西不吐不快,如鲠在喉,如芒在背。尤其去年年末和一位好友聊至深夜,席间的一番话令我思绪万千,怀念起曾经那个稚嫩青涩的我。


我想是时候该记录一些东西,哪怕这些东西是混乱的,潦草的。这些内容看似和工作相关,实则背后都是人生的态度,价值的选择。它们写给路上的伙伴,更写给未来的自己。毕竟,在这慌乱走过的人生中,如果不留些印记,回头顾盼时将只剩茫然。


人生重要的是选择,还是努力?


这个问题的答案取决于你如何理解人生。当下流行的回答是“选择”,而我的回答是“努力”。


我打小生活在小镇上,属于严格意义上的小镇青年。条件谈不上优渥,但也算不上艰辛。或许得益于良好的家庭氛围,我的内心从未有过“短缺感”。所以那种“不上进”的小富即安的状态,在我看来颇为自足。穷人和富人都有烦恼,也都有欢乐。二者体验到的事物可能有差别,但通过事物体验到的快乐未必有差别。这就好比躺在豪车和草地上都不是快乐,但是躺下之后能够心无旁骛地哼着小曲,这是快乐的。


我选择“努力”,是因为我更欣赏把一件事做到极致的态度。这可能和我的父亲有关。他是一名柴油机修理工,从小教导我的就是“要么不做,要么做到极致”的人生态度。在他30多年的修理生涯里,一直引以为豪的就是自己的修理质量。别家修好的机器可以管一到两年,他修好的可以管三到五年。要说这其中有什么秘诀,说破天也只有“用心”二字。你说赛道的选择重要么?当然重要。但是各行各业都要有人做,“择一行,爱一行,精一行”的态度,才是更普适,更让每个人都心安的选择(我讨厌当下价值体系的一个主要原因就是它不普适,它只让少数人心安)。


我选择“努力”,也是希望做到尽量务实。因为看重选择,背后的心理通常是从众。什么是好选择?是媒体口中的?长辈口中的?还是内心深处的?社会风向变来变去,时间一久就容易浮躁。浮躁到后来只关注结果而忽略过程。不过事情的发展却很玄妙,你越是盯着某样东西,就越是得不到它。你紧盯着财富,往往也得不到财富。


我选择“努力”,还因为在我看来选择是自然发生的。太过刻意的选择,会有反向的作用。譬如,当一个人修行不足,内心不够坚定时,过高的荣誉和财富都会将他推向深渊。他会面临更多的诱惑,更多的苛责,处理不好便会失去原先宝贵的家庭和健康。因此,但行好事,莫问前程。


接着谈谈工作中的一些感受。


我日常的工作是处理稳定性问题。当一个问题暴露出来后,多数人在流程中唯一的作用就是施压和传话,他们只关心问题有没有解决,何时解决。如果这个问题不再出现,那么围观的人群将立即散开。它仿佛一个从未被打开过的黑盒,被一群人用灼热的目光炙烤后,又无情地抛弃。从始至终,没有人关心过它的前世今生,它的前因后果。当我们碰到新问题时总希望能举一反三,然而举一反三的前提一定是充分理解这个“一”。否则所有的问题在我们脑中只是漂浮的孤岛。可是现实就是很多问题被我们当成了黑盒。或许是手头的任务太重,或许是兴趣寥寥,总之愿意追本溯源,探究举一反三中的“一”的人很少。


这个话题再延展一下,国内很多所谓的科技公司都偏好商业运营而轻技术。它们眼中的技术只是实现业务的手段,或许它们更应该叫做“消磨时间公司”、“线上百货公司”或者“跑腿服务公司”。我知道这话一说完,肯定有人要站起来说:好个不懂商业,不懂管理的小白!懂也好,不懂也罢。然而我知道一个浅显的道理:一个饭馆想要生意好,就应该把精力重点放在提升口味上,而不是放在店面装修和营销广告上。同理,国内的科技想要真有起色,就要把技术当成技术,而不是其他目的的附庸。也唯有把技术当成技术,我们才能尊重技术。同样位于东亚的日本人,或许正是因为身上有比我们更为专注的匠人精神,才能在高端科技占有一席之地。


此外还有一个工作责任心的问题。


最低一个等级的责任心是“太极推手”,遇到棘手任务时想的是如何脱责。他们工作的目标就是将棘手问题转移出去,只处理那些简单不费脑且容易产生汇报成绩的活。这种人每个公司都不少,君不见那些人一天邮件好多封,一看内容全空空。


稍微高一个等级的责任心是“听命行事”,老板让我干我就干,至于干的质量和结果如何,抱歉,不在本人考虑范围内。他们每天没有主见地做事,付出了时间,但未必付出努力。


再稍微高一点等级的责任心是“自扫门前雪”,他们对自己所负责的领域普遍有了主人翁意识,在意别人对自己领地的评价,因此比较尽心尽责。不过这份尽责也止于自己领地的边界,越线的部分他是压根不会伸手。


再往上一个等级的责任心是“舍我其谁”,那些无人负责却又对公司有利的事情谁来干?这帮人往往冲在前面。他们对边界以外且力所能及的事情愿意伸手,也更享受为公司带来利益后的价值认同。


最高一个等级的责任心是“社会主人”,如果说之前的责任心还仅仅局限在工作和利益的维度,那么这个等级将会扩大到社会责任。他会思考自己的工作对社会所产生的影响,以及个人能力在其中起到的正面作用。


一个人选择什么样的责任心,通常是性格和价值观的产物。公司可以通过绩效这根大棒来影响员工的责任心,但从员工个人角度来看,它不会是决定性因素。


今日且谈到此,希望这篇文章也能开个先河,让自己在输出技术内容的同时多一些个人思考。当然,文中观点尚且稚嫩,还请各位看官不吝赐教。


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

Gradle 浅入浅出

环境介绍 OpenJDK 17.0.5 Gradle 7.6 示例代码 fly-gradle Gradle 项目下文件介绍 如果你的电脑安装了 gradle,可以使用 gradle init 去初始化一个新的 gradle 工程,然后使用电脑安装的 gra...
继续阅读 »

环境介绍



Gradle 项目下文件介绍


如果你的电脑安装了 gradle,可以使用 gradle init 去初始化一个新的 gradle 工程,然后使用电脑安装的 gradle 去执行构建命令。


但是每个开发电脑上的 gradle 版本不一样,为了统一构建环境,我们可以使用 gradle wrapper 限定项目依赖 gradle 的版本。

# 会生成 gradle/wrapper/* gradlew gradlew.bat
gradle wrapper --gradle-version 7.5.1 --distribution-type bin
.
├── build.gradle
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle

gradlew 和 gradlew.bat


运行项目 wrapper 下定义的 gradle 去构建项目。


gradlew 是 macos 和 linux 系统下。


gradlew.bat 是 windows 系统下使用的。


wrapper


wrapper-workflow.png


wrapper 定义项目依赖那个版本的 gradle,如果本地 distributionPath 没有对应版本的 gradle,会自动下载对应版本的 gradle。

# gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
# 如果是国内项目,只需要修改这个url 就可以提高下载速度
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

GRADLE_USER_HOME 没有配置的话,默认是 ~/.gradle


zipStoreBasezipStorePath 定义了下载的 gradle (gradle-7.6-bin.zip) 存储的本地路径。


distributionBasedistributionPath 定义下载的 gradle 解压的本地目录。


gradlew 实际是运行 gradle-wrapper.jar 中的 main 方法,传递给 gradlew 的参数实际上也会传递给这个 main 方法。


gradle-wrapper.jar 会判断是否下载 wrapper 配置的 gradle,并且将传递参数给下载的 gradle,并运行下载的 gralde 进行构建项目。


升级 wrapper 定义的 gradle 版本

./gradlew wrapper --gradle-version 7.6

settings.gradle

pluginManagement {
repositories {
maven {
url="file://${rootDir}/maven/plugin"
}
gradlePluginPortal()
}
plugins {
// spring_boot_version 可以在 gradle.properties 配置
id 'org.springframework.boot' version "${spring_boot_version}"
}
}
rootProject.name = 'fly-gradle'
include 'user-manage-service','user-manage-sdk'
include 'lib-a'
include 'lib-b'

settings.gradle 主要用于配置项目名称,和包含哪些子项目。


也可以用于配置插件的依赖版本(不会应用到项目中去,除非项目应用这个插件)和插件下载的


build.gradle


build.gradle 是对某个项目的配置。配置 jar 依赖关系,定义或者引入 task 去完成项目构建。


gradle.properties


主要用于配置构建过程中用到的变量值。也可以配置一些 gradle 内置变量的值,用于修改默认构建行为。

org.gradle.logging.level=quiet
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xms512m -Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

org.gradle.jvmargs 用来配置 Daemon 的 JVM 参数,默认值是 -Xmx512m "-XX:MaxMetaspaceSize=384m"


当我们的项目比较大的时候,可能会由于 JVM 堆内存不足导致构建失败,就需要修改此配置。


org.gradle.logging.level 调整 gradle 的日志级别。参考 gradle logging 选择想要的日志级别。


Gradle Daemon


为加快项目构建,gralde 会启动一个常驻 JVM 后台进程去处理构建,Daemon 进程默认三小时过期且当内存压力大的时候也会关闭。


Gradle 默认会启用 Daemon 进程去构建项目。

# 查看 daemon 运行状态
./gradlew --status
# stop daemon 进程
./gradlew --stop
# 重启 daemon 进程
./gradlew --daemon

构建生命周期


Gradle 是基于 task 依赖关系来构建项目的,我们只需要定义 task 和 task 之间的依赖关系,Gradle 会保证 task 的执行顺序。


Gradle 在执行 task 之前会建立 Task Graphs,我们引入的插件和自己构建脚本会往这个 task graph 中添加 task。


task-dag-examples.png


Gradle 的构建过程分为三部分:初始化阶段、配置阶段和执行阶段。


初始化阶段



  • 找到 settings.gradle,执行其中代码

  • 确定有哪些项目需要构建,然后对每个项目创建 Project 对象,build.gradle 主要就是配置这个 Project 对象
// settings.gradle
rootProject.name = 'basic'
println '在初始化阶段执行'

配置阶段



  • 执行 build.gradle 中的配置代码,对 Project 进行配置

  • 执行 Task 中的配置段语句

  • 根据请求执行的 task,建立 task graph
println '在配置阶段执行 Task 中的配置段语句'

tasks.register('configured') {
println '在配置阶段执行 Task 中的配置段语句'
doFirst {
println '在执行阶段执行'
}
doLast {
println '在执行阶段执行'
}
}

执行阶段


根据 task graph 执行 task 代码。


依赖管理


Maven 私服配置


我们一般都是多项目构建,因此只需要在父项目 build.gradle 配置 repositories。

allprojects {
repositories {
maven {
url "${mavenPublicUrl}"
credentials {
username "${mavenUsername}"
password "${mavenPassword}"
}
}
mavenLocal()
mavenCentral()
}
}

credentials 配置账号密码,当私服不需要权限下载的时候可以不配置。


Gradle 会按照配置的仓库顺序查询依赖下载。


配置依赖来自某个目录

dependencies {
compile files('lib/hacked-vendor-module.jar')
}
dependencies {
compile fileTree('lib')
}

有的时候第三方库没有 maven 供我们使用,可以使用这个。


依赖冲突


默认依赖冲突


::: tip
当出现依赖冲突的时候,gradle 优先选择版本较高的,因为较高版本会兼容低版本。
:::

dependencies {
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.google.code.findbugs:jsr305:3.0.0'

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

我们可以执行下面命令查看项目依赖的版本:

./gradlew dependency-management:dependencies --configuration compileClasspath

------------------------------------------------------------
Project ':dependency-management'
------------------------------------------------------------

compileClasspath - Compile classpath for source set 'main'.
+--- org.springframework.boot:spring-boot-dependencies:3.0.2
+--- com.google.guava:guava:31.1-jre
| +--- com.google.guava:failureaccess:1.0.1
| +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
| +--- com.google.code.findbugs:jsr305:3.0.2
| +--- org.checkerframework:checker-qual:3.12.0
| +--- com.google.errorprone:error_prone_annotations:2.11.0
| \--- com.google.j2objc:j2objc-annotations:1.3
\--- com.google.code.findbugs:jsr305:3.0.0 -> 3.0.2

我们可以看到,gradle 选择了 com.google.code.findbugs:jsr305:3.0.2 这个版本。


强制使用某个版本


如果我们想使用 com.google.code.findbugs:jsr305:3.0.0 版本

dependencies {
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.google.code.findbugs:jsr305:3.0.0', {
force = true
}
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
./gradlew -q dependency-management:dependencyInsight --dependency jsr305 --configuration compileClasspath
com.google.code.findbugs:jsr305:3.0.0 (forced)
Variant compile:
| Attribute Name | Provided | Requested |
|--------------------------------|----------|--------------|
| org.gradle.status | release | |
| org.gradle.category | library | library |
| org.gradle.libraryelements | jar | classes |
| org.gradle.usage | java-api | java-api |
| org.gradle.dependency.bundling | | external |
| org.gradle.jvm.environment | | standard-jvm |
| org.gradle.jvm.version | | 17 |

com.google.code.findbugs:jsr305:3.0.0
\--- compileClasspath

com.google.code.findbugs:jsr305:3.0.2 -> 3.0.0
\--- com.google.guava:guava:31.1-jre
\--- compileClasspath

禁用依赖传递


guava 不会传递依赖它依赖的库到当前库,可以看到

dependencies { 
implementation 'com.google.guava:guava:31.1-jre', {
transitive = false
}
implementation 'com.google.code.findbugs:jsr305:3.0.0'
}
./gradlew dependency-management:dependencies --configuration compileClasspath

------------------------------------------------------------
Project ':dependency-management'
------------------------------------------------------------

compileClasspath - Compile classpath for source set 'main'.
+--- org.springframework.boot:spring-boot-dependencies:3.0.2
+--- com.google.guava:guava:31.1-jre
\--- com.google.code.findbugs:jsr305:3.0.0

可以看到 guava 依赖的 jar 没有传递到当前项目中来。


排除某个依赖


Guava 依赖的别的 jar 可以传递进来,而且排除了 findbugs, 项目依赖的版本为 3.0.0

dependencies { 
implementation 'com.google.guava:guava:31.1-jre', {
exclude group: 'com.google.code.findbugs', module: 'jsr305'
}
implementation 'com.google.code.findbugs:jsr305:3.0.0'
}
./gradlew dependency-management:dependencies --configuration compileClasspath

------------------------------------------------------------
Project ':dependency-management'
------------------------------------------------------------

compileClasspath - Compile classpath for source set 'main'.
+--- org.springframework.boot:spring-boot-dependencies:3.0.2
+--- com.google.guava:guava:31.1-jre
| +--- com.google.guava:failureaccess:1.0.1
| +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
| +--- org.checkerframework:checker-qual:3.12.0
| +--- com.google.errorprone:error_prone_annotations:2.11.0
| \--- com.google.j2objc:j2objc-annotations:1.3
\--- com.google.code.findbugs:jsr305:3.0.0

可以看到 guava 传递到当前项目的依赖少了 findbugs


配置依赖之间继承

configurations {
integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}

configurations.all {
resolutionStrategy {
force 'org.apache.tomcat.embed:tomcat-embed-core:9.0.43'
}
exclude group: 'org.slf4j', module: 'slf4j-simple'
}

api 和 implementation 区别


jar b 包含一下依赖

dependencies {
api 'org.apache.commons:commons-lang3:3.12.0'
implementation 'com.google.guava:guava:31.1-jre'
}

项目 a 引入 jar b ,commons-lang3 和 guava 都可以被工程 a 使用,只是二者 scope 不一样。


api 对应 compile,在 工程 a 可以直接使用,编译可以通过。


implementation 对应 runtime,编译找不到 guava 中的类。


Task


我们引用的插件实际是添加 task 到 task graph 中去。


我们知道 build.gradle 实际是用来配置对应项目的 org.gradle.api.Project。


因此我们可以在 build.gradle 中引用 org.gradle.api.Project 暴露的属性。


我们可以在 gradle dsl 和 Project 接口中可以知道可以访问哪些属性。


tasks 实际就是 Project 暴露的一个属性,因此我们可以使用 tasks 往当前项目中注册 task。

tasks.register('hello') {
doLast {
println 'hello'
}
}

推荐使用 tasks.register 去注册 task,而不是 tasks.create 去直接创建。


tasks.register 注册的 task 只会在用到的时候才会初始化。


Groovy 闭包


Groovy Closures

{ [closureParameters -> ] statements }

闭包的示例

{ item++ }                                          

{ -> item++ }

{ println it }

{ it -> println it }

:::tip


当方法的最后一个参数是闭包时,可以将闭包放在方法调用之后。


:::


比如注册一个 task 的接口是

register(String var1, Action<? super Task> var2)

tasks.register("task55"){
doFirst {
println "task55"
}
}

tasks.register("task66",{
doFirst {
println "task66"
}
})

Task Type


gradle 已经定义好一些 task type,我们可以使用这些 task type 帮助我们完成特定的事情。比如我们想要执行某个 shell 命令。


Exec - Gradle DSL Version 7.6

tasks.register("task3", Exec) {
workingDir "$rootDir"
commandLine "ls"
}

Plugin


插件分类


Gradle 有两种类型的插件 binary plugins and script plugins


二进制插件就是封装好的构建逻辑打成 jar 发布到线上,供别的项目使用。


脚本插件就是一个 *.gradle 文件。


buildSrc


一般我们的项目是多项目构建,各个子项目会共享一些配置,比如 java 版本,repository 还有 jar publish 到哪里等等。


我们可以将这些统一配置分组抽象为单独的插件,子项目引用这个插件即可。便于维护,不用在各个子项目都重复配置相同的东西。


buildSrc 这个目录必须在根目录下,它会被 gradle 自动识别为一个 composite build,并将其编译之后放到项目构建脚本的 classpath 下。


buildSrc 也可以写插件,我们可以直接在子项目中使用插件 id 引入。

buildSrc/
├── build.gradle
├── settings.gradle
└── src
├── main
│   ├── groovy
│   │   └── mflyyou.hello2.gradle
│   ├── java
│   │   └── com
│   │   └── mflyyou
│   │   └── plugin
│   │   ├── BinaryRepositoryExtension.java
│   │   ├── BinaryRepositoryVersionPlugin.java
│   │   └── LatestArtifactVersion.java

buildSrc/build.gradle


groovy-gradle-plugin 对应的是使用 groovy 写插件。


java-gradle-plugin 对应 java

plugins {
id 'groovy-gradle-plugin'
id 'java-gradle-plugin'
}
gradlePlugin {
plugins {
helloPlugin {
id = 'com.mflyyou.hello'
implementationClass = 'com.mflyyou.plugin.BinaryRepositoryVersionPlugin'
}
}
}

我们就可以在子项目使用插件

plugins {
id 'com.mflyyou.hello'
id 'mflyyou.hello2'
}

插件使用



  • Applying plugins with the plugins DSL
plugins {
id 'java'
}


  • Applying a plugin with the buildscript block
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
}
}

apply plugin: 'com.jfrog.bintray'


  • Applying a script plugin
apply from: 'other.gradle'

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

[Git废弃提交]需求做一半,项目停了,我该怎么废弃commit

Git
在实际开发中我们要拥抱变化。我们都知道需求它是很不稳定的,在实际开发过程中会经常改变。经常会遇到一个需求,已经开始进入开发阶段了,开发到一半的时候说这个功能不需要了。甚至会出现我们已经实现某一个功能,然后被告知,这个功能被砍掉了。 那么针对这种代码已经写了,现...
继续阅读 »

在实际开发中我们要拥抱变化。我们都知道需求它是很不稳定的,在实际开发过程中会经常改变。经常会遇到一个需求,已经开始进入开发阶段了,开发到一半的时候说这个功能不需要了。甚至会出现我们已经实现某一个功能,然后被告知,这个功能被砍掉了。


那么针对这种代码已经写了,现在要废弃的情况我们应该怎么操作呢?


当然,如果这个功能都在一个单独的分支上,且这个分支只有这个功能的代码,那么可以直接废弃这个分支。(这也是为什么会有多种Git工作流的原因,不同的软件需求场景,需要配合不同的工作流。)


代码还没有提交


如果代码还在本地,并没有提交到仓库上。可以用reset来重置代码。
git reset是将当前的分支重设(reset)到指定的commit或者HEAD(默认),并且根据参数确定是否更新索引和工作目录。

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
git reset [file]
# 重置暂存区与工作区,与上一次commit保持一致
git reset --hard
# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
git reset [commit]
# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
git reset --hard [commit]
# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
git reset --keep [commit]

其实reset也可以指定某个commit,这样就会重置到对应的commit,该commit之后的commit都会丢失。如果你没法确定这些commit中是否有需要保留的commit,就不要这样操作。


如果代码已经提交


如果代码已经提交了,且提交的分支上还有其他的代码。那么操作起来就比较麻烦。我们需要用revert来删除。


revert


git revert命令用来「撤销某个已经提交的快照(和reset重置到某个指定版本不一样)」。它是在提交记录最后面加上一个撤销了更改的新提交,而不是从项目历史中移除这个提交,这避免了Git丢失项目历史。

# 生成一个撤销最近的一次提交的新提交
git revert HEAD
# 生成一个撤销最近一次提交的上n次提交的新提交
git revert HEAD~num
# 生成一个撤销指定提交版本的新提交
git revert <commit_id>
# 生成一个撤销指定提交版本的新提交,执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息
git revert <commit_id> --no-edit

比如我现在的dev分支,最近3次提交是"10","11","12"。



我现在要11这个提交去掉,我就直接revert "11" 这个commit



运行后,他会出现一个冲突对比,要求我修改完成后重新提交。(revert是添加一个撤销了更改的新提交)


这个提交,会出现"10"和"12"的对比。



修改完对比后,我们commit这次修改。



看下日志,我们可以看出,revert是新做了一个撤销代码的提交。



撤销(revert)被设计为撤销公共提交的安全方式,重设(reset)被设计为重设本地更改。
因为两个命令的目的不同,它们的实现也不一样:重设完全地移除了一堆更改,而撤销保留了原来的更改,用一个新的提交来实现撤销。「千万不要用 git reset 回退已经被推送到公共仓库上的提交,它只适用于回退本地修改(从未提交到公共仓库中)。如果你需要修复一个公共提交,最好使用 git revert」。


rebase


前面课程说过,rebase是对已有commit的重演,rebase命令也可以删除某个commit的。他和reset一样,删除掉的commit就彻底消失了,无法还原。
具体用法,这里不在再次介绍,可以去看前面的课程。


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

事件传递(android)常见面试题

一.事件分发传递流程 Android 事件分发传递流程主要分为三个阶段:事件分发阶段、事件捕获阶段和事件处理阶段。下面我将进一步解释这三个阶段的具体内容: 事件分发阶段 事件分发阶段是从 Activity 的 dispatchTouchEvent() 方法...
继续阅读 »

一.事件分发传递流程


Android 事件分发传递流程主要分为三个阶段:事件分发阶段、事件捕获阶段和事件处理阶段。下面我将进一步解释这三个阶段的具体内容:



  1. 事件分发阶段


事件分发阶段是从 Activity 的 dispatchTouchEvent() 方法开始,在这一阶段中,系统会先将事件传递给 ViewRootImpl,再由 ViewRootImpl 分发到 Activity 的 decorView,最后通过 View 的 onTouchEvent() 方法交给子 view。


具体流程如下:




  1. Activity 从 ViewRootImpl 中获取 MotionEvent 事件。




  2. ViewRootImpl 调用 ViewGroup 中的 dispatchTouchEvent() 方法,将事件分发给对应的子 View 进行处理。




  3. 子 View 依次递归处理事件,如果有子 View 拦截了事件,那么事件就不会再继续传递下去。




  4. 如果根据 onTouchEvent() 返回结果判断当前 View 处理了事件,那么事件就不会向子 View 再次传递。




  5. 如果事件都没有被拦截,那么这个事件将传递到 Activity 中的 onTouchEvent() 方法中。




  6. 事件捕获阶段




事件捕获阶段是指事件从根 View 开始,依次由父 View 捕获,直到当前 View,同时将事件自下向上传递给其祖先 View 的一种机制。主要是为了方便更改事件的属性,例如事件的坐标等。


具体流程如下:




  1. 系统会先将事件传递给该事件的目标 View,并将事件从根节点向下传递。




  2. 在向下子 View 传递事件时,父级 View 可以拦截事件并记录事件的坐标。




  3. 当事件传递到目标 View 的时候,事件的坐标信息会被用来进行对 View 执行操作的计算和判定。




  4. 如果当前 View 拦截了事件,那么此后的再次触摸事件都会被该 View 标记为 “拦截状态”,直到事件处理完毕,被放手传递给上级 View。




  5. 事件处理阶段




事件处理阶段是指最终处理 MotionEvent 的对象(除系统级别的)或者被设置成 Action.Cancel 的最后一个 View。在处理阶段中,会根据 View 的事件类型判断该事件是否可以被处理,如果可以则会继续进行处理,否则会将事件传递给父 View 或者其他 View 进行处理,直至事件被处理或者被放弃。


具体流程如下:




  1. 当一个 View 接收到一个 MotionEvent 事件时,它会首先检查自己的 onTouchEvent() 方法是否可以处理该事件。




  2. 如果当前 View 无法处理该事件,那么事件会被传递给上一级父 View 进行处理。




  3. 如果事件一直没有被处理,那么最终会将事件交给系统完成处理。




总的来说,Android 事件分发传递流程的机制是基于 View 树的节点结构,并且支持通过拦截事件、处理事件和返回事件等手段实现对事件的监测、干涉和响应。对于 Android 开发人员而言,深刻理解这个事件流程,有助于更完善地开发自己的 Android 应用程序。


二. Android常见的事件类型以及监听方式


Android 事件驱动机制是指通过一系列的事件监听器和回调函数来实现对用户交互操作的监听和相应的机制。Android 系统支持多种类型的事件监听器,包括触摸事件监听器、键盘事件监听器、手势事件监听器等,并且通过回调函数来实时监听用户的操作,从而实现对应用程序的控制和交互。下面我将分别介绍一下 Android 事件驱动机制中的各种监听器和回调函数:



  1. 触摸事件监听器


触摸事件监听器是 Android 事件驱动机制中的核心部分,主要用于对触摸事件的监听和处理。在 Android 中,触摸事件可以通过 MotionEvent 类来描述,主要包括三种基本的事件类型:ACTION_DOWN、ACTION_MOVE 和 ACTION_UP。可以通过实现 OnTouchListener 接口来监听触摸事件,并重写 onTouch() 方法来处理事件。例如:

view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理按下事件
break;
case MotionEvent.ACTION_MOVE:
// 处理移动事件
break;
case MotionEvent.ACTION_UP:
// 处理抬起事件
break;
}
return false;
}
});


  1. 键盘事件监听器


键盘事件监听器与触摸事件监听器类似,主要用于对键盘事件的监听和处理。在 Android 中,键盘事件可以通过 KeyEvent 类来描述,主要包括 ACTION_DOWN 和 ACTION_UP 两种基本的事件类型。可以通过实现 OnKeyListener 接口来监听键盘事件,并重写 onKey() 方法来处理事件。例如:

view.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
// 处理音量键加事件
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// 处理音量键减事件
break;
}
}
return false;
}
});


  1. 手势事件监听器


手势事件监听器主要用于对手势事件的监听和处理。在 Android 中,手势事件需要使用 GestureDetector 类来进行监听,并实现 OnGestureListener 和 OnDoubleTapListener 接口来处理手势事件。例如:

// 在 Activity 中实例化 GestureDetector,并将它绑定到 View 上
GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// 处理单击事件
return super.onSingleTapConfirmed(e);
}

@Override
public boolean onDoubleTap(MotionEvent e) {
// 处理双击事件
return super.onDoubleTap(e);
}
});

// 绑定 OnTouchListener 和 OnGestureListener
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});

除了上述的三种事件监听器,Android 还提供了多种其他类型的事件监听器,例如文本变化监听器、列表滚动监听器、视图绘制监听器等,这些监听器通过实现相应的接口和重写对应的回调函数来实现对应用程序的控制和响应。


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

Android 中将多个子模块合并成一个 aar

1. 项目的结构 目标:将模块A打包成 aar,提供给其他工程师使用。 模块之间的关系:模块A引用模块B,模块B引用模块C。 2. 使用 fat-aar-android 三方库进行实现 fat-aar-android 中文使用文档 添加以下代码到工程...
继续阅读 »

1. 项目的结构




image.png


目标:将模块A打包成 aar,提供给其他工程师使用。


模块之间的关系:模块A引用模块B,模块B引用模块C。


2. 使用 fat-aar-android 三方库进行实现




  1. 添加以下代码到工程根目录的build.gradle文件中:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.github.kezong:fat-aar:1.3.8'
}
}


  1. 添加以下代码到主模块的build.gradle中(Module A):
apply plugin: 'com.kezong.fat-aar'


  1. 添加以下代码到主模块的build.gradle中(Module A):
embed project(path: ':ModuleB', configuration: 'default')
embed project(path: ':ModuleC', configuration: 'default')


Module B 引用 Module C,则需要在 Module B 的 build.gradle中进行引用


eg: implementation project(path: ':Module B')




  1. 执行 assemble 命令
# assemble all 
./gradlew :ModuleA:assemble

到这里的话就顺利完成了,遇到其它问题的话,我暂时不会。


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

为什么需要拥有「不靠工作赚钱的技能」

为什么要不靠工作赚钱 一个关于时间的故事 前几天看到一个故事,有一个探险家路过一个村庄,发现村庄外竖立了很多墓碑,这些墓碑上记录了这些过世的人的生命时间。 探险家感到很好奇,因为这些人的生命时间都非常短,短的只有一两年,长的也只有不到十年。 探险家感到很悲伤,...
继续阅读 »

为什么要不靠工作赚钱


一个关于时间的故事


前几天看到一个故事,有一个探险家路过一个村庄,发现村庄外竖立了很多墓碑,这些墓碑上记录了这些过世的人的生命时间。


探险家感到很好奇,因为这些人的生命时间都非常短,短的只有一两年,长的也只有不到十年。


探险家感到很悲伤,以为这个村子里发生了什么意外,导致这么多小孩去世,于是他进入这个村庄,想要一探究竟。


他问村子里的人,这里曾经是否发生过灾难,村子里的人表示并没有啊,这里从来都是一个非常安静、祥和的村庄。


于是探险家把村口墓碑的事情告诉了村里人,问为什么死者都是可怜的孩子呢,村里人一听哈哈大笑起来。


原来,这个村子有一个习俗,每个人都会有一个记录时间的册子。当感到快乐的时候,就把这些时间记录在册子上。


等到这个人离世的时候,人们会打开他的本子,把这个人所有快乐的时间加在一起,刻在墓碑上,所以墓碑上的时间都特别短。


其实人生真正快乐的时间竟然如此短暂。


除去睡觉的时间、焦虑的时间、难过的时间、忙碌奔波的时间,真正留给自己享受生活的时间其实非常少。


工作大概率不能带来快乐


工作通常占了我们人生很大一部分的时间,而工作又大概率不能带来快乐。


如果能找到一份自己很喜欢热爱,并且能从中收获非常多成就感和快乐的工作,当然很好。但大部分人没有这样的幸运。


而且有的时候,明明很喜欢一件事情,但是把这件事情变成工作后,就会丧失之前的热情。


在成为程序员之前,我是非常喜欢写代码的,我觉得这件事情能带给我很大的成就感。其实就在工作的前几年,我也很享受写代码的感觉。


但是最近,却感觉越来越疲惫,工作中单纯的写代码的时间越来越少,更多的是与人沟通扯皮的过程。这样的工作内容,已经渐渐不再是我喜欢中的样子。


如果能不靠工作赚钱,会多出很多时间,去做自己喜欢的快乐的事情。


工作无法带来安全感


公司的存在,就是为了让各个岗位标准化,大家都做很小的一部分螺丝钉的工作,这样公司离开了谁都能马上找到一个类似的螺丝钉替换上。


但对于个人来说,如果你只拥有螺丝钉的技能,离开了公司这个平台,可能会发现,你的技能一文不值。


35岁危机是很多程序员都在关注和担忧的问题。在互联网行业,永远都有比你年轻、你比便宜、比你能加班的人出现,那么你的优势在哪里。


国内的互联网其实不需要太多高深的技术,更多的是需要快速迭代、堆人力堆时间。


如果没有不可替代性(其实这在公司的发展中就是伪命题),等到中年家庭压力变大、工作时间变少、薪资还高,自然会被更便宜的人力所取代。


在现在行业整体下行的情况下,各个公司都在降本增效,更会追求人力的性价比。


不靠工作赚钱


靠工作赚钱,必须付出大量的时间成本,而人真正的价值,不在于拥有多少金钱,而在于拥有多少有效时间。


金钱只是身外之物,时间才是一个人最宝贵的财富。用最宝贵的财富,去换取身外之物,是很不划算的事情。


生命只有一次,去追求自己喜欢的事情,真正把时间好好利用起来,去体验人生,才能不虚此行。


怎样才能不靠工作赚钱


凯文凯利曾经说过一个1000个铁杆粉丝的理论,意思是,如果你有1000个铁杆粉丝,就可以靠创作生存。


粉丝数量不需要太多,但是一定要对足够支持,愿意主动为你的创作付费。


发展一个真正属于自己的事业,在一个细分领域上,做到领先水平,通过满足这个领域内的需求获取收入。


如果你能在某个垂直领域里,成为专家,有自己独一无二的价值,有固定的粉丝,那这些价值是无法被他人剥夺的。


在这个领域上的积累,也不是一两年就能完成的,可能需要5-10年的长期积累。


在积累的前期,可以先通过副业的形式,利用业余时间在职做。


等到副业的收入可以几乎与主业匹配时,就可以考虑放弃主业全职做副业了。


寻找好的副业方向


好的副业一定要能撬动更大的杠杆。


因为主业通常是没有什么杠杆的,很简单的用时间和劳动来换金钱的模式。


如果副业,还是选择同样的,用时间换金钱的套路,那收入也是固定的,没有太大的想象空间。


所以,我觉得好的副业,一定是能撬动更大收益的方式,一旦成功能带来大量的超额收益。


比如,接私活就不是一个好的副业,因为接私活的收益完全取决于你干了多少活,手停口停,无法带来被动收益。


程序员可以考虑的方向


1. 投资


身边炒股的程序员非常多,赚钱的也很多,程序员与一般的股市投资者相比,通常还是有一定的优势。


首先是基础知识,在炒股之前,掌握多一些经济金融知识,学习基本面分析,还是非常有帮助的。


其次是数据分析能力,程序员可以借助自己在代码方面的优势,做一些数据分析的工作,帮助进行买卖决策。


2. 做在线课程


做在线课程,本质也是在做内容,必须要先有优质的内容,后面的运营和推广才有意义。


这个副业的好处是,只需要制作一次课程,就可以一直售卖,成本有限,而收益无限。


而且做在线课程,对本职工作通常也有帮助。


因为要做出某个方向上的好课程,需要对这个领域有深入的了解,如果和主业是相同的方向,对主业也会有积极的帮助。


3. 做自媒体


自媒体也是打造个人IP,个人影响力的重要渠道。需要花费不少的精力,持续输出优质内容。


和做在线课程一样,内容才是王道。很多人其实把自媒体和在线课程一起在做。


不需要等到比99%的人都厉害了才开始输出,如果目前的水平只要比70%的人强,那你输出的东西也可以帮助到70%的人。


所以不用等你完全准备好了才开始,先做起来,在做的过程中慢慢锻炼能力,也是可以的。


4. 运营自己的产品


身边有不少程序员同事,全职或兼职做自己的产品,前几年我也和同学合作开发过一些App产品。


我个人其实很喜欢开发一款产品的过程,那段时间我每天下班回到家后,就坐到书桌前敲代码,一直写到1点也不觉得累。看到每天都有人购买我们App的会员,就觉得非常开心。


对于喜欢做产品的同学,开发和运营自己的产品,也是一个不错的选择,而且一旦项目稳定之后,收入也是很不错的。


更多副业方向


我上面介绍的几个副业方向仅仅只是冰山一角,而且这些也是被大家尝试最多的方向,可能也是最卷的方向。


我个人目前准备尝试3自媒体和4运营自己的产品。


做自媒体的原因,我前面的文章也有写到,更多的是想分享一些东西,以输出push输入,帮助自己整理出知识体系。如果在这个过程中能帮助到有需要的小伙伴,我也会很开心。但这个过程中,我不强求一定要涨粉一定要赚钱。


尝试运营自己产品的原因,是因为我个人很喜欢做一款产品的感觉,这个会给我带来很大的成就感。


大家可以根据自己的观察,去发现一些小众的未被满足的需求,然后想办法去满足这些需求。可能这个才是不卷,又能获得不错收益的方式。


每个人的特长和喜欢的事情都不一样,想做的擅长的事情也都不一样,如果能找到你有而别人没有的技能,那就能走上不卷的道路。


读这篇文章的你,有没有什么你有别人没有的特长呢?如果实现了财务自由,最想去做什么呢?


欢迎大家评论区交流呀~


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

228欢乐马事件,希望大家都能平安健

我这个人体质很奇怪,总能遇到一些奇怪的事。 比如六年前半夜打车回家,差点被出租车司机拉到深山老林。 比如两年前去福州出差,差点永远回不了家。 比如十点从实验室下班,被人半路拦住。 比如这次,被人冒充 (在我心里这事和前几件同样恶劣) 不过幸好每次都是化险为...
继续阅读 »

我这个人体质很奇怪,总能遇到一些奇怪的事。



  • 比如六年前半夜打车回家,差点被出租车司机拉到深山老林。

  • 比如两年前去福州出差,差点永远回不了家。

  • 比如十点从实验室下班,被人半路拦住。

  • 比如这次,被人冒充 (在我心里这事和前几件同样恶劣)


不过幸好每次都是化险为夷,所以我顺顺利利活到现在。




事情起因是这样的:


去年朋友B突然告诉我:发现了你的闲鱼账号。


:我没有闲鱼啊?


他给我截图,那个人卖掘金的周边,名字叫LolitaAnn


image.png


因为我遇到太多离谱的事,再加上看的一堆被冒充的新闻,所以我第一反应是:这人也在冒充我


当时朋友F 说我太敏感了,他觉得只是巧合。


但我觉得不是巧合,因为LolitaAnn是我自己造的词。




把我的沸点搬到小红书


又是某一天, 朋友H告诉我:你的小红书上热门了。


:?我没有小红书啊?


然后他们给我看,有个人的小红书完全照搬我的沸点。


为此我又下载小红书和他对线。起初正常交涉,但是让他删掉,他直接不回复我了,最后还是投诉他,被小红书官方删掉的。


image.png


现在想了想,ip一样,极有可能是一个人干的。




闲鱼再次被挖出来


今年,有人在掘金群里说我卖周边。


我跑到那个群解释,说我被人冒充了。


群友很热心,都去举报那个人的昵称。昵称被举报下去了。


但是几天之后:


image.png


看到有人提醒我,它名字又改回来了。


当时以为是热心群友,后来知道就是它冒充我,现在回想起来一阵恶寒。


把名字改回去之后还在群里跟炫耀一样,心里想着:我改回来了,你们不知道是我吧。




冒充我的人被揪出来了


2.28的时候, 朋友C突然给我发了一段聊天记录。


是它在群里说 它的咸鱼什么掘金周边都有。结果打开一看,闲鱼是我的名字和头像。


image.png


事情到今天终于知道是谁冒充我了


虽然Smile只是它的微信小号之一,都没实名认证。但是我还是知道了一些强哥的信息。


发现是谁冒充我,我第一反应必然是喷一顿。


刚在群里被我骂完,它脑子也不太好使,马上跑去改了自己掘金和闲鱼的名字。这不就是自爆了? 证明咸鱼和掘金都是他的号。


奔跑姐妹兄弟(原名一只小小黄鸭) ←点击链接即可鞭尸。


image.png




牵扯出一堆小号


本来我以为事情已经结束了,我就去群里吐槽他。结果一堆认识他的掘友们给我说它还有别的掘金号。因为它和不同掘友用不同掘金号认识的。所以掘友们给我提供了一堆。我就挨个搜。


直到我看到了这两条:


image.png


因为我搜欢乐马出来上万个同名账号, 再加上他说自己有脚本,当时我以为都是他的小号。


后来掘友们提醒我,欢乐马是微信默认生成的,所以有一堆欢乐马,不一定是他的小号。


但是我确信他有很多小号,比如:


image.png


比如咸鱼卖了六七个掘金鼠标垫,卖了掘金的国行switch……




  • 你们有没有想过为什么fixbug不许助攻了




  • 你们有没有想过为什么矿石贬值了,兑换商店越来越贵了?




  • 你们有没有想过为什么掘金活动必得奖励越来越少了?




有这种操作在,普通用户根本没办法玩吧。


所以最后,我就把这个事交给官方了。




处理结果


所幸官方很给力,都处理了,感谢各位官方大大。



本次事件,共涉及主要近期活跃UserID 4个,相关小号570个。 具体可以看
2023 年 2 月社区治理数据公布





我再叨叨几句




  • 卖周边可以, 你别冒充别人卖吧,又不是见不得光,做你自己不丢人。




  • 开小号可以,你别一开开一堆,逼得普通玩家拿不到福利吧。




  • 先成人后成才,不指望能为国家做多大贡献,起码别做蛀虫吧。




  • 又不是没生活,专注点自己的东西,别老偷别人沸点。




  • 我以后改名了嗷。叫Ann的八百万个,别碰瓷你爹了。






最后祝大家生活愉快,正常的掘友们身体健康,工作顺利。


不正常的也收敛点,别把自己搞进去。


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

特斯拉靠谱不靠谱 ?

去年9月底提了特斯拉modelY丐版,大半年时间开了大概8000多公里,简单总结分享一下,以答一些朋友的疑问。 首先聊聊为什么选择modelY,主要还是 个人喜好 。 具体来说分价格、用车体验两个方面。订购之前大概了解沃尔沃v90cc,奥迪a6旅行版,理想,蔚...
继续阅读 »

去年9月底提了特斯拉modelY丐版,大半年时间开了大概8000多公里,简单总结分享一下,以答一些朋友的疑问。


首先聊聊为什么选择modelY,主要还是 个人喜好


具体来说分价格、用车体验两个方面。订购之前大概了解沃尔沃v90cc,奥迪a6旅行版,理想,蔚来,比亚迪,大众等,试驾过奔驰glc,沃尔沃xc60,极氪001。现在看起来看的功课好像做的很多,但是很多品牌都是走马观花;即使是试驾,里程较短,实际上了解比较有限。这导致了买完就降价的大亏损,被市场割韭菜了。


价格当初心理价位是30~35w左右,恰逢油价高峰,所以更倾向购买电动车,降低一下日常使用成本。其实更偏爱v90cc这种旅行版小众车,但是小众车保有量比较低,二手交易折价会较高,钱包有限,只好放弃。再加上新能源的税费优惠,北京市转换燃油牌照到新能源牌照还有额外补贴。还有比较看重的一点是,国内国外车价格的差异,相对来说特斯拉在国内比国外便宜,让人感觉比较赚。


用车体验主要是平常爱好出去露营,modelY的收纳空间不错,可以带较多的装备;自己又比较懒,特斯拉的极简(廉价)风,比较容易打理,吸尘器加抹布就搞定。智能化比较适中,传统车的车机感觉较浓的安卓风,不是特别流畅,大屏也有点强加块屏硬凑的感觉;新势力呢,则是有点太浮夸了,娱乐性太多,觉得钱都花到娱乐设施上了,车主要还是开的。品牌这块呢,特斯拉争议很大,不吹不黑,作为码农还是对科技 先行者 有一些好感。铁锂比三元锂更稳定,又没有飙车的需求,所以标准版就够用了。


这半年使用起来,整体感觉还是比较满意。


先谈谈几个缺点吧。 第一是特斯拉基本没有客户服务。买完后第一周就降价了,从销售到支持专员,没有任何解释和安慰,官方也没有任何说法,感受还是很糟糕的。买车不是买股票,提车也有专员催促的,多多少少造成提前提车的预期。只能够阿Q自己,自己是原装正版,别人是打折促销货了:) 。


第二是有一些功能很鸡肋,自动辅助FSD会免费试用三个月,但是刚提车完全不熟悉,不敢使用;自动泊车,好像也不行,尝试了一次,感觉都要撞柱子,放弃后再也没有使用;语音识别准确率较低,地图不怎么好用。


第三是OTC升级略频繁,让人有不稳定的感觉。


最后是冬天里程衰减较多,大概打了 六折 吧,这应该是所有纯电的通病。


满意的地方也有一些。第一是里程,买之前对满电可以跑多远没有太了解。官网都标的CLTC工况545公里,实际拿到车才知道可以跑435公里,少了100公里。这样上班每天单程20公里,一周充一次电没有什么压力。


第二是充电时间,同样开始也了解有限。现在国网的超充很方便,速度和特斯拉的超充差不多,相反特斯拉超充价格较贵,还收超时费,一般用的不多。大概情况是100A左右直流桩,可以 50分钟从30%充到90% 。充电时间是不等比的,需要特别介绍一下,初始的10%要预热电池,最后的10%可能要防止过热,都充的较慢。交流桩则充电很慢,5个小时多,一般是夜间休息使用。之前会每周白天跑步时候使用快充(直流)一次电,现在则是晚上使用慢充(交流)一次电,据说慢充对电池较好。


第三是使用体验很很傻瓜式,拿手机上车,给电就走,中途不用换挡,自动Hold;关门就走,不用拉手刹,熄火和锁车;远光近光自动切换,雨刷也可以自动,转向灯可以自动回收。


第四是辅助驾驶在高速上帮助比较大,春节期间驾车去湖北中部,全程来回3000公里,没有轮换驾驶员,中途使用车道保持放松,并没有特别累人的感觉。


以上都是个人感受向的内容,下面简单介绍几个重点问题。


刹车问题


首先来说,特斯拉是有刹车的。普通人会看到特斯拉单踏板模式的说法,进而产生特斯拉没有刹车的印象。这不是愚人节笑话,我有次洗车的时候,旁边大姐就是这样问我的:) 我打开车窗让她看了一下。


所谓单踏板模式,是指可以使用电车油门(其实应该叫电门吧,油门叫习惯了)进行加速和减速控制,和油车的油门加速和刹车减速的逻辑有较大差别。




  • 可以看到驾驶位右侧一样有大个的刹车踏板和小个的油门踏板

  • 特斯拉很简陋,所以需要自己假装一些配件,比如手机导航支架,很怪异


单踏板模式标准说法大概叫 保持 , 可以在刹车的时候利用惯性反转电机给电池充电,从而提高里程。



这样日常使用会有两个需要适应的地方。第一个是如果有 备刹 的习惯,则需要调整。备刹是指在过路口,需要将右脚放刹车上,防止突然来的行人电动车,可以快速刹车。但是单踏板模式下,不踩油门车不走,就没法备刹车。我的应对方式是,路口减速,多观察,同时养成车辆报警就移动脚踩刹车的习惯。


第二个是停车的时候,不给油不走,而且给油走的还挺快,需要注意。


总体来说,我个人觉得,单踏板是可以适应的,适应后用起来还是不错,但是尽量别油车和电车混着开。就像安卓和苹果混着用,有时候会迷茫一下下。


至于刹不住车的问题,不在讨论范围。开车都要谨慎驾驶,车撞了还好,人受伤可受不了,生命权至上。


长途问题


电动车能够不能够跑长途?我的答案是能够,但是不多。春节期间,单程1200公里的长途,出于安全,中途都住了一晚,如果是油车按说是不需要住宿的。



高德导航可以使用新能源模式,提醒那些服务区可以进行充电补能。上图显示需要进行三次充能,每次充能按一个小时算,看起来只会比油车慢3个小时。但是这些是理论值,实际上我是充了4次,因为叠合了冬天气温低。


除了电池电量对长途有影响,还有一个重要的点是电车的工作模式。电车是高速状况下能耗偏高,低速状况下能够较低,这和油车是相反的。一般推荐是时速110比较高效,所以高速上实际跑的比油车慢。


另外就是服务区的充电状况了,目前是服务区的桩少,车也不多,大多数情况下不用排队。实际上油车有时候加油也是要排长队的。需要提前做好规划,最好副驾协助进行充电规划。


如果再让我选择跑超过1000公里的长途,我大概不会选电车了:)


能耗问题


modelY标配是435公里,这是新车满电理论状况。实际上半年后,我这里显示是433,衰减了2公里。我猜测原因除了电池衰减,应该还有胎压的关系。刚提车胎压是3.1,现在都是2.8,摩擦力增大,所以持续里程也有减少。




  • 315+118=433


从上图可以看到最近的平均能耗要比总体能耗低不少,应该是天气和日常市区行车的原因。里程的计算也是根据驾驶习惯,实时动态计算的,比如下面的能耗曲线,预测里程是368,比表现高了53公里。




  • 368-315=53


成本问题


电车基本不用怎么维护,比如不用油车常见的半年/1万公里的保养。我的近期维护清单如下, 近期看就是1年换一下滤网:



上图可以看到制动液检测的进度条有bug,实际上车机是会有bug。有发生过一次高速上车机黑屏的问题,第一次碰到吓的够呛,所以一定要多了解这个大玩具。还有一次,从延庆回城,里程能耗特别低感觉计算也不太对。


最省钱的地方就是电费。我贴了一个直流和一个交流的订单如下:



简单的说,使用公用电桩一次充电大概60,一周一次,一个月大概260块钱,平均 = 260 / ( 40*22 )三毛钱一公里。 使用公用桩都会收取服务费,如果是私桩,这一部分可以免去,还可以大大降低,大概可以达到 一毛钱一公里 , 对比油车大概是 一块钱一公里,是真省。


好了,以上就是modelY的开箱报告了,希望对选车的朋友有所帮助。 当然,电车就是未来!


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

程序员IT行业,外行眼里高收入人群,内行人里的卷王

程序员 一词,在我眼里其实是贬义词。因为我的其他不是这行的亲朋友好友,你和他们说,你是一名程序员· 他们 第一刻板影响就是,秃头,肥胖,宅男,油腻,不修边幅 反正给人一种不干净,不好形象,,,,不知道什么时候开始网络上也去渲染这些,把程序员和这些联想在一起了。...
继续阅读 »

程序员 一词,在我眼里其实是贬义词。因为我的其他不是这行的亲朋友好友,你和他们说,你是一名程序员·


他们 第一刻板影响就是,秃头,肥胖,宅男,油腻,不修边幅 反正给人一种不干净,不好形象,,,,不知道什么时候开始网络上也去渲染这些,把程序员和这些联想在一起了。


回到正题,我们来聊聊,我们光鲜靓丽背后高工资。


是的作为一名程序员,在许多人的眼中,IT行业收入可能相对较高。这是不可否认的。但是,在这个职业领域里,我们所面对的困难和挑战也是非常的多。


持续的学习能力



程序员需要持续地学习,不断地掌握新技能。



随着技术的不断发展,我们需要不断地学习新的编程语言、开发框架、工具以及平台等等,这是非常耗费精力和时间的。每次技术更新都需要我们拿出宝贵的时间,去研究、学习和应用。


尤其在公司用项目中,用到新技术需要你在一定时间熟悉并使用时候,那个时候你自己只有硬着头皮,一边工作一边学习,如果你敢和老板说不会,那,,,我是没那个胆量


高强度抗压力



ICU,猝死,996说的就是我们



我们需要经常探索和应对极具挑战性的编程问题。解决一个困难的问题可能需要我们数小时,甚至数天的时间,这需要我们付出大量的勤奋和耐心。有时候,我们会出现程序崩溃或运行缓慢的情况,当然,这种情况下我们也需要更多的时间去诊断和解决问题,


还要保持高效率工作,同时保证项目的质量。有时候,团队需要在紧张的时间内完成特别复杂的任务,这就需要我们花费更多的时间和精力来完成工作。


枯燥乏味生活


由于高强度工作,和加班,我们的业余生活可能不够丰富,社交能力也会不足


高额经济支出


程序员IT软件行业,一般都是在一线城市工作,或者新一线,二线城市,所以面临的经济支持也会比较大,


最难的就是房租支持,生活开销。


一线城市工作,钱也只能在一线城市花,有时候也是真的存不了什么钱,明明自己什么也没有额外支持干些什么,可是每月剩下的存款也没有多少


短暂职业生涯


“背负黑匣子”:程序员的工作虽然看似高薪,但在实际工作中,我们承担了处理复杂技术问题的重任。


“独自快乐?”:程序员在工作中经常需要在长时间内独立思考和解决问题,缺乏团队合作可能会导致孤独和焦虑。


“冰山一角的技能”:程序员需要不断学习和更新技能,以适应快速变化的技术需求,这需要不断的自我修炼和付出时间。


“猝不及防的技术变革”:程序员在处理技术问题时需要时刻保持警惕,技术日新月异,无法预测的技术变革可能会对工作带来极大的压力。


“难以理解的需求”:客户和管理层的需求往往复杂而难以理解,程序员需要积极与他们沟通,但这也会给他们带来额外的挑战和压力。


“不请自来的漏洞”:安全漏洞是程序员必须不断面对和解决的问题,这种不确认的风险可能会让程序员时刻处于焦虑状态。


“高度聚焦的任务”:程序员在处理技术问题时需要集中精力和关注度,这通常需要长时间的高度聚焦,导致他们缺乏生活平衡。


“时刻警觉”:程序员在工作中必须时刻提醒自己,保持警觉和冷静,以便快速识别和解决问题。


“枯燥重复的任务”:与那些高度专业的技术任务相比,程序员还需要完成一些枯燥重复的工作,这让他们感到无聊和疲惫。


“被误解的天才”:程序员通常被视为是天才,但是他们经常被误解、被怀疑,这可能给他们的职业带来一定的负担。


程序员IT,也是吃年轻饭的,不是说你年龄越大,就代表你资历越深。 职业焦虑30岁年龄危机 越来越年轻化


要么转行,要么深造,

Yo,这是程序员的故事

高薪却伴随着堆积如山的代码

代码缺陷层出不穷,拯救业务成了千里马

深夜里加班的钟声不停响起

与bug展开了无尽的搏斗,时间与生命的角逐

接口返回的200,可前端却丝毫未见变化

HTTP媒体类型不支持,世界一团糟

Java Spring框架调试繁琐,无尽加班真让人绝望

可哪怕压力再大,我们还是核心开发者的倡导者

应用业务需要承载,才能取得胜利的喝彩

程序员的苦工是世界最稀缺的产业

我们不妥协,用技术创意为行业注入新生命

我们坚持高质量代码的规范

纵使压力山大,我们仍能跨过这些阻碍

这是程序员的故事。

大家有什么想法和故事吗,在工作中是否也遇到了和我一样的问题


可以关注 程序员三时公众号 进行技术交流讨论


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

程序员增强自控力的方法

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法: 1. 设定明确的目标和计划 制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任...
继续阅读 »

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法:


1. 设定明确的目标和计划


制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任务清单、时间追踪工具等,来帮助我们控制时间并更有效地完成任务。


2. 掌控情绪


作为程序员,我们需要面对很多挑战和压力,容易受到情绪的影响。因此,掌握情绪是一个非常重要的技能。可以通过冥想、呼吸练习、运动等方法,来帮助我们保持冷静、积极和乐观的心态。


3. 管理焦虑和压力


焦虑和压力是我们常常遇到的问题之一,所以我们需要学会如何管理它们。我们可以使用放松技巧、适度锻炼、交流沟通等方法,来减轻我们的焦虑和压力。


4. 培养自律习惯


自律是一个非常重要的品质。我们可以通过设定目标、建立规律和强化自我控制等方式,来培养自律习惯。


5. 自我反思和反馈


经常进行自我反思和反馈可以帮助我们更好地了解自己的优缺点和行为模式。我们可以使用反馈工具或与他人交流,来帮助我们成长和改进。


6. 持续学习和自我发展


程序员需要不断学习和自我发展,以保持竞争力和提升自己的技能。通过阅读书籍、参加培训、探究新技术等方式,可以帮助我们持续成长,增强自我控制力。


结论


自控力是我们工作和生活中重要的的品质之一,可以帮助我们更好地应对各种挑战和压力。通过设定目标、掌控情绪、管理焦虑和压力、培养自律习惯、自我反思和反馈、持续学习和自我发展等方法,我们可以帮助自己增强自我控制

作者:郝学胜
来源:juejin.cn/post/7241015051661312061
能力并提高工作效率。

收起阅读 »

来自智能垃圾桶的诱惑,嵌入式开发初探

背景原因 最近裸辞离职在老家,正在收拾老家的房子准备住进去,但迫于经济压力,只能先装非常有必要的东西,例如床、马桶、浴室柜等。 但是垃圾桶是生活必备品,我发现大家现在都在用智能垃圾桶,那种可以感应开盖的,还有那种自动封袋的。我极其的羡慕。但是迫于经济压力,...
继续阅读 »

背景原因



最近裸辞离职在老家,正在收拾老家的房子准备住进去,但迫于经济压力,只能先装非常有必要的东西,例如床、马桶、浴室柜等。



但是垃圾桶是生活必备品,我发现大家现在都在用智能垃圾桶,那种可以感应开盖的,还有那种自动封袋的。我极其的羡慕。但是迫于经济压力,我没有足够的资金购买智能垃圾桶!



作为一个学美术的程序员,我在想我要不要去少儿美术培训教小孩,或者去街头卖艺,甚至去给人画遗照赚点钱呢?我想了想算了,给别人留点活路吧,我这么专业的美术人才去了,那些老师、街头艺人、画师不得喝西北风去。我想了想,我除了是学美术的,我还是个程序员啊!


虽然我没有学过嵌入式、硬件、IOT等等技术,但是入门应该不太困难吧!已经好多年没有从0开始学习一门技术了,非常怀念当时学习Web技术的那种感觉,可以没日没夜的去探索新知识,学习东西。之前上班的时候根本没有精力去学习或者搞一些有趣的东西,下班只想躺着看动漫,听爽文。既然现在赋闲在家,那就搞一搞吧!


项目启动


俗话说得好,找到一个好师傅就是成功的一半!在这方面我有着得天独厚的优势,我的前公司,北京犬安科技就是一个做车联网安全的公司,这家公司的Slogan是:从硅到云,守护网联汽车安全。可以看出,犬安的硬件软件安全能力都非常的强。像我,天天和测试组的同事去山西面馆吃鱼香肉丝盖饭,喝青岛雪花,他们组里面都是硬件大佬,甚至有人造过火箭。此时不向他们学习,更待何时?


他们告诉我,我需要一个开发板、一个超声波传感器、一个舵机。


我还在研究什么是stm32/esp32的时候,他们告诉我用Arduino,特别简单。我就去研究Arduino,在B站上搜了 Arduino,有一个播放量比较高的系列教程看了一下。我顿时就悟了!


我本来以为我需要买烙铁,焊电路板的,后来我发现,只需要买以上提到的三个东西,外加一个面包版,一些杜邦线就可以,甚至可以不需要面包版。



当然为了学习,我在淘宝上买了一些乱七八糟的东西,比如按钮、各种电阻、各种颜色led灯、面包版。买各种颜色的LED灯的时候,购买欲泛滥了,就想每个颜色都买一些,什么白发红,白发黄,白发翠绿,后来我买回来发现,不亮的情况下都是白色的,我根本分不清颜色。原来那个白发黄的意思就是不亮的时候是白色,亮了以后发黄色的光~我还买了接受RGB三色的LED灯,不知道为啥不是RGBA,难道不能让灯透明吗?



我还发现不同欧姆的电阻他上面的“杠杠”是不一样的。



主要是这玩意太便宜了,两块钱就买好多,但是作为新手玩家来说,根本用不了。我只买了公对公的杜邦线,然后我又单独下单了其他杜邦线,果然两块多就能包邮。



我在不同的店铺里面选了很多配件,包括我使用的主要配件:Arduino uno 开发板、SG90舵机、HC-SR04 超声波传感器。


开搞


设备买回来以后,废了9牛2虎之力才成功的能把我写的程序上传到开发板里。


一开始一直报下面这个错误,网上众说纷纭,始终没有找到解决方案。


avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x30

后来我去淘宝详情页仔细研究了一番才搞明白,我本以为我买的是Arduino的开发板,其实是esp8266,只是兼容Arduino。我从淘宝详情页找到了正确姿势,成功的刷入了程序。


看了 Arduino 的视频,我主要悟出来了什么?


开发板、舵机、传感器上有很多小尖尖或者小洞洞,他叫引脚,我们需要把舵机、传感器的引脚,通过一种名为杜邦线的线(通常有公对公/母对母/公对母,代表线两头是小尖尖还是小洞洞),连接到开发板的引脚上。然后使用程序控制某个引脚的电平向舵机发送信号,或者接受传感器的信号。


第一个项目


我举一个最简单的LED灯闪烁的例子: 线是这么接的:



面包版上面,竖着的五个小洞洞里面其实是连在一起的。我的电路是从一个引脚出来,到了一个电阻上,然后连接一个LED灯,然后接地。 接地的概念应该和负极的概念很像,但是不是,不过我暂且不关心它。 为什么不直接从引脚出来接LED,再接地呢,因为视频上说,LED几乎是没有电阻的,如果直接从串口出来接到LED,直接接地的话,开发板就废了,所以加了个电阻。


接下来我让ChatGPT帮我写一个小LED灯闪烁的代码:



我们可以看到在loop里面,调用了digitalWrite,把那个引脚进行了高低电平的切换,这样就实现了小LED灯闪烁的效果,高电平就相当于给小LED灯供电了。


我们只需要把他写的代码中的引脚的编号改成我们的编号就好了。


每种开发板的引脚编号都不一样!我在淘宝上买的这个板,他没有给我资料,我干了一件特别愚蠢的事情,分别给不同的数字编号供电,插线看哪个亮,就记录对应开发板上的引脚号和数字。


第二天我在网上稍微搜了一下资料就找到了。后来我发现,竟然还有一个编号对应两个引脚的,具体为什么我也不知道。


不过具体怎么给舵机传信号,或者接受传感器的信号呢?如果在 Arduino IDE 里面的话就是简单的调用API就好了。这一块我没写代码,都是ChatGPT帮我写的。



实现目标的主要部分


我使用同样的方法,制作了垃圾桶的功能的电路和代码。 使用超声波传感器的时候,我发现这玩意都好神奇啊,仿佛是魔法一样。 比如超声波传感器,要先发送一个超声波信号出去,然后等待回波信息,拿到发送和接收的时间差,通过计算得到距离。



我在调试超声波传感器的时候,我发现我能听到超声波传感器发出的声音,只要耳朵对着它不用靠太近就可以听得到。我朋友说我是超级耳。


有了LED灯的经验以后,超声波识别距离,调用舵机旋转很快就实现了。



最开始我在考虑这个舵机的力道到底能不能支撑起来垃圾盖子,不过现在感觉力道还是挺大的。不过现在没有办法试,因为我还没有做出来合适的垃圾桶。


接下来


接下来我要去找到一个合适的垃圾桶,我也不确定需要用什么方式去打开垃圾桶的盖子,是不是需要其他的比如初中学过的滑轮、齿轮、杠杆原理什么的?这块还没有开始涉猎,等我接下来研究研究来写第二部分。



作者:明非
来源:juejin.cn/post/7215217068803145785

感谢大家阅读~

收起阅读 »

一个大龄小前端的年终悔恨

web
今年都做什么了? 刷视频 打王者 空余时间维护了一个项目 就这样吧 仔细想了想今年也没有做什么呀! 真是年纪越大时间越快 为什么有大有小啊? 95的够大了吧 步入前端也才不到3年 So一个大龄的小前端 技术有长进么? 一个PC端项目 用了 react a...
继续阅读 »

image.png




今年都做什么了? 刷视频 打王者 空余时间维护了一个项目 就这样吧



仔细想了想今年也没有做什么呀! 真是年纪越大时间越快




为什么有大有小啊?


95的够大了吧


步入前端也才不到3年


So一个大龄的小前端


技术有长进么?


一个PC端项目 用了 react antd redux-toolkit react-router ahooks axios 也就这样吧,就一点简单的项目,react熟练了么?有点会用了,可是我工作快3年了,写项目还是要来回查文档,antd用的熟练的时候倒是可以不用去查文档,可是过了就忘了,今天写项目就有点想不起来怎么用了,查了文档才可以继续写下去


有长进么?




  1. react熟练了一些,可以自己看源码了




  2. 自己解决问题的能力有了一点提升




  3. 技术的广度认识有了(23年目标是深度)




  4. 数据结构了解一点了 二叉树 队列 链表 队列 (还学了一点算法,不过忘了🤣)




  5. 写代码喜欢封装组件了




  6. node学了一点又忘了




  7. ts会的多了一点




  8. antd也好一点了,以前在群里问一些小白问题,还好有个大哥经常帮我




  9. css 还是不咋地 不过我刚买了一个掘金小册 s.juejin.cn/ds/hjUap4V[…




生活上有什么说的呢?


生活很好 吃喝不愁


就是太久没有回家了 老家黑龙江 爷爷奶奶年纪大了 有时候想不在杭州了 回哈尔滨吧 这样可以多陪陪他们 可是回哈尔滨基本就是躺平了 回去我能做什么? 继续做前端? 好好补补基础去做一个培训讲师?


回去的好处是房子压力小 可以买一个车 每天正常上班 下班陪家人 到家有饭吃 想想也挺好


不过女朋友想在杭州,所以我还会在杭州闯一下的,毕竟我们在杭州买房子也是可以努力一下的


女朋友对我很好 我们在一起也快3年了 我刚步入前端的时候我们刚在一起 2020-05-20 她把我照顾的很好 她很喜欢我我感觉的到 我平时不太会表达 其实我是想跟她结婚的我也喜欢她 我对她耐心少了一点 这一点我会改的 以后我想多跟她分享我每天发生的事 我想这样她会更开心一点吧


今年她给我做了好多的饭,有段时间上班都是她晚上下班回来做的(她下班的早 离家近) 第二天我们好带去(偶尔我们吃一段时间的轻食) 可是我还是胖了




image.png


2023要怎么做?


我想成为大佬 我想自律一些 还有工资也要多一点吧



  • 开年主要大任务 两个字 搞钱 咱们不多来 15万可以吧 嗯 目标攒15W

  • 紧接上条 要是买 20W-30W的车 那你可以少攒点 8万到10万 (买车尽量贷款10W)

  • MD 减肥可以吧 你不看看你多胖了呀 175的身高 快170斤了减到140斤 (总觉得不胖,壮)

  • 技术一定要提升 你不能再这样下去了 要被清除地~





技术我们来好好的捋一下,该怎么提升




  1. 现有项目自己codeReview(改改你的垃圾代码吧)

  2. css多学点

    1. css in js

    2. Tailwindcss

    3. css Module less 写法好好研究一下

    4. css 相关配置要会



  3. react源码要搞一下

    1. fiber

    2. hooks

    3. diff

    4. 一些相关的库的源码 (router,redux等)



  4. webpack vite (要能写出来插件)

  5. node 这个一定要学会 (最起码能自己写接口和工具)

  6. 文章要搞起来 (最起码要写20篇,前5篇要一周一篇文章)


2023 搞一个 pc端 H5 小程序 后台接口 要齐全 必须搞出来一个 加

作者:奈斯啊小刘超奈斯_
来源:juejin.cn/post/7174789490580389925
油💪🏻

收起阅读 »

转转商品到手价

1 背景介绍 1.1 问题 搜索结果落地页,按照价格筛选及排序,结果不太准确; 用户按照价格筛选后的商品与实际存在的商品不符,可能会缺失部分商品,影响到用户购物体验。 1.2 到手价模块在促销架构中所处的位置 在整体架构中,商品的到手价涉及红包,...
继续阅读 »

1 背景介绍


1.1 问题




  • 搜索结果落地页,按照价格筛选及排序,结果不太准确;




  • 用户按照价格筛选后的商品与实际存在的商品不符,可能会缺失部分商品,影响到用户购物体验。




image


1.2 到手价模块在促销架构中所处的位置


在整体架构中,商品的到手价涉及红包,活动等模块的协同工作。通过将商品售价、红包、活动等因素纳入综合考虑,计算出最终的到手价,为顾客提供良好的购物体验。


image


2 设计目标



  • 体验:用户及时看到商品的最新到手价,提升用户购物体验;

  • 实时性:由原来的半小时看到商品的最新到手价,提升到3分钟内。


3 技术方案核心点


3.1 影响因素


image


影响商品到手价的主要因素:




  1. 商品,发布或改价;




  2. 红包,新增/删除或过期;




  3. 活动/会馆,加入或踢出。




3.2 计算公式


image


如图所示,商品详情页到手价的优惠项明细可用公式总结如下:


商品的到手价 = 商品原价 - 活动促销金额 - 红包最大优惠金额


4 落地过程及效果


image


随着业务需求的变化,系统也需要不断地增加新功能或者对现有功能进行改进,通过版本演进,可以逐步引入新的功能模块或优化现有模块,以满足业务的需求。


商品的到手价设计也是遵循这样规则,从开始的v1.0快速开发上线,响应业务; 到v2.0,v3.0进行性能优化,升级改造,使用户体验更佳,更及时。


4.1 v1.0流程


image


v1.0流程一共分为两步:




  1. 定时任务拉取拉取特定业务线的全量商品,将这批商品全量推送给各个接入方;




  2. 促销系统提供回查接口,根据商品id等参数,查询商品的到手价;




4.2 v1.0任务及接口


image




  1. v1.0任务执行时间长,偶尔还会出现执行失败的情况;而在正常情况下用户大概需要半小时左右才能感知到最新的商品到手价;




  2. 需要提供额外的单商品及批量商品接口查询到手价,无疑会对系统产生额外的查询压力,随着接入方的增加,接口qps会成比例增加;




4.3 v2.0设计


针对v1.0版本长达半个小时更新一次到手价问题,v2.0解决方案如下:



  • 实时处理部分


商品上架/商品改价;


商品加入/踢出活动;


商品加入/踢出会馆;


这部分数据的特点是,上述这些操作是能实时拿到商品的infoId,基于这些商品的infoId,可以立即计算这些商品的到手价并更新出去。


image


商品发布/改价,加入活动/会馆,踢出活动/会馆;接收这些情况下触发的mq消息,携带商品infoId,直接计算到手价。



  • 3min任务,计算特定业务线的全量商品到手价


红包: 新增/更新/删除/过期;


这部分数据的特点是,一是不能很容易能圈出受到影响的这批商品infoIds,二是有些红包的领取和使用范围可能会涉及绝大部分的商品,甚至有些时候大型促销会配置一些全平台红包,影响范围就是全量商品。


综上,结合这两种情况,以及现有的接口及能力实现v2.0;


image


推送商品的到手价,消息体格式如下,包括商品id,平台类型,到手价:


[
{"infoId":"16606xxxxxxx174465"
"ptType":"10""
realPrice"
:"638000"}
]

image


首先在Redis维护全量商品,根据商品上架/下架消息,新增或删除队列中的商品;其次将全量商品保存在10000队列中,每个队列只存一部分商品:


queue1=[infoId...]

queue2=[infoId...]

...

queue9999=[infoId...]

右图显示的是每个队列存储的商品数,队列商品数使其能保持在同一个量级。


image


多线程并发计算,每个线程只计算自己队列的商品到手价即可;同时增加监控及告警,查看每个线程的计算耗时,右图可以看到大概在120s内各线程计算完成。


注意事项:




  1. 避免无意义的计算: 将每次变化的红包维护在一个队列中,任务执行时判断是否有红包更新;




  2. 并发问题: 当任务正在执行中时,此时恰巧商品有变化(改价,加入活动等),将此次商品放入补偿队列中,延迟执行;




综上,结合这两种场景可以做到:




  1. 某些场景影响商品的到手价(如改价等),携带了商品infoId,能做到实时计算并立即推送最新的到手价;




  2. 拆分多个商品队列,并发计算; 各司其职,每个线程只计算自己队列商品的到手价;




  3. 降低促销系统压力,接入方只需要监听到手价消息即可。




4.4 v3.0设计


image


可以看到随着商品数的增加,计算耗时也成比例增加。


image


解决办法:




  1. 使用分片,v2.0是将一个大任务,由jvm多线程并发执行各自队列的商品;
    v3.0则是将这个大任务,由多个分片去执行各自队列中的商品,使其分布式执行来提高任务的执行效率和可靠性;




  2. 扩展性及稳定性强,随着商品的增多,可以适当增加分片数,降低计算耗时。




5 总结




  • 系统扩展性 数据量日渐增大,系统要能做升级扩展;




  • 系统稳定性 业务迭代,架构升级,保持系统稳定;




  • 完备的监控告警 及时的监控告警,快速发现问题,解决问题;




  • 演进原则 早期不过度设计,不同时期采用不同架构,持续迭代。







关于作者



熊先泽,转转交易营销技术部研发工程师。代码创造未来,勇于挑战,不断学习,不断成长。



转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~


作者:转转技术团队
来源:juejin.cn/post/7240006787947135034

收起阅读 »

分享近期研究的 6 款开源API网关

随着API越来越广泛和规范化,对标准化、安全协议和可扩展性的需求呈指数级增长。随着对微服务的兴趣激增,这一点尤其如此,微服务依赖于API进行通信。API网关通过一个相对容易实现的解决方案来满足这些需求。 也许最重要的是,API网关充当用户和数据之间的中介。AP...
继续阅读 »

随着API越来越广泛和规范化,对标准化、安全协议和可扩展性的需求呈指数级增长。随着对微服务的兴趣激增,这一点尤其如此,微服务依赖于API进行通信。API网关通过一个相对容易实现的解决方案来满足这些需求。


也许最重要的是,API网关充当用户和数据之间的中介。API网关是针对不正确暴露的端点的基本故障保护,而这些端点是黑客最喜欢的目标。考虑到一个被破坏的API在某些情况下可能会产生惊人的灾难性后果,仅此一点就使得API网关值得探索。网关还添加了一个有用的抽象层,这有助于将来验证您的API,防止由于版本控制或后端更改而导致的中断和服务中断。


不幸的是,许多API网关都是专有的,而且价格不便宜!值得庆幸的是,已经有几个开源API网关来满足这一需求。我们已经回顾了六个著名的开源API网关,您可以自行测试,而无需向供应商作出大量承诺。


Kong Gateway (Open Source)


Kong Gateway(OSS)是一个受欢迎的开源API网关,因为它界面流畅、社区活跃、云原生架构和广泛的功能。它速度极快,重量轻。Kong还为许多流行的基于容器和云的环境提供了现成的部署,从Docker到Kubernetes再到AWS。这使您可以轻松地将Kong集成到现有的工作流程中,从而使学习曲线不那么陡峭。


Kong支持日志记录、身份验证、速率限制、故障检测等。更好的是,它有自己的CLI,因此您可以直接从命令行管理Kong并与之交互。您可以在各种发行版上安装开源社区Kong Gateway。基本上,Kong拥有API网关所需的一切。


Tyk Open-Source API Gateway


Tyk被称为“行业最佳API网关”。与我们列表中的其他API网关不同,Tyk确实是开源的,而不仅仅是开放核心或免费增值。它为开源解决方案提供了一系列令人印象深刻的特性和功能。和Kong一样,Tyk也是云原生的,有很多插件可用。Tyk甚至可以用于以REST和GraphQL格式发布自己的API。


Tyk对许多功能都有本机支持,包括各种形式的身份验证、配额、速率限制和版本控制。它甚至可以生成API文档。最令人印象深刻的是,Tyk提供了一个API开发者门户,允许您发布托管API,因此第三方可以注册您的API,甚至管理他们的API密钥。Tyk通过其开源API网关提供了如此多的功能,实在令人难以置信。


KrakenD Open-Source API Gateway


KrakenD的开源API网关是在Go中编写的,它有几个显著的特点,尤其是对微服务的优化。它的可移植性和无状态性是其他强大的卖点,因为它可以在任何地方运行,不需要数据库。由于KrakenDesigner,它比我们列表中的其他一些API网关更灵活、更易于接近,这是一个GUI,它可以让您直观地设计或管理API。您还可以通过简单地编辑JSON文件轻松地编辑API。


KrakenD包括速率限制、过滤、缓存和授权等基本功能,并且提供了比我们提到的其他API网关更多的功能。在不修改源代码的情况下,可以使用许多插件和中间件。它的效率也很高-据维护人员称,KrakenD的吞吐量优于Tyk和Kong的其他API网关。它甚至具有本地GraphQL支持。所有这些,KrakenD的网关非常值得一看。


Gravitee OpenSource API Management


Gravite.io是另一个API网关,它具有一系列令人印象深刻的功能,这次是用Java编写的。Gravitee有三个模块用于发布、监控和记录API:




  • API管理(APIM):APIM是一个开源模块,可以让您完全控制谁访问您的API以及何时何地。




  • 访问管理(AM):Gravite为身份和访问管理提供了一个本地开源授权解决方案。它基于OAuth 2.0/OpenID协议,具有集中的身份验证和授权服务。




  • 警报引擎(AE):警报引擎是一个用于监视API的模块,允许您自定义多渠道通知,以提醒您可疑活动。




Gravitee还具有API设计器Cockpit和命令行界面graviteio-cli。所有这些都使Gravitee成为最广泛的开源API网关之一。您可以在GitHub上查看Gravite.io OpenSource API管理,或直接下载AWS、Docker、Kubernetes、Red Hat,或在此处作为Zip文件。


Apinto Microservice Gateway


显然,Go是编写API网关的流行语言。Apinto API网关用Go编写,旨在管理微服务,并提供API管理所需的所有工具。它支持身份验证、API安全以及流控制。


Apinto支持HTTP转发、多租户管理、访问控制和API访问管理,非常适合微服务或具有多种类型用户的任何开发项目。Apinto还可以通过多功能的用户定义插件系统轻松地为特定用户定制。它还具有API健康检查和仪表板等独特功能。


Apinto Microservice针对性能进行了优化,具有动态路由和负载平衡功能。根据维护人员的说法,Apinto比Nginx或Kong快50%。


Apache APISIX API Gateway


我们将使用世界上最大的开源组织之一Apache软件基金会的一个开源API网关来完善我们的开源API网关列表。Apache APISIX API网关是另一个云原生API网关,具有您目前已经认识到的所有功能——负载平衡、身份验证、速率限制和API安全。然而,有一些特殊的功能,包括多协议支持和Kubernetes入口控制。


关于开源API网关的最后思考


不受限制、不受限制的API访问时代已经结束。随着API使用的广泛普及,有无数理由实现API网关以实现安全性,因为不正确暴露的API端点可能会造成严重损害。API网关可以帮助围绕API设置速率限制,以确保安全使用。而且,如果您向第三方供应商支付高昂的价格,开源选项可能会减少您的每月IT预算。


总之,API网关为您的API环境添加了一个重要的抽象层,这可能是它们最有用的功能。这样的抽象层是防止API端点和用户数据不当暴露的一些最佳方法。然而,几乎同样重要的是它为您的API增加了灵活性。


如果没有抽象层,即使对后端的微小更改也可能导致下游的严重破坏。添加API网关可以使敏捷框架受益,并有助于

作者:CV_coder
来源:juejin.cn/post/7241778027876401213
简化CI/CD管道。

收起阅读 »

一位25岁普通女程序员的年中总结

前言 距离上一次的年中总结已经过去了一年,来深圳的日子也即将一年过半,看了看去年的年终总结,基本上已经全部完成,除了依然没吃胖点,反而有了两三次低血糖之外(T_T)。 工作 去年2月26上完最后一天班晚上到的深圳,3月8号来现在的公司入职(印象比较深是因为入...
继续阅读 »

前言


距离上一次的年中总结已经过去了一年,来深圳的日子也即将一年过半,看了看去年的年终总结,基本上已经全部完成,除了依然没吃胖点,反而有了两三次低血糖之外(T_T)。


image.png


工作


去年2月26上完最后一天班晚上到的深圳,3月8号来现在的公司入职(印象比较深是因为入职当天有500的女神节红包,哈哈哈)。


今年年初的时候,公司换了办公地方,再也不用过地铁转公交的日子了(^▽^),现在可以从坪洲坐8站直接到深大,通勤时间少了一点;


年后陆陆续续做了三四个公司内部用的系统,用到了一个腾讯的UI框架,使用感还不错,顺便学习了TDesign 提供了一个脚手架 tdesign-starter-cli,通过它来初始化项目,有兴趣的可以看看(新手不会搭建脚手架的可以做个参考)。


image.png


image.png


上个月调薪(10%),虽然没有预期的涨薪幅度高,不过涨了总比不涨好(O(∩_∩)O哈哈哈~),经过一次涨薪,也明白了这个涨薪幅度跟什么有直接的关系,如果明年年初不跳槽的话,到年中的时候能涨个15%吧,虽然不多,但是福利好,工作不多,不加班,如果明年离职,掘友们有兴趣的话,可以来找我内推(^▽^)。


学习


今年在一边工作之余,也一直有在坚持学习,当然有时候还是会摸鱼偷懒(捂嘴笑(^▽^))。




  1. 收货



    • 输出30余篇文章(不过写的水平很一般,基本上都是记录自己的学习,没有给广大掘友带来什么收货)

    • 学习了vue3

    • 学习了react(不精通)

    • 学习了react native(为了接项目挣钱)

    • 接了个小程序的项目,顺道回顾了小程序的写法(长时间不写,真的会忘记)




  2. 不足



    • 学习的vue3一直拖延重构,没有实际用到项目中去

    • react前面学着,后面忘着(记性真的差)




生活


早上一口气写完了这半年的工作和学习,到了生活这里,好像卡壳了——




  1. 吐槽


    好像没什么说的,又好像想说的很多,今年这半年,是对这慢无休止的疫情最烦闷最讨厌的半年,马上快八月了,核酸做的已经变得麻木,想必辛苦做核酸的护士们也麻木了吧,这么热的天气做的一次比一次敏捷(敷衍),从年后回到深圳,基本上一天一做,最少的时候三天要做一次。


    后来上海疫情爆发,封城,看着曾经的朋友们封城后的生活,心情五味杂陈,难以言说(此处省略500字)。


    吐槽结束,把刚刚的收起来。




  2. 平淡


    其实除了疫情,无休止的核酸,就是工作,出租屋两点一线的平淡了;趁着清明,和朋友们一起自驾游了一次,我是纯游,因为有俩司机开车,哈哈哈哈。在五一也去了一直没去的珠海玩了一趟,虽然上了外伶仃岛,被困在岛上了,但在海边,心情真的有被治愈。


    图片放到文章最后啦!




总结


曾经看到的一句话:



生活可以忙忙碌碌随大流,思想必须偷偷摸摸求上进



其实以前挺不喜欢这种生活方式的,后来发现自己也是这样的活着,还是和去年文章中一句话说的那样:



行到水穷处,坐看云起时是选择,卧薪尝胆,三千越甲可吞吴也是选择,怎么选,都有理,怎么选,都对



因为我们都在为自己想要的生活,或努力或躺平着。


接下来,还是该工作工作,该学习学习,该玩玩,该吃吃,该喝喝。


晒图



  1. 清明——潮州-南澳岛
    dcee0296084f2dd6ad8489bd2ca416f.jpg


fa2b81d23593eb83b48f18246442024.jpg


0e22613cabf1391c8ee71653afb9c0b.jpg


7413ffa236e32ab90cffd36ed6d9fed.jpg


e77b1233aa72d335cfd5e236b03a5e5.jpg
304e9e51a55173f4ff234a8e27ae0eb.jpg



  1. 五一——珠海-外伶仃岛


b705e7f1638136458d54638e7891c06.jpg


54c1fb0813c4df0e117ab323769b663.jpg


e564f7e708f55b817e0e87fb7f44f17.jpg


0f3e70a4f93e0230def4773ac5489c3.jpg


a16ec85ebee0af6bba8460459863fcd.jpg


a088d73521a1bec7e750a786305ffb1.jpg
3. 吃吃喝喝


f9f79ad3138bf3d0054f2b9bbe4f933.jpg


bdf75dd09aea6c79bce24789a157727.jpg


8feeb742aac2475e6f55e05bcce63b0.jpg

收起阅读 »

变“鼠”为“鸭”——为SVG Path制作FIFO路径变换动画,效果丝滑

web
一个月前我曾撰文《使用batik在kotlin中将TTF字体转换为SVG图像》,介绍了如何将汉字转为SVG Path路径进行展示和变换,以此为基础不妨畅想一下,用动画将一个汉字变为另一个汉字,听上去是不是很简单呢?下面动手实践一下: 我随便找了一个字体Aa剑豪...
继续阅读 »

一个月前我曾撰文《使用batik在kotlin中将TTF字体转换为SVG图像》,介绍了如何将汉字转为SVG Path路径进行展示和变换,以此为基础不妨畅想一下,用动画将一个汉字变为另一个汉字,听上去是不是很简单呢?下面动手实践一下:


我随便找了一个字体Aa剑豪体,然后随机选取了两个汉字:,再用上文提到的文章介绍的提取整体字形区块方法取出了SVG:


image.png


image.png


可以看到很简单就提取出了两个字整体的字形,下面用D3做一个简单的变换动画展示:


初始变换


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>鼠鼠我鸭</title>
</head>
<body style="text-align: center"></body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script type="module">
const _svg = d3
.select("body")
.append("svg")
.attr("width", "1000")
.attr("height", "1000")
.style("zoom", "0.3")
.style("transform", "rotateX(180deg)");
_svg.append("g")
.attr("transform", "translate(0, 160)")
.append("path")
.attr("fill", "#3fd")
.attr("stroke", "black")
.attr("stroke-width", "4")
.attr("d", 上面提到的鼠的SVG_Path...)
.transition().delay(1000).duration(1200)
.attr("fill", "#ef2")
.attr("d", 上面提到的鸭的SVG_Path...);
</script>
</html>

这里调整了svg尺寸以及zoomtransform等属性更好的适应画面,还做了个g标签套住并将定位交给它,动画效果如下图所示:


Animation.gif


很明显的看到,效果非常奇怪,怎么一开始就突然变得很乱?一开始乱这一下显得很突兀,这是因为两段图像的path长度就相差很多,绘进方式也完全不一样,很难真正的渐变过去,我试了一个有优化此过程的库d3-interpolate-path,用上去效果也没有什么差别,而且它用的还是d3@v5版本的,不知道怎么path中还会穿插一些NaN的值,很怪异,看来只能自己做了。


想真正的点对点的渐移过去,可能还是有些难的,所以我想出了一个较为简单的方案,实现一种队列式的效果,的笔画慢慢消失,而则跟随在后面逐步画出,实现一种像队列中常说的FIFO(先进先出)的效果


首先就是拆解,做一个while拆分两个字所有的节点,然后再一步步绘上拆出来的节点以验证拆的是否完整,再才能进行后面的处理。


事先要将“鸭鼠”各自的path定义为常量sourceresult,将二者开头的M与结尾的Z都去掉(中间的M不要去掉),因为动画中字形是流动的,起止点不应提前定义。


拆分路径点


const source = 鼠的SVG_Path(没有MZ)...
const result = 鸭的SVG_Path(没有MZ)...
const actionReg = new RegExp(/[a-z]/, "gi");
const data = new Array();
let match;
let lastIndex;
while ((match = actionReg.exec(result))) {
data.push(result.substring(lastIndex, match.index));
lastIndex = match.index;
}
data.push(result.substring(lastIndex));

就这样就能把的部分拆开了,先直接累加到试验一下是否成功:


叠加试验


let tran = g
.append("path")
.attr("fill", "red")
.attr("stroke", "black")
.attr("stroke-width", "4")
.attr("d", "M" + source + "Z")
.transition()
.delay(800);

let step = "L";
data.map(item => {
step += item + " ";
tran = tran.transition().attr("d", "M" + source + step.trimEnd() + "Z").duration(20);
});

首先是把上面path独立出来改一改,变成红色的利于观看,然后下面慢慢的拼合上每个节点,效果如下:


Animation.gif


是理想中的效果,那么下一步就是加速FIFO先进先出的变换了:


FIFO先进先出


这一步是不能用SVG动画的,要用setInterval定时器进行动画调节,SVG始终还是只能处理很简单的path变化,效果不如直接变来的好,这里设计成把每一帧的动画存进一个方法数组然后交给setInterval计时器循环执行(写起来比较方便),先是改一下tran的定义,因为不是动画了,所以现在改叫path就好了,border也不需要了:


let path = g
.append("path")
.attr("fill", "red")
.attr("d", "M" + source + "Z");

就这样简单的初始化一下就好了,然后就是最核心的一个过程,path的绘制循序就像一个FIFO队列:


let step = "";
let pre = source;
const funs = new Array();
data.map(async function (item, i) {
step += item + " ";
match = pre && actionReg.exec(source);
if (!match) {
pre = "";
} else if (~["M", "L", "T"].indexOf(match[0])) {
pre = source.substring(match.index + 1);
}
const d = "M" + pre + (pre ? "L" : "") + step.trimEnd() + "Z";
funs.push(() => path.attr("d", d));
});

首先是pre负责的字形,这个字形是要慢慢消失的前部,这个前部不是所有的节点都能用的,而是"M", "L", "T"这种明确有点位的动作才行,毕竟这是动画的起始点。然后step就是代表,要一步一步累加。循环结束funs数组也就累计好了所有的帧(方法),然后用定时器执行这些带参方法即可:


const animation = setInterval(() => {
if (!funs.length) {
clearInterval(animation);
return;
}
funs.shift()();
}, 20);

这种方式虽然非常少见,不过这个定时器流程还是很好理解的过程,效果如下:


Animation.gif


是想象中的效果,但稍微有些单调,可以加上一段摇摆的动画配合变换:


摇摆动画


let pathTran = path;
Array(8)
.fill(0)
.map(function () {
pathTran = pathTran
.transition()
.attr("transform", "skewX(10)")
.duration(300)
.transition()
.attr("transform", "skewX(-10)")
.duration(300);
});
pathTran.transition().attr("transform", "").duration(600);

这段动画要不断赋值才能形成连贯动画,所以直接用path处理动画是不行的,因为上面计时器也是用到这个path对象,所以要额外定义一个pathTran专门用于动画,这段摇摆动画效果如下:


Animation.gif


时间掐的刚刚好,那边计时器停掉,这边摇摆动画也缓停了。


写的十分简便,一点小创

作者:lyrieek
来源:juejin.cn/post/7241826575951200293
意,供大家参考观赏。

收起阅读 »

图像识别,不必造轮子

闲来无事研究了百度图像识别 API,发现该功能还算强大,在此将其使用方法总结成教程,提供大家学习参考 首先预览下效果 从以上预览图中可看出,每张图片识别出5条数据,每条数据根据识别度从高往下排,每条数据包含物品名称、识别度、所属类目 准备工作 1、注册百度账...
继续阅读 »

闲来无事研究了百度图像识别 API,发现该功能还算强大,在此将其使用方法总结成教程,提供大家学习参考


首先预览下效果


图片


从以上预览图中可看出,每张图片识别出5条数据,每条数据根据识别度从高往下排,每条数据包含物品名称识别度所属类目


准备工作


1、注册百度账号


2、登录百度智能云控制台


3、在产品列表中找到 人工智能->图像识别


4、点击创建应用,如下图:


图片


图片


图片


已创建好的应用列表


代码部分


1、获取access_token值


注意:使用图像识别需用到access_token值,因此需先获取到,以便下面代码的使用


access_token获取的方法有多种,这里使用PHP获取,更多有关access_token获取的方法以及说明可查看官方文档:


ai.baidu.com/docs#/Auth/…


创建一个get_token.php文件,用来获取access_token值


PHP获取access_token代码示例:


<?php

//请求获取access_token值函数
function request_post($url = '', $param = '') {

if (empty($url) || empty($param)) {
return false;
}

$postUrl = $url;
$curlPost = $param;
$curl = curl_init();//初始化curl
curl_setopt($curl, CURLOPT_URL,$postUrl);//抓取指定网页
curl_setopt($curl, CURLOPT_HEADER, 0);//设置header
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_POST, 1);//post提交方式
curl_setopt($curl, CURLOPT_POSTFIELDS, $curlPost);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$data = curl_exec($curl);//运行curl
curl_close($curl);

return $data;
}

$url = 'https://aip.baidubce.com/oauth/2.0/token'; //固定地址
$post_data['grant_type'] = 'client_credentials'; //固定参数
$post_data['client_id'] = '你的 Api Key'; //创建应用的API Key;
$post_data['client_secret'] = '你的 Secret Key'; //创建应用的Secret Key;
$o = "";
foreach ( $post_data as $k => $v )
{
$o.= "$k=" . urlencode( $v ). "&" ;
}
$post_data = substr($o,0,-1);

$res = request_post($url, $post_data);//调用获取access_token值函数

var_dump($res);

?>

返回的数据如下,红框内的就是我们所要的access_token值


图片


2、图片上传及识别


2.1、在项目的根目录下创建一个upload文件夹,用于存放上传的图片


2.2、创建一个index.html文件,用于上传图片及数据渲染


代码如下:


<!DOCTYPE html>  
<html>
<head>
<meta charset="utf-8"> 
<title>使用百度 API 实现图像识别</title> 
<style type="text/css">
  .spanstyle{
    display:inline-block;
    width:500px;
    height:500px;
    position: relative;
  }
</style>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>

<script>

  function imageUpload(imgFile) {

    var uploadfile= imgFile.files[0]  //获取图片文件流

    var formData = new FormData();    //创建一个FormData对象

    formData.append('file',uploadfile);
    //将图片放入FormData对象对象中(由于图片属于文件格式,不能直接将文件流直接通过ajax传递到后台,需要放入FormData对象中。在传递)

    $("#loading").css("opacity",1);


     $.ajax({
          type: "POST",       //POST请求
          url: "upload.php",  //接收图片的地址(同目录下的php文件)
          data:formData,      //传递的数据
          dataType:"json",    //声明成功使用json数据类型回调

          //如果传递的是FormData数据类型,那么下来的三个参数是必须的,否则会报错
          cache:false,  //默认是true,但是一般不做缓存
          processData:false, //用于对data参数进行序列化处理,这里必须false;如果是true,就会将FormData转换为String类型
          contentType:false,  //一些文件上传http协议的关系,自行百度,如果上传的有文件,那么只能设置为false

         success: function(msg){  //请求成功后的回调函数


              console.log(msg.result)

              //预览上传的图片
              var filereader = new FileReader();
              filereader.onload = function (event) {
                  var srcpath = event.target.result;
                  $("#loading").css("opacity",0);
                  $("#PreviewImg").attr("src",srcpath);
                };
              filereader.readAsDataURL(uploadfile);


                //将后台返回的数据进行进一步处理
                var data=  '<li style="margin:2% 0"><span>物品名称:'+msg.result[0].keyword+';</span> <span style="padding: 0 2%">识别度:'+msg.result[0].score*100+'%'+';</span><span>所属类目:'+msg.result[0].root+';</span></li>'

                data=data+  '<li style="margin:2% 0"><span>物品名称:'+msg.result[1].keyword+';</span> <span style="padding: 0 2%">识别度:'+msg.result[1].score*100+'%'+';</span><span>所属类目:'+msg.result[1].root+';</span></li>'

                data=data+  '<li style="margin:2% 0"><span>物品名称:'+msg.result[2].keyword+';</span> <span style="padding: 0 2%">识别度:'+msg.result[2].score*100+'%'+';</span><span>所属类目:'+msg.result[2].root+';</span></li>'

                data=data+  '<li style="margin:2% 0"><span>物品名称:'+msg.result[3].keyword+';</span> <span style="padding: 0 2%">识别度:'+msg.result[3].score*100+'%'+';</span><span>所属类目:'+msg.result[3].root+';</span></li>'


                data=data+  '<li style="margin:2% 0"><span>物品名称:'+msg.result[4].keyword+';</span> <span style="padding: 0 2%">识别度:'+msg.result[4].score*100+'%'+';</span><span>所属类目:'+msg.result[4].root+';</span></li>'



                //将识别的数据在页面渲染出来
               $("#content").html(data);


        }
  });


   }



</script>
</head>
<body>

  <fieldset>
     <input type="file"  onchange="imageUpload(this)" >
     <legend>图片上传</legend>
  </fieldset>



<div style="margin-top:2%">
    <span class="spanstyle">
      <img id="PreviewImg" src="default.jpg" style="width:100%;max-height:100%"  >
      <img id="loading" style="width:100px;height:100px;top: 36%;left: 39%;position: absolute;opacity: 0;" src="loading.gif" >
    </span>


    <span class="spanstyle" style="vertical-align: top;border: 1px dashed #ccc;background-color: #4ea8ef;color: white;">
        <h4 style="padding-left:2%">识别结果:</h4>
        <ol style="padding-right: 20px;" id="content">

        </ol>
    </span>

</div>



</body>
</html>

2.3、创建一个upload.php文件,用于接收图片及调用图像识别API


备注:百度图像识别API接口有多种,这里使用的是【通用物体和场景识别高级版】 ;该接口支持识别10万个常见物体及场景,接口返回大类及细分类的名称结果,且支持获取图片识别结果对应的百科信息


该接口调用的方法也有多种,这里使用PHP来调用接口,更多有关通用物体和场景识别高级版调用的方法以及说明可查看官方文档:


ai.baidu.com/docs#/Image…


PHP请求代码示例:


<?php  

        //图像识别请求函数    
        function request_post($url ''$param ''){

            if (empty($url) || empty($param)) {
                return false;
            }

            $postUrl $url;
            $curlPost $param;
            // 初始化curl
            $curl curl_init();
            curl_setopt($curl, CURLOPT_URL, $postUrl);
            curl_setopt($curl, CURLOPT_HEADER, 0);
            // 要求结果为字符串且输出到屏幕上
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            // post提交方式
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $curlPost);
            // 运行curl
            $data curl_exec($curl);
            curl_close($curl);

            return $data;
        }

        $temp explode("."$_FILES["file"]["name"]);
        $extension end($temp);     // 获取图片文件后缀名


        $_FILES["file"]["name"]=time().'.'.$extension;//图片重命名(以时间戳来命名)

        //将图片文件存在项目根目录下的upload文件夹下
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);


        $token '调用鉴权接口获取的token';//将获取的access_token值放进去
        $url 'https://aip.baidubce.com/rest/2.0/image-classify/v2/advanced_general?access_token=' . $token;
        $img file_get_contents("upload/" . $_FILES["file"]["name"]);//本地文件路径(存入后的图片文件路径)
        $img base64_encode($img);//文件进行base64编码加密

        //请求所需要的参数
        $bodys array(
            'image' => $img,//Base64编码字符串
            'baike_num'=>5  //返回百科信息的结果数 5条
        );
        $res request_post($url$bodys);//调用请求函数

        echo $res;  //将识别的数据输出到前端


?>

结语补充


在实际开发过程中,获取access_token值并不是单独写成一个页面文件,而是写在项目系统的配置中;由于access_token值有效期为30天,可通过判断是否失效,来重新请求acc

作者:程序员Winn
来源:juejin.cn/post/7241874482574770235
ess_token值

收起阅读 »

《环信十周年趴——我的程序人生之一路向西》

六年前,我毕业于一个著名的计算机学院。在校期间,我就非常热爱计算机专业,对编程有着浓厚的兴趣。就像很多人一样,我梦想能写出自己的程序,让它变得更好。 于是我开始了另一段工作旅程。我加入了一家小程序公司,开始专注于小程序的开发,这是新的开始,也是全新的挑战。在这...
继续阅读 »

六年前,我毕业于一个著名的计算机学院。在校期间,我就非常热爱计算机专业,对编程有着浓厚的兴趣。就像很多人一样,我梦想能写出自己的程序,让它变得更好。


当我进入我的第一家公司时,我的兴奋和期待之情无以言表。这家公司以开发iOS应用为主,我也开始从事iOS开发。在这个公司里,我经历了很多挑战和机遇。在这里我学到了很多关于软件开发的知识,也养成了很好的开发习惯和团队协作能力。我从一名初学者变成了一个熟练的iOS开发工程师。但是,在某个阶段,我突然发现自己好像停滞不前,感到很无聊,也觉得缺乏动力。


于是我开始了另一段工作旅程。我加入了一家小程序公司,开始专注于小程序的开发,这是新的开始,也是全新的挑战。在这家公司里,我需要从头开始学习新的开发技能,适应小程序的开发环境和工作方式。在这个过程中,我也发现了小程序和iOS尽管有着共同的底层技术,但是却有着截然不同的开发方式,和值得深入研究的地方。在这家公司里,我经历了团队的合作,让我感受到了小程序技术能够如何影响一个团队的凝聚力和升华。


作为一个开发者,我非常喜欢关注新技术,不断地尝试新东西。这让我尝试学习 Flutter,并在一家制造业公司担任 Flutte 工程师,继续我的职业生涯。Flutter 能够提供极高的开发效率和跨平台兼容性,这让我非常留下深刻的印象。同时在这家公司里,我应对更为复杂和有挑战性的技术难题,这让我不断成长和进步。


除了不断学习新技能,我的程序人生也因为自己的勇气而得以改变,我曾在几年间换过不同的公司和城市。我从一个陌生的城市一步一步地适应过来,也从完全新的团队和开发环境中学会自我调节和协作。换工作或换城市,可能会让你失去一些舒适和熟悉的东西,但是也会给你带来新的成长和机会。


这六年的程序人生,让我成长为一个更加成熟和自信的人。我已经拥有了丰富的代码编写经验和技术能力,同时也学会了如何处理工作上的各种挑战,看各种複雜问题,并持续保持了学习的动力和热情。虽然这些年我经历了很多疲惫和挑战,但我也再一次发现自己的阻力和激情,让我不断前进并充满信心地继续我的程序人生。

收起阅读 »

高级程序员和新手小白程序员区别你是那个等级看解决bug速度

IT入门深似海 ,程序员行业,我觉得是最难做的。加不完的班,熬不完的夜。 和产品经理,扯不清,理还乱的宿命关系 一直都在 新需求-做项目-解决问题-解决bug-新需求 好像一直都是这么一个循环。(哈哈哈)我觉得一个好的程序员,判断根本取决于,遇到生产问题和...
继续阅读 »

IT入门深似海 ,程序员行业,我觉得是最难做的。加不完的班,熬不完的夜。



和产品经理,扯不清,理还乱的宿命关系



一直都在 新需求-做项目-解决问题-解决bug-新需求

好像一直都是这么一个循环。(哈哈哈)我觉得一个好的程序员,判断根本取决于,遇到生产问题和bug,解决的问题的思路,和解决问题时间效率


大家平时都是怎么解决bug和问题的。


入门程序员


遇到了问题如。服务器启动不了端口8080已经被占用。会第一时间去查找百度。
然后按照百度给的各种解决方案去实操。最终在一定时间内完美解决bug。


哈哈不过我不建议使用百度搜索了。广告太多,搜索出来内容质量太差了。有时候我想去搜索一下官网。搜索了结果筛选了几页,才筛选到官网。



懂得都懂不过多,解释


初级程序员


开始会间接使用 谷歌搜索必应搜索。 我觉得谷歌在搜索内容和质量,确实是吊打某度了。你给他垂直的内容。搜索出来的内容第一页首页首条,可能就是你要的官网。


或者说是你要的答案,而且广告内垃圾内容几乎很少看到。 搜索出来内容质量也挺高不一样。


这里访问谷歌需要一些技巧, 大家可以通过这个去访问。


点击进入


当然必应搜索。也可以用至少比某度很多。


中级程序员


使用更垂直IT社区内容,进行问题站内搜索。
比如 博客园,CSDN
掘金 等IT博客内容社区网站。


相信大家,在这个时候,自己也会写技术博客,或者记录文章吧,这些IT社区,是不错选择,可以看到很多大牛,或者好的技术文章。


我觉得写博客挺重要的,不管是自己想写,还是处于记录。养成写作是一个好习惯。



  1. 是写文章时候可以提升自己学习能力和写作能力

  2. 更是巩固自己所学习的知识内容。

  3. 也是对自己学习的一个记录,后面遇到忘记了或者同样问题可以查看

  4. 也是对自己业余时间养成一个爱好。


高级程序员


开始接触开源社区,技术论坛等,通过GitHub
isssues 或者 stack overflow
进行问题解决,和提问。


这类效率往往是最快的,直达的,


软件开发工程师


间接开始阅读源码 遇到问题第一时间,去看程序报错我信息


通过断点和本地调试自己先尝试解决。可以通过直接阅读官方文档来解决问题。


当然上面所有解决问题的手段,只是你个人能力循序渐进过程。随着你入行年限,和工作年限,你会接触越多,遇到问题,也不会和开始一样慌张,毫无头绪。


解决问题时间效率,也越来越高,会开始注重代码质量,刻意与避免一些低级bug产生


对自己会有更高的要求。


我来讲讲我目前遇到问题的解决思路大概流程。




  1. 自行本地断点调试。查看具体错误信息代码分析具体业务逻辑问题场景。一般能解决70%问题




  2. 问AI智能ChatGPT ,然后通过谷歌搜索引擎,IT技术论坛 去查询类似问题。




  3. 通过官方文档,或者github等去解决,或者直接提isssues




这里我提到了ChatGPT 我觉得ChatGPT 至少目前能基本取代我用搜索引擎时间。 效率比搜索引擎要高很多。


如果不知道如何使用的,这里我提供了免费的在线使用


点击进入


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

2023年35大龄程序员最后的挣扎

一、自身情况 我非科班出身,年龄到了35岁。然后剧本都差不多,2022年12月各种裁员,失业像龙卷风一样席卷社会各个角落。 其实30岁的时候已经开始焦虑了,并且努力想找出路。 提升技术,努力争增加自己的能力。 努力争取进入管理层,可是卷无处不在,没有人离开这...
继续阅读 »

一、自身情况


我非科班出身,年龄到了35岁。然后剧本都差不多,2022年12月各种裁员,失业像龙卷风一样席卷社会各个角落。



  1. 其实30岁的时候已经开始焦虑了,并且努力想找出路。

  2. 提升技术,努力争增加自己的能力。

  3. 努力争取进入管理层,可是卷无处不在,没有人离开这个坑位,你努力的成效很低。

  4. 大环境我们普通人根本改变不了。

  5. 自己大龄性价比不高,中年危机就是客观情况。

  6. 无非就是在本赛道继续卷,还是换赛道卷的选择了。


啊Q精神:我还不是最惨的那一批,最惨的是19年借钱买了恒大的烂尾楼,并且在2021年就失业的那拨人。简直不敢想象,那真是绝望啊。心里不够坚强的,想不开轻生的念头都会有。我至少拿了点赔偿,手里还有些余粮,暂时饿不死。


image.png


二、大环境情况




  1. 大环境不好已经不是秘密了,整个经济走弱。大家不敢消费,对未来信心不足已经是板上钉钉的事了。




  2. 这剧本就是30年前日本的剧本,不敢说一摸一样。可以说大差不差了,互联网行业的薪资会慢慢的回归平均水平,或者技术要求在提升一个等级。




  3. 大部分普通人,还是做应用层拧螺丝,少部分框架师能造轮子也就是2:8理论。




  4. 能卷进这20%里,就能在上一层楼。也不是说这行就不行了,只不过变成了存量市场,而且坑位变少,人并没有变少还增加了。




  5. 不要怀疑自己的能力,这也不是你的问题了,是外部环境导致的市场萎缩。我们能做的就是,脱下孔乙己的长衫,先保证生活。努力干活,不违法乱纪做什么都是光荣了,不要带有色眼镜看待任何人。




三、未来出路


未来的出路在哪里?


这个我也很迷惑,因为大佬走的路,并不是我们这些普通的不能在普通的人能够走的通的。当然也有例外的情况,这些就是幸存者偏差了。


我先把chartGPT给的答应贴出来:


image.png


可以看到chartGPT还是给出,相对可行有效的方案。当然这些并不是每个人都适用。


我提几个普通人能做的建议(普通人还是围绕生存在做决策):



  1. 有存款的,并且了解一些行业的可以开店,比如餐饮店,花店,水果店等。

  2. 摆摊,国家也都改变政策了。

  3. 超市,配送员,外卖员。

  4. 开滴滴网约车。

  5. 有能力的,可以润出G。可以吸一吸GW“free的air”,反正都是要被ZBJ榨取的。


以上都是个人不成熟的观点,jym多多包涵。


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

flutter有哪些架构的框架?该怎么选择

flutter有哪些架构的框架? Flutter是一种新兴的跨平台移动应用开发框架,它提供了丰富的UI组件和工具,使得应用开发更加容易。在Flutter中,有很多架构框架可供选择,以下是几个比较常用的架构框架: BLoC (Business Logic Co...
继续阅读 »

flutter有哪些架构的框架?


Flutter是一种新兴的跨平台移动应用开发框架,它提供了丰富的UI组件和工具,使得应用开发更加容易。在Flutter中,有很多架构框架可供选择,以下是几个比较常用的架构框架:



  1. BLoC (Business Logic Component):BLoC是一种状态管理模式,它将应用程序中的业务逻辑和UI分离,使得应用程序更易于维护和测试。在Flutter中,可以使用flutter_bloc库来实现BLoC架构。
    Provider:Provider是Flutter中的一个轻量级状态管理库,它使用InheritedWidget实现状态共享,可以有效地解决Flutter应用中的状态管理问题。

  2. MobX:MobX是一种基于响应式编程的状态管理库,它使用可观察对象来管理应用程序的状态,并自动更新与之相关的UI组件。在Flutter中,可以使用mobx库来实现MobX架构。

  3. Redux:Redux是一种流行的状态管理模式,在Flutter中也有相应的实现库redux_flutter。Redux通过单一数据源管理应用程序的状态,并使用纯函数来处理状态的更新,可以有效地解决Flutter应用中的状态管理问题。
    以上是常用的Flutter架构框架,每个框架都有其优点和适用场景,开发者可以根据自己的需求选择合适的架构框架。


除了上面提到的框架之外,还有以下几个Flutter架构框架:



  1. GetX:GetX是一种轻量级的Flutter架构框架,它提供了路由管理、状态管理和依赖注入等功能,可以大大简化Flutter应用的开发。

  2. MVC:MVC是一种经典的软件架构模式,它将应用程序分为模型、视图和控制器三个部分,可以有效地分离关注点,使得应用程序更易于维护和扩展。

  3. MVP:MVP是一种衍生自MVC的架构模式,它将应用程序分为模型、视图和Presenter三个部分,Presenter负责处理业务逻辑,将模型数据展示到视图上。

  4. MVVM:MVVM是一种流行的架构模式,它将应用程序分为模型、视图和视图模型三个部分,视图模型负责处理业务逻辑,将模型数据展示到视图上。


总之,Flutter中有很多架构框架可供选择,每个框架都有其优点和适用场景,开发者可以根据自己的需求选择合适的架构框架。


Flutter BLoC


Flutter BLoC是一种状态管理模式,它将应用程序中的业务逻辑和UI分离,使得应用程序更易于维护和测试。BLoC这个缩写代表 Business Logic Component,即业务逻辑组件。
BLoC的核心思想是将UI层和业务逻辑层分离,通过Stream或者Sink等异步编程方式,将UI层和业务逻辑层连接起来。具体来说,BLoC模式包含以下三个部分:
Events:事件,即UI层的用户操作或其他触发条件,例如按钮点击,网络请求完成等等。
Bloc:业务逻辑层,用于处理Events,处理业务逻辑,并向UI层提供状态更新。
State:状态,即UI层的显示状态,例如页面的loading状态,数据请求成功或失败状态等等。


BLoC的核心是Bloc和State之间的联系,Bloc接收Events,并根据业务逻辑处理后,通过Stream向UI层提供状态更新。UI层监听Bloc的Stream,获取最新的State,并根据State更新UI状态。
在Flutter中,可以使用StreamController来创建BLoC。StreamController是一个异步数据流控制器,可以创建一个Stream用于事件流,创建一个Sink用于事件的注入。
Flutter框架提供了一个非常好的BLoC框架flutter_bloc,它封装了BLoC的核心逻辑,使得开发者可以更加方便地使用BLoC模式进行状态管理。使用flutter_bloc框架,只需要定义好Events、Bloc和State,然后将它们组合起来,就可以实现状态管理,从而将UI层和业务逻辑层分离。


总之,Flutter BLoC是一种状态管理模式,它通过将UI层和业务逻辑层分离,使用Stream或Sink等异步编程方式将它们连接起来,实现了应用程序的业务逻辑和UI分离。


如何选择使用


选择使用哪种Flutter架构框架取决于具体的应用程序需求和开发团队的技术水平。以下是一些常见的应用场景和推荐的架构框架:
对于小型应用程序,可以考虑使用轻量级的状态管理库,如Provider或GetX。
对于需要处理复杂业务逻辑的应用程序,可以使用BLoC、MobX或Redux等状态管理框架。
对于需要实现高度可测试性的应用程序,可以考虑使用MVC、MVP或MVVM等经典的软件架构模式。
总之,选择哪种Flutter架构框架取决于具体的应用程序需求和开发团队的技术水平。开发者应该根据自己的需求和技能水平选择最适合的架构框架,以提高开发效率和代码质量。


GetX和BLoC的优缺点


GetX和BLoC都是常见的Flutter架构框架,它们各有优缺点,下面是它们的主要特点和优缺点的比较:


GetX


优点:


简单易用:GetX是一种轻量级的Flutter框架,提供了简单易用的依赖注入、路由管理和状态管理等功能,可以大大简化Flutter应用的开发。
性能优秀:GetX使用原生的Dart语言构建,不需要任何代码生成,因此运行速度非常快,同时也具有很好的内存管理和性能优化能力。
功能完备:GetX提供了路由管理、依赖注入、状态管理、国际化、主题管理等功能,可以满足大多数应用程序的需求。


缺点:


社区相对较小:相比其他流行的Flutter框架,GetX的社区相对较小,相关文档和教程相对较少,需要一定的自学能力。
不适合大型应用:由于GetX是一种轻量级框架,不适合处理大型应用程序的复杂业务逻辑和状态管理,需要使用其他更加强大的框架。


BLoC


优点:


灵活可扩展:BLoC提供了灵活的状态管理和业务逻辑处理能力,可以适应各种应用程序的需求,同时也具有良好的扩展性。
可测试性强:BLoC将UI和业务逻辑分离,提高了代码的可测试性,可以更容易地编写和运行测试代码。
社区活跃:BLoC是一种流行的Flutter框架,拥有较大的社区和用户群体,相关文档和教程比较丰富,容易入手。


缺点:


学习曲线较陡峭:BLoC是一种相对复杂的框架,需要一定的学习曲线和编程经验,初学者可能需要花费较多的时间和精力。
代码量较大:由于BLoC需要处理UI和业务逻辑的分离,因此需要编写更多的代码来实现相同的功能,可能会增加开发成本和维护难度。
总之,GetX和BLoC都是常见的Flutter架构框架,它们各有优缺点。选择哪种框架取决于具体的应用程序需求和开发团队的技术水平。


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

现代化 Android 开发:基础架构 古哥E下

Android 开发经过 10 多年的发展,技术在不断更迭,软件复杂度也在不断提升。到目前为止,虽然核心需求越来越少,但是对开发速度的要求越来越高。高可用、流畅的 UI、完善的监控体系等都是现在的必备要求了。国内卷的方向又还包括了跨平台、动态化、模块化。 目前...
继续阅读 »

Android 开发经过 10 多年的发展,技术在不断更迭,软件复杂度也在不断提升。到目前为止,虽然核心需求越来越少,但是对开发速度的要求越来越高。高可用、流畅的 UI、完善的监控体系等都是现在的必备要求了。国内卷的方向又还包括了跨平台、动态化、模块化。


目前的整体感觉就是,移动开发基本是奄奄一息了。不过也不用过于悲观:一是依旧有很多存量的 App 堪称屎山,是需要有维护人员的,就跟现在很多人去卷 framework 层一样,千万行代码中找 bug。 二是 AI 日益成熟,那么应用层的创新也会出现,在没有更简洁的设备出现前,手机还是主要载体,总归是需要移动开发去接入的,如果硬件层越来越好,模型直接跑在手机上也不是不可能,所以对跨平台技术也会是新一层的考验,有可能直接去跨平台化了。毕竟去中台化也成了历史的选择。


因而,在这个存量市场,虽然竞争压力很大,但是如果技术过硬,还是能寻求一席之地的。因而我决定用几篇文章来介绍下,当前我认为的现代化 Android 开发是怎样的。其目录为:



  • 现代化 Android 开发:基础架构(本文)

  • 现代化 Android 开发:数据类

  • 现代化 Android 开发:逻辑层

  • 现代化 Android 开发:组件化与模块化的抉择

  • 现代化 Android 开发:多 Activity 多 Page 的 UI 架构

  • 现代化 Android 开发:Jetpack Compose 最佳实践

  • 现代化 Android 开发:性能监控


Scope


提到 Android 基础架构,大家可能首先想到的是 MVCMVPMVVMMVI 等分层架构。但针对现代化的 Android 开发,我们首要有的是 scope 的概念。其可以分两个方面:



  • 结构化并发之 CoroutineScope:目前协程基本已经是最推荐的并发工具了,CoroutineScope 的就是对并发任务的管理,例如 viewModelScope 启动的任务的生命周期就小于 viewModel 的存活周期。

  • 依赖注入之 KoinScope:虽然官方推荐的是 hilt,但其实它并没有 koin 好用与简洁,所以我还是推荐 koinKoinScope 是对实例对象的管理,如果 scope 结束, 那么 scope 管理的所有实例都被销毁。


一般应用总会有登录,所以大体的 scope 管理流程图是这样的:


scope



  • 我们启动 app, 创建 AppScope,对于 koin 而言就是用于存放单例,对于协程来说就是全局任务

  • 当我们登录后,创建 AuthSessionScope, 对于 koin 而言,就是存放用户相关的单例,对于协程而言就是用户执行相关的任务。当退出登录时,销毁当前的 AuthSessionScope,那么其对应的对象实例、任务全部都会被销毁。用户再次登录,就再次重新创建 AuthSessionScope。目前很多 App 对于用户域内的实例,基本上还是用单例来实现,退出登录时,没得办法,就只能杀死整个进程再重启, 所以会有黑屏现象,实现不算优雅。而用 scope 管理后,就是一件很自然而实现的事情了。所以尽量用依赖注入,而不要用单例模式

  • 当我们进入界面后,一般都是从逻辑层获取数据进行渲染,所以依赖注入没多大用了。而协程的 lifecycleScopeviewModelScope 就比较有用,管理界面相关的异步任务。


所以我们在做架构、做某些业务时,首要考虑 scope 的问题。我们可以把 CoroutineScope 也作为实例存放到 KoinScope 里,也可以把 KoinScope 作为 Context 存放到 CorutineScope 里。


岐黄小筑是将 CoroutineScope 放到 koin 里去以便依赖查找

val sessionCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + coroutineLogExceptionHandler(TAG))
val sessionKoinScope = GlobalContext.get().createScope(...)
sessionKoinScope.declare(sessionCoroutineScope)


其实我们也完全可以用 CoroutineScopeContext 来做实例管理,而移除 koin 的使用。但是 Context 的使用并没有那么便捷,或许以后它可以进化为完全取代 koin



架构分层


随着软件复杂度的提升,MVCMVPMVVMMVI 等先后被提出,但我觉得目前所有的开发,都大体遵循某一模式而又不完全遵循,很容易因为业务的节奏,很容易打破,变成怎么方便怎么来。所以使用简单的分层 + 足够优秀的组件化,才是保证开发模式不被打破的最佳实践。下图是岐黄小筑的整体架构图:



整体架构不算复杂,其实重点是在于组件库,emo 已经有 20 个子库了,然后岐黄小筑有一些对于通用逻辑的抽象与封装,使得逻辑层虽然都集中在 logic 层,但整体都是写模板式的代码,可以面向 copy-paste 编程。


BookLogic 为例:


// 通过依赖注入传参, 拿到 db 层、网络层、以及用户态信息的应用
class BookLogic(
val authSession: AuthSession,
val kv: EmoKV,
val db: AccountDataBase,
private val bookApi: BookApi
) {
// 并发请求复用管理
private val concurrencyShare = ConcurrencyShare(successResultKeepTime = 10 * 1000L)

// 加载书籍信息,使用封装好的通用请求组件
fun logicBookInfo(bookId: Int, mode: Int = 0) = logic(
scope = authSession.coroutineScope, // 使用用户 session 协程 scope,因为有请求复用,所以退出界面,再进入,会复用之前的网络请求
mode = mode,
dbAction = { // 从 db 读取本地数据
db.bookDao().bookInfo(bookId)
},
syncAction = { // 从网络同步数据
concurrencyShare.joinPreviousOrRun("syncBookInfo-$bookId") {
bookApi.bookInfo(bookId).syncThen { _, data ->
db.runInTransaction {
db.userDao().insert(data.author)
db.bookDao().insert(data.info)
}
SyncRet.Full
}
}
}
)
// 类似的模板代码
suspend fun logicBookClassicContent(bookId: Int, mode: Int = 0) = logic(...)
suspend fun logicBookExpoundContent(bookId: Int, mode: Int = 0) = logic(...)
...
}

//将其注册到 `module` 中去,目前好像也可以通过注解的方式来做,不过我还没采用那种方式:
scopedOf(::BookLogic)

ViewModel 层浮层从 Logic 层读取数据,并可以进行特殊化处理:

class BookInfoViewModel(navBackStackEntry: NavBackStackEntry) : ViewModel() {
val bookId = navBackStackEntry.arguments?.getInt(SchemeConst.ARG_BOOK_ID) ?: throw RuntimeException("book_id is required!.")

val bookInfoFlow = MutableStateFlow(logicResultLoading<BookInfoPojo>())

init {
viewModelScope.launch {
runInBookLogic {
logicBookInfo(bookId, mode).collectLatest {
bookInfoFlow.emit(it)
}
}
}
}
}

Compose 界面再使用 ViewModel

@ComposeScheme(
action = SchemeConst.ACTION_BOOK_INFO,
alternativeHosts = [BookActivity::class]
)
@SchemeIntArg(name = SchemeConst.ARG_BOOK_ID)
@Composable
fun BookInfoPage(navBackStackEntry: NavBackStackEntry) {
LogicPage(navBackStackEntry = navBackStackEntry) {
val infoVm = schemeActivityViewModel<BookInfoViewModel>(navBackStackEntry)
val detailVm = schemeViewModel<BookDetailViewModel>(navBackStackEntry)
val bookInfo by infoVm.bookInfoFlow.collectAsStateWithLifecycle()
//...
}
}

这样整个数据流从网络加载、到存储到数据库、到传递给 UI 进行渲染的整个流程就结束了。


对于其中更多的细节,例如逻辑层具体是怎么封装的?UI 层具体是怎么使用多 ActivityPage?可以期待下之后的文章。


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

大专前端,三轮面试,终与阿里无缘

因为一些缘故,最近一直在找工作,再加上这个互联网寒冬的大环境,从三月找到六月了,一直没有合适的机会 先说一下背景,目前三年半年经验,base 杭州,大专学历+自考本科 就在前几天,Boss 上收到了阿里某个团队的投递邀请(具体部门就不透露了),因为学历问题...
继续阅读 »

因为一些缘故,最近一直在找工作,再加上这个互联网寒冬的大环境,从三月找到六月了,一直没有合适的机会



先说一下背景,目前三年半年经验,base 杭州,大专学历+自考本科



就在前几天,Boss 上收到了阿里某个团队的投递邀请(具体部门就不透露了),因为学历问题,基本上大厂简历都不会通过初筛,但还是抱着破罐子破摔的心态投递给了对方,出乎意料的是简历评估通过了,可能是因为有两个开源项目和一个协同文档加分吧。


进入到面试环节,首先是两道笔试题,算是前置面试:


第一道题目是算法题:


提供了一个数组结构的 data,要求实现一个 query 方法,返回一个新的数组,query 方法内部有 过滤排序分组 等操作,并且支持链式调用,调用最终的 execute 方法返回结果:

const result = query(list)
.where(item => item.age > 18)
.sortBy('id')
.groupBy('name')
.execute();

console.log(result);

具体实现这里就不贴了,过滤用原生的数组 filter 方法,排序用原生的数组 sort 方法,分组需要手写一下,类似 lodash/groupBy 方法。


过滤和排序实现都比较顺利,在实现分组方法的时候不是很顺利,有点忘记思路了,不过最后还是写出来了,关于链式调用,核心是只需要在每一步的操作最后返回 this 即可。


第二道题目是场景题:


要求用 vue 或者 react 实现一个倒计时抢券组件,页面加载时从 10s 开始倒计时,倒计时结束之后点击按钮请求接口进行抢券,同时更新文案等等功能。因为我对 react 比较熟悉一点,所以这里就选择了 react。


涉及到的知识点有 hook 中对 setTimeout 的封装、异步请求处理、状态更新CSS基本功 的考察等等……


具体实现这里也不贴了,写了一堆自定义 hook,因为平时也在参与 ahooks 的维护工作,ahooks 源码背的滚瓜烂熟,所以直接搬过来了,这道题整体感觉没啥难度,算是比较顺利的。


笔试题整个过程中唯一不顺利的是在线编辑器没有类似 vscode 这样的 自动补全 功能,不管是变量还是保留字,很多单词想不起来怎么拼写,就很尴尬,英文太差是硬伤 :(


笔试过程最后中出现了一点小插曲,因为笔试有时间限制,需要在规定的时间内完成,但是倒计时还没结束,不知道为什么就自动交卷了,不过那个时候已经写的差不多了,功能全部实现了,还剩下卡片的样式没完成,css 还需要完善一下,于是就在 Boss 上跟对方解释了一下,说明了情况。


过了几分钟,对面直接回复笔试过了,然后约了面试。


一面:




  • 自我介绍


    这里大概说了两分钟,介绍了过往工作经历,做过的业务以及技术栈。




  • 七层网络模型、和 DNS 啥的


    计网这方面属于知识盲区了,听到这个问题两眼一黑,思索了一会儿,直接说回答不上来。




  • 然后问了一些 host 相关的东西



    • 很遗憾也没回答上来,尴尬。对方问我是不是计算机专业的,我坦诚的告诉对方是建筑工程。




  • React 代码层的优化可以说一下么?



    • 大概说了 class 组件和 function 组件两种情况,核心是通过减少渲染次数达到优化目的,具体的优化手段有 PureComponentshouldComponentUpdateReact.memoReact.useMemoReact.useCallbackReact.useRef 等等。




  • 说一下 useMemouseCallback 有什么区别



    • 很基础的问题,这里就不展开说了。




  • 说一下 useEffectuseLayoutEffect 有什么区别



    • 很基础的问题,这里就不展开说了。




  • 问了一下 useEffect 对应在 class 中都生命周期怎么写?



    • 很基础的问题,这里就不展开说了。




  • 如果在 if 里面写 useEffect 会有什么表现?



    • 开始没听清楚,误解对方的意思了,以为他说的是在 useEffect 里面写 if 语句,所以胡扯了一堆,后面对方纠正了一下,我才意识到对方在问什么,然后回答了在条件语句里面写 useEffect 控制台会出现报错,因为 hook 的规则就是不能在条件语句或者循环语句里面写,这点在 react 官方文档里面也有提到。




  • 说一下 React 的 Fiber 架构是什么




    • 这里说了一下 Fiber 本质上就是一个对象,是 React 16.8 出现的东西,主要有三层含义:




      1. 作为架构来说,在旧的架构中,Reconciler(协调器)采用递归的方式执行,无法中断,节点数据保存在递归的调用栈中,被称为 Stack Reconciler,stack 就是调用栈;在新的架构中,Reconciler(协调器)是基于 fiber 实现的,节点数据保存在 fiber 中,所以被称为 fiber Reconciler。




      2. 作为静态数据结构来说,每个 fiber 对应一个组件,保存了这个组件的类型对应的 dom 节点信息,这个时候,fiber 节点就是我们所说的虚拟 DOM。




      3. 作为动态工作单元来说,fiber 节点保存了该节点需要更新的状态,以及需要执行的副作用。




      (这里可以参考卡颂老师的《自顶向下学 React 源码》课程)






  • 前面提到,在 if 语句里面写 hook 会报错,你可以用 fiber 架构来解释一下吗?



    • 这里说了一下,因为 fiber 是一个对象,多个 fiber 之间是用链表连接起来的,有一个固定的顺序…… 其实后面还有一些没说完,然后对方听到这里直接打断了,告诉我 OK,这个问题直接过了。




  • 个人方面有什么规划吗?



    • 主要有两个方面,一个是计算机基础需要补补,前面也提到,我不是科班毕业的,计算机底层这方面比起其他人还是比较欠缺的,尤其是计网,另一方面就是英文水平有待提高,也会在将来持续学习。




  • 对未来的技术上有什么规划呢?



    • 主要从业务转型工程化,比如做一些工具链什么的,构建、打包、部署、监控几个大的方向,node 相关的,这些都是我感兴趣的方向,未来都可以去探索,当然了现在也慢慢的在做这些事情,这里顺便提了一嘴,antd 的 script 文件夹里面的文件是我迁移到 esm + ts 的,其中一些逻辑也有重构过,比如收集 css token、生成 contributors 列表、预发布前的一些检查等等…… 所以对 node 这块也有一些了解。




  • 能不能从技术的角度讲一下你工作中负责业务的复杂度?




    • 因为前两份工作中做的是传统的 B 端项目和 C 端项目,并没有什么可以深挖的技术难点,所以这里只说了第三份工作负责的项目,这是一个协同文档,既不算 B 端,也不算 C 端,这是一款企业级的多人协作数据平台,竞品有腾讯文档、飞书文档、语雀、WPS、维卡表格等等。


      协同文档在前端的难点主要有两个方面:




      1. 实时协同编辑的处理:当两个人同时进入一个单元格编辑内容,如果保证两个人看到的视图是同步的?那么这个时候就要提到冲突处理了,冲突处理的解决方案其实已经相对成熟,包括:




        • 编辑锁:当有人在编辑某个文档时,系统会将这个单元格锁定,避免其他人同时编辑,这种方法实现方式最简单,但也会直接影响用户体验。




        • diff-patch:基于 Git 等版本管理类似的思想,对内容进行差异对比、合并等操作,也可以像 Git 那样,在冲突出现时交给用户处理。




        • 最终一致性实现:包括 Operational Transformation(OT)、 Conflict-free replicated data type(CRDT,称为无冲突可复制数据类型)。






      2. 性能问题




        • 众所周知,互联网一线大厂的协同文档工具都是基于 canvas 实现,并且有一套自己的 canvas 渲染引擎,但是我们没有,毕竟团队规模没法跟大厂比,这个项目前端就 2 个人,所以只能用 dom 堆起来(另一个同事已经跑路,现在就剩下我一个人了)。这导致页面卡顿问题非常严重,即使做了虚拟滚动,但是也没有达到很好的优化效果。老板的要求是做到十万量级的数据,但是实际上几千行就非常卡了,根本原因是数据量太大(相当于一张很大的 Excel 表格,里面的每一个单元格都是一个富文本编辑器),渲染任务多,导致内存开销太大。目前没有很好的解决方案,如果需要彻底解决性能问题,那么就需要考虑用 canvas 重写,但是这个基本上不太现实。




        • 因为卡顿的问题,暴露出来另一个问题,状态更新时,视图同步缓慢,所以这时候不得不提到另一个优化策略:乐观更新。乐观更新的思想是,当用户进行交互的时候,先更新视图,然后再向服务端发送请求,如果请求成功,那么什么都不用管,如果请求失败,那么就回滚视图。这样做的好处是,用户体验会好很多,在一些强交互的场景,不会阻塞用户操作,比如抖音的点赞就是这样做的。但是也会带来一些问题,比如:如果用户在编辑某个单元格时,另一个用户也在编辑这个单元格,那么就会出现冲突,这个时候就需要用到前面提到的冲突处理方案了。










  • 可以讲一下你在工作中技术上的建设吗?



    • 这里讲了一下对 hooks 仓库的建设,封装了 100 多个功能 hook业务 hook,把不变的部分隐藏起来,把变化的部分暴露出去,在业务中无脑传参即可,让业务开发更加简单,同时也提高了代码的复用性。然后讲了一下数据流重构之类的 balabala……




  • 你有什么想问我的吗?



    • 问了一下面试结果大概多久能反馈给我,对方说两三天左右,然后就结束了。





结束之后不到 20 分钟,对方就在 Boss 上回复我说面试过了,然后约了二面。



二面:




  • 自我介绍



    • 跟上一轮一样,大概说了两分钟,介绍了过往工作经历,做过的业务以及技术栈。




  • 在 js 中原型链是一个很重要的概念,你能介绍一下它吗?



    • 要介绍原型链,首先要介绍一下原型,原型是什么…… 这块是纯八股,懒得打字了,直接省略吧。




  • object 的原型指向谁?



    • 回答了 null。(我也不知道对不对,瞎说的)




  • 能说一下原型链的查找过程吗?



    • 磕磕绊绊背了一段八股文,这里省略吧。




  • node 的内存管理跟垃圾回收机制有了解过吗?




    • 暗暗窃喜,这个问题问到点子上了,因为两年前被问到过,所以当时专门写了一篇文章,虽然已经过去两年了,但还是背的滚瓜烂熟:




    • 首先分两种情况:V8 将内存分成 新生代空间老生代空间




      • 新生代空间: 用于存活较短的对象




        • 又分成两个空间: from 空间 与 to 空间




        • Scavenge GC 算法: 当 from 空间被占满时,启动 GC 算法



          • 存活的对象从 from space 转移到 to space

          • 清空 from space

          • from space 与 to space 互换

          • 完成一次新生代 GC






      • 老生代空间: 用于存活时间较长的对象




        • 新生代空间 转移到 老生代空间 的条件(这个过程称为对象晋升



          • 经历过一次以上 Scavenge GC 的对象

          • 当 to space 体积超过 25%




        • 标记清除算法:标记存活的对象,未被标记的则被释放



          • 增量标记:小模块标记,在代码执行间隙执,GC 会影响性能

          • 并发标记:不阻塞 js 执行










  • js 中的基础类型和对象类型有什么不一样?



    • 基础类型存储在栈中,对象类型存储在堆中。




  • 看你简历上是用 React,你能简单的介绍一下 hooks 吗?



    • 本质上就是一个纯函数,大概介绍了一下 hooks 的优点,以及 hooks 的使用规则等等。




  • 简单说一下 useEffect 的用法:



    • useEffect 可以代替 class 中的一些生命周期,讲了一下大概用法,然后讲了一下 useEffect 的执行时机,以及 deps 的作用。




  • 说一下 useEffect 的返回值用来做什么?



    • 返回一个函数,用来做清除副作用的工作,比如:清除定时器清除事件监听等等。




  • 你知道 useEffect 第二个参数内部是怎么比较的吗?



    • 说了一下内部是浅比较,源码中用 for 循环配合 Object.is 实现。(感觉这个问题就是在考察有没有读过 React 源码)




  • 前端的话可能跟网络打交道比较多,网络你了解多少呢?



    • 这里直接坦诚的说了一下,网络是我的弱项,前面一面也问到了网络七层模型,没回答出来。




  • 那你回去了解过七层模型吗?我现在再问你一遍,你能回答出来吗?



    • 磕磕绊绊回答出来了。




  • 追问:http 是在哪一层实现的?



    • 应用层。




  • 说一下 getpost 有什么区别?



    • 两眼一黑,脑子一片空白,突然不知道说什么了,挤了半天挤出来一句:get 大多数情况下用来查询,post 大多数情况下用来提交数据。get 的入参拼在 url 上,post 请求的入参在 body 里面。面试官问我还有其它吗?我说想不起来了……




  • 说一下浏览器输入 url 到页面加载的过程:




    • 输入网址发生以下步骤:



      1. 通过 DNS 解析域名的实际 IP 地址

      2. 检查浏览器是否有缓存,命中则直接取本地磁盘的 html,如果没有命中强缓存,则会向服务器发起请求(先进行下一步的 TCP 连接)

      3. 强缓存协商缓存都没有命中,则返回请求结果

      4. 然后与 WEB 服务器通过三次握手建立 TCP 连接。期间会判断一下,若协议是 https 则会做加密,如果不是,则会跳过这一步

      5. 加密完成之后,浏览器发送请求获取页面 html,服务器响应 html,这里的服务器可能是 server、也可能是 cdn

      6. 接下来是浏览器解析 HTML,开始渲染页面




    • 顺便说了渲染页面的过程:



      1. 浏览器会将 HTML 解析成一个 DOM 树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

      2. 将 CSS 解析成 CSS Rule Tree(css 规则树)。

      3. 解析完成后,浏览器引擎会根据 DOM 树CSS 规则树来构造 Render Tree。(注意:Render Tree 渲染树并不等同于 DOM 树,因为一些像 Headerdisplay:none 的东西就没必要放在渲染树中了。)

      4. 有了 Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的 CSS 定义以及他们的从属关系。下一步进行 layout,进入布局处理阶段,即计算出每个节点在屏幕中的位置。

      5. 最后一个步骤就是绘制,即遍历 RenderTree,层绘制每个节点。根据计算好的信息绘制整个页面。




    • 渲染完成之后,开始执行其它任务:



      1. dom 操作

      2. ajax 发起的 http 网络请求等等……

      3. 浏览器处理事件循环等异步逻辑等等……






  • 菜单左中右布局,两边定宽,中间自适应,说一下有几种实现方式



    • 比较经典的面试题,说了 flexfloat 两种方式。




  • 项目难点



    • 和一面一样,说了协同文档的两大难点,这里就不重复了。




  • 你有什么想问我的吗?



    • 和一面一样,问了一下面试结果大概多久能反馈给我,对方说两三天左右,然后就结束了。




  • 最后问了期望薪资什么的,然后就结束了。




二面结束之后,大概过了几个小时,在 Boss 上跟对方说了一声,如果没过的话也麻烦跟我说一下,然后这时候,对方在 Boss 上问我,第一学历是不是专科?我说是的,感觉到不太妙的样子,


然后又过了一会儿,对方说定级应该不会高,他后续看一下面试官的反馈如何……


然后又追问我,换工作的核心诉求是涨薪还是能力的提升,这里我回答的比较委婉,其实两个都想要 QAQ

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

TypeScript中的枚举,点燃你的代码创意!

前言 枚举是 TypeScript 中一个非常有趣且实用的特性,它可以让我们更好地组织和管理代码。 什么是枚举? 在 TypeScript 中,枚举(Enum)是一种用于定义命名常量集合的数据类型。它允许我们为一组相关的值赋予一个友好的名字,从而使代码更加可读...
继续阅读 »

前言


枚举是 TypeScript 中一个非常有趣且实用的特性,它可以让我们更好地组织和管理代码。


什么是枚举?


在 TypeScript 中,枚举(Enum)是一种用于定义命名常量集合的数据类型。它允许我们为一组相关的值赋予一个友好的名字,从而使代码更加可读和易于理解。枚举可以帮助我们避免使用魔法数值,提高代码的可维护性和可读性。


枚举的基本用法


让我们从枚举的基本用法开始,以一个简单的例子来说明。

enum Direction {
Up,
Down,
Left,
Right,
}

在这个例子中,我们定义了一个名为 Direction 的枚举,它包含了四个值:Up、Down、Left 和 Right。这些值默认情况下是从0开始自增的索引值。


我们可以使用枚举中的值来进行变量的赋值和比较。

let myDirection: Direction = Direction.Up;

if (myDirection === Direction.Up) {
console.log("向上");
} else if (myDirection === Direction.Down) {
console.log("向下");
}

在这个例子中,我们声明了一个名为 myDirection 的变量,并将其赋值为 Direction.Up。然后,我们使用 if 语句对 myDirection 进行比较,并输出相应的信息。


枚举的进阶用法


除了基本的用法外,枚举还有一些进阶的用法,让我们一起来看看。


1. 指定枚举成员的值


我们可以手动为枚举成员指定具体的值,而不是默认的自增索引值。

enum Direction {
Up = 1,
Down = 2,
Left = 3,
Right = 4,
}

在这个例子中,我们手动指定了每个枚举成员的值。这样,Up 的值为1,Down 的值为2,依此类推。


2. 使用枚举成员的名称


我们可以使用枚举成员的名称来访问其值。

enum Direction {
Up,
Down,
Left,
Right,
}

console.log(Direction.Up); // 输出

: 0
console.log(Direction[0]); // 输出: "Up"

在这个例子中,我们分别通过成员的名称和索引值来访问枚举成员的值。


3. 枚举的反向映射


枚举还具有反向映射的特性,可以通过值找到对应的名称。

enum Direction {
Up,
Down,
Left,
Right,
}

console.log(Direction.Up); // 输出: 0
console.log(Direction[0]); // 输出: "Up"

在这个例子中,我们通过 Direction.Up 输出了 0,通过 Direction[0] 输出了 "Up"。这种反向映射可以在某些场景下非常有用。


总结


枚举是一种用于定义命名常量集合的数据类型,可以帮助我们更好地组织和管理代码。我们了解了枚举的基本用法,以及一些进阶的技巧,如指定枚举成员的值、使用枚举成员的名称和枚举的反向映射。


希望能够帮助到大家更好地掌握 TypeScript 中的枚举,并在实际开发中灵活运用。


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

年后被吊打的第一面

背景 base重庆,面试中高级,目标先检验一下自己的水平和能力顺便看看薪资,好直接开始把。 自我介绍 讲了一下自己的技术栈:掌握vue全家桶,底层及上层框架、掌握react底层原理、熟悉js、熟悉工程化、熟悉微信小程序、使用过node、关注前端趋势有开源经历、...
继续阅读 »

背景


base重庆,面试中高级,目标先检验一下自己的水平和能力顺便看看薪资,好直接开始把。


自我介绍


讲了一下自己的技术栈:掌握vue全家桶,底层及上层框架、掌握react底层原理、熟悉js、熟悉工程化、熟悉微信小程序、使用过node、关注前端趋势有开源经历、主方向工程化等。


大概说了1分钟把,可能是我一边自我介绍一边在笑,面试官就问了一下:“你看起来心态很好啊!你是不是要写面经啊?”


我:“我是紧张才笑,应该是可以吗?要不多问点把~”


面试官:“可以,行”


记住这句话,我现在很后悔


步入正题


面试官:"那我们先来点基础把",下面都是我原话,小伙伴们可以纠正一下。


JS浏览器基础篇


1、dom树是怎么生成的


"浏览器是多进程架构,而其中有一个渲染进程,负责页面的渲染和js脚本的执行,而在渲染进程中有一个HTML解析器,oh对还有一个网络进程,网络进程负责根据content-type创建渲染进程,然后渲染进程用类似stream流管道那种接字节流将它解析为dom"


“而解析时,我觉得可以对标现在的各种转移编译工具,都有一个词法分析、语法分析、transfrom、genoretor的流程”


“你能具体说说这个过程吗?”


“(心理活动:当时脑子就蒙了、有点超纲啊、我要不猜一下)稍等我想一下、跟babel应该很像吧、会对一些声明命令赋值注释分词,这块我不是很了解,但应该对于html分词就是分的标签和文本内容,然后再通过算法去转换成dom”


“行,你上面提到了分析器,那如果当分析器遇到了script标签那”


“(心理活动:......这八股文味不对劲啊),我不知道对不对,但表现的是当遇到了scrpit会暂停html转换dom,去解析jascript,而async和defer会异步加载不会阻塞html转换”。


2、渲染进程


还可以你可以自信点虽然有些地方不是很对,但已经够用了。刚听你说了渲染进程,你说说它下面的几个线程把。


“emmm,下面的主线程吗,有主线程、GUI渲染线程、事件触发线程、定时器触发线程(后面发现漏了一个http线程),em,主线程和GUI是互斥的、js执行太长会造成页面渲染卡顿,但现在有很多解决方案,比如:在react中的调度器预留的5ms空闲时间、web worker之类的。然后是事件触发线程和定时器线程都是在任务队列去做Loop”


“行,那事件循坏我就不问你了,问问你V8的垃圾回收把”


“(你问啊!你问啊!)”


3、v8垃圾回收


“首先js因为是单线程,垃圾回收会占用主线程,导致页面卡顿,所以需要一个算法或者说策略,而v8采用的是分代式回收,而垃圾回收在堆中分成了很多部分用作不同的作用(我在说什么啊!当时),回收主要表现在新老生代上,新生代就活得短一点的对象,老生代就活得长一点的对象。


“在新生代里有一个算法,将新生代分成了两个区,一个FORM,一个TO,每次经过Scavenge会将FORM区中的没引用的销毁,然后活着的TO区调换位置,反复如此,当经过一次acavange后就会晋升的老生代还有个条件就是TO区的内存超过多少了也会晋升。”


“而老生代,采用标记清除和标记整理,但标记清除会造成内存不连续,所以会有标记整理取解决掉内存碎片,就是清理掉边界碎片”


“为什么TO超过25%要晋升老生代?标记清除是怎么清除的?”


“不知道~”


“第一个问题是为了不影响后续FORM空间的分配,第二个问题你应该看过有关这方面的文章把,垃圾回收会构建一个根列表,从根节点去访问那些变量,可访问到位活动,不可就是垃圾”


4、浏览器缓存


就强制缓存,协商缓存,浏览器内存那些,有兴趣看看文章,讲细点就行。写着太累了,当时讲了一大滩,直接说面试官问题把。


“因为提到了这些缓存,你觉得他们对于我们实际的业务场景下怎么运用”


“(蒙蔽),很大一部分是浏览器优化,一些http缓存我们可以做一些控制,本质上我感觉这些都属于性能优化的部分。”


“行”


5、JS上下文执行栈和闭包


“几个概念把,esc、上下文:作用域链,AO/VO,this。esc存储执行的上下文”


“算了我以一个函数来说把,主要是创建和执行。假设有一个A函数,过程是这样的创建全局执行上下文、压入esc、全局上下文初始化、执行A函数、创建A函数执行上下文,压入esc,A函数上下文初始化,这个初始化过程是这样的:创建作用域链、emm我上面提漏了一个A函数被创建全局上下文被保存到scope中的过程,是复制scpoe创建作用域链,用arguments创建活动对象,初始化活动对于,将活动对象压入链顶,执行完毕,上下文弹出。”


“但是全局上下文一直在栈底,而VO和AO的确认,我感觉是取决是是否可访问的。”


“而闭包就是上下文链中上下文scope被销毁了,但因为保持了对scope中某个变量的引用,这应该就是你上面说的回收原理的根节点实现的这个东西把,导致没销毁干净,留存在了内存中,完成了闭包”


“你怎么看待闭包的副作用”


“emmm,其实我觉得闭包是语言特性,虽然有副作用但我觉得其实挺好的,但只要管理好它就好了。况且又不是只有闭包会造成这些问题,就比如:react里面还有专门去清理一些链表和难回收的东西,去帮助v8回收。我觉得这得取决于写代码的人。”


“可以的,我感觉你的基础还是挺好的,你说下es6的东西把,控制下时间”


“你想听哪方面的那?因为东西太多了”


“工程化把,因为我前面听你介绍主方向是工程化”


“(...我怎么感觉工程化相关的只有一个esm模块化啊,这个怎么分类啊)esm:异步加载、引入抛出,编译时,值的引用。大概就这些东西把,其他的不知道了”


“行”


“那您觉得还有哪些那”


“就比如:Promise和async之类的啊”


“(..................)”


“来手写几道题把”


6、bind pipe compose 深拷贝


这个网上太多了,大伙自己去看。


CSS基础篇


“我可能CSS有点烂,但基础的应该都知道”


“先说说BFC把”


1、BFC


“BFC给我的感觉就像是个封闭盒子,不会在布局上影响到外面的元素。平常会触发BFC比较多的就是body,浮动元素、绝对定位、flex、overflow之类的。在BFC可以清除浮动、阻止元素被浮动环绕之类的。(然后我一边说一边尴尬的笑)”


“大概知道你CSS是个啥水平了,简单问点把,你说BFC可以清除浮动吗?为什么?”


“不知道”


“其实准确的说不是清除,是因为浮动元素也是BFC,两个BFC互不影响。你提到了BODY触发BFC?”


“emmm,可能是也许不是BODY,是HTML根元素”


“是的,不是BODY”


2、居中


"flex布局,positon,flex margin:auto,position transform,table-cell"


"行了,层叠上下文和层叠顺序"


3、层叠上下文


“(.......这时候感觉每多问我一个CSS都是煎熬啊),em我其实一般对于会遇到有层叠上下文不清晰的情况都是指定z-index.”


“行”


4、flex布局


“这样吧,你平时用得比较多的是什么布局?”


“flex布局把”


“那我们来聊一下flex布局把”


“(拜托我真的会哭,我感觉面试官他好兴奋),emmmm,我觉得布局无非是控制内部元素和控制容器,而flex布局对于容器的控制是基于轴这个概念的,而flex中的轴分为:主轴、垂直轴、换行轴。”


“主轴指的就是元素排列的方向轴,而flex-direction是最重要的属性在主轴中,row和col控制角度,reverse控制方向,但我们其实平时用得比较多的就默认的row和column,column会把元素垂直去排列。而主轴的另一个属性justify-content是控制元素在轴上的排列,然后我说了一下常用的几种就start end center between around”


“垂直轴就是垂直于主轴的方向轴~然后我停了大概有20秒(又开始笑了)”


“没了?”


“(啊啊啊啊啊!)可能就我只知道align-items控制垂直轴上的位置,然后说了下,start end center。”


“还有换行轴那”


“嗷对,就是刚才提漏了,垂直轴是针对于当前行,但换行轴是针对于整个容器。”


“这个针对怎么说?你继续说换行轴属性。”


就是高度嘛,布局换行后,垂直轴的高度只会是当前行高度。flex-wrap,但我只用过wrap,emm对于控制内部容器我了解得很粗浅。


"你可以了解一下wrap-reverse,下来可以去看一下正负剩余空间和flex-grow flex-shrink这些"


“抢答!就是flex:1这种写法的哈”


“对的,你知道吗”


“不知道”


我们两个同时沉默了(啊!!!!!!!!)。


"没事其实比我想象得稍微好一点,至少你在你不擅长的东西上也是花了时间去学的,css就不问了,下面问点框架把"


“谢谢!谢谢!谢谢!”


框架基础篇


“你简历里是React和Vue都会,那先说说你是怎么看这两个框架的把”


1、对React和Vue的看法


“在开发模式上,React发明了JSX这种开发模式,并且用了很多年时间让社区去接受,而Vue则是采用现成的模版语法的开发模式。但感觉就这两年这两个框架都在往一个函数式组件的方向靠,不应该说靠是已经实现了,Vue3中的函数式组件甚至在某种层面上说比react更自由。当然洛现在声明式的编程是主流嘛”


“在实现层面上说的话,就那一套,单向数据流,双向绑定,数据不可变性,更智能的依赖收集,优化的方式不同等”


“听到你说了更自由和智能的依赖收集,具体指的是?”


"比如react useEffect要手动加依赖,但vue3的wachEffect和computed就不用"。


“自由,em就比如,hook得顶层,不要条件循坏用,而且react重心智负担:就闭包陷阱那一套过时变量嘛,依赖的选择,还有重复渲染这些问题,我一直不理解为什么不把这些心智负担收到框架里,我觉得react是有这个能力的。vue3的话,你想咋用咋样api咋样,setup也会只用一次,也不会像新旧树对比hook多次调用”


“哈哈感觉你对react怨气好大,因为我看你文章写了很多react源码的嘛,如果是你你会怎么去收敛这个心智负担到框架内部?”


(.......绷不住了啊,吹过头了),稍等想1分钟,就是hook顶层那个和条件循坏应该不好动,因为react不能去破坏hooks链表的结构,对于过时变量react18已经将usestate全改为异步了,依赖的选择的心智问题我觉得是否说可以更明确一点再未来加入配置项的话,将依赖的收集在updateEffect和mountedEffect前去提前收集,就做一个依赖系统专门去处理这个事情,感觉可以从编译器自动生成依赖数组,现在react只是一层浅比较。但其实这么想,大部分问题的根源,是React函数组件机制所限:每次组件渲染,组件里的所有代码都会被重新调用一次,在这上面其实也可以动下手(自己说了感觉当没说,感觉好尴尬啊硬吹)。就长话短说就是,react的心智模型让我要花很多精力去处理边界情况。


“其实你最后句话说得挺好的,因为react要求你用声明式的方式去写组件,你就不该去纠结引用会不会变,多添加个依赖很不舒服,重新渲染这种事情,你需要保证的是无论重新渲染多少次这个组件都不会变。假设你useEffect依赖于AB,但你的B就可能只在首次创建后永远不变,它确实显得很“多余”但你不用纠结这个。真正的问题可能就在于你觉得的永远不会变只是你觉得,我们平时出现问题很多都是这种以为的边界问题导致B变造成的”


2、为什么react需要合成事件


“兼容性的考虑把,可以抹平不同浏览器事件对象差异,还有个就是避免了垃圾回收。”


“我们公司主要是Vue,你的简历里Vue也更擅长一些,我们谈一下Vue把”


3、生命周期


随便了一下,每个生命周期,父子生命周期,每个生命周期的定义和写法。


4、路由


5、指令


6、响应式原理


7、数组处理


8、key,diff算法


9、V3组合式API


10、一些TS系统


11、V3编译原理


“上面也问了挺多的了,你讲讲Vue3里面的模版编译和函数式组件编译把。”


“(我崩不住了,妈妈我想回家)先巴拉巴拉扯了一下pnpm和menorepo和整体v3源码,然后讲到complier,同样vue模版的编译也是通俗流程就是parse transform gen,我没具体研究过,但parse应该也是对标签属性文本指令等一系列做处理去干成AST,然后transform做转换最后生成。”


“行上午先面到这,二面通知你,有什么要问的吗?”


“em,你觉得我咋样(我好直白~)”


“挺不错的,2年经验,感觉你知识的掌握程度大于你的年限,有点好奇平时你怎么安排学习时间的”


“就可能目前这家公司比较清闲把,再加上学习写代码对我比较快乐,就能投入很多时间和精力,哎,就二面能不能快点!可以加个微信吗~”


“行”


总结


然后二面就是项目面了,啊好累啊,现在前端真卷啊,其实感觉可以多问点工程化,虽然准备了很多但是没被问上。


emmm,因为看评论区嘛,就想说,乐观点,其实也没这么卷,只要自己多写写代码和看看八股文都可以的,当然最重要的还是思考。emm想加群一起卷的可以看沸点


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

抓包技术的应用示例:薅瑞幸羊毛 🦙

前言 最近瑞幸在搞活动,每天免费送10000份咖啡,我是个不喝咖啡的人儿,所以没咋关注,今早我们的团宠小妹,拉着我 10点整拼手速,想着帮她抢一杯,于是点开瑞幸咖啡小程序主页,banner 栏轮播图中有一张海报入口,操作一通下来,果然,没抢到。 手速不够快不是...
继续阅读 »

前言


最近瑞幸在搞活动,每天免费送10000份咖啡,我是个不喝咖啡的人儿,所以没咋关注,今早我们的团宠小妹,拉着我 10点整拼手速,想着帮她抢一杯,于是点开瑞幸咖啡小程序主页,banner 栏轮播图中有一张海报入口,操作一通下来,果然,没抢到。


手速不够快不是主要原因,手指操作延迟 + 系统页面跳转耗时加起来到 http 发出就已经耽误了1 -2 秒钟了,这个时间才是关键,本文从技术角度探讨下怎么在最小成本比如几分钟内,实现一个小工具,来解决这个问题。


抓包工具


首先需要一个抓包工具,iphone 手机可以用 stream, 有几个注意点:


1、默认安装后是无法抓取 https 类型的,需要在设置里进行相关配置:



如果您要抓取 HTTPS 的请求,需要先启动抓包,然后安装 CA 证书后,去设置-通用-关于-证书信任设置 里信任 CA,才可以查看请求的内容。



Pasted image 20230601122258.png


2、注意小程序里面哦(原生的可能抓不到),拿到的接口名如下:


https://mkt.lkcoffee.com/ladder/capi/resource/m/promo/activity/send


stream 提供了 curl 的拷贝,将其复制并导入到 postman 中。


WechatIMG247.png


postman 导入&复现


点击 import 按钮,在弹窗中选择 raw text 将复制的 curl 字符串粘贴进去,点击确认,就成功的将 这个 http 接口导入到了 postman 中,尝试点击 send 按钮,发现拿到了正确的响应,验证了该接口已经可以正常使用。


截屏2023-06-01 12.43.31.png


Pasted image 20230601122933.png


自动化脚本?


其实到这一步,已经实现了目标,点击 send 直接发送请求,大大提升了抢到的概率,如果你还想更进一步,那么可以尝试将其封装成 自动化脚本,来实现定时、自动、重复发送;


点开右侧代码块,选择语言,假设选择 python(也可以任意选择你擅长的语言),然后就自动生成 python 版本的可执行代码片段,我们就在这个基础上拓展功能;


截屏2023-06-01 12.48.19.png


示例代码如下:

import requests
import time

url = "http://example.com" # 将此处的 URL 替换为你要请求的地址
payload = {}
headers = {
#将 postman 中的headers 复制过来
}

start_time = "09:59:55" # 设置开始请求的时间
end_time = "10:00:30" # 设置结束请求的时间

def make_request():
response = requests.get(url, headers=headers, data=payload)
if "成功" in response.text:
print("响应内容:", response.text)
raise SystemExit # 中断程序

while True:
current_time = time.strftime("%H:%M:%S", time.localtime())
if current_time >= start_time and current_time <= end_time:
make_request()
time.sleep(1) # 每秒检查一次当前时间


将其保存到本地并通过 python 指令来执行,就可以运行了。


总结


用今天的午睡时间,写了这篇文,以瑞幸的营销活动为例子,带你感受了下技术的魅力,其中涉及到了抓包、自动化脚本、定时任务、请求策略、stream 和 postman 等知识;


然后我想问下大家,对于其带来的潜在公平问题,你们怎么看呢?欢迎讨论。


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

什么是优雅的代码设计

今天我来解释一下什么样的代码才是优雅的代码设计。当然我们的代码根据实际的应用场景也分了很多维度,有偏向于底层系统的,有偏向于中间件的,也有偏向上层业务的,还有偏向于前端展示的。今天我主要来跟大家分析一下我对于业务代码的理解以及什么样才是我认为的优雅的业务代码设...
继续阅读 »

今天我来解释一下什么样的代码才是优雅的代码设计。当然我们的代码根据实际的应用场景也分了很多维度,有偏向于底层系统的,有偏向于中间件的,也有偏向上层业务的,还有偏向于前端展示的。今天我主要来跟大家分析一下我对于业务代码的理解以及什么样才是我认为的优雅的业务代码设计。


大家吐槽非常多的是,我们这边的业务代码会存在着大量的不断地持续的变化,导致我们的程序员对于业务代码设计得就比较随意。往往为了快速上线随意堆叠,不加深入思考,或者是怕影响到原来的流程,而不断在原来的代码上增加分支流程。


这种思想进一步使得代码腐化,使得大量的程序员更失去了“好代码”的标准。


那么如果代码优雅,那么要有哪些特征呢?或者说我们做哪些事情才会使得代码变得更加优雅呢?


结构化


结构化定义是指对某一概念或事物进行系统化、规范化的分析和定义,包括定义的范围、对象的属性、关系等方面,旨在准确地描述和定义所要表达的概念或事物。



我觉得首要的是代码,要一个骨架。就跟我们所说的思维结构是一样,我们对一个事物的判断,一般都是综合、立体和全面的,否则就会成为了盲人摸象,只见一斑。因此对于一个事物的判断,要综合、结构和全面。对于一段代码来说也是一样的标准,首先就是结构化。结构化是对一段代码最基本的要求,一个有良好结构的代码才可能称得上是好代码,如果只是想到哪里就写到哪里,一定成不了最优质的代码。


代码的结构化,能够让维护的人一眼就能看出主次结构、看出分层结构,能够快速掌握一段代码或者一段模块要完成的核心事情。


精简



代码跟我们抽象现实的物体一样,也要非常地精简。其实精简我觉得不仅在代码,在所有艺术品里面都是一样的,包括电影。电影虽然可能长达一个小时,两个小时,但你会发现优雅的电影它没有一帧是多余的,每出现的一个画面、一个细节,都是电影里要表达的某个情绪有关联。我们所说的文章也是一样,没有任何一个伏笔是多余的。代码也是一样,严格来说代码没有一个字符、函数、变量是多余的,每个代码都有它应该有的用处。就跟“奥卡姆剃刀”原理一样,每块代码都有它存在的价值包括注释。


但正如我们的创作一样,要完成一个功能,我们把代码写得复杂是简单的,但我们把它写得简单是非常难的。代码是思维结构的一种体现,而往往抽象能力是最为关键的,也是最难的。合适的抽象以及合理的抽象才能够让代码浓缩到最少的代码函数。


大部分情况来说,代码行数越少,则运行效率会越高。当然也不要成为极端的反面例子,不要一味追求极度少量的代码。代码的优雅一定是精要的,该有的有,不该有的一定是没有的。所以在完成一个业务逻辑的时候,一定要多问自己这个代码是不是必须有的,能不能以一种简要的方式来表达。


善用最佳实践


俗话说太阳底下没有新鲜事儿,一般来说,没有一个业务场景所需要用到的编码方式是需要你独创发明的。你所写的代码功能大概率都有人遇到过,因此对于大部分常用的编码模式,也都大家被抽象出来了一些最佳实践。那么最经典的就是23种设计模式,基本上可以涵盖90%以上的业务场景了。


以下是23种设计模式的部分简单介绍:

  1. 单例模式(Singleton Pattern):确保类只有一个实例,并提供全局访问点。
  2. 工厂模式(Factory Pattern):定义一个用于创建对象的接口,并让子类决定实例化哪个对象。
  3. 模板方法模式(Template Method Pattern):提供一种动态的创建对象的方法,通过使用不同的模板来创建对象。
  4. 装饰器模式(Decorator Pattern):将对象包装成另一个对象,从而改变原有对象的行为。
  5. 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口,以使其能够与不同的对象交互。
  6. 外观模式(Facade Pattern):将对象的不同方面组合成一个单一的接口,从而使客户端只需访问该接口即可使用整个对象。

我们所说的设计模式就是一种对常用代码结构的一种抽象或者说套路。并不是说我们一定要用设计模式来实现功能,而是说我们要有一种最高效,最通常的方式去实现。这种方式带来了好处就是高效,而且别人理解起来也相对来说比较容易。


我们也不大推荐对于一些常见功能用一些花里胡哨的方式来实现,这样往往可能导致过度设计,但实际用处可能反而会带来其他问题。我觉得要用一些新型的代码,新型的思维方式应该是在一些比较新的场景里面去使用,去验证,而不应该在我们已有最佳实践的方式上去造额外的轮子。


这个就比如我们如果要设计一辆汽车,我们应该采用当前最新最成熟的发动机方案,而不应该从零开始自己再造一套新的发动机。但是如果这个发动机是在土星使用,要面对极端的环境,可能就需要基于当前的方案研制一套全新的发动机系统,但是大部分人是没有机会碰到土星这种业务环境的。所以通常情况下,还是不要在不需要创新的地方去创新。


除了善用最佳实践模式之快,我们还应该采用更高层的一些最佳实践框架的解决方案。比如我们在面对非常抽象,非常灵活变动的一些规则的管理上,我们可以使用大量的规则引擎工具。比如针对于流程式的业务模型上面,我们可以引入一些工作流的引擎。在需要RPC框架的时候,我们可以根据业务情况去调研使用HTTP还是DUBBO,可以集百家之所长。


持续重构



好代码往往不是一蹴而就的,而是需要我们持续打磨。有很多时候由于业务的变化以及我们思维的局限性,我们没有办法一次性就能够设计出最优的代码质量,往往需要我们后续持续的优化。所以除了初始化的设计以外,我们还应该在业务持续的发展过程中动态地去对代码进行重构。


但是往往程序员由于业务繁忙或者自身的懒惰,在业务代码上线正常运行后,就打死不愿意再动原来的代码。第一个是觉得跑得没有问题了何必去改,第二个就是改动了反而可能引起故障。这就是一种完全错误的思维,一来是给自己写不好的线上代码的一个借口,二来是没有让自己持续进步的机会。


代码重构的原则有很多,这里我就不再细讲。但是始终我觉得对线上第一个要敬畏,第二个也要花时间持续续治理。往往我们在很多时候初始化的架构是比较优雅的,是经过充分设计的,但是也是由于业务发展的迭代的原因,我们持续在存量代码上添加新功能。


有时候有一些不同的同学水平不一样,能力也不一样,所以导致后面写上的代码会非常地随意,导致整个系统就会变得越来越累赘,到了最后就不敢有新同学上去改,或者是稍微一改可能就引起未知的故障。


所以在这种情况下,如果还在追求优质的代码,就需要持续不断地重构。重构需要持续改善,并且最好每次借业务变更时,做小幅度的修改以降低风险。长此以往,整体的代码结构就得以大幅度的修改,真正达到集腋成裘的目的。下面是一些常见的重构原则:

  1. 单一职责原则:每个类或模块应该只负责一个单一的任务。这有助于降低代码的复杂度和维护成本。
  2. 开闭原则:软件实体(类、模块等)应该对扩展开放,对修改关闭。这样可以保证代码的灵活性和可维护性。
  3. 里氏替换原则:任何基类都可以被其子类替换。这可以减少代码的耦合度,提高代码的可扩展性。
  4. 接口隔离原则:不同的接口应该是相互独立的,它们只依赖于自己需要的实现,而不是其他接口。
  5. 依赖倒置原则:高层模块不应该依赖低层模块,而是依赖应用程序的功能。这可以降低代码的复杂度和耦合度。
  6. 高内聚低耦合原则:尽可能使模块内部的耦合度低,而模块之间的耦合度高。这可以提高代码的可维护性和可扩展性。
  7. 抽象工厂原则:使用抽象工厂来创建对象,这样可以减少代码的复杂度和耦合度。
  8. 单一视图原则:每个页面只应该有一个视图,这可以提高代码的可读性和可维护性。
  9. 依赖追踪原则:对代码中的所有依赖关系进行跟踪,并在必要时进行修复或重构。
  10. 测试驱动开发原则:在编写代码之前编写测试用例,并在开发过程中持续编写和运行测试用例,以确保代码的质量和稳定性。

综合


综上所述,代码要有结构化、可扩展、用最佳实践和持续重构。追求卓越的优质代码应该是每一位工程师的基本追求和基本要求,只有这样,才能不断地使得自己成为一名卓越的工程师。



作者:ali老蒋
来源:juejin.cn/post/7241115614102863928

收起阅读 »

何谓实事求是地工作?

提到实事求是,大家第一时间会想到什么?我想大部分是客观,事实,脚踏实地?这么一想,大家都会觉得,自己挺实事求是的呀,没毛病。但是,我会经常在工作中感受到不是那么实事求是的行为,比如张嘴就来,不带思考,做事全靠猜的行为,真太多了。 随着我这两年的学习和总结,我越...
继续阅读 »

提到实事求是,大家第一时间会想到什么?我想大部分是客观,事实,脚踏实地?这么一想,大家都会觉得,自己挺实事求是的呀,没毛病。但是,我会经常在工作中感受到不是那么实事求是的行为,比如张嘴就来,不带思考,做事全靠猜的行为,真太多了。


随着我这两年的学习和总结,我越发觉得实事求是非常重要,并把它视为我做事情和成长的基石。对于实事求是,我主要有以下 3 层理解。


首先,尊重客观事实,探寻真理。我们要承认事实,即使这个事实有多么的难以置信,但存在即是合理,我们首先要尊重它,承认它。然后我们还要积极主动地面对它,探寻事实背后的真理,获得真知,这样才能真正的成长,并有可能寻得机会。当某个事情的进展超出自己预期的时候,我们正确的态度应该是思考为什么会这样,而不是去想对错得失。


其次,数据说话,数据驱动。事实如何去量化?答案是数据。使用数据去表达事实,是我们程序员应该有的技能。工作的本质就是解决问题,之前的文章有讲解,问题就是理想状态和现实状态之间的差别,因此,我们在工作当中做的每一项决策的依据、制定的每一个目标,都应该用数据说话。我们应该使用数据表达现状,并使用数据衡量目标,驱动自己去工作。一些沟通的细节就能够体现出他是不是在实事求是地工作,比如“这个页面加载太慢了,需要优化”。那这个页面加载到底有多慢?业界标准或者竞品的加载耗时是多少?优化的目标值是多少?


最后,从客观事实中获取反馈,不断迭代。工作中想要获得成功和成长,最核心的一个环节是反馈。很多人没有意识到这点。更多的人没有意识到的是,获取反馈其实很简单,随处都是。敏捷开发、精益创业、增长黑客,这些理论的底层核心都是基于事实和数据的反馈,不断迭代改进自己的产品,从而获得成功。对于个人成长来说也是一样的,我们要从客观事实中获取反馈,思考总结,不断迭代自己的能力。


总结一下,实事求是地工作有 3 个层次,首先,要正视事实,并主动探究真理;然后我们慢慢地开始用数据驱动自己的工作;最后让数据驱动变成循环,不断迭代,并把这种循环融入到各个方面,包括工作和个人成长,让它成为自己下意识的动作。


我在努力学习和践行实事求是地工作,我也希望我的团队可以用实事求是的态度来工作,以此文共勉!



作者:潜龙在渊灬
来源:juejin.cn/post/7241394138260160568

收起阅读 »

什么是 HTTP 长轮询?

web
什么是 HTTP 长轮询? Web 应用程序最初是围绕客户端/服务器模型开发的,其中 Web 客户端始终是事务的发起者,向服务器请求数据。因此,没有任何机制可以让服务器在没有客户端先发出请求的情况下独立地向客户端发送或推送数据。 为了克服这个缺陷,Web 应用...
继续阅读 »

什么是 HTTP 长轮询?


Web 应用程序最初是围绕客户端/服务器模型开发的,其中 Web 客户端始终是事务的发起者,向服务器请求数据。因此,没有任何机制可以让服务器在没有客户端先发出请求的情况下独立地向客户端发送或推送数据。


为了克服这个缺陷,Web 应用程序开发人员可以实施一种称为 HTTP长轮询的技术,其中客户端轮询服务器以请求新信息。服务器保持请求打开,直到有新数据可用。一旦可用,服务器就会响应并发送新信息。客户端收到新信息后,立即发送另一个请求,重复上述操作。


什么是 HTTP 长轮询?


那么,什么是长轮询?HTTP 长轮询是标准轮询的一种变体,它模拟服务器有效地将消息推送到客户端(或浏览器)。


长轮询是最早开发的允许服务器将数据“推送”到客户端的技术之一,并且由于其寿命长,它在所有浏览器和 Web 技术中几乎无处不在。即使在一个专门为持久双向通信设计的协议(例如 WebSockets)的时代,长轮询的能力仍然作为一种无处不在的回退机制占有一席之地。


HTTP 长轮询如何工作?


要了解长轮询,首先要考虑使用 HTTP 的标准轮询。


“标准”HTTP 轮询


HTTP 轮询由客户端(例如 Web 浏览器)组成,不断向服务器请求更新。


一个用例是想要关注快速发展的新闻报道的用户。在用户的浏览器中,他们已经加载了网页,并希望该网页随着新闻报道的展开而更新。实现这一点的一种方法是浏览器反复询问新闻服务器“内容是否有任何更新”,然后服务器将以更新作为响应,或者如果没有更新则给出空响应。浏览器请求更新的速率决定了新闻页面更新的频率——更新之间的时间过长意味着重要的更新被延迟。更新之间的时间太短意味着会有很多“无更新”响应,从而导致资源浪费和效率低下。


HTTP 轮询


上图:Web 浏览器和服务器之间的 HTTP 轮询。服务器向立即响应的服务器发出重复请求。


这种“标准”HTTP 轮询有缺点:



  • 更新请求之间没有完美的时间间隔。请求总是要么太频繁(效率低下)要么太慢(更新时间比要求的要长)。

  • 随着规模的扩大和客户端数量的增加,对服务器的请求数量也会增加。由于资源被无目的使用,这可能会变得低效和浪费。


HTTP 长轮询解决了使用 HTTP 进行轮询的缺点



  1. 请求从浏览器发送到服务器,就像以前一样

  2. 服务器不会关闭连接,而是保持连接打开,直到有数据供服务器发送

  3. 客户端等待服务器的响应。

  4. 当数据可用时,服务器将其发送给客户端

  5. 客户端立即向服务器发出另一个 HTTP 长轮询请求


HTTP 长轮询


上图:客户端和服务器之间的 HTTP 长轮询。请注意,请求和响应之间有很长的时间,因为服务器会等待直到有数据要发送。


这比常规轮询更有效率。



  • 浏览器将始终在可用时接收最新更新

  • 服务器不会被永远无法满足的请求所搞垮。


长轮询有多长时间?


在现实世界中,任何与服务器的客户端连接最终都会超时。服务器在响应之前保持连接打开的时间取决于几个因素:服务器协议实现、服务器体系结构、客户端标头和实现(特别是 HTTP Keep-Alive 标头)以及用于启动的任何库并保持连接。


当然,许多外部因素也会影响连接,例如,移动浏览器在 WiFi 和蜂窝连接之间切换时更有可能暂时断开连接。


通常,除非您可以控制整个架构堆栈,否则没有单一的轮询持续时间。


使用长轮询时的注意事项


在您的应用程序中使用 HTTP 长轮询构建实时交互时,需要考虑几件事情,无论是在开发方面还是在操作/扩展方面。



  • 随着使用量的增长,您将如何编排实时后端?

  • 当移动设备在WiFi和蜂窝网络之间快速切换或失去连接,IP地址发生变化时,长轮询会自动重新建立连接吗?

  • 通过长轮询,您能否管理消息队列并如何处理丢失的消息?

  • 长轮询是否提供跨多个服务器的负载平衡或故障转移支持?


在为服务器推送构建具有 HTTP 长轮询的实时应用程序时,您必须开发自己的通信管理系统。这意味着您将负责更新、维护和扩展您的后端基础设施。


服务器性能和扩展


使用您的解决方案的每个客户端将至少每 5 分钟启动一次与您的服务器的连接,并且您的服务器将需要分配资源来管理该连接,直到它准备好满足客户端的请求。一旦完成,客户端将立即重新启动连接,这意味着实际上,服务器将需要能够永久分配其资源的一部分来为该客户端提供服务。当您的解决方案超出单个服务器的能力并且引入负载平衡时,您需要考虑会话状态——如何在服务器之间共享客户端状态?您如何应对连接不同 IP 地址的移动客户端?您如何处理潜在的拒绝服务攻击?


这些扩展挑战都不是 HTTP 长轮询独有的,但协议的设计可能会加剧这些挑战——例如,您如何区分多个客户端发出多个真正的连续请求和拒绝服务攻击?


消息排序和排队


在服务器向客户端发送数据和客户端发起轮询请求之间总会有一小段时间,数据可能会丢失。


服务器在此期间要发送给客户端的任何数据都需要缓存起来,并在下一次请求时传递给客户端。


HTTP 长轮询 MQ


然后出现几个明显的问题:



  • 服务器应该将数据缓存或排队多长时间?

  • 应该如何处理失败的客户端连接?

  • 服务器如何知道同一个客户端正在重新连接,而不是新客户端?

  • 如果重新连接花费了很长时间,客户端如何请求落在缓存窗口之外的数据?


所有这些问题都需要 HTTP 长轮询解决方案来回答。


设备和网络支持


如前所述,由于 HTTP 长轮询已经存在了很长时间,它在浏览器、服务器和其他网络基础设施(交换机、路由器、代理、防火墙)中几乎得到了无处不在的支持。这种级别的支持意味着长轮询是一种很好的后备机制,即使对于依赖更现代协议(如 WebSockets )的解决方案也是如此。


众所周知,WebSocket 实现,尤其是早期实现,在双重 NAT 和某些 HTTP 长轮询运行良

作者:demo007x
来源:juejin.cn/post/7240111396869161020
好的代理环境中挣扎。

收起阅读 »

10个让你爱不释手的一行Javascript代码

web
在这篇博客中,我们将分享 10+ 个实用的一行 JavaScript 代码,这些代码可以帮助你提高编码效率和代码简洁度。这些代码片段将涵盖各种用途,从操作数组和字符串,到更高级的概念,如异步编程和面向对象编程。 获取数组中的随机元素 使用 Math.rand...
继续阅读 »

freysteinn-g-jonsson-s94zCnADcUs-unsplash.jpg
在这篇博客中,我们将分享 10+ 个实用的一行 JavaScript 代码,这些代码可以帮助你提高编码效率和代码简洁度。这些代码片段将涵盖各种用途,从操作数组和字符串,到更高级的概念,如异步编程和面向对象编程。


获取数组中的随机元素


使用 Math.random() 函数和数组长度可以轻松获取数组中的随机元素:


const arr = [1, 2, 3, 4, 5];
const randomElement = arr[Math.floor(Math.random() * arr.length)];
console.log(randomElement);

数组扁平化


使用 reduce() 函数和 concat() 函数可以轻松实现数组扁平化:


const arr = [[1, 2], [3, 4], [5, 6]];
const flattenedArr = arr.reduce((acc, cur) => acc.concat(cur), []);
console.log(flattenedArr); // [1, 2, 3, 4, 5, 6]

对象数组根据某个属性值进行排序


const sortedArray = array.sort((a, b) => (a.property > b.property ? 1 : -1));

从数组中删除特定元素


const removedArray = array.filter((item) => item !== elementToRemove);

检查数组中是否存在重复项


const hasDuplicates = (array) => new Set(array).size !== array.length;

判断数组是否包含某个值


const hasValue = arr.includes(value);

首字母大写


const capitalized = str.charAt(0).toUpperCase() + str.slice(1);

获取随机整数


const randomInt = Math.floor(Math.random() * (max - min + 1)) + min;

获取随机字符串


const randomStr = Math.random().toString(36).substring(2, length);

使用解构和 rest 运算符交换变量的值:


let a = 1, b = 2
[b, a] = [a, b]
console.log(a, b) // 2, 1

将字符串转换为小驼峰式命名:


const str = 'hello world'
const camelCase = str.replace(/\s(.)/g, ($1) => $1.toUpperCase()).replace(/\s/g, '').replace(/^(.)/, ($1) => $1.toLowerCase())
console.log(camelCase) // "helloWorld"

计算两个日期之间的间隔


const diffInDays = (dateA, dateB) => Math.floor((dateB - dateA) / (1000 * 60 * 60 * 24));

查找日期位于一年中的第几天


const dayOfYear = (date) => Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);

复制内容到剪切板


const copyToClipboard = (text) => navigator.clipboard.writeText(text);

copyToClipboard("Hello World");

获取变量的类型


const getType = (variable) => Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();

getType(''); // string
getType(0); // number
getType(); // undefined
getType(null); // null
getType({}); // object
getType([]); // array
getType(0); // number
getType(() => {}); // function

检测对象是否为空


const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;



作者:shichuan

来源:juejin.cn/post/7230810119122190397

收起阅读 »

初学后端,如何做好表结构设计?

前言 最近有不少前端和测试转Go的朋友在私信我:如何做好表结构设计? 大家关心的问题阳哥必须整理出来,希望对大家有帮助。 先说结论 这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从...
继续阅读 »

前言


最近有不少前端和测试转Go的朋友在私信我:如何做好表结构设计?


大家关心的问题阳哥必须整理出来,希望对大家有帮助。


先说结论


这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。


收获最大的还是和大家的交流讨论,总结一下:

  1. 首先,一定要先搞清楚业务需求。比如我的例子中,如果不需要灵活设置,完全可以写到配置文件中,并不需要单独设计外键。主表中直接保存各种筛选标签名称(注意维护的问题,要考虑到数据一致性)
  2. 数据库表结构设计一定考虑数据量和并发量,我的例子中如果数据量小,可以适当做冗余设计,降低业务复杂度。

4个方面


设计数据库表结构需要考虑到以下4个方面:

  1. 数据库范式:通常情况下,我们希望表的数据符合某种范式,这可以保证数据的完整性和一致性。例如,第一范式要求表的每个属性都是原子性的,第二范式要求每个非主键属性完全依赖于主键,第三范式要求每个非主键属性不依赖于其他非主键属性。
  2. 实体关系模型(ER模型):我们需要先根据实际情况画出实体关系模型,然后再将其转化为数据库表结构。实体关系模型通常包括实体、属性、关系等要素,我们需要将它们转化为表的形式。
  3. 数据库性能:我们需要考虑到数据库的性能问题,包括表的大小、索引的使用、查询语句的优化等。
  4. 数据库安全:我们需要考虑到数据库的安全问题,包括表的权限、用户角色的设置等。

设计原则


在设计数据库表结构时,可以参考以下几个优雅的设计原则:

  1. 简单明了:表结构应该简单明了,避免过度复杂化。
  2. 一致性:表结构应该保持一致性,例如命名规范、数据类型等。
  3. 规范化:尽可能将表规范化,避免数据冗余和不一致性。
  4. 性能:表结构应该考虑到性能问题,例如使用适当的索引、避免全表扫描等。
  5. 安全:表结构应该考虑到安全问题,例如合理设置权限、避免SQL注入等。
  6. 扩展性:表结构应该具有一定的扩展性,例如预留字段、可扩展的关系等。

最后,需要提醒的是,优雅的数据库表结构需要在实践中不断迭代和优化,不断满足实际需求和新的挑战。



下面举个示例让大家更好的理解如何设计表结构,如何引入内存,有哪些优化思路:



问题描述



如上图所示,红框中的视频筛选标签,应该怎么设计数据库表结构?除了前台筛选,还想支持在管理后台灵活配置这些筛选标签。


这是一个很好的应用场景,大家可以先自己想一下。不要着急看我的方案。


需求分析

  1. 可以根据红框的标签筛选视频
  2. 其中综合标签比较特殊,和类型、地区、年份、演员等不一样
  • 综合是根据业务逻辑取值,并不需要入库
  • 类型、地区、年份、演员等需要入库

3.设计表结构时要考虑到:

  • 方便获取标签信息,方便把标签信息缓存处理
  • 方便根据标签筛选视频,方便我们写后续的业务逻辑

设计思路

  1. 综合标签可以写到配置文件中(或者写在前端),这些信息不需要灵活配置,所以不需要保存到数据库中
  2. 类型、地区、年份、演员都设计单独的表
  3. 视频表中设计标签表的外键,方便视频列表筛选取值
  4. 标签信息写入缓存,提高接口响应速度
  5. 类型、地区、年份、演员表也要支持对数据排序,方便后期管理维护

表结构设计


视频表


字段注释
id视频主键id
type_id类型表外键id
area_id地区表外键id
year_id年份外键id
actor_id演员外键id

其他和视频直接相关的字段(比如名称)我就省略不写了


类型表


字段注释
id类型主键id
name类型名称
sort排序字段

地区表


字段注释
id类型主键id
name类型名称
sort排序字段

年份表


字段注释
id类型主键id
name类型名称
sort排序字段

原以为年份字段不需要排序,要么是年份正序排列,要么是年份倒序排列,所以不需要sort字段。


仔细看了看需求,还有“10年代”还是需要灵活配置的呀~


演员表


字段注释
id类型主键id
name类型名称
sort排序字段

表结构设计完了,别忘了缓存


缓存策略


首先这些不会频繁更新的筛选条件建议使用缓存:


  1. 比较常用的就是redis缓存
  2. 再进阶一点,如果你使用docker,可以把这些配置信息写入docker容器所在物理机的内存中,而不用请求其他节点的redis,进一步降低网络传输带来的耗时损耗
  3. 筛选条件这类配置信息,客户端和服务端可以约定一个更新缓存的机制,客户端直接缓存配置信息,进一步提高性能

列表数据自动缓存


目前很多框架都是支持自动缓存处理的,比如goframe和go-zero


goframe


可以使用ORM链式操作-查询缓存


示例代码:


package main

import (
"time"

"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)

func main() {
var (
db = g.DB()
ctx = gctx.New()
)

// 开启调试模式,以便于记录所有执行的SQL
db.SetDebug(true)

// 写入测试数据
_, err := g.Model("user").Ctx(ctx).Data(g.Map{
"name": "xxx",
"site": "https://xxx.org",
}).Insert()

// 执行2次查询并将查询结果缓存1小时,并可执行缓存名称(可选)
for i := 0; i < 2; i++ {
r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "vip-user",
Force: false,
}).Where("uid", 1).One()
g.Log().Debug(ctx, r.Map())
}

// 执行更新操作,并清理指定名称的查询缓存
_, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: -1,
Name: "vip-user",
Force: false,
}).Data(gdb.Map{"name": "smith"}).Where("uid", 1).Update()
if err != nil {
g.Log().Fatal(ctx, err)
}

// 再次执行查询,启用查询缓存特性
r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "vip-user",
Force: false,
}).Where("uid", 1).One()
g.Log().Debug(ctx, r.Map())
}

go-zero


官方都做了详细的介绍,不作为本文的重点。


讨论


我的方案也在我的技术交流群里引起了大家的讨论,也和大家分享一下:


Q1 冗余设计和一致性问题



提问: 一个表里做了这么多外键,如果我要查各自的名称,势必要关联4张表,对于这种存在多外键关联的这种表,要不要做冗余呢(直接在主表里冗余各自的名称字段)?要是保证一致性的话,就势必会影响性能,如果做冗余的话,又无法保证一致性



回答:


你看文章的上下文应该知道,文章想解决的是视频列表筛选问题。


你提到的这个场景是在视频详情信息中,如果要展示这些外键的名称怎么设计更好。


我的建议是这样的:

  1. 根据需求可以做适当冗余,比如你的主表信息量不大,配置信息修改后同步修改冗余字段的成本并不高。
  2. 或者像我文章中写的不做冗余设计,但是会把外键信息缓存,业务查询从缓存中取值。
  3. 或者将视频详情的查询结果整体进行缓存

还是看具体需求,如果这些筛选信息不变化或者不需要手工管理,甚至不需要设计表,直接写死在代码的配置文件中也可以。进一步降低DB压力,提高性能。


Q2 why设计外键?



提问:为什么要设计外键关联?直接写到视频表中不就行了?这么设计的意义在哪里?



回答:

  1. 关键问题是想解决管理后台灵活配置
  2. 如果没有这个需求,我们可以直接把筛选条件以配置文件的方式写死在程序中,降低复杂度。
  3. 站在我的角度:这个功能的筛选条件变化并不会很大,所以很懂你的意思。也建议像我2.中的方案去做,去和产品经理拉扯喽~

总结


这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。


收获最大的还是和大家的交流讨论,总结一下:

  1. 首先,一定要先搞清楚业务需求。比如我的例子中,如果不需要灵活设置,完全可以写到配置文件中,并不需要单独设计外键。主表中直接保存各种筛选标签名称(注意维护的问题,要考虑到数据一致性)
  2. 数据库表结构设计一定考虑数据量和并发量,我的例子中如果数据量小,可以适当做冗余设计,降低业务复杂度

作者:王中阳Go
来源:juejin.cn/post/7212828749128876092


收起阅读 »

写给焦虑,迷茫的前端人的思考

前言 现在好多人说程序员的红利时代已经过去 裁员的企业比比皆是 简历投出去基本没人理,2,3个月能找到工作就不错了,而且还是降薪 有重点学校毕业的也只能找到外包的工作 千万别学计算机相关专业,无异于50年加入国军 车贷,房贷都要还,降薪我也接受 等等上面现...
继续阅读 »

前言


现在好多人说程序员的红利时代已经过去



  • 裁员的企业比比皆是

  • 简历投出去基本没人理,2,3个月能找到工作就不错了,而且还是降薪

  • 有重点学校毕业的也只能找到外包的工作

  • 千万别学计算机相关专业,无异于50年加入国军

  • 车贷,房贷都要还,降薪我也接受


等等上面现象都是我或听说或看到的,或亲身经历的。


现在行业不景气,也不知道是否有恢复生机的一天,或者也许一直会走下坡路,也触动着你我的神经。


真实经历


去年年底的时候我为我们公司面试员工,通过率变得很低,所以面试难度要相对加大,有一次面试一个女生,她技术我觉得一般,感觉没怎么准备,八股文也没有背熟,所以我给了她一些建议,然后我问了一下她从上一个公司的离职原因,她说是被裁掉了,然后说现在找工作很困难,已经找了很长时间了,上家公司工资16,现在要12,13好多公司都还在压价,她说主要现在有车贷,房贷,还有小孩,所以迫不得已得赶紧找到下一家,所以薪资她一降再降,只希望能快点找到工作,等等,说了很多。


非常同情她的遭遇,但是遇到现在行业不景气,而且技术也一般,所以这就很难受,感觉她的焦虑要比平常人大很多,因为还有车贷,房贷,小孩的原因在里面。说到这里屏幕前的你是否有些焦虑呢?


焦虑由来


如果我们踏入程序员这一行业,那么就意味着终生学习。如果平时喜欢躺平,喜欢摸鱼,那么随着年龄的增大,或者行业不景气,焦虑就会不断找上你。用大多数的人来说,'这个行业就是吃青春饭的',可能在行情好的时候,有些人感觉不出来,觉得自己没有问题的,能干到40岁,但是行情下滑,失业率加大的时候,可能这个问题又会被放大。但是焦虑一定就是坏事么?


什么是焦虑?


引用百度百科里的解释:



焦虑是人对现实或未来事物的价值特性出现严重恶化趋势所产生的情感反映 。与之相反的情感形式是企盼,即企盼是人对现实或未来事物的价值特性出现明显利好趋势所产生的情感反映。


焦虑是指个人对即将来临的、可能会造成的危险或威胁所产生的紧张、不安、忧虑、烦恼等不愉快的复杂情绪状态。



所以我的意思还是希望大家能够主动拥抱变化,主动改变自己,行动起来,那么问题又来了,我们努力的方向在哪里?努力是有方法的,努力要选择好努力的方向,否则辛苦付出了,收获并不会很大。所以说到这里你是否又开始迷茫了?


你是否有这种迷茫?


在技术的学习中大家应该有过下面这种现象,有的同学感觉付出了很多努力,但是收获很少。尤其进阶阶段,研究源码阶段,精通阶段等等,付出了大量时间,但是收效甚微,有时候我们就会产生这样的想法,我选择的这条道路对么?为什么感觉自己还是很菜?我花这么多时间研究源码干什么?


于是这个时候你开始变得焦虑,迷茫?想想工作1,2年的程序员2周就能学会vue并上手了,而你花了相同的时间看了《javascript高级程序设计》这本书还不到20页, oh my god, 这样下去会不会下一个被裁的就是我


这个时候在我脑子里浮现了一个问题'你说工作7,8年的程序员相对于工作两三年的程序员优势在哪里呢?'


如何选择好以后的道路


针对程序员该如何选择自己的道路,我采访过一些工作比较久的程序员。


我的问题是这样的


'你说工作7,8年的程序员相对于工作2,3年的程序员优势在哪里呢?'


下面是一些回答



有的人觉得优势是经验,包括成本预估、架构策略、性能评估、风险评估等, 但是除了这些也不得不承认其他基本都是劣势




有的人觉得工作5年的程序员应该达到技术的至高点,根据统计学规律,程序员以后主要往两条路发展,一种是系统架构师,一种是业务负责人,要早做规划




还有的人说找准目标,缺啥补啥,当然目前是根据环境动态调整的,先把自己能把控好的事做好,大环境不好就降低目标,也能把更多精力放到其他上面




还有的人给出了更加详细的解答



    1、更多关注全局架构的精力超过专注细节的精力
工作早期,接到的任务大多是单点的功能模块和系统,更多关注编码、技术实现问题。
工作7、8年会,会逐渐承担整块的业务,进而会从业务的整体角度来思考系统的架构,目标从“实现某个功能”转变到“支撑某个业务”,把业务当作整体来看,视角逐渐从“语言、框架、数据库”转换到“通信、架构、安全合规、网络、数据”。

2、意识到对技术的表达能力很重要
这不是指为人处事的沟通表达能力,而是对技术的表达能力;
例如更加关注流程图、架构图,不再排斥ppt、文档,会更多思考怎么更加准确、简洁的描述出你的系统架构、业务流程,能够让合作方、上下游更加准确的理解你的思路和设计,更加高效的合作,避免出现理解偏差。
例如编码,早起很喜欢炫技,喜欢各种高大上的编码风格,例如各种函数式语法,工作久了就不再喜欢追求这些,更加看重你的代码是否能够很容易理解,是否足够健壮,能够被测试到,特殊逻辑能否充分注释;

3、不再纠结于单点的性能提升
新人很容易陷入性能崇拜,过分关注有限的性能优化,为了有限的性能提升,直接在db层面写复杂的sql直接处理,导致代码可读性变差。
后来会更加关注有限在符合基础性能的前提下,优先满足业务的诉求,再根据业务的特性、预判一段时间内的发展,适当的在性能和可读性、可复用性上做一定的取舍。

4、更加追求简单的架构、代码
好的代码、架构一定是简洁的,如果一段代码、设计很多人不容易理解,那么这样的设计迟早会出问题。
给你的上下游以简洁、清晰的接口、文档和功能,避免给到用户各种条件选择,避免过度原子化拆分,在可拓展性、易用性方面需要做取舍,

在这里非常感谢以上各位的回答,相信上面的回答能帮助大家或多或少的解决努力方向的问题


just do it


首先说明一点,没有人不迷茫的,大家都第一次做人,怎么会知道接下来每一步该怎么走呢?


有一个人十分崇拜杨绛。高中快毕业的时候,他给杨绛写了一封长信,表达了自己对他的仰慕之情以及自己的一些人生困惑。


杨绛回信了,淡黄色的竖排红格信纸,毛笔字。除了寒暄和一些鼓励晚辈的句子外,杨绛的信里其实只写了一句话,诚恳而不客气:


“你的问题主要在于读书不多而想得太多”。


参考


收起阅读 »

低代码的那些事

web
在当今数字化的时代,前端开发已成为构建出色用户体验的重要领域。然而,传统的前端开发过程往往需要耗费大量时间和精力,对于那些没有技术背景或时间有限的人来说,这无疑是一个巨大的挑战。然而,随着技术的不断进步,低代码开发正迅速崛起,为我们提供了一种简化开发流程的全新...
继续阅读 »

在当今数字化的时代,前端开发已成为构建出色用户体验的重要领域。然而,传统的前端开发过程往往需要耗费大量时间和精力,对于那些没有技术背景或时间有限的人来说,这无疑是一个巨大的挑战。然而,随着技术的不断进步,低代码开发正迅速崛起,为我们提供了一种简化开发流程的全新方法。


终端概念


终端 = 前端 + 客户端


在讲低代码之前,我们先来聊一聊终端概念,这个终端不是指敲命令行的小窗口,而是 终端 = 前端 + 客户端。乍一听,这个不是和大前端的概念类似吗?为什么又提出一个重复的名词,实际上它俩还是有很大区别的,在大前端里面,岗位是有不同区分的,比如前端开发工程师、客户端开发工程师,每个岗位的分工是不一样的,但是你可以把终端看成一个岗位。


image.png


下面是阿里巴巴终端开发工程师招聘的 JD,因为内容较长,我将他分成了三张图片,我们从上到下依次看。


第一张图片:
2024届实习生的招聘,招聘岗位为终端开发工程师





第二张图片:
这是他们对终端开发工程师的描述,大家主要看标了特殊颜色的字体就行



它包括原有的“前端工程师”和“移动端工程师” 相较过去,我们强调面向最终的用户进行交付,不局限于“前端〞、“移动端〞,这将显著拓宽工程师的职责、 能力边界。






第三张图片:
这是他们对终端开发工程师的岗位要求,可以从要求的第1、2、3项看到,这个岗位更侧重于基础技术、终端界面,而不是在于要求你会使用某某框架。





大家对终端概念有了一定了解之后,那么这个终端概念是谁提出的呢?没错,就是阿里巴巴。

阿里巴巴公众号改名史


这个公众号可能会有一些朋友之前关注过,它会发布前端和客户端相关的文章,但是之前的名字并不叫阿里巴巴终端技术。


image.png


我们来看看他的改名史:



  • 2019年05月10日注册 "Alibaba FED"(FED:Front-end Developer 前端开发者)

  • 2019年06月12日 "Alibaba FED" 认证 Alibaba F2E"(F2E:Front-end Engineer 前端工程师)

  • 2022年07月08日 "Alibaba F2E" 帐号迁移改名"阿里巴巴终端技术"


所以是从此又多了一个终端开发工程师的岗位吗,显然不是的,终端开发工程师最终是要取代前端开发工程师和客户端开发工程师的,最终的目的是达到降本增效。


那如何让前端开发工程师和客户端开发工程师过渡成为终端开发工程师。


终端走向荧幕


在阿里 2022 年举办的第 17 届 D2 终端技术大会上,当然他们是这一届将大会名字改成了终端,其中有一篇卓越工程的主题简介如下:


image.png



在过去十年,不管是前端的工具链还是客户端的版本交付效能等都在快速演进,面向未来,我们升级工程体系走向终端工程一体化,覆盖前端及客户端工程师从研发交付到运维的全生命周期,利用低代码、极速构建、全链路运维、Serverless 等新型的工程技术,在卓越工程标准推动下引领终端工程师走向卓越。



可以看到,低代码是可以作为实践终端的一种技术方案,并且将其放在了第一位,那么什么是低代码,低代码能做什么事情,为什么使用低代码可以让终端开发工程师变的更加卓越?低代码对我们普通的一线开发又能带来什么改变或者赋能?
好,接下来,我们就来聊一聊低代码。


什么是低代码


Low-Code


低代码来源于英语翻译——Low-Code,当然,此“Low”非彼“Low”,它意指一种快速开发的方式,使用最少的代码、以最快的速度来交付应用程序。


低代码的定义是什么


虽然低代码已经是个老生常谈的话题了,但关于它的定义我觉得还是有必要描述一遍(来自ChatGPT):


低代码是一种软件开发方法,旨在通过最小化手动编码的工作量,利用可视化工具和组件来快速构建应用程序。它提供了一个图形化的界面,使开发者能够以图形化方式设计和创建应用程序的用户界面、业务逻辑和数据模型,而无需编写大量的传统代码。


低代码它作为一种软件的开发方法,他不仅仅可以作为终端的解决方案,也可以在后端、IOT、大数据等领域上进行使用,并且这些领域也都有现成的低代码开源工具或者平台。


传统前端开发 VS 低代码开发


传统的前端开发方式,我们需要使用 HTML + CSS 描绘出页面的骨架,再使用 JAVASCRIPT 完成页面的功能逻辑。


image.png


可以看到图片上,我们定义了三个 div 元素,并且给每个 div 元素加上了背景颜色,并且加上了点击事件。每一块的元素,都需要我们使用相应的代码去描述。页面越复杂,描述的代码量就会越多。页面的代码量越多,相应的维护的成本就会越高。


我们在来看下如何使用低代码进行开发:


Untitled2.png


左侧物料区,中间画布区,右侧物料配置区,这种区域划分也是比较常见的低代码平台布局。选择物料以后,将物料拖进画布区,接下来我们就可以对物料进行属性配置。


相较于故枯燥难懂的代码,直观的拖拉拽显得更加简单,也更加容易理解。


背后的原理是什么


通过简单的托拉拽后我们可以看到一份表格页面,那么页面实际上是如何生成的呢?


背后实际对应的一份 Schema 描述,主要由物料描述和布局描述组成。


10921685966317_.pic.jpg


我们从左到右依次看,componentsMap 记录了我们当前页面使用的组件,可以看到我们使用了Form.Item、Input、Table,以及这些组件来自的 package 包信息。


componentsTree 里面记录了整个页面布局的信息,最外层由Page组件包裹,然后依次是 Form.Item组件,label 标签为姓名,里面还有一个 input 作为子元素,后面还有两个 Form.Item,为年龄和地址,最后的元素是 Table 组件,通过这些信息,我们就可以布局出一份简单的表格页面。


componentsMap 描述了我们当前页面所需的物料,componentsTree 描述了我们当前页面的布局顺序,将这两份数据组合,通过特定的解析器,就可以得到一份页面。低代码的页面渲染是通过我们事先约定好的数据结构进行生成的。


Schema ⇒ 页面,会不会使我的页面加载变慢


可能会有一些同学心中会有疑问,通过 Schema 生成页面,应该是需要一层 runtime 层吧,通过一段运行时的代码,将 Schema 转换为页面。


那在将 Schema 的内容转换为页面的时候,难免会产生性能上的损耗吧?


性能 VS 可维护性


这里就涉及到了性能 和 可维护性的取舍了,平台的意义在于为你掩盖底层代码的操作。让你用更直观的方式来描述你的目的,其实这里可以牵扯出另外一个相似的话题。


真实DOM VS 虚拟DOM


10931685966489_.pic.jpg


现代化的前端框架都会使用虚拟 DOM,那大家觉得真实DOM更快还是虚拟DOM更快?


框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。


没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。


针对任何一处基准,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。


你会发现低代码的 Schema 和 虚拟 DOM 是比较相似的,都是通过对象的形式去描述 DOM 节点,
虚拟 DOM 的另外一个优势,是在于实现跨端,将底层对 DOM 操作的 API 更换为对 Android 或者 IOS 的 UI 操作。同理低代码的 Schema 也可以比较好的实现跨端,思路基本是一致的,Schema 只是一份组件 + 页面的描述,你可以根据不同的场景,进行不同平台的组件的渲染。


有没有办法可以优化页面渲染的性能吗?


10941685966667_.pic.jpg


那么有没有解决方案呢?


是有的,我们可以通过 Schema 的方式对页面进行出码,出码后是一套完整的应用工程,基于应用工程在去对应用进行一次构建,将这部分负担转移到编译时去完成。


生成的应用工程和我们平常开发的应用工程基本相似:


10951685966768_.pic.jpg


什么是低代码?


如果之前你没有听过低代码,到这里你还是没有明白低代码是什么。没关系,你可以暂时的把他理解一个可视化编辑器,通过拖拽元素就能生成页面,页面背后实际对应的是一串 Schema JSON 数据。到最后,我们会重新对低代码进行一个定义。


低代码发展趋势


低代码发展时间线


image.png


我们来看下低代码发展的时间线:



  • 1980年代:出现了第四代编程语言(ABAP, Unix Shell, SQL, PL/SQL, Oracle Reports, R)第四代编程语言指的是非过程的高级规范语言,包括支持数据库管理、报告生成、数学优化、图形用户界面(GUI)开发和 web 开发。

  • 2000年:出现了 VPL 语言(即 visual programming language 可视化变成语言)

  • 2014年:提出了低代码 / 零代码概念

  • 2016年:国内独立的低代码开发平台开始相继发布

  • 2021年:中国市场逐渐形成完整的低代码、无代码生态体系


发展趋势


image.png


这是 OSS Insight 上关于低代码的一组统计数据,他们分析了50亿的 Github event数据,得出了这些报告,我从里面摘选了和低代码相关的部分。


首先,在2022年热门话题的开源存储库活跃度,LowCode 以76.3%活跃度位居第一,其次是Web3、Github Actions、Database、AI,可见大部分低代码开源仓库都处于一个开发或者维护的状态。


image.png


我们在来看下,低代码发展的趋势图,从2016年到2020年,低代码整体处于上升趋势,并且新增仓库在2020年达到最高点,新增低代码相关的仓库高达了300%以上。


在2020年野蛮生长后,2021年的新增仓库趋近于0,但是在2021低代码相关仓库的start数量将近增长了200%,2022年的数据开始趋于平缓,并且新增仓库数量减少,标志着低代码技术沉淀趋于平稳,百花齐放的时代已经过去。


没有规矩,不成方圆


百花齐放下其实是关于低代码标准的缺失,每套低代码平台都有自己的行为标准,各个平台之间的物料是有隔阂的,无法通用的。就有点像现在百花齐放的小程序,比如微信小程序、支付宝小程序、百度小程序等等,从一定程度上来讲,标准的缺失,会给用户和开发者带来了一定的困扰。


如果有行业组织或者技术社区可以积极推动低代码标准化的倡议,制定统一的行为标准和规范,标准物料的
定义,那么对于低代码的未来发展是一件非常有利的事情。


低代码产品矩阵


我们看来下国外的低代码产品矩阵,种类和平台还是非常多的。
10981685967331_.pic.jpg


可以看到关于低代码的落地场景其实有非常多,并且已经有了大量成熟应用流入市场,所以低代码作为一种开发软件的方法,可以将其灵活运用在各个场景。


而且每个场景的低代码,在其下还可以细分领域,比如 Web 应用程序开发,可以细分为中后台管理搭建、活动推广页面、图表大盘页面等。


一款低代码平台,通常都会有它自己的定位,是为了解决某种特定领域下的特定业务而生。所以一个公司内部有十几个低代码平台是很正常的,他们在细分下的业务场景有不同的分工。


我们正在做什么


这一章节分为三个小块来讲,为什么要做低代码、怎么去做低代码、现在做的怎么样了


为什么要做低代码


我们为什么要做低代码,低代码其实能解决的问题和场景有非常多,那么低代码能帮我们研发团队解决哪些问题?


1.由繁去简


image.png


通常一个需求下来,



  1. 产品会先对需求进行规划,产出一份原型图交付给 UI 设计

  2. UI 通过产品提供的原型图,对原型进行美化,产出一份设计稿

  3. 前端对照设计稿进行页面开发,交付高保真的页面

  4. 最后进行接口联调,将静态的数据更改为接口获取的数据


做程序本质上也是在做交流,假设我们现在是一位前端或者客户端的开发工程师,



  1. 我们需要先和产品 battle 原型图

  2. 和 UI 讨论设计稿

  3. 交付高保真的页面

  4. 和后端进行接口联调


可以看到绝大部分的时间都花在了如何去做页面上,在加上关于各个环节对页面的讨论和修改,这中间会产生大量的浸没成本。


如果说,现在有一个工具,可以做到产品交付的就是高保真页面,你会选择用还是不用?


image.png


这个是使用低代码后的开发流程,由产品直接生成高保真页面交付给前端,极大提高了开发生产力。那么,这个时候前端可以更加聚焦于业务逻辑,聚焦于工程体系,而不是页面本身。


2. 我不想在去改样式了


好像所有的产品经理都喜欢在项目即将上线前,对页面的样式进行调整,没错,既不是测试阶段,也不是预发阶段,而是即将发布前,改完一个,在改一个,好像总是也改不完。


而使用低代码平台后,将页面生成的权利递到产品经理手中,他们可以随心所欲的修改,尽情的展示自己的创造力,我们也不在需要反复的修改样式,反复的重新构建应用发布,你可以专心的去做其它事情。


3. 真正的所见即所得


真正的所见即所得,相比于黑盒子的代码,低代码平台显得更加直观,操作的是页面,而不是代码,你可以在平台上尽情的组装,就像是搭积木一样。


怎么去做低代码


image.png


在能够协调足够多的资源的情况下,选择自研是比较好的一条路,因为一切都是可控的。


但是在资源有限的情况下,选择开源或许是一种最快最便捷的方法了。我们在低代码发展趋势中,可以发现低代码平台和开源技术已经趋于稳定。


使用稳定的开源框架可以更快的帮助我们创建低代码平台,并且有足够多懂低代码底层的人,去帮助我们维护基础设施,站在巨人的肩膀上出发,往往会事半功倍。


我们选择的是阿里开源的 lowcode-engine,在其基础上进行二次开发,选择它的理由有很多:


10991685967710_.pic.jpg


现在做的怎么样了


下面是平台的真实演示,目前已经支持开发以及发布预览了。
_d.gif


低代码架构图:
image.png


平台使用流程的步骤图:


image.png



  • 第一步是创建应用

  • 第二步是创建页面,当然一个应用下面可能会有多个页面,每个页面都会是相互独立的,

  • 第三步是布局调整,可以在选中的页面进行拖拽物料进行布局

  • 第四步是属性配置,配置物料的属性,配置物料的事件或者样式,提供自定义样式和逻辑的功能支持

  • 第五步是保存发布,将当前各个页面的schema进行保存上传,存储到数据库

  • 第六步是页面渲染,可以直接通过平台生成的页面地址,打开页面


被误解的低代码


我相信是会有一大部分的程序员从内心抵制低代码的,一方面,作为一个技术工种,对自己的技术是有底气的,有傲骨的,人为写的代码都不怎么样,还指望低代码平台上的代码吗,另一方面,在低代码平台的代码上维护,那简直就是在屎山上维护,维护的成本会更大吧


出码 VS 不出码


这里的痛点是在于需不需要维护低代码产出的代码,前面我们讲到过出码,出码可以用于产物构建。但构建这一块,是平台去做的,用户并不会感知到背后实际的应用工程。


出码同时也可以用于用户的自定义需求,如果平台的物料完全覆盖了你的业务场景,你是不需要去出码的。但是,如果平台的物料无法满足你的业务场景,你需要的组件又具备足够的特殊性,这个时候你可能需要使用出码功能,在出码的应用工程基础下,添加自己的业务代码,那么这个时候,你是需要维护整个应用工程的。


对低代码的分歧往往是这个时候产生的,每个人心中都有自己的标准代码,都会本能的去抵触和自己标准不同的代码。


不出码如何自定义开发需求?


那有没有既不出码,又可以满足自定义开发的需求呢?


因为我们更多的是希望平台去维护工程,而不是通过人为方式去维护。像我们平时开发页面,本质上就是在写组件,通过拼装组件,形成页面。


我们的思想上可以做个转变,既然 80%~90% 的工作平台可以承接,剩余的平台无法实现,可以自己实现自定义组件进行发布,发布的组件可以沉淀到市场,你的其它项目可以使用自己的业务组件,其他同事也可以使用你的组件。


低代码会不会导致前端岗位变少?


其实完全可以将低代码看成提升工作效率的一个工具,低代码解决页面视图,页面逻辑和数据联调需要开发者在平台上进行完成,开发者的关注点更多的聚焦于业务逻辑,聚焦于如何去做好工程化体系。


AI Code 不是更好吗?


那好像 AI Code 也可以做到低代码能做的地步?


在今年3月份 GPT-4 的发布会上,只需要在草稿本上用纸笔画出一个非常粗糙的草图,再拍照告诉GPT-4需要一个这样的网站,AI 就可以在10秒钟左右根据草图,生成一个网站完整的前端 HTML 代码。


GPT-4发布:一张草图,一秒生成网站


image.png


这简直就是低代码 plus,回归我第一次使用 GPT 的时候,我确实是被惊讶到,特别是他能衔接上下文关系,给出你想要的答案。


我们开发应用,其实本身就是一个庞大的上下文,版本一直迭代,需求一直新增,通过人本身去记住业务上下文,在一个足够复杂的应用下,他的上下文会足够大,足够的冗余,我们去抽离组件,抽离函数,使用数据结构优化代码,实际上就是在优化上下文,写代码并不难,难的是如何梳理页面的组件和那些难以理解的业务以及那些人与人的沟通。


至少现在看来,GPT 无法做到承接复杂应用的上下文,现在的他只能帮助你快速产出一个 demo 应用,前提你需要做到甄别代码的能力,以及还需要面临后续版本迭代更新的窘境问题。


或者说,如果 AI 真的能做到独立开发复杂应用,程序员就真的会被取代吗,做程序本身就是一个相对复杂的活,需要持续学习,持续精进。如果AI真的能做到独立开发这一步,那我觉得离真正的无人驾驶也不远了,出租车司机全部都得失业,因为做一个程序相比于驾驶车辆,会难上不少,当然还包括其它行业,80% 以上的职业都极有可能面临下岗危机。


这个是政客、政府不允许的,虽然科技进步是好事,但是 AI 并没有带来实际的增量工作岗位,反而导致失业率变高,失业率若变高,整体社会的稳定性结构就会变差。
所以,我们更多的将 AI 看成工具,关注点在于,如何用 AI 去做成更多的事情。


什么是低代码?


讲到这里,基本上今天的分享就已经进入尾声了,最后我们在来确定下什么是低代码?



低代码是一种软件开发方法,旨在通过最小化手动编码的工作量,利用可视化工具和组件来快速构建应用程序。它提供了一个图形化的界面,使开发者能够以图形化方式设计和创建应用程序的用户界面、业务逻辑和数据模型,而无需编写大量的传统代码。



一千个人眼中,有一千个哈姆雷特,每个人对低代码的理解都会有些许不同,首先低代码是一种软件开发的方法,这套方法可以用在很多场景。如果一个平台提供了可视化的工具和组件并且又提供部分手动编码的能力,它就可以是一个低代码平台。


在前端低代码的方案中,并不是不再使用 HTML、CSS、JAVASCRIPT 进行开发,而是大大减少他们的使用频率,通过少量的代码,就可以完成一个页面的开发。


参考


收起阅读 »

新时代,你需要了解一下苹果的 VisionOS 系统

这是一个全新的平台。熟悉的框架和工具。请准备好为 Apple vision Pro 设计和构建全新的应用程序和游戏世界。 沉浸的光谱。 Apple vision Pro 提供无限的空间画布供您探索、试验和玩耍,让您自由地完全重新思考您的 3D 体验。人们可以在...
继续阅读 »

这是一个全新的平台。熟悉的框架和工具。请准备好为 Apple vision Pro 设计和构建全新的应用程序和游戏世界。


沉浸的光谱。


Apple vision Pro 提供无限的空间画布供您探索、试验和玩耍,让您自由地完全重新思考您的 3D 体验。人们可以在与周围环境保持联系的同时与您的应用互动,或者完全沉浸在您创造的世界中。您的体验可以是流畅的:从一个窗口开始,引入 3D 内容,过渡到完全身临其境的场景,然后马上回来。


选择权在您手中,这一切都始于 visionOS 上的空间计算构建块。


figure_2x.webp


窗口(Windows)


您可以在 visionOS 应用程序中创建一个或多个窗口。它们使用 SwiftUI 构建,包含传统视图和控件,您可以通过添加 3D 内容来增加体验的深度。


体积(Volumes)


使用 3D 体积为您的应用添加深度。 Volumes 是一种 SwiftUI 场景,可以使用 RealityKit 或 Unity 展示 3D 内容,从而创建可从共享空间或应用程序的完整空间中的任何角度观看的体验。


空间(Space)


默认情况下,应用程序启动到共享空间,在那里它们并排存在——很像 Mac 桌面上的多个应用程序。应用程序可以使用窗口和音量来显示内容,用户可以将这些元素重新放置在他们喜欢的任何位置。为了获得更身临其境的体验,应用程序可以打开一个专用的完整空间,其中只会显示该应用程序的内容。在完整空间内,应用程序可以使用窗口和体积、创建无限的 3D 内容、​​打开通往不同世界的门户,甚至可以让某人完全沉浸在某个环境中。




Apple 框架 - 扩展空间计算


SwiftUI


无论您是要创建窗口、体积还是空间体验,SwiftUI 都是构建新的 visionOS 应用程序或将现有 iPadOS 或 iOS 应用程序引入该平台的最佳方式。凭借全新的 3D 功能以及对深度、手势、效果和沉浸式场景类型的支持,SwiftUI 可以帮助您为 Vision Pro 构建精美且引人入胜的应用程序。 RealityKit 还与 SwiftUI 深度集成,以帮助您构建清晰、响应迅速且立体的界面。 SwiftUI 还可以与 UIKit 无缝协作,帮助您构建适用于 visionOS 的应用程序。


RealityKit


使用 Apple 的 3D 渲染引擎 RealityKit 在您的应用程序中呈现 3D 内容、​​动画和视觉效果。 RealityKit 可以自动调整物理光照条件并投射阴影、打开通往不同世界的门户、构建令人惊叹的视觉效果等等。为了创作您的材料,RealityKit 采用了 MaterialX,这是一种用于指定表面和几何着色器的开放标准,由领先的电影、视觉效果、娱乐和游戏公司使用。


ARKit


在 vision Pro 上,ARKit 可以完全了解一个人的周围环境,为您的应用提供与周围空间交互的新方式。默认情况下,ARKit 支持内核系统功能,您的应用程序在共享空间中时会自动受益于这些功能——但是当您的应用程序移动到完整空间并请求许可时,您可以利用强大的 ARKit API,例如平面估计、场景重建、图像锚点、世界轨道和骨骼手部轨道。所以在墙上泼水。从地板上弹起一个球。通过将现实世界与您的内容融合在一起,打造令人惊叹的体验。


Accessibility


visionOS 的设计考虑了可访问性,适用于希望完全通过眼睛、声音或两者的组合与设备交互的人。对于喜欢以不同方式导航内容的人,Pointer Control 允许他们选择食指、手腕或头部作为替代指针。您可以使用已在其他 Apple 平台上使用的相同技术和工具为 visionOS 创建易于访问的应用程序,并帮助使 vision Pro 成为每个人的绝佳体验。




您需要的所有工具。


Xcode


visionOS 的开发从 Xcode 开始,其中包括 visionOS SDK。将 visionOS 目标添加到您现有的项目或构建一个全新的应用程序。在 Xcode 预览中迭代您的应用程序。在全新的 visionOS Simulator 中与您的应用程序交互,探索各种房间布局和照明条件。创建测试和可视化以探索空间内容的碰撞、遮挡和场景理解。


reality composer Pro


探索全新的 reality composer Pro,旨在让您轻松预览和准备 visionOS 应用程序的 3D 内容。随 Xcode 一起提供的 reality composer Pro 可以帮助您导入和组织资产,例如 3D 模型、材料和声音。最重要的是,它与 Xcode 构建过程紧密集成以预览和优化您的 visionOS 资产。


Unity


现在,您可以使用 Unity 强大、熟悉的创作工具来创建新的应用程序和游戏,或者为 visionOS 重新构想现有的 Unity 创建的项目。除了熟悉的 Unity 功能(如 AR foundation)之外,您的应用程序还可以获得 visionOS 的所有优势,例如直通和动态注视点渲染。通过将 Unity 的创作和模拟功能与 RealityKit 管理的应用程序渲染相结合,使用 Unity 创建的内容在 visionOS 上看起来和感觉起来就像在家里一样。




您的 visionOS 之旅从这里开始。


visionOS SDK 本月晚些时候与 Xcode、visionOS 模拟器、reality composer Pro、文档、示例代码、设计指南等一起发布。


为 visionOS 做准备


无论您已经在 App Store 上拥有应用程序,还是这是您第一次为 Apple 平台开发应用程序,您现在都可以做很多事情来为 visionOS SDK 的到来做好准备。了解如何更新您的应用程序并探索现有框架,让您更轻松地开始使用 visionOS。


Prepare for visionOS


了解 visionOS


visionOS 拥有一流的框架和工具,是帮助您创造令人难以置信的空间体验的完美平台。无论您是在构想游戏、构建媒体体验、设计与 SharePlay 的连接和协作时刻、创建业务应用程序,还是更新您的网站以支持 visionOS,我们都有会议和信息来帮助您制定计划。为第 46 场 WWDC23 会议准备好 visionOS SDK,以帮助您了解平台开发、空间体验设计以及测试和工具。


Learn about visionOS


与苹果合作


在为 visionOS 开发应用程序和游戏时,获得 Apple 的直接支持。了解即将举行的活动、测试机会和其他计划,以支持您为此平台创造令人难以置信的体验。


Learn about working with Apple


#visionOS #苹果MR #苹果VR #苹果AR



翻译原文地址

收起阅读 »

环信十周年趴——程序如人生,常历常新

    “人之生也,与忧患俱来,知其无可奈何,而安之若命”。    二零一五年,正是移动互联网产业井喷爆发的中期。在那个毕业季的夏天,无数走出校园的学子摩拳擦掌,准备在当时遍地黄金的互联网世界中大展宏...
继续阅读 »

    “人之生也,与忧患俱来,知其无可奈何,而安之若命”。


    二零一五年,正是移动互联网产业井喷爆发的中期。在那个毕业季的夏天,无数走出校园的学子摩拳擦掌,准备在当时遍地黄金的互联网世界中大展宏图,发挥自己的价值。作为百万毕业大军中的一员,我参加了培训班的培训,加上之前在学校时移动互联网课程的专业培训的积累,幸运的找到了一家P2P金融公司,担任iOS开发工程师岗位。

    这是一家初创公司,老板非常年轻,是北京某财经大学毕业的高材生,毕业后和几个同学合伙,找到投资人,创办了这家公司,员工都是和我一样同龄的九零后,八零后都极少,刚入职得我没有隐瞒培训经验,顺利通过了面试,老板和技术团队的领导也接纳了我成为他们的一员。从此我便跟着同组的同事,一边工作完成安排的开发任务,一边继续学习积累经验。团队成员相处之间都很融洽,在那一个全民创业的年代中,做这样一份行业并非是件很困难的事情,我们的发展也算顺风顺水。在这期间,我通过学习iOS的知识,在同事和领导的帮助下,我逐渐成长为可以独立开发独当一面的项目组成员。

    时间过得很快,转眼到了二零一六年,公司内初创团队的人员流动了不少,因为在当时满地的机会,大家随便跳槽薪资就可以轻松上涨数千元,甚至翻倍,尽管跳槽带来的薪资十分诱人,但是我仍然稳住了心态,因为当时的经验还不足,需要足够的时间积累,而没有积累足够的开发经验和开发的眼界,盲目的跳槽也不会长久。况且我不是一个喜欢跳槽和追求不稳定的人。这样的心态下,我一直没有动作,一心想着安心做好我负责的事情,并且牢记人而不学,其犹正墙面而立的道理,继续拜读技术文章和书籍,提升自己的开发技能水平。


    就这样,我在这家公司一直做了三年,时间来到了二零一八年,互联网的那股热潮开始褪去。P2P行业也出现一些变故,似乎预示着暴风雨前来临的宁静。我们就如同大海中稳定的小舟,虽小,但是也能挡住惊涛骇浪。我们当时的App经过多次迭代已经稳定,只有小改小动的小需求,团队也没有加班任务,大家正常上下班,一切似乎朝着安稳的方向发展了。我也放松了警惕,但是正如点题中所说,人一生下来,忧患就是随之而来的,若放弃了这一点,则遇到真正的问题时会措手不及。不懂这些道理的我,就遇到了我的程序人生中的第一个转折点。同年八月的一天凌晨,我们公司老板在没有任何预警的情况下,在App内发布了一个清盘公告,告知投资人,将逐步清盘,有序退出投资人的资金,没多久这个公告就撤下了。但是仍然已经被用户截图发到了各个投资群中。第二天一早我们去上班的时候,已经有投资人坐在公司门口等着我们了。那是漫长的一天,在那一天里,投资人、民警、公司领导、看热闹的其他公司员工,凑在公司门口,上演了一出闹剧。我们甚至中间出去躲避了几个钟头,下午才回来办公。到了晚上,事情又发生了变化,出乎所有人意料。经济侦查的执法同志来了,并告知我们,老板已经被控制,目前公司涉嫌自融犯罪以及虚假标的,让我们每个人记下个人身份信息和手机号,拿走自己的个人物品,离开公司。

    大家都懵了,执法同志也没有为难我们,放我们走了以后,大家都在公司楼下不肯离去,最后有人提议吃一顿饭去。餐后,大家商量了一下对策,决定先回家等待公司消息。当晚,人事在公司群里说,已经帮大家社保减员,各自可以去找新工作了,我们才知道,公司黄了,相当于遣散了各位。我意识了点题的后半句,既然知道了这是无可奈何的事情,就接受命运的安排。


    八月底,秋似乎来得过于早了些,虽然正午的阳光仍然毒辣,但是在北京这片钢铁丛林的阴影下的风,已经不再燥热了。我奔波在一栋一栋的大楼间,进出一个又一个公司,去参加对我来说已经十分陌生的面试。由于对未来的不确定,以及自己给自己制造的焦虑,我匆匆忙忙入职一家做企业资源管理软件的公司。然而,忧患和安逸,如同鸟之两翼,车之双轮,互为条件,彼此支撑。不可不思进取,但也不能因盲目担忧从而给自己做出了错误的选择。我入职的这家公司,也因为整体经济形势的冲击,导致遇到了裁员,在十二月份我即将通过试用期时,和一旁的安卓同事,以及两位后端一起遭到了公司的裁员。

    没有任何辩解和理由,我也没有去争取赔偿,但是我没有意识到盲目的焦虑是不可取的,仍然如同热过上蚂蚁一般继续面试,对待专业技能的相关文章也是泛泛阅览,缺乏思考,正所谓眼中了了,心下匆匆,方寸无多,不仅影响自己,而且对职业生涯的提升也无益处。在骑驴找马的心态下,我匆匆入职一家小公司,不仅单休而且工作很累,经常加班,我工作了十天,在这十天里,我对自己的选择进行了反省,对自己的心态进行了反思,也对大环境现状理解了一些,正所谓天下将兴,其积必有源;天下将亡,其发必有门。移动互联网的发展早有征兆,我在要拼搏的时候选择了安逸,却要在要安逸的时候选择拼搏,因此才会在这半年过得如此艰难。


    正巧,之前投的一份简历约到了面试,是一家央企旗下的子公司,开出的待遇低于我的心理预期,但是我仍然下定决心入职,于是迅速地和目前的公司办理离职,拿到离职证明后,终于入职了这家我心仪的公司。

    二零一八年底,我来到了这家公司,我在这里工作了已经四年半了,也渡过了整个疫情期间席卷移动互联网的经济风暴,但是我仍然牢记着之前留下的教训。我没有落下继续进步学习的步伐,尽管在这家公司入职的头两年依然加班很累,工作压力也很大,也遭受了一些领导的排挤。但是我也积累了一些自己的经验教训,也逐渐改变了领导的看法,我学到了心有所畏,方能言有所戒,行有所止。我得到的不仅仅是技术上的提升,更是做人做事的道理,在未来,也许不知道哪一天,我的程序人生可能会换到全新的道路上去,但是不管做任何事,要和做人一样,要坚持三省吾身,谓之思危思退思变,牢记生于忧患死于安乐的道理,那么在未来,也一定能继续实现自己的人生价值,为社会做出自己的一份贡献。


    最后,感谢环信可以提供这样一个平台,仅以此文祝贺环信十周年生日快乐,祝愿环信在新的十年大展宏图,乘风破浪,奋勇向前,继续开创新的辉煌。



本文参与环信十周年活动,活动链接:https://www.imgeek.net/question/474026

收起阅读 »

环信十周年趴——我的从业之路

        我的从业经历,对大家来说就是一个避坑史,感觉自己啥坑都遇到过。       2015年,我大学毕业了。毕业即失业,校招压根没公司来看一眼我们。你肯定会说,这怪谁,谁...
继续阅读 »

        我的从业经历,对大家来说就是一个避坑史,感觉自己啥坑都遇到过。

       2015年,我大学毕业了。毕业即失业,校招压根没公司来看一眼我们。你肯定会说,这怪谁,谁让你学校垃圾。是的,不可否认,学校确实不行,这也导致我们无人问津。毕业之后回到了所在的城市,整天往人才市场跑,奈何这个城市没啥网络企业以及科技公司,全是招聘销售人员。药品销售,保险销售,地产销售,我看着人来人往的人才市场,偌大的城市好像容不下我一人。迎面走来一个招聘的小姐姐,婀娜多姿,我的目光在她身上移不开。在当时我感觉她的声音就像乡间的轻铃,清脆悦耳,字字敲击耳膜。她看向我,目光流转,翘眉生盼,然后诚恳的让我加入她们,一起为保险事业做贡献。我欣喜若狂,使劲的点头,但是我又还想找互联网的公司,我不甘的又摇头。她说你不想跟着我一起为众人的健康事业而努力奋斗?我点头又摇头,她拽着我就要去办理手续,最终我还是坚守住了本心,我知道我们俩的相遇只是命运的一个玩笑,因为她结婚了。

       后来,我在这个城市找了份网管的工作,日常任务就是跟着一个老师傅去这个城市的各个地方机房里去维护。都是些没人想干的脏活累活扔给外包公司,然后我们去干,去加油站里面给机房走线,整理机房,布置服务器机架,没有丝毫的技术含量。自己也在慢慢沉沦,可能也就要这样,匆匆忙忙,无所事事的度过往后,但是我又好不甘心...

       就这样在一次次的纠结,煎熬中,来到了2016年5月份。我厌倦了这样的工作内容,转身投奔了在北京的朋友。来到北京一切都是那么的新奇,心情是那么的兴奋,感觉就连空气都是甜的,四周都是自由的气息。朋友把我带到了他在城中村租的一个屋子,虽简陋但却很整洁,他说:有wifi有空调就够了。是啊,还奢求什么呢,北京本来就令人向往。我们坐在一起吃着肉,喝着啤酒,高谈阔论。唯一一点让我感觉不爽的是空调,因为它不仅制冷,还喷水,向内喷,喷的身上到处都是,也不知道房东通过什么神通手段安装了一个这么个奇葩空调。后面我在朋友这,边住边找工作,最终找到了一个愿意收留我的公司,虽然我会的不多,但是公司看我还算本分,一问三不知,那是确实一点都不知道。我在公司跟了一个老员工开启了Android之路,他会分我点特别简单的工作,然后把自己珍藏多年的种子,哦,不是,是搜集的Android项目,让我学习,尽快能承担更多的工作。在这个公司我一直在成长,学的也很快,公司看我本分,所以工资也很本分,后面也有调整,但是还是调整的很本分。我感觉我是有野心的,所以在两年之后,我选择了离开,之后便开启了找工作之旅。

       没多久我就进入了一家做线上游戏陪玩的公司,因为工资足够的低。我进去之后才知道公司之前融资过2000w,但不肯在我身上多花一分钱。钱呢大部分被老板挥霍了,挥霍到哪了呢,我猜大部分是挥霍到自己兜里了,因为他又给自己买了辆50来万的车。随着资金越来越少,我们的办公场地,办公环境以肉眼可见的速度在迅速变差,不仅越来越小,最后只能跟其他公司在一个屋里面拼凑,要不是同事早都认识了,我估计大家都会认为,这个屋里的都是自己同事。2019年开始,公司颓势越发严重,工资也开始断断续续,但是好在老板还有点良心,每个月给发,只是日期不固定了。在4月的时候,终于还是元气耗尽,
公司不行了。我再一次的开始了找工作,没几天就去了一家搞平行进口车的公司,这家公司有3个前端维护网站,然后新招一个Android跟一个ios。移动端项目接口用网页端的,进来按着原型图搞就行了。上班第一天就开始匆匆忙忙开始写项目。晚上加班更是家常便饭,时不时还有人一直催。功夫不负有心人,在2个多月的时候,项目搞完了。这个公司的转正需要提前半个月申请,我们申请之后发现,批准就没动过,也找人事主管咨询过,她说不清楚是怎么回事,然后在某一天,人事找到我跟ios,告诉我们转正不批了,让我们走人吧。费半天劲,项目搞完,卸磨杀驴,遇到这样的也是恶心至极。


       干了三个月,又要匆匆忙忙的找工作,随后来到了一家搞广告传媒的公司。本以为可以安安稳稳的待一段时间,但是谁料屋漏偏逢连夜雨。在经过四个月的试用期80%工资之后,疫情突起,铺天盖地而至,开启了在家办公、值班的方式,公司业务也大受打击,随即开始降薪,只发一半工资。又在当前公司挣扎了5个月之后,看着日渐空扁的钱包,实在是无以为继,只能重新开始找工作。后来呢入职了一家稍正常的公司,正常呢,也只是说可以正常发工资,日期也是充满随机。随着年龄的增长,钱包却还是依然空扁如故,看到别人都是锦衣玉食,衣冠华丽,而我却还在原地兜兜转转。最终在2022年,离开了北京,一个待了6年的地方,一个充满回忆的地方,去的时候充满了向往,回来的时候带走了满眼的沧桑...


本文参与环信十周年活动,活动链接:https://www.imgeek.net/question/474026

收起阅读 »

2023和自己聊聊

自我质疑,他人质疑 前几天约了面试,被面试官问了一个问题,你做这么久的开发,有哪些技术沉淀呢,或者自己擅长哪些呢,我突然楞了一下,其实自己也想过这个问题,结论是啥也不是,很一般。给面试官说目前 vue 用的比较久,做过不少项目,对这个比较擅长一点吧,从开始做项...
继续阅读 »

自我质疑,他人质疑


前几天约了面试,被面试官问了一个问题,你做这么久的开发,有哪些技术沉淀呢,或者自己擅长哪些呢,我突然楞了一下,其实自己也想过这个问题,结论是啥也不是,很一般。给面试官说目前 vue 用的比较久,做过不少项目,对这个比较擅长一点吧,从开始做项目都是自己摸索,从最开始的 vue2 到现在的 vue3 和 react 项目技术框架是我负责主导的,然后也会帮组员处理一些问题之类的。但是我从你的面试结果来看,多数情况下是了解或者知道某个知识点的简单使用,但细节的问题就看你支支吾吾的,应该是理解不到位吧。有什么比较好的项目,或者攻克了哪些技术难点可以做下分享吗。我沉思了一下,好像觉得没什么值得去展示的,总觉得自己的项目很平常,就算是平常遇到一些问题,很快就能解决,不是自己厉害,而是实在是项目简单而已。那你为啥觉得你能帮助别人解决问题,帮助其他组员成长呢,我陷入了无限的沉思...。


工作经历




  1. 第一家是一个外包公司,算是用了包装的简历蒙混进去的,结果是差一点给自己干离职,压力太大了,真的是s什么都不会,感觉实在是扛不下来了,于是在项目交付的前三天说自己家里有事,提了辞职。结果没辞成,老板说你忙完了再回来就行,你的工作我先让其他同事接替你。(当时也去了新的面试,但是结果可想而知)于是在请假这两周中迅速恶补,像是要奔赴战场,硬着头皮回去了,在那个接替我的同事的帮助下终于开心的(提心吊胆,每天想着二次辞职,又碍于没有脸面再提,咬咬牙终于坚持了下来,整理了八百字的小作文描述当时的过程,后来想想还是不写出来了吧)完成了第一个jsp版的项目。




  2. 后来公司接了一个新的项目,做一个后台管理系统,让我来做前端,说写好页面给到java那边,让他们来开发,还是用jsp那套。当时心想着是用 vue 脚手架搭建,来做前后端分离模式,但是我一点经验也没有,问了我那个同事,她也没这做过这种模式的,她坚持自己的意见是说用老一套,类似 jsp 那样。毕竟她比我有经验一些,那就听她的先做下试试,但心里还是想着用前后端分离来做,没人指导,只能自己去摸索,最后还是找我领导商量前后端分离模式开发。他之前做 java 的,对前端也不懂,问了我前后端分离的东西,我也是现学现卖,告诉他怎么好怎么好,但是我之前没用过,是有试错成本的,他问了我这些技术目前成熟吗,我说好多公司都开始用了,以后这个是主流。在我的忽悠下同意了这个方案。当然一切都没那么顺利,也是一步一个坑,一步步趟了过来。也感谢我这个领导,在五月份我准备辞职回去毕业答辩时帮我申请了两周的假,顺利毕业。在这个后台管理项目如期上线以后,我也终于松了一口气,没有辜负领导的信任。也感谢当时的自己坚持了自己的想法,虽然过程很难,但是也扛了下来。




  3. 慢慢的发现遇到了技术瓶颈,最开始的自己像一个海绵,进入公司后一直在吸水给自己充电,后来充电越来越慢,甚至出现了漏电的情况。于是准备跳槽,在这个外包公司离职后进入了外派的这家公司,等于从乙方进了甲方,等于好像并没有跳。日复一日的上班,加班,下班好像做了很多,但是又好像什么都没做,整天做一些表单,表格的增删改查,没什么长进,差不多一年。于是准备第二次跳槽。然后准备过完年开始第二次跳槽。就遇上了疫情,然后又呆了一段时间,准备再过了年跳槽,然后在已经开始谈 offer ,准备再多面几家时,上海又开始了疫情,直接封了三个月,那个 offer 也就不了了之了。去年年底约了些面试,都不太理想,多数都是外包,然后就到了现在。想想还是因为自己不够坚决吧。




精神内耗


一方面觉得自己不够优秀,想要去努力,另一个方面在学习时发现很多东西太难了,然后就放弃了。于是在一边想要躺平,一边想要好好学习的的状态下无限循环。然后开始了自我怀疑,自己适合做这方面的工作吗,自己做这方面有优势吗,自己有什么技术上的优点值得拿出来说说吗,好像都没有。一次次的面试,一次次的没了下文,然后都把原因归结于自己不够优秀。于是又进入了,那为啥不好好学,我试着去学了,但是学不进去,学不会的轮循怪圈。


反思与醒悟


2023年了,想着自己要去改变些什么,但是又不知如何去做,之前买了不少的书,但看的也就几本其他都在吃灰。看朋友圈有人在微信读书,于是也试着看一些书看解决一下心理浮躁的问题,不能这么浑浑噩噩下去,不然真就废了。工作,生活,情感压力感觉都快抑郁了。直到最近看了大佬分享的书,才开始有所醒悟,是自己太急于求成了。太想在刚投入一点精力就要看到成果了,平常是看了不少学习的资料,但也都是在自己舒适区内,一旦遇到难的就告诉自己肯定学不会,所以就放弃了,不会将难题碎片化,一次解决一个小问题,爬山也不都是一步一步走上去的嘛。学会去接受自己的平凡,但是不能以自己是个普通人为理由而不去努力。实践是验证真理的唯一标准,所以我们在学习时也更要去思考,去试着用自己的话看能不能书写出来,讲给别人听,看对方能听明白不。如果只是以为自己去学习了,就万事大吉了,但过段时间可能就会忘记了,这一点我最近特别有体会。就拿写的两篇 vue 的基础知识点来说,以为自己很容易就能写出来,但写的时候发现没那么容易的。有的地方可能还需要再查下资料才能搞明白,不过也加深了对这些东西的理解,如果在帮助自己的同时能帮助别人就更好了。


一起共勉


书上的几个观点觉得很有用,分享给大家,如果目前有小伙伴也有我上面的焦虑

1. 试着跟自己和解,停止精神内耗,接受自己的普通,但不能因此而止步不前,摆烂

2. 在自己跳一跳就能够得着的地方做拉伸,在舒适区和困难区要么无所事事,要么备受打击

3. 不要急于求成,罗马不是一天建成了,只管按照自己的节奏去努力,事实会告诉你答案

4. 输入的同时也要去输出,形成闭环,实践是验证真理的唯一标准,试着去做到知行合一


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

程序员增强自控力的方法

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法: 1. 设定明确的目标和计划 制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任...
继续阅读 »

作为一名程序员,我们经常会面临工作压力和时间紧迫的情况,因此有一个好的自控力对于我们的工作和生活都是至关重要的。以下是一些可以帮助程序员增强自控力的方法:


1. 设定明确的目标和计划


制定明确的目标和计划可以帮助我们更好地管理时间和精力。我们可以使用日程表、任务清单、时间追踪工具等,来帮助我们控制时间并更有效地完成任务。


2. 掌控情绪


作为程序员,我们需要面对很多挑战和压力,容易受到情绪的影响。因此,掌握情绪是一个非常重要的技能。可以通过冥想、呼吸练习、运动等方法,来帮助我们保持冷静、积极和乐观的心态。


3. 管理焦虑和压力


焦虑和压力是我们常常遇到的问题之一,所以我们需要学会如何管理它们。我们可以使用放松技巧、适度锻炼、交流沟通等方法,来减轻我们的焦虑和压力。


4. 培养自律习惯


自律是一个非常重要的品质。我们可以通过设定目标、建立规律和强化自我控制等方式,来培养自律习惯。


5. 自我反思和反馈


经常进行自我反思和反馈可以帮助我们更好地了解自己的优缺点和行为模式。我们可以使用反馈工具或与他人交流,来帮助我们成长和改进。


6. 持续学习和自我发展


程序员需要不断学习和自我发展,以保持竞争力和提升自己的技能。通过阅读书籍、参加培训、探究新技术等方式,可以帮助我们持续成长,增强自我控制力。


结论


自控力是我们工作和生活中重要的的品质之一,可以帮助我们更好地应对各种挑战和压力。通过设定目标、掌控情绪、管理焦虑和压力、培养自律习惯、自我反思和反馈、持续学习和自我发展等方法,我们可以帮助自己增强自我控制能力并提高工作效率。


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

🎖️怎么知道我的能力处于什么水平?我该往哪里努力?

🎖️职业水平怎么样才算达到平均标准?我来告诉你 嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️ 毕业后进入社会,我像大家一样感到恐惧和不安。在这个新的阶段,我们都投入了大量时间和精力来从事各种社会劳动,同时也努力满足自己的经济需求。我们每个人在这个过程中都会去思考...
继续阅读 »

🎖️职业水平怎么样才算达到平均标准?我来告诉你


嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️


毕业后进入社会,我像大家一样感到恐惧和不安。在这个新的阶段,我们都投入了大量时间和精力来从事各种社会劳动,同时也努力满足自己的经济需求。我们每个人在这个过程中都会去思考如何实现自己的人生价值,追求小时候美好的憧憬和期盼。💼


然而,在这个思考的过程中,没有人能为我提供确切答案。离开了学校的庇护和老师的指导,我感到比学校学习时更加困惑。未来的方向不太清晰,这使我在面对职业选择、个人发展和人生道路时遇到了许多挑战和困惑。🤔


有没有想过你职业生涯的下一步应该是什么呢?🤔


你怎么知道接下来要学习什么工具、原则或编程语言呢?📚


我想和大家分享一个超级简单的程序员分级定义思路,也许它可以帮助你这个处于职业生涯各个阶段的开发人员找到下一个目标并迈向更高的境界!✨


🚩声明:不一定正确,只是一组思路


以下的内容可能不一定正确,因为不同企业对员工能力的定义可能会有所不同。甚至每个人对这些级别的定义也会有很大的差异。🚫


然而,排除了内卷化的分级标准后,我接下来要介绍的每个阶段都代表了职业生涯中大多数人可能达到的“位置”。🎯


在每个等级之间,都存在一些过渡,可能需要在特定领域获得更多的知识和经验,也可能需要提升社交方面的知识和经验。🔀


每个等级都是在上一个等级的基础上进一步发展而设立的,我对此有着自己的职场经验启发。💡


然而,请注意:我所说的这些并不一定与你目前所处的职位相对应。 🚫


在某些公司,拥有“高级开发工程师”职称的人,实际上在技能和专业知识能力方面可能只是初级开发工程师!👨‍💻🏢


在职场中,许多人之所以被晋升,仅仅是因为他们在该领域(无论是前端、后端还是运维)有几年的经验,并非因为他们具备胜任所需的技能和知识。📚


同时,很多情况下,他们之所以成为公司中业务经验最丰富的“高级开发工程师”,仅仅是因为他们在同一家公司工作了很长时间,从而“熬掉”了许多老员工。⏳


这个世界并不公平,我相信大多数人都已经看到并经历了这种情况。🌍


当然,我还想补充一点,我所描述的这些等级并不是一成不变的标准。在你所在的领域中,有些地方对这些要求可能并不那么严格,所以你不需要过于关注我所提到的要求。🤔


以下内容仅供参考,希望能够帮助你更好地管理和掌握你未来的职业规划。说到底这仅仅是一种思路,我不是行业领袖,它仅仅是一组思路。🔍


1️⃣编程爱好者



“我有点不知道该怎么给这个阶段的 coder 定个级,算了,咱们姑且称他们为"编程爱好者"吧,但其实我还是觉得这个说法不太准确。😕”



我这里所指的“编程爱好者”是指广义上的 coder ,也就是那些会写代码或者热衷于写代码的人。💻


这些人可能有以下特征:



  1. 他们并非以“编程”为主业,而只是因为兴趣或者作为该专业的学生而加入到我们这个圈子中。对于那些以编程为职业的开发人员来说,他们算是“业余”的。🔍

  2. 这些开发爱好者了解编程语言的语法,并且能够熟练运用他们擅长的编程语言,甚至有时候比一些专业开发人员表现得更出色!📚

  3. 他们有能力独立开发一些小型项目,例如脚本、网页、游戏或应用程序。🚀

  4. 他们擅长使用搜索引擎自发解决问题。🔎

  5. 然而,在这个阶段,他们的编程能力并不能直接转化为经济利益,也就是说他们并不能通过技能获得收入。🚫


2️⃣初级开发工程师


"初级开发工程师"代表着那些已经以专业人士的身份进入IT领域的人,他们需要与其他专业人士合作,一起完成工作任务。👩‍💻


他们可能有以下特征:



  1. 他们是以编程为主要职业的专业人士,企业需要支付报酬雇佣他们加入生产。💼

  2. "初级开发工程师"会被分配到一个或多个项目中工作,但他们可能无法完全理解整个项目的结构,因为对于他们来说,项目可能还是“太大”了。🔨 在这个阶段,他们更多地承担一些被拆分成小模块的任务,对于项目的整体认识,他们并不清晰。🔎

  3. 他们可能只对自己专业领域有了解,在工作中需要继续学习前后端通信和数据库连接等跨系统的知识。📚

  4. 他们需要在中级开发工程师或高级开发工程师的指导下完成工作。🤝



“这些特征是一般情况下的描述,具体的职位要求和工作内容可能因公司和行业而异。📋💼”



3️⃣中级开发工程师


到了"中级开发工程师"阶段,他们已经适应了业内的整体开发节奏,成为了一名合格的开发团队成员和代码贡献者。🚀


在这个阶段,他们具备以下特征:



  1. 能够独立构建业务模块,并熟悉最佳实践。例如,在Web应用中开发单点登录模块。🏗️

  2. 开始了解项目的基本系统架构,对领域内的架构、性能和安全性有一定的了解。🏢

  3. 能够熟练使用专业工具来提高工作效率。🛠️

  4. 对设计模式和良好的编码习惯有基本的了解。🎨

  5. 能够在常规工作中独立操作,无需过多监督。💪

  6. 对于高级开发工程师来说,他们可能缺乏经验,需要经历几次完整的开发周期和遇到很多“坑”之后,才能学会如何在下次避免它们。🔍



“这个阶段的开发工程师最缺乏的就是项目实践经验。只要有不断地项目经历,通过实践和经验积累,他们就会不断成长。🌱”



4️⃣高级开发工程师


遗憾的是我们中大多数人在职业生涯中大部分时间都在面临从“中级开发工程师”到“高级开发工程师”的门槛。


有些“开发工程师”可能在整个职业生涯中一直停留在中级水平。


“高级开发工程师”之所以与众不同,是因为他们知道什么可以做,什么不可以做。这种洞察力是通过过去犯过的错误和经验教训获得的。


开发经验对于成为“高级开发工程师”至关重要。


根据我的理解,“高级开发工程师”应该具备以下特征:



  1. 精通团队所使用的核心技术,对其应用得非常熟练。💪

  2. 熟悉系统架构设计和设计模式,并能够在团队项目中应用这些概念,构建更复杂的系统。🏢

  3. 拥有构建“完整”解决方案的经验,能够考虑到项目的各个方面并提供全面的解决方案。🔍

  4. 在服务器部署和维护方面有一定的经验,了解负载平衡、连接池等跨领域知识。🖥️

  5. 作为团队的核心成员,能够担任导师的角色,积极指导中级和初级开发工程师。👥


其中最后一条是最最重要的。如果不能把你的经验、专业知识和知识传授给你的团队成员,我认为这就不是一个合格的“高级开发工程师”。


成为“高级开发工程师”的一个重要指标:一定是团队的其他成员经常向你寻求建议和帮助



“如果你还在沮丧为什么同事老是问我问题,也许现在可以改变一下想法了。💼


因为你是你们团队最重要的百科全书呢!也许现在是时候考虑向老板提出加薪的要求了呢?💰”



5️⃣开发领袖



这个阶段我也有点困惑,不知道要给他们这个等级取一个准确的称号。我想了两个名字:“高级架构师”和“团队领导者”,但是我又想,其实高级工程师也可以领导团队,也有架构能力啊。那就还是加“领袖”两个字,突出在技术领域的高级能力、团队领导能力和架构能力。这样看起来就更厉害了!👨‍💼



在这个阶段,程序员们已经不再仅仅为一个团队服务。他们可能同时为多个团队提供支持,并向下属团队提供更底层的指导,特别是在设计和早期产品开发阶段。💪


在国内,由于很难找到同时在业务领域和专业领域都深耕的人才,这类职位可能被企业分拆为不同的职能,更加注重管理能力而非专业能力。 🤔最终可能招聘了一个“高级监工”(毕竟,同时在业务领域和专业领域同时深耕的人真的少之又少,而且一般企业也不愿意花费与之对等的报酬)。


因此,大部分人可能会不同意我这个阶段的观点。 😕开发领袖的职能范围可能涵盖“敏捷教练(scrum master)”、“DevOps”、“项目经理(PM)”、“CTO”等管理职务。


因此,开发领袖最重要的特征是:



  1. 对业务领域有深刻的理解,能够消除开发团队与企业其他业务部门之间的沟通障碍。🌐

  2. 发挥"PM"职能: 协助规划产品开发和时间表,向营销或销售团队提供反馈。📈

  3. 发挥"CTO"职能: 协助高层管理,实现企业愿景,领导开发团队实现企业的业务目标。📊


因此,开发领袖必须对所处的业务领域(如医疗、金融、人力资源等)的产品有深入的了解。🏥 基于这些了解,他们能够理解软件所解决的业务问题,并且必须了解其他学科,如管理、产品开发、营销等,以消除各部门合作之间的沟通障碍。


简而言之,高级开发工程师和开发领袖的区别在于:



  1. 高级开发工程师也担任团队领导的角色,但主要面向开发团队的“内部”。👥

  2. 开发领袖则超越团队内部管理,他们的管理职能是面向“外部”的,致力于消除开发团队与公司其他部门之间的沟通障碍。🌍


因此,成为开发领袖需要具备高层领导的全局视野,并能够将业务术语和技术术语相互转化。🔑


如果你能够在公司内很好地与业务同事交流技术解决方案,并让其理解,那么你已经拥有了“开发领袖”其一的核心能力。💡


6️⃣领域专家


这个阶段的他们已经跳出了企业的限制,在一些特定领域也颇负盛名。他们的解决方案不再是只为一家企业服务,他们擅长的领域也不是一般的学科分类,而是一个非常有针对性地细分领域。🚀


可惜的是,一般的开发者们很难接触到这些领域,你想要了解他们的知识都不知道从哪儿下手,因为他们的知识分享大多是封闭的,只在内部共享,不对外传播。🔒



“可能你会觉得这与你对开源软件行业的理解不太一样,开源难道不是互联网发展的第一推动力吗?是啊,我同意你的观点,但你不了解不代表它不存在。其实大部分的技术分享都是在内部进行的,许多讲座和峰会也只限邀请制🔐。”



他们可能是某种编程语言的奠基人,可能是Web安全领域的重要任务驱动者,也可能是教导其他前端开发者如何使用React的大师,甚至还有那些在特定行业中扮演技术导师角色的人!👨‍💻


他们还可能是某个社区的建设者,在互联网和社会上有一群人将他们视为直接或间接的导师。🏢


他们也可能是支持特定事业或理念,并为之做出显著贡献的思想领袖。💡


他们会公开地讨论自己的专业领域和他们所推崇的理念。🗣️



“如果你也有自己的小圈子。比如在掘金社区;比如在GITHUB,拥有自己的互联网开源项目,并且有一大群粉丝用户支持和拥护你的产品和理念。那你也可以算是某一细分领域的专家了。👥”



总而言之,他们的一举一动都可能对互联网技术的发展产生重大影响。😄




🎉 你觉得怎么样?你认为自己处于哪个阶段?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨


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

Android抓包环境搭建

文章不会从零开始教你搭建抓包环境,需要你具备一定的环境基础,满足以下条件才能继续看文章: 电脑已经安装好Fiddler或则Charles; 手机已经root且具备Xposed环境; 如果已经具备上诉环境,那么可以开始继续搭建环境了。 搭建入门抓包环境 An...
继续阅读 »

文章不会从零开始教你搭建抓包环境,需要你具备一定的环境基础,满足以下条件才能继续看文章:



  • 电脑已经安装好Fiddler或则Charles;

  • 手机已经root且具备Xposed环境;


如果已经具备上诉环境,那么可以开始继续搭建环境了。


搭建入门抓包环境


Android手机抓包


手机有Xposed环境,抓包就很简单了,按照下面的步骤操作即可:



  1. 安装Xposed模块—TrustMeAlready_1.11.apk,软件可强制跳过证书验证。

  2. TrustMeAlready.apk内勾选需要抓包的app。

  3. 安装抓包工具-小黄鸟(HttpCanary.apk)

  4. 开始抓包


这种抓包方式有个缺点:有的请求会抓不到。


电脑抓包


电脑抓包的话需要安装根证书,可以参考这篇文章


抓包环境进阶


上面入门环境有时候会出现抓不到某些接口的情况,这时需要将手机端所有的请求进行拦截转发,然后用电脑端抓包工具查看。具体的步骤如下



  1. 安装拦截转发工具(VProxid_1.2.0.apk),可将流量劫持到PC端。

  2. 配置VProxid


这里详细的说下如何配置VProxid,它的界面如下


image20230103172419726.png




  1. 点击右下角的➕,界面如下


    image20230103204810203.png




  2. 设置完成后,回到主界面,点击绿色三角就可以劫持应用的网络通讯




  3. 电脑端抓包工具就可以看到应用的请求记录了。




这种抓包方式,可以抓到应用的所有网络请求。


总结


抓包可以说是逆向的第一步,本篇文章介绍了手机抓包和电脑抓包的方式,进一步的介绍了如何劫持应用的流量到PC端,通过劫持应用的流量到PC端,可以抓到app的所有网络请求,建议使用这种方式,不然可能找不到想要的内容。


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