<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>An Li - Software engineer,creator,and indie developer.</title><description>I&apos;m An Li, a software engineer and entrepreneur based in Hangzhou China. I&apos;m the co-founder of Profity.dev organization, where we connect to developers, designers, and incubate indie apps.</description><link>https://kidylee.com/</link><item><title>规律的生活是为了松弛</title><link>https://kidylee.com/blog/a-regular-life-is-meant-for-relaxation/</link><guid isPermaLink="true">https://kidylee.com/blog/a-regular-life-is-meant-for-relaxation/</guid><description>《松弛感》读后感</description><pubDate>Sun, 28 Aug 2022 23:52:24 GMT</pubDate><content:encoded>&lt;p&gt;知道Tinyfool（郝培强）是在推特上，有天看到一个小胖子晒了一张日系女孩子的照片，配文是：老婆的存款已经有一百万了。我心想这小胖子有点东西，于是点了一波关注。再后来，了解到原来是前辈，现在自由职业，靠写文章和独立开发养活自己，很是羡慕这样的生活，看到他的文章结集成书，于是买回来学习一下，看看自己哪方面还可以提高。&lt;/p&gt;
&lt;p&gt;这本书可以说是个人成长的修炼手册了，特别是对于有十年以上工作经验的人，非常有帮助。除了提供具体的生活、工作、成长等方面的指导，更难能可贵的是，书中还有一系列心理建设方面的辅导。当然，这本书也适合年轻人阅读，个人成长这件事，什么时候开始都不晚，当然越早开始收益越大。&lt;/p&gt;
&lt;p&gt;这本书拿到手之后，可以说是一气读完的，因为里面有太多的感受可以和作者共鸣。作为一个工作接近20年的程序员来说，特别是在职业发展遇到瓶颈期的时候，这本书最有帮助，虽然这本书里没有一个字是在说“松弛感”，但是如果能按照书里的建议去生活，去做事，你就会的到松弛感。特别是站在2023年的档口，经济预期不会好到和前二十年高速发展的情况下，应该用怎么样的生活工作态度来面对世界。&lt;/p&gt;
&lt;p&gt;这本书也给了我一些启发，和对过去一些想法的佐证。优秀的人都有优秀的习惯使之优秀，而优秀本身就是使自己更优秀的动力，我称之为优秀循环。我个人学习和工作的习惯都不太好，简单讲就是拖延症，一点事情拖拖拉拉做一整天。在读了Cal Newport的《深度工作》，《优秀到无法超越》，James Clear的《习惯的力量》，以及《每天工作4小时》《Make time》等书之后，心理总有个疑问，作为一个工程师，每天只工作4～6小时，到底够不够。Tinyfool身体力行的告诉我们，是可行的，不但可行，而且很幸福。&lt;/p&gt;
&lt;p&gt;另外一个优秀的习惯是认真：连社交都认真去做。我以为的上网冲浪是放松娱乐，人家连使用Quora都有KPI考核。不经审视的人生是不值得过的，每天按时起床，花一些时间安排今天的todo list，再花一些时间在休息前给自己写一个日报，规律的生活，稳定的产出，为自己建立工作流，建立系统。而且不只有工作，也安排娱乐，虽然他在社交网络上花的时间比谁都多，但这也是安排好的。这样的每一天才是值得过的。&lt;/p&gt;
&lt;p&gt;非常推荐大家都读一读这本书，很多建议或习惯大家能接受，哪怕一点点，都是值得的。如果看完以后觉得都是大道理也没关系，加深一下印象，为了更好的知行合一。&lt;/p&gt;
</content:encoded></item><item><title>如何为已有Spring项目新增 APIKey 认证登录</title><link>https://kidylee.com/blog/adding-apikey-auth-in-existing-spring-project/</link><guid isPermaLink="true">https://kidylee.com/blog/adding-apikey-auth-in-existing-spring-project/</guid><description>如何为已有Spring项目新增 APIKey 认证登录</description><pubDate>Sun, 28 Aug 2022 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;技术向的SaaS平台多数都会提供API提供给开发者，或者来支撑客户端和SDK，我司也不例外。虽然浏览器保存Oauth2 token很方便，但token自身存在过期时间，并且由于token本身是第三方颁发的，对于客户端和sdk来说是一种潜在的隐患，经常因为第三方验证服务出问题，造成用户无法登录。因此，提供API Key方式访问成为了一种更好的选择。&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;p&gt;新增APIKey访问方式有两个思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复用原有Web API，在现有项目提供OAuth2的基础上，再并行引入API Key的认证方式来访问相同的服务&lt;/li&gt;
&lt;li&gt;完全另起一套服务，通过代码共享来提供一致的服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个选项个有优劣，在产品前中期，web和api访问方式大致相同的情况下，第一种的效果会好一些，而当项目进入中后期，web和api的差异，无论是功能方面去考虑还是安全方面去考虑，完全隔离可能会是一个更好的选择。因为目前大部分和web功能是一致的决定在后端已有OAuth2的认证之上，再添加一个并行的API Key的认证方式，从而方便用户使用。&lt;/p&gt;
&lt;p&gt;本文主要介绍如何为已有Spring项目新增APIKey认证登录的最佳实践。&lt;/p&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;p&gt;{{&amp;lt; admonition type=warning title=&quot;注意！&quot; open=true &amp;gt;}}
该方法之适用于Spring Security 小于等于5.2.2（Spring boot &amp;lt;2.2.5），最新的Spring Secuirty 5.7（Spring boot ^2.3.0）有bug，并且API也会有较大变动，不适用本方法。详情见文末。
{{&amp;lt; /admonition &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;获取API Key并生成AuthenticationToken&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@Component(&quot;apiKeyFilter&quot;)
public class APIKeyFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorization = request.getHeader(&quot;application-api-key&quot;);
        if (authorization != null &amp;amp;&amp;amp; SecurityContextHolder.getContext().getAuthentication() == null) {
            SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
            emptyContext.setAuthentication(new APIKeyAuthenticationToken(authorization));
            SecurityContextHolder.setContext(emptyContext);
        }
        filterChain.doFilter(request, response);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过查看HttpRequest的header来获取APIKey，将之封装进自定义的&lt;a href=&quot;https://github.com/kidylee/spring-new-authentication/blob/master/src/main/java/com/kidylee/spring/security/addnewauthentication/apikey/APIKeyAuthenticationToken.java&quot;&gt;Authentication Token&lt;/a&gt;，并将其放在SecurityContext之中。这里要注意，因为Servlet是多线程环境，需要先创建一个新的空context，再通过setContext设置回去，而不要直接&lt;code&gt;SecurityContextHolder.getContext().setAuthentication(token)&lt;/code&gt;，这样会有发生race condition。
Servlet Filter在编写的时候一定要小心，特别是exception的处理，处理的不好会造成用户无法访问。&lt;/p&gt;
&lt;h3&gt;创建APIKeyAuthenticationProvder&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class APIKeyAuthenticationProvider implements AuthenticationProvider {

    UserDetailsService userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String apiKey = (String) authentication.getCredentials();
        UserDetails userdetails = null;
//        userdetails = userDetailsService.loadUserByAPIKey(authentication.getPrincipal().toString());
        APIKeyAuthenticationToken newAuth = new APIKeyAuthenticationToken(userdetails, userdetails.getAuthorities());
        newAuth.setAuthenticated(true);
        return newAuth;
    }

    @Override
    public boolean supports(Class&amp;lt;?&amp;gt; authentication) {
        return APIKeyAuthenticationToken.class.isAssignableFrom(authentication);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ProviderManager会根据SecurityContext中的Token去匹配所有的AuthenticationProvider，&lt;code&gt;supports&lt;/code&gt;方法就是用于匹配。匹配到了之后就可以调用&lt;code&gt;authenticate&lt;/code&gt;方法去认证。这里就可以通过查询数据库或者调用其他服务的方式来获取用户信息。这里同样要注意exception的处理，任何认证不通过的请求，都要以抛出AuthenticationException子类的方式异常返回，这样Spring Filter就可以捕获到异常，并且返回相应的错误信息。&lt;/p&gt;
&lt;h3&gt;注册APIKeyAuthenticationProvider&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    final APIKeyFilter apiKeyFilter;
    public SecurityConfiguration(APIKeyFilter apiKeyFilter) {
        this.apiKeyFilter = apiKeyFilter;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new APIKeyAuthenticationProvider());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;未完成的工作&lt;/h2&gt;
&lt;p&gt;Spring Security 5.7之后将&lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt;已经弃用了，按照官方文档改造后的代码并不能正常工作，现象是Token在filter中设置之后，并没有触发&lt;code&gt;ProviderManager&lt;/code&gt;去调用我们自定义的&lt;code&gt;APIKeyAuthenticationProvider&lt;/code&gt;. 这应该是一个bug，在社区内发现有合并&lt;code&gt;AuthenticationManager&lt;/code&gt;和&lt;code&gt;AuthenticationProvider&lt;/code&gt;两个接口的issue，所以先暂缓解决5.7的问题。&lt;/p&gt;
&lt;h2&gt;写在后面&lt;/h2&gt;
&lt;p&gt;Java相对于其他语言来说，组件和框架比较丰富，但这同时也是一把双刃剑：组件多的开发速度快，但同时学习成本也高。Spring Security能快速完成90%的工作，但是剩下10%特殊要求，需要多出三五倍的时间来配置和调试。
我喜欢小而精，物尽其用的哲学主张：如果还没有上Java或spring，建议选择更简单的语言和框架，例如golang或者pythong；如果已经上了Spring security，最好是把他研究透彻，并好好利用。当然Spring Security作为一款开源框架，设计和实现都是值得去学习的。&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;源代码：&lt;a href=&quot;https://github.com/kidylee/spring-new-authentication&quot;&gt;https://github.com/kidylee/spring-new-authentication&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-security-authentication-provider&quot;&gt;Spring Security Custom Authentication Provider&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/architecture.html&quot;&gt;Architecture :: Spring Security.&lt;/a&gt;  Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html&quot;&gt;Servlet Authentication Architecture :: Spring Security&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter&quot;&gt;Spring Security without the WebSecurityConfigurerAdapter. 21 Feb. 2022&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>给外接显示器增加环境光亮度调节</title><link>https://kidylee.com/blog/adding-auto-brightness-to-external-monitor/</link><guid isPermaLink="true">https://kidylee.com/blog/adding-auto-brightness-to-external-monitor/</guid><description>给外接显示器增加环境光亮度调节</description><pubDate>Sat, 09 Jul 2022 16:24:38 GMT</pubDate><content:encoded>&lt;p&gt;macOS会根据环境光的强弱，自动调节显示器亮度，但如果有外接显示器，亮度则并不会被随之调整。从白天工作到晚上，两个屏幕的亮度差距会越来越大。&lt;/p&gt;
&lt;p&gt;在网上找了一些方案开源和商业的方案，都不是很满意，主要原因是它们都会占用顶部一个menu位，而对于一个系统自动调节的功能来说，并没有用鼠标单击这个menu的必要。还有一些并不太需要的功能，比如根据调节颜色和色温等。另外一些应用，同步亮度居然还需要订阅。&lt;/p&gt;
&lt;p&gt;这个方案很简单，只使用了&lt;code&gt;AppleScript&lt;/code&gt;和&lt;code&gt;Shell&lt;/code&gt;，优点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动调节亮度&lt;/li&gt;
&lt;li&gt;开机自动启动&lt;/li&gt;
&lt;li&gt;不占用dock和menu item&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然因为能力有限，还有一些改进的空间：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更优雅的方式获取系统当前亮度，目前用的是两个正则表达式&lt;/li&gt;
&lt;li&gt;通过监听系统调节亮度的事件，而不是30秒的定时去监控&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;制作流程&lt;/h1&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;需要显示器支持ddc/ci.&lt;/li&gt;
&lt;li&gt;需要用ddctl提供的调节亮度工具：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;brew install ddcctl
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;脚本制作&lt;/h3&gt;
&lt;p&gt;打开Script Editor编辑器，复制下面代码，保存为AutoBrightness，：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on idle --设置定时任务
	set brightness_num to do shell script &quot;ioreg -c AppleBacklightDisplay | grep -oE &apos;(?:\&quot;brightness\&quot;).*?(\&quot;value\&quot;).*?},&apos; | grep -oE &apos;\&quot;value\&quot;=(\\d{1,5})&apos;|awk -F= &apos;{print $2}&apos;&quot; --获取当前系统亮度，能力实在有限，只能通过两个正则获取，help needed.
	set brightness_num to (brightness_num / 65535) * 100 --单位转换
	do shell script &quot;/usr/local/bin/ddcctl -d 1 -b &quot; &amp;amp; brightness_num --调整亮度， 需要确认外接显示器是在display(-d) 1
	return 30 --每30秒运行一次
end idle
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;保存时，文件格式选择 &lt;code&gt;Application&lt;/code&gt;，否则 &lt;code&gt;timer&lt;/code&gt; 不会触发。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;开机自动启动&lt;/h3&gt;
&lt;p&gt;System Preferences -&amp;gt; User &amp;amp; Groups -&amp;gt; (tab)Login Items -&amp;gt; 添加AutoBrightness&lt;/p&gt;
&lt;h3&gt;隐藏应用图标&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;右键点击 &lt;code&gt;AutoBrightness.app&lt;/code&gt; -&amp;gt; Show Package Contents -&amp;gt; Contents&lt;/li&gt;
&lt;li&gt;打开info.plist&lt;/li&gt;
&lt;li&gt;在第一个&lt;code&gt;&amp;lt;dict&amp;gt;&amp;lt;/dict&amp;gt;&lt;/code&gt;中插入下面代码，保存退出。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;key&amp;gt;LSUIElement&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;1&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EOF&lt;/p&gt;
</content:encoded></item><item><title>《态度》 —— 喜欢看吴军吹牛</title><link>https://kidylee.com/blog/attitude/</link><guid isPermaLink="true">https://kidylee.com/blog/attitude/</guid><description>《态度》读后感</description><pubDate>Wed, 04 Mar 2020 07:58:26 GMT</pubDate><content:encoded>&lt;p&gt;第一次看到吴军这个名字，是在谷歌的黑板报上。当时他在板报上连载一系列文章，介绍了斯坦福大学的一个荒废校区，如何一步一步变成今天的硅谷的故事。从仙童公司的八叛徒创立英特尔，直到新世纪互联网泡沫破灭之后，以及谷歌和脸书等公司成立的历史。这一系列文章既不是日常见到的如何创业的鸡汤，也不是从各家公司成功的经验中总结的教训，而是把我抓住科技发展的大趋势，以及公司创建时的基因，来推演了一个顺势者昌，逆势者亡的从新复盘。我很惊讶于一个从事技术的工作者，能有这样的文学功底和对史料的运用技巧。后来这套文章集结成书，名叫《浪潮之巅》。后来他还在黑板报上连载了一系列工程师会用到的数学知识，后来也集结成书——《数学之美》。我把他它在书架上，归类在“儿子数学兴趣培养”之下。&lt;/p&gt;
&lt;p&gt;2016年，Alpha Go击败了李世石，窦文涛请来吴军作为锵锵三人行的嘉宾，谈论人工智能。这是第一次看他参加访谈类节目，节目中他思路清晰，表达明确，看得出是一个经常给别人开班授课的人，但并没有很强的说教意味，不是那种高谈阔论，想法激进，指点江山式的人生导师。 看起来像是一个保守主义者。后来锵锵又请他讲过几期美国大选，他表示他本人确实是投票给共和党和特朗普的。&lt;/p&gt;
&lt;p&gt;同年，得到App上线，知识付费着实火了。吴军也在得到开设了专栏，第一期叫做《硅谷来信》，为期一年，每个工作日给读者一封信。对于写信的这种形式，真是逼格满满。回想我上一次写信，还要追述到高中时期。那时没有手机更没有微信，一个星期只够和女神通一封信，真的像歌词一样：车马邮件很慢，一辈子只够爱一个人。我其实并不喜欢开个专栏还要装一装的感觉，但是对内容还是比较有期待的，于是就订阅了。&lt;/p&gt;
&lt;p&gt;专栏前期的文章确实收获还是比较大的，文章涉及到常识，投资，工作，旅游，摄影和音乐等。吴军本人同时在中国和美国都有很长的生活工作经历，但并不是那种典型的中国式吃苦耐劳型的研究人员或程序员，他在美国高校的学术界和硅谷的工业界都取的很高成就，而且还是约翰霍普金斯大学的学校董事会成员及计算机系顾问。可以说，他同时是中美两国的精英。也是他的这一系列文章帮助我认识了美国社会。&lt;/p&gt;
&lt;p&gt;虽然我没有看过来豆瓣关于专栏的评价，但我猜评论区肯定是满满的装X犯。特别是专栏第二期《谷歌方法论》，里面还穿插几封给女儿的家书，更甚的是，还有类似：“女儿你还记得吗，你们MIT的副校长来我们家吃饭时，我们讨论...”这种文字。我到今天还记得，当时拿着手机在躺在床上看这段话时，感觉的很酸很酸，分不清是因为文字的酸，还是胳膊举着太久的酸。&lt;/p&gt;
&lt;p&gt;一年之后的今天，当这本吴军写给自己两个女儿的家书合集——《态度》放在我面前书桌上的时候，我想我应该如何看待吴军：事业上他是约翰霍普金斯的校董和计算机系的顾问，财富上他用的起拥有五千万美金门槛的高盛财富，下一代的培养上女儿的大学是MIT麻省理工。我是否应该焦虑？是否应该好好感受到他的逼格？&lt;/p&gt;
&lt;p&gt;实话是，刚开始一定会。但当我阅读后，合上书，更多的是感谢。感谢他为我们展现了精英的生活和思考方式，感谢他展示了什么是父爱，感谢他为我们介绍了先进的教育理念，感谢他提供了摆脱原生桎梏的智慧。至于吹嘘，谁有机会不想来两下呢？&lt;/p&gt;
&lt;p&gt;最后一句话总结这本书：
仰视者，猎奇；俯视着，鄙夷；平等待之者，得真谛。&lt;/p&gt;
</content:encoded></item><item><title>用 Beancount 为团队记账</title><link>https://kidylee.com/blog/beancount-for-team/</link><guid isPermaLink="true">https://kidylee.com/blog/beancount-for-team/</guid><description>用 Beancount 为团队记账的具体例子</description><pubDate>Mon, 29 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;关于 Beancount 如何为团队记账的资料并不是很多，周末研究了两天，感觉有一点点收获，在这里做一下分享，如果对 Beancount 还不是很了解，可以先读一下入门系列文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://byvoid.com/zhs/blog/beancount-bookkeeping-1/&quot;&gt;Beancount复式记账（一）：为什么&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://byvoid.com/zhs/blog/beancount-bookkeeping-2/&quot;&gt;Beancount复式记账（二）：借贷记账法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://byvoid.com/zhs/blog/beancount-bookkeeping-3/&quot;&gt;Beancount复式记账（三）：结余与资产&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://byvoid.com/zhs/blog/beancount-bookkeeping-4/&quot;&gt;Beancount复式记账（四）：项目管理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;团队目前的收入主要来源于苹果，而且收支账户用的是我个人的银行卡，我们就用这个现状来做例子，来讨论一下：如何通过使用 Beancount 在同一个账户下区分个人和团队的资产。团队在刚启动的时候没有启动资金来购买必需品，例如购买“苹果开发者凭证”，因此需要 beancount 能够正确记录出借资金，以及后续的归还操作。除此之外，还需要能够单独维护每个团队成员的账户，并且能够让团队成员随时查看自己的账户余额信息。下面用具体的例子来说明，满足这三个需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;资产区分&lt;/li&gt;
&lt;li&gt;借贷明晰&lt;/li&gt;
&lt;li&gt;团队成员随时查看自己的账目&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后再讨论一下如何用 Beancount 给团队成员发股票。&lt;/p&gt;
&lt;h2&gt;托管账户与会计账户&lt;/h2&gt;
&lt;p&gt;开始之前先看一下账本的基本结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;teamcount
  ├── accounting
  │   ├── Alice
  │   ├── Bob
  │   ├── Carol
  │   ├── David
  │   ├── Edward
  │   ├── ledger.bean
  │   └── team
  ├── custody
  │   ├── ledger.bean
  │   ├── personal.bean
  │   └── team.bean
  ├── ledger.bean
  └── shares.bean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里面借鉴了银行的记账方式，使用&lt;code&gt;Cusody&lt;/code&gt; 和 &lt;code&gt;Accounting&lt;/code&gt; 两个账户来记账。&lt;code&gt;Custody&lt;/code&gt; 即托管账户，它的主要目的是保证账户数字是对的，银行账户本质上就是一个托管账户，它能保证你存 500，再取 300，余额一定是 200，但是它不负责记录为什么存 500 和为什么取 300，用 Beancount 的术语来说，就是 &lt;code&gt;Custody&lt;/code&gt; 的记录只有 Income 和 Expenses两类。与托管账户相反，&lt;code&gt;Accounting&lt;/code&gt; 即会计账户，不但要记录这 500 为什么来，还要记录是从哪里来的，记录在哪个资产下面。我们平时用 Beancount 做的记录其实都是会计账户。&lt;/p&gt;
&lt;p&gt;可以用一个简单的方法来区分 &lt;code&gt;Custody&lt;/code&gt;和&lt;code&gt;Accounting&lt;/code&gt;：在记录一笔交易时，如果银行账户余额会随着交易完成而有所变动，则记录在 &lt;code&gt;Custody&lt;/code&gt; 目录下。反之，如果交易不会导致银行余额变化，则记录在 &lt;code&gt;Accounting&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;资产区分&lt;/h2&gt;
&lt;p&gt;Beancount 的使用始于开设帐号，好的账户设计是成功的一半，假设我个人的美国银行帐号有 $5000 美金，团队账户初始资金为 $0：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-01 * &quot;Initial Account&quot;
  Assets:US:BofA:Joint:Personal               5000.00 USD
  Assets:US:BofA:Joint:Team                         0 USD
  Equity:Opening-Balances
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我使用了 &lt;code&gt;Joint&lt;/code&gt; 关联帐号，而没有给 &lt;code&gt;Team&lt;/code&gt; 单独开独立的虚拟账户主要有两个原因：一是因为这两个帐号的资金就是在同一个银行账户里，同时有能做到很好的区分。比如：当产品有收入从苹果转账进来时，可以直接入账团队账户，个人支出走个人账户：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-05 * &quot;Income from Apple&quot;
  Income:Apple:ProductA                         -500 USD
  Assets:US:BofA:Joint:Team                      500 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;2024-01-02 * &quot;Netflex&quot; &quot;Purchase Subscription&quot;
  Assets:US:BofA:Joint:Personal                  -9.9 USD
  Expenses:Netflex                                9.9 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;二是这样用 beancount 的 &lt;code&gt;balance&lt;/code&gt; 校验时，可以用一行同时校验两个账户的总和，而这个总和恰好应该等于银行账户的余额：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-06 balance Assets:US:BofA             5490.1 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比较上下两种校验的写法，可以看到这里使用了 beancount “父结点等于所有子节点之和”这一特性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-06 balance Assets:US:BofA:Joint:Personal  4990.1 USD
2024-01-06 balance Assets:US:BofA:Joint:Team      500 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这样的账户设置，就可以将同一个银行账户中，原来“不分你我”的资金，清晰的区分成个人和团队资金。下面我们再来看看如何从个人账户借款给团队账户。&lt;/p&gt;
&lt;h3&gt;借款与还款&lt;/h3&gt;
&lt;p&gt;初期阶段，团队资金为 0，团队花费的一些花费需要向我的个人账户借款购买，比如：域名，开发者证书等。等团队有了收入，再把这笔借款还上。以团队购买“苹果开发者许可”为例，来看一下一个完整的借贷过程是如何在 Beancount 中记录的。&lt;/p&gt;
&lt;p&gt;首先在我个人账户里记录一笔应收帐款，表示借给了团队 99 刀：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-15 * &quot;Team&quot; &quot;Lend Money to Team&quot;
  Assets:LA:Receivables:Profity                    99 USD
  Assets:US:BofA:Joint:Personal                   -99 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是此时团队账户Assets:US:BofA:Joint:Team 账户里并没有钱，因此还要从团队账户的角度，记录一笔借款债务，并将借来的钱入库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-15 * &quot;Team&quot; &quot;Borrow Money From Personal&quot;
  Liabilities:Team:Payable:Personal               -99 USD
  Assets:US:BofA:Joint:Team                        99 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以进行购买了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-15 * &quot;Apple&quot; &quot;Purchase Apple Developer License&quot;
  Assets:US:BofA:Joint:Team                      -99 USD
  Expenses:Apple:License                          99 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样一笔记录分别记两笔看起来很麻烦，能不能直接从我的账户转钱给团队账户呢？Beancount 是允许这么操作的，但是缺点是，直接转账在账目看不到借款记录，将来可能就会忘记归还。而如果用了这样标准的出借流程，我个人的资产 Assets 里面有一笔应收帐款 99 USD，同时团队债务 Liabilities 里面也有一笔 -99 USD 的应付账款，就很清晰的标明有一笔借款未还。&lt;/p&gt;
&lt;p&gt;还款的流程刚好相反，你也是需要做两笔记录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;团队账户偿还借款债务：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;2024-01-06 * &quot;Personal&quot; &quot;Pay Back Borrowed Money&quot;
  Assets:US:BofA:Joint:Team                       -99 USD
  Liabilities:Team:Payable:Personal                99 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;个人账户确认收款入账&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;2024-01-06 * &quot;LA&quot; &quot;Received Lend Money&quot;
  Assets:Personal:Receivables:Team                -99 USD
  Assets:US:BofA:Joint:Personal                    99 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了更严格的&lt;code&gt;Custody&lt;/code&gt; 里面的每一笔交易，最终的结果都能和银行余额对上，可以使用插件 &quot;beancount.plugins.mark_unverified&quot;，强制每一笔交易之后都必须有一个 balance 的检查，否则这条 txn 会被打上 unverified 的 metadata，这个metadata 信息在使用 fava 时，可以在 journal 视图中被标记出来。&lt;/p&gt;
&lt;h2&gt;团队成员如何查看自己账目&lt;/h2&gt;
&lt;p&gt;首先假设有团队账户有一笔收入,将这笔收入入账到团队账户:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-05 * &quot;Income from Apple&quot;
  Income:Apple:ProductA                         -500 USD
  Assets:US:BofA:Joint:Team                      500 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这笔收入在归还购买开发许可的欠款之后，还剩 401 USD&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-21 balance Assets:US:BofA:Joint:Team     401 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设我们团队有 5 个人，每人分 80 USD，由于这个操作只是把资金分给团队成员的子账户上，并没有真的通过转账，打到团队成员的银行上，因此这个操作不会改变银行账户余额，所以这些记录放在 &lt;code&gt;Accounting&lt;/code&gt; 这边。具体记录如下：&lt;/p&gt;
&lt;p&gt;首先为每个团队成员在团队账户下创建一个子账户，然后从团队角度给员工发工资：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-25 * &quot;Salary&quot;
  Assets:US:BofA:Joint:Team                     -400 USD
  Expenses:Salary:Alice                           80 USD
  Expenses:Salary:BoB                             80 USD
  Expenses:Salary:Carol                           80 USD
  Expenses:Salary:David                           80 USD
  Expenses:Salary:Personal                        80 USD ;myself in the team
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再以员工角度使用 Income 收工资:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-25 * &quot;Salary&quot;
  Assets:US:BofA:Joint:Team:Alice                 80 USD
  Income:Salary:Team                             -80 USD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样记两笔虽然麻烦,但是的好处多多: 当这两本账目单独看的时候，都是完整的，符合 Beancount 记账规则的完整账目；而合起来看的时候，income 和 expenses 也都有相应的记录可以对账。&lt;/p&gt;
&lt;p&gt;当员工需要把钱从虚拟账户里转到自己的账户里时，可以记录为 Expenses:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-30 * &quot;Alice&quot; &quot;Transfer Money to her account&quot;
  Assets:US:BofA:Joint:Team:Alice                -80 USD
  Expenses:Salary:Alice
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如何让团队成员随时查看呢，这时候就要用到 github 了，在 Github 上为每个员在组织账户里创建一个私有 repo，用来放 beancount 文件，例如：&lt;code&gt;example-team/alice&lt;/code&gt;，然后给该员工只读权限，最后也是最重要的一步，你把这个账户作为 submodule 克隆回到我们的总账目录下面。
这样员工可以随时查看他自己的账目，而我也可以在总账目下做完修改，通过提交 submodule 的修改，把账目同步出去即可。&lt;/p&gt;
&lt;h2&gt;发股票&lt;/h2&gt;
&lt;p&gt;在会计准则里，股票发行总量，会提现在 Equity 里面，以负数的形式来记录，比如说，我们 5 个人，股票均分，一个人 20%，Equity 应该减记股票 Share -100%。 由于 beancount 不支持百分比，因此就可以记录如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-01 * &quot;Alice&quot; &quot;Team Share&quot;
  Assets:US:BofA:Joint:Team:Alice                 20 SHARE
  Equity:Team:Share
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加一条记录,确认一下股票没有超发:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-01-30 balance Equity:Team:Share            -100 SHARE
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最后总结&lt;/h2&gt;
&lt;p&gt;Beancount 有很多灵活的特性，它不但可以为个人记账，还可以为团队记账。我们使用“资产类别：组件：组件：组件”的方式，来定义出不同的账户，包括个人账户，团队账户，以及团队成员账户,&lt;code&gt;父结点等于所有子节点之和&lt;/code&gt;这一特性非常好用。同时利用“每个文件都是一个单独账本入口”的特性，让这些账户都可以独立浏览。&lt;/p&gt;
&lt;p&gt;当然，灵活性也是有代价了，在借贷过程中，需要重复记录两条数据，才能使每条记录”件件又着落“。我们还利用beancount 的纯文本文件，配合 github 为每个人开设了独立的账本空间，实现了简单的权限控制。最后还用到多币种的特性，为股票发放做了记录。&lt;/p&gt;
&lt;p&gt;可以在 GitHub 上找到完整的例子源码: &lt;a href=&quot;https://github.com/kidylee/teamcount&quot;&gt;https://github.com/kidylee/teamcount&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>How to Add API Key Access Method for Spring Projects</title><link>https://kidylee.com/blog/en/adding-apikey-auth-in-existing-spring-project/</link><guid isPermaLink="true">https://kidylee.com/blog/en/adding-apikey-auth-in-existing-spring-project/</guid><description>How to Add API Key Access Method for Spring Projects</description><pubDate>Sun, 28 Aug 2022 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Most technical SaaS platforms provide APIs for developers or to support clients and SDKs, and our company is no exception. Although saving OAuth2 tokens in browsers is convenient, tokens have expiration times and are issued by third parties, which can be a potential risk for clients and SDKs. Often, issues with third-party authentication services cause users to be unable to log in. Therefore, providing API Key access has become a better choice.
&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;p&gt;There are two approaches to adding API Key access:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reuse the existing Web API, introducing API Key authentication in parallel with the existing OAuth2-based services&lt;/li&gt;
&lt;li&gt;Start a completely separate service, providing consistent services through code sharing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both options have their pros and cons. In the early and middle stages of a product, when web and API access methods are roughly the same, the first approach may be better. However, when a project enters the middle and later stages, complete isolation may be a better choice considering the differences between web and API in terms of functionality and security. Since most of the web functionalities are consistent, we decided to add a parallel API Key authentication method on top of the existing OAuth2 authentication in the backend, making it more convenient for users.&lt;/p&gt;
&lt;p&gt;This article mainly introduces the best practices for adding API Key authentication to existing Spring projects.&lt;/p&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This method is only applicable to Spring Security &amp;lt;= 5.2.2 (Spring Boot &amp;lt;2.2.5). The latest Spring Security 5.7 (Spring Boot ^2.3.0) has bugs, and the API will undergo significant changes, making it unsuitable for this method. See the end of the article for details.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Obtaining the API Key and Generating AuthenticationToken&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@Component(&quot;apiKeyFilter&quot;)
public class APIKeyFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorization = request.getHeader(&quot;application-api-key&quot;);
        if (authorization != null &amp;amp;&amp;amp; SecurityContextHolder.getContext().getAuthentication() == null) {
            SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
            emptyContext.setAuthentication(new APIKeyAuthenticationToken(authorization));
            SecurityContextHolder.setContext(emptyContext);
        }
        filterChain.doFilter(request, response);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Get the API Key by checking the HttpRequest header, wrap it in a custom &lt;a href=&quot;https://github.com/kidylee/spring-new-authentication/blob/master/src/main/java/com/kidylee/spring/security/addnewauthentication/apikey/APIKeyAuthenticationToken.java&quot;&gt;Authentication Token&lt;/a&gt;, and place it in the SecurityContext. Note that because Servlets operate in a multi-threaded environment, you should first create a new empty context and then set it back using setContext, rather than directly using &lt;code&gt;SecurityContextHolder.getContext().setAuthentication(token)&lt;/code&gt;, which can lead to race conditions.&lt;/p&gt;
&lt;h3&gt;Custom APIKeyAuthenticationProvider&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class APIKeyAuthenticationProvider implements AuthenticationProvider {

    UserDetailsService userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String apiKey = (String) authentication.getCredentials();
        UserDetails userdetails = null;
        userdetails = userDetailsService.loadUserByAPIKey(authentication.getPrincipal().toString());
        APIKeyAuthenticationToken newAuth = new APIKeyAuthenticationToken(userdetails, userdetails.getAuthorities());
        newAuth.setAuthenticated(true);
        return newAuth;
    }

    @Override
    public boolean supports(Class&amp;lt;?&amp;gt; authentication) {
        return APIKeyAuthenticationToken.class.isAssignableFrom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ProviderManager&lt;/code&gt; will match all &lt;code&gt;AuthenticationProviders&lt;/code&gt; based on the Token in the &lt;code&gt;SecurityContext&lt;/code&gt;, and the &lt;code&gt;supports&lt;/code&gt; method is used for matching. Once matched, the &lt;code&gt;authenticate&lt;/code&gt; method can be called for authentication. Here, you can obtain user information by querying the database or calling other services. Note that when handling &lt;code&gt;exception&lt;/code&gt;s, any requests that fail authentication must be returned as an exception of the &lt;code&gt;AuthenticationException&lt;/code&gt; subclass, so that Spring Filter can capture the exception and return the corresponding error message.&lt;/p&gt;
&lt;h3&gt;APIKeyAuthenticationProvider Registery&lt;/h3&gt;
&lt;p&gt;After custom this provider, the next step is register it into filter chain.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    final APIKeyFilter apiKeyFilter;
    public SecurityConfiguration(APIKeyFilter apiKeyFilter) {
        this.apiKeyFilter = apiKeyFilter;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new APIKeyAuthenticationProvider());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Work to be done&lt;/h3&gt;
&lt;p&gt;After Spring Security 5.7, &lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt; has been deprecated. The code modified according to the official documentation does not work properly. After the Token is set in the filter, &lt;code&gt;ProviderManager&lt;/code&gt; did not trigger the call to our custom &lt;code&gt;APIKeyAuthenticationProvider&lt;/code&gt;. This is probably a bug. In the community, there is an issue that combines the &lt;code&gt;AuthenticationManager&lt;/code&gt; and &lt;code&gt;AuthenticationProvider&lt;/code&gt; interfaces, so the solution to the problem with version 5.7 has been temporarily postponed.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Compared with other languages, Java has a rich set of components and frameworks, but this is a double-edged sword: having many components allows for faster development speed, but also increases the learning cost. Spring Security can quickly complete 90% of the work, but the remaining 10% of special requirements may take three to five times longer to configure and debug.&lt;/p&gt;
&lt;p&gt;I prefer the philosophy of being precise and efficient: if you haven&apos;t started using Java or Spring, I recommend choosing a simpler language and framework, such as Golang or Python; if you have already started using Spring Security, it is best to study it thoroughly and make full use of its features. Of course, as an open-source framework, Spring Security&apos;s design and implementation are worth learning.&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Source code：&lt;a href=&quot;https://github.com/kidylee/spring-new-authentication&quot;&gt;https://github.com/kidylee/spring-new-authentication&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-security-authentication-provider&quot;&gt;Spring Security Custom Authentication Provider&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/architecture.html&quot;&gt;Architecture :: Spring Security.&lt;/a&gt;  Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html&quot;&gt;Servlet Authentication Architecture :: Spring Security&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter&quot;&gt;Spring Security without the WebSecurityConfigurerAdapter. 21 Feb. 2022&lt;/a&gt; Accessed 29 Aug. 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Adding Ambient Light Brightness Adjustment to External Monitors</title><link>https://kidylee.com/blog/en/adding-auto-brightness-to-external-monitor/</link><guid isPermaLink="true">https://kidylee.com/blog/en/adding-auto-brightness-to-external-monitor/</guid><description>Adding Ambient Light Brightness Adjustment to External Monitors</description><pubDate>Sat, 09 Jul 2022 16:24:38 GMT</pubDate><content:encoded>&lt;p&gt;macOS automatically adjusts the brightness of its built-in display according to the ambient light, but if you have an external monitor, its brightness will not be adjusted accordingly. As a result, the brightness difference between the two screens will become more noticeable as you work from day to night.&lt;/p&gt;
&lt;p&gt;I&apos;ve looked online for some open-source and commercial solutions, but I wasn&apos;t satisfied with them, mainly because they all occupy a menu bar slot, which is unnecessary for a system that should automatically adjust its brightness. Additionally, some of these solutions come with features that are not really needed, such as color and temperature adjustments, and some even require a subscription just to sync the brightness levels.&lt;/p&gt;
&lt;p&gt;This solution is very simple, using only &lt;code&gt;AppleScript&lt;/code&gt; and &lt;code&gt;Shell&lt;/code&gt;, and it has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically adjusts brightness levels&lt;/li&gt;
&lt;li&gt;Starts up automatically upon boot&lt;/li&gt;
&lt;li&gt;Does not occupy the dock or menu bar items.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, there is still room for improvement due to my limited abilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A more elegant way of obtaining the current brightness level of the system, currently using two regular expressions.&lt;/li&gt;
&lt;li&gt;Listening to system brightness adjustment events instead of monitoring them every 30 seconds with a timer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Workflow&lt;/h1&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The monitor must support ddc/ci.&lt;/li&gt;
&lt;li&gt;The brightness adjustment tool provided by ddctl is needed:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;brew install ddcctl
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Let&apos;s begin&lt;/h2&gt;
&lt;h3&gt;Scripting&lt;/h3&gt;
&lt;p&gt;Open the &lt;code&gt;Script Editor&lt;/code&gt; app, copy the code below, and save it as &quot;AutoBrightness&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on idle --设置定时任务
	set brightness_num to do shell script &quot;ioreg -c AppleBacklightDisplay | grep -oE &apos;(?:\&quot;brightness\&quot;).*?(\&quot;value\&quot;).*?},&apos; | grep -oE &apos;\&quot;value\&quot;=(\\d{1,5})&apos;|awk -F= &apos;{print $2}&apos;&quot; -- help needed.
	set brightness_num to (brightness_num / 65535) * 100 --单位转换
	do shell script &quot;/usr/local/bin/ddcctl -d 1 -b &quot; &amp;amp; brightness_num
	return 30
end idle
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When saving the file, choose &quot;Application&quot; as the file format, otherwise the timer won&apos;t trigger.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Setup Launch at Login&lt;/h3&gt;
&lt;p&gt;System Preferences -&amp;gt; User &amp;amp; Groups -&amp;gt; (tab)Login Items -&amp;gt; Add AutoBrightness&lt;/p&gt;
&lt;h3&gt;Hide Application Icon&lt;/h3&gt;
&lt;p&gt;Right-click on &lt;code&gt;AutoBrightness.app&lt;/code&gt; -&amp;gt; Show Package Contents -&amp;gt; Contents
Open info.plist
Insert the following code in the first &lt;code&gt;&amp;lt;dict&amp;gt;&amp;lt;/dict&amp;gt;&lt;/code&gt; block, then save and exit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;key&amp;gt;LSUIElement&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;1&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EOF&lt;/p&gt;
</content:encoded></item><item><title>F**k 443</title><link>https://kidylee.com/blog/fuck-443/</link><guid isPermaLink="true">https://kidylee.com/blog/fuck-443/</guid><description>家庭宽带访问问题的完美解决方案：利用 Cloudflare Rules 避开端口监控</description><pubDate>Fri, 25 Apr 2025 07:58:26 GMT</pubDate><content:encoded>&lt;p&gt;众所周知，国内家庭宽带的网络接口 80 和 443 是被监控的，如果这两个接口有外部 http 访问，过一段时间，运营商就会阻断所有发往这两个端口的请求。这样一来，我们就不得不使用带有端口的网址，例如 https://nas.kidylee.com:8443 来访问自己搭建的服务。&lt;/p&gt;
&lt;p&gt;Cloudflare 提供的 Rule 功能可以完美地解决这个问题。它的基本原理是：Cloudflare 会代理接收所有发往域名的请求，在转发到真实地址之前，可以对请求应用一些规则，比如更改转发端口。&lt;/p&gt;
&lt;p&gt;具体设置如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在路由器上设置端口映射功能，将外网接收端口改为 8443，确认 &lt;a href=&quot;https://nas.kidylee.com:8443&quot;&gt;https://nas.kidylee.com:8443&lt;/a&gt; 可以正常访问。&lt;/li&gt;
&lt;li&gt;打开 Cloudflare，选中域名，在该该域名下创建一个 Rule： F**k 443， 选择： &lt;em&gt;Custom filter expression&lt;/em&gt;, 在 Field 里选择 URI Full - wildcard - https://*.kidylee.com/* 。我的这个配置是将所有的子域名，全部选中，你也可以只选一个域名。最后的*不能省略，否则域名下的资源则无法访问。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Destination Port&lt;/em&gt; 选择 Rewrite to... ， 这里填写 8443. 之后保存即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在完成上述配置后，既可以在 url 上免去端口了: https://nas.kidylee.com:8443。&lt;/p&gt;
</content:encoded></item><item><title>Homelab - 基础设施</title><link>https://kidylee.com/blog/homelab/</link><guid isPermaLink="true">https://kidylee.com/blog/homelab/</guid><description>自己搭建 Homelab 实现远程访问与自动 SSL 证书管理</description><pubDate>Fri, 25 Apr 2025 23:52:24 GMT</pubDate><content:encoded>&lt;p&gt;以前我算是个云服务爱好者，存储和算力全都跑在云上。虽然家里也有群晖 NAS 和旁路由，但整体还是追求稳定，开箱即用就好，从不折腾太多。&lt;/p&gt;
&lt;p&gt;直到最近国补活动，我入手了一台不到 ¥3000 的 Mac mini——比起每年三五千起步的云服务费，便宜太多。而且家里本来就有 NAS，可以实现存储和计算分离，所以就决定搞个 Homelab，搭建自己的 deepseek 服务、数据分析平台等等。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;当前的硬件设备：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;光猫 ×2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;电信光猫：¥200/月，送 5 张电话卡无限流量&lt;/li&gt;
&lt;li&gt;联通光猫：今年免费，明年起 ¥9/月&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;硬件旁路&lt;/strong&gt;
2019 年在淘宝买的老设备，两千多，性价比一般，性能也不强，先将就用着，等坏了就拿国补 Mac mini 顶上。当前基于 ESXi 跑了 iKuai + OpenWRT：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;iKuai 负责双宽带拨号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenWRT 负责旁路由功能&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;群晖 NAS&lt;/strong&gt;
作为存储单元，性能太弱，跑不了高负载计算任务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;国补 Mac mini&lt;/strong&gt;
作为计算单元，SSD 太贵，不适合存储，刚好和 NAS 互补&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Infra 软件组件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dnsmasq&lt;/strong&gt;（OpenWRT 自带）
主要提供 DHCP 和本地 DNS 域名解析。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于使用了旁路由模式，需要“翻墙”的设备，其网关设置为 OpenWRT；不需要翻墙的设备，则将网关设为 iKuai。&lt;/p&gt;
&lt;p&gt;本地 DNS 还可以用来给设备分配友好的域名，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;群晖：&lt;code&gt;nas.kidylee.com&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相册服务：&lt;code&gt;photo.kidylee.com&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在局域网内可以直接用这些域名访问设备。如果想要这些域名在外网也能访问，还需要用到两个软件：&lt;strong&gt;Caddy&lt;/strong&gt; 和 &lt;strong&gt;ddns-go&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Caddy&lt;/h3&gt;
&lt;p&gt;Caddy 是一个 https 服务器，支持反向代理，并内置了自动申请 SSL 证书的功能。&lt;/p&gt;
&lt;p&gt;比如：局域网发起对 &lt;code&gt;nas.kidylee.com&lt;/code&gt; 的请求，Dnsmasq 会将域名解析为 Caddy 所在机器的地址，Caddy 接到请求后再代理到群晖 NAS 上。这样，局域网的服务也能跑在 https 上。&lt;/p&gt;
&lt;p&gt;外网访问时，域名先经 Cloudflare 解析到家里的公网 IP，通过 iKuai 的端口转发再传给 Caddy，同样也能访问服务。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;ddns-go&lt;/h3&gt;
&lt;p&gt;为了让外网请求能准确找到家里的公网 IP，我们需要动态更新域名解析记录，这就是 ddns 的作用。&lt;/p&gt;
&lt;p&gt;我用的是 &lt;strong&gt;ddns-go&lt;/strong&gt;，它原生支持 Cloudflare，配置简单，只要提供 Cloudflare 的 API，就能自动更新 IP，甚至可以直接在它的 Web UI 里新增子域名，不需要登录 Cloudflare 页面，超方便。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;openclash&lt;/h3&gt;
&lt;p&gt;这个就不用多说了，懂的都懂。&lt;/p&gt;
&lt;p&gt;但需要特别注意一点：Caddy 所在的机器，网关&lt;strong&gt;必须指向 iKuai，而不是 OpenWRT&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因为 OpenClash 在 OpenWRT 上使用防火墙转发请求，对于内网向外的访问没问题，但对于&lt;strong&gt;外网访问内网&lt;/strong&gt;的请求，Clash 分不清方向，会直接断掉连接。&lt;/p&gt;
&lt;p&gt;我本来打算魔改 Clash 来解决，后来一想还是要尊重“&lt;strong&gt;Do one thing, do it well&lt;/strong&gt;”的原则，索性把 Caddy 安装到群晖上，再把群晖的网关指到 iKuai——群晖本身不需要翻墙，这样设置最稳。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;云服务&lt;/h2&gt;
&lt;p&gt;目前唯一还用的云服务就是赛博菩萨——&lt;strong&gt;Cloudflare&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;除了做 DNS 服务，我还主要用它的 Rule 功能。&lt;/p&gt;
&lt;p&gt;众所周知，国内宽带对家庭用户的 80 和 443 端口有监听。如果你在这两个端口上跑了 HTTP 服务，过一阵子这些端口就会被封掉。所以我一般只能用像 &lt;code&gt;https://nas.kidylee.com:8443&lt;/code&gt; 这种非常规端口来访问服务。&lt;/p&gt;
&lt;p&gt;但通过 Cloudflare 的 Rule，我可以将发往 443 的请求转发到 8443 上，从而实现无端口的访问方式，配置名我就干脆叫它 “fuck 443”。可以参考另外一篇文章具体设置： https://kidylee.com/blog/fuck-443/&lt;/p&gt;
&lt;h2&gt;这样的配置带来了几个好处：&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内外网访问一致&lt;/strong&gt;：
无论在家还是在外，访问服务都是同一个域名，局域网访问时不会绕外网再回来，速度快、体验佳。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;可使用 443 端口访问服务&lt;/strong&gt;：
避免因 443 被封影响访问，同时又保留了 https 的兼容性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;密码软件支持更友好&lt;/strong&gt;：
密码管理工具一般只识别域名，不区分端口。如果多个服务都在同一个域名 + 不同端口下，密码软件很容易搞混，甚至覆盖掉原密码。
现在每个服务一个独立子域名 + 独立证书，密码软件识别准确，https 访问也更安全。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>独立开发？ 没问题！但和你想的可能不太一样</title><link>https://kidylee.com/blog/review-the-minimalist-entrepreneur/</link><guid isPermaLink="true">https://kidylee.com/blog/review-the-minimalist-entrepreneur/</guid><description>《小而美 —— 持续盈利的经营法则》读后感</description><pubDate>Tue, 25 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2023年的今天，是否还值得尝试独立开发这条路？答案是肯定的，但方式可能和你想象的不一样。现在应用市场已经不再是一片蓝海，任何一个拍个脑门想出来的app，投放在app store，都能有不错的收入。靠碰运气发财的阶段已经过去，而独立开发者需要进化到下一阶段，这本由&lt;a href=&quot;https://twitter.com/gumroad&quot;&gt;@gumroad&lt;/a&gt;创始人&lt;a href=&quot;https://twitter.com/shl&quot;&gt;@shl&lt;/a&gt;编写的《小而美》就是一本独立开发的进化指南。&lt;/p&gt;
&lt;p&gt;对于独立开发来说，要将自己进化成一个六边形战士：产品，销售，市场，研发，运营，以及组建团队，各个方面强度都要想尽办法去拉满。幸运的是，针对这六个方面，本书不止给出了具体示例，也给出了可操作的方法：&lt;/p&gt;
&lt;h3&gt;产品&lt;/h3&gt;
&lt;p&gt;曾经看过一档真人秀节目《Undercover Bilionary》，请千万级富豪在一个陌生的城市，从100刀开始白手起家，第一位富豪在尝试销售小商品，接连受挫之后，回想起了创业时的心法：先找到有需求的客户，而不是先有产品再去找客户。这个道理看似简单，但大部分创业者做不到，作者这本书中给出了类似的建议：先找到community，然后围绕community打造产品。但有时候社区可能还没有被建立起来，那么可以先从Reddit等社交平台作为起点，打造这么一个社区，构建社区的过程，也是验证想法的过程。&lt;/p&gt;
&lt;h3&gt;研发&lt;/h3&gt;
&lt;p&gt;研发是老生常谈了，不要憋大招，尽快打造出原型产品并收费。快速推出产品以便听取社区的建议，进一步验证和优化产品。收费是进一步筛选优质客户，也是验证产品是否可以变现的手段。&lt;/p&gt;
&lt;h3&gt;销售&lt;/h3&gt;
&lt;p&gt;销售是程序员的薄弱环节，这本书也给出了具体建议：可以通过社区或朋友，找到前100个客户，并维护好关系。冷启动的Email销售看起来很慢，但实际比投放广告好得多，需要注意的是，需要放弃以“说服”对方使用产品的策略，转变为使对方“发现”你的产品。重视每一个早期用户，他们帮助你推销你的给他的朋友或他所在的社区，从而形成二次销售或病毒是传播。&lt;/p&gt;
&lt;h3&gt;市场&lt;/h3&gt;
&lt;p&gt;作者对于如何做市场有一些具体的操作建议，他推崇的方式是market by being you，也就是营建个人的影响力，他给了一个可以说是终极策略：发送有价值的信息。不只是实用价值，也可以是情感价值。同时也列举了一些具体的技巧：在社交媒体发送的内容可以从一下三个角度出发：教育（培养用户），启发用户和娱乐，当然如果在一条推文里能够三位一体是最好的。&lt;/p&gt;
&lt;h3&gt;运营和团队&lt;/h3&gt;
&lt;p&gt;你需要和产品共同成长，尽量多的利用软件和AI帮助，让自己do more with less。在组建团队之前，尽量多的事情外包出去，包括但不限于：行政，客服，设计，甚至研发等工作，选择合伙人要慎之又慎。&lt;/p&gt;
&lt;p&gt;市场的空间还是很大的，不只有大投资，高杠杆；也有细分领域，小的投入就有足够的产出，在裁员的大背景下，值得每个程序员和有志创业的人读一读这本书并去实践。&lt;/p&gt;
</content:encoded></item><item><title>山姆奥特曼的笔记系统</title><link>https://kidylee.com/blog/the-note-taking-system-you-ever-needed/</link><guid isPermaLink="true">https://kidylee.com/blog/the-note-taking-system-you-ever-needed/</guid><description>打造和山姆奥特曼一样的笔记系统</description><pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { YouTube,LinkPreview } from &apos;astro-embed&apos;
import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;最近,OpenAI 的 CEO Sam Altman 在一档写作播客节目中分享了他多年来精心打磨的笔记系统。作为一个热爱记录的人,这个话题立即引起了我的兴趣。更巧的是,我当时正好在日本,可以直接去实地探索他提到的这些要求。原视频：&lt;/p&gt;
&lt;p&gt;&amp;lt;YouTube id=&quot;https://www.youtube.com/watch?v=6pxmdmlJCG0&quot;/&amp;gt;
&amp;lt;LinkPreview id=&quot;https://www.youtube.com/watch?v=6pxmdmlJCG0&quot; hideMedia /&amp;gt;&lt;/p&gt;
&lt;h2&gt;奥特曼的笔记本标准&lt;/h2&gt;
&lt;p&gt;与普通人可能想象的不同,奥特曼并不推崇昂贵的笔记本,而是更注重实用性。他的选择标准包括:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;便携性&lt;/strong&gt; - 必须能装进口袋,随时携带&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;螺旋装订&lt;/strong&gt; - 这点颇为出人意料。传统观念认为线装本更专业,但螺旋装订有其独特优势:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;可以插笔&lt;/li&gt;
&lt;li&gt;方便撕下同主题的页面进行整理&lt;/li&gt;
&lt;li&gt;任意页面都能平铺&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;硬皮保护&lt;/strong&gt; - 确保在各种环境下都能稳定书写&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;在友达八喜的寻宝之旅&lt;/h2&gt;
&lt;p&gt;作为一个技术宅,我特别喜欢友达八喜。与其他商场一楼摆满奢侈品不同,这里整层都是文具的天堂。除了参考奥特曼的标准,我还加入了自己的几点要求:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本子需要在国内有售,方便后续购买&lt;/li&gt;
&lt;li&gt;笔的尺寸要与本子螺旋圈完美匹配&lt;/li&gt;
&lt;li&gt;考虑到经常需要绘制图形,要找一款不易积油的笔&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最终的选择&lt;/h2&gt;
&lt;p&gt;在深入探索两小时后,我找到了理想的组合:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记忆女神 197 笔记本&lt;/li&gt;
&lt;li&gt;斑马 tapliclip 0.5 中性笔&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;Image src={&quot;https://img.kidylee.com/IMG_2086.jpeg&quot;}
width=&quot;800&quot;
height=&quot;600&quot;
alt=&quot;Notebook and pen.&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;这两款产品都价格适中,在淘宝上容易买到。最重要的是,它们都能带来那种令人愉悦的高品质触感 —— 这种感觉,只有当你真正拿在手中才能体会。&lt;/p&gt;
&lt;p&gt;选对了工具,写作和记录的过程本身就成了一种享受。&lt;/p&gt;
</content:encoded></item><item><title>GPG 实践手册</title><link>https://kidylee.com/blog/what-is-gpg/</link><guid isPermaLink="true">https://kidylee.com/blog/what-is-gpg/</guid><description>密钥管理最佳实践</description><pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { YouTube,LinkPreview } from &apos;astro-embed&apos;
import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;非对称加密的使用有一个大前提：对方需要先知道你的公钥，这就涉及到两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;公钥对机器友善，但让人来管理公钥是非常麻烦的，例如很难区分下面两段公钥：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;公钥的分发也需要一种可信的机制： 首先需要独立的第三方帮助认证&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GnuPG 可以理解为——为公钥分发提供了解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为解决公钥不好管理的问题，提出了两个解决方案：
1. 为公钥提供一个短名称
2. 为本地提供一个管理公钥的客户端&lt;/li&gt;
&lt;li&gt;提供 key server 网络设施，可以认证公钥，例如，在网站上下载软件时，会提供一个 MD5 的哈西值来验证软甲的完整性，但还缺少一个签名，用以确认软件是由谁来分发的，可以利用下面命令，来对分发的软件进行签名（*），客户在拿到这个命令之后，可以用下面的命令去验证&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于个人来说，不同场景下，需要用到不同的&lt;/p&gt;
</content:encoded></item></channel></rss>