<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>信言软件博客</title><link>https://blog.xinyanruanjian.com/</link><description>技术改变生活</description><item><title>自由职业程序员该如何报价？</title><link>https://blog.xinyanruanjian.com/post/16.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;最近接到一个App升级的需求，我负责Android端，另外的兄弟负责iOS端。在报价的时候，我报了W为单位的价格，iOS的兄弟报了K单位的价格，形成了数量级的差别……&lt;br&gt;我只好向客户解释并不是我胡乱报价，而是可以计算的，计算方法是：假设公司在当地找一个人全职做，他一个月的用人成本除以大概22天就是日薪，他修改&amp;amp;测试一个模块要多少时间，总共几个模块，一相乘就可以得出结论了。然而客户坚决不能接受两端报价差异如此悬殊。&lt;br&gt;之前说过，不建议接低价单，这里再提一个不成熟的小建议：评估项目周期和报价尽量不要乐观估计。&lt;br&gt;我以前犯过很多这种“乐观主义”的错误，导致身心俱疲。&lt;br&gt;技术人员容易以技术难度为主要因素衡量各种问题，但实际上业务逻辑、测试、沟通等等可能是花费时间精力更多的方面。所以无论你第一直觉认为这个项目需要花费多少时间，一定要保留相当的缓冲时间。即使是团队开发，人力资源非常充足，也要注意“人月神话”的陷阱。&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Wed, 16 Apr 2025 14:28:44 +0800</pubDate></item><item><title>关于开源的一些思考</title><link>https://blog.xinyanruanjian.com/post/15.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;今天登录了一下QQ，发现有一条加群的申请，是这样的：  &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2025/03/202503291124358345203.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;事情的起因是这样的：我最近在兼职开发和维护一个“纯血鸿蒙”的项目，由于“纯血鸿蒙”发布还不太久，生态自然是不太完善，导致有一些需求没有现成的组件可用，于是我自己开发并开源了几个组件上传到了OpenHarmony三方库中心仓，不知道这位在使用过程中遇到了什么问题，遂心生不满。   &lt;/p&gt;
&lt;p&gt;年轻的时候，我还对技术充满了热情，沉迷于各种“XXX编程思想”、“代码整洁之道”、“架构整洁之道”，热衷于阅读各种技术大佬的文章、开源代码，希望有一天也能达到这些大佬们的高度。那时的我看到这种言论，会一面毫不客气地回怼you can you up并用一些中国传统文化用语问候他家里长辈，一方面暗下决心提高自己的技术让自己的作品无懈可击！  &lt;/p&gt;
&lt;p&gt;而现在，面对这种事情，更多的是无感。我并不靠这几个组件赚钱，把它们开源出来也不意味着我有责任和义务帮助任何人解决他们的问题。他们如何评价，对我也产生不了实质性的影响。像本文开头提到的这位的行为，除了让我意识到世界上有这么一号S13，没有任何意义。  &lt;/p&gt;
&lt;p&gt;但要说是完全无感也不准确，它引发了我对开源的一些思考。以我对开源的贡献——事实上绝对可以说是：毫无贡献——来说，其实也谈不上什么思考不思考的，就是一些乱七八糟不成条理的想法。  &lt;/p&gt;
&lt;p&gt;常有人说开源不等于免费，但实际上开源等于免费的观念早已经深入人心了，尤其是以Apache、MIT和BSD这类协议开源的项目。其中更有甚者潜意识里认为使用你的开源项目是给你面子，有任何问题开源作者必须无条件负责到底，而这些人往往对开源社区又没有什么贡献，甚至连ISSUE都不会好好提，问题也不会描述清楚，只会无限制地消耗开源作者的时间、精力和热情。这也就是俗称的“伸手党”。  &lt;/p&gt;
&lt;p&gt;一直以来，开源作者对于“伸手党”可以说是深恶痛绝。我问DeepSeek伸手党对开源的贡献，DeepSeek回答：  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;伸手党是开源的双刃剑：短期增加维护负担，长期看却是生态活跃的基石。高效社区管理（如自动化、分层贡献机制）能将其转化为潜在贡献者，而纯粹排斥可能削弱项目影响力。开源的本质是协作，不同角色的共存——包括被动的使用者——共同构成了完整的生态链。 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“伸手党是生态活跃的基石”这就多少有点搞笑了，说是开源生态的寄生虫更为贴切。另外，“伸手党”也不只是个人开发者，很多商业公司也是一样。近些年来，知名开源项目作者因为其项目被商业公司用于大量盈利产品自己却没有得到经济回报，而商业公司甚至都不愿意参与维护项目，愤而删库跑路的事情常有发生。大名鼎鼎的&lt;code&gt;OpenSSL&lt;/code&gt;在2014年“Heartbleed”漏洞爆发时，人们惊讶地发现其核心维护者仅靠兼职维护，年收入不足2000美元。  &lt;/p&gt;
&lt;p&gt;我本身并不反对在开源协议允许的范围内免费使用开源项目，因为在今天来说这几乎是每个开发者不可避免的事情，每个人都一样，我也一样，只是有一个前提：对他人的劳动和付出保持最起码的尊重。  &lt;/p&gt;
&lt;p&gt;人们往往习惯了为有形的产品付费，比如去早餐店买个面包当早餐是需要花钱的，这是所有人的共识。而无形的产品背后的各种劳动成本往往被忽略，甚至得不到尊重。比如在各种应用市场的App下面，我们常常能看到一些类似“这个破软件居然还收费”、“收费软件，一星也不想给”的差评，甚至是免费的App加点广告有时候也会被骂得体无完肤。他们并没有意识到这些软件背后的公司、开发者付出了劳动，需要收入来维持公司运转和日常生活。开源项目也是一样，在这些项目背后的开发者，他们有自己的生活，有家庭，有父母，有孩子。可以不喜欢，可以不用，甚至可以讨厌，但是没必要有这么多恶意。  &lt;/p&gt;
&lt;p&gt;我希望无论是开源还是别的什么活动，始终是“以人为本”的。前几年沸沸扬扬的“996.ICU”就是对以人为本的思考。任他杰克马如何舌灿莲花、偷换概念也不可能让大家相信“996”是一种“福报”，因为“996”不以人为本。  &lt;/p&gt;
&lt;p&gt;词不达意，嘎然而止，下次再写哈哈。&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Sat, 29 Mar 2025 11:24:08 +0800</pubDate></item><item><title>oh-date-picker: Openharmony &amp;amp; HarmonyOS平台日期选择器增强版，支持选择年月、年月日、年月日时分等多种格式。  </title><link>https://blog.xinyanruanjian.com/post/14.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;💡 简介&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;💡 简介&lt;/h2&gt;&lt;p&gt;oh-date-picker: Openharmony &amp;amp; HarmonyOS平台日期选择器增强版，支持选择年月、年月日、年月日时分等多种格式。  &lt;/p&gt;
&lt;p&gt;代码仓库：&lt;a href=&quot;https://github.com/sahooz/oh-date-picker&quot;&gt;oh-date-picker&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;⚙️ 下载安装&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;⚙️ 下载安装&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ohpm i @xinyansoft/oh-date-picker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OpenHarmony ohpm 环境配置等更多内容，请参考: &lt;a href=&quot;https://ohpm.openharmony.cn/#/cn/help/downloadandinstall&quot;&gt;下载安装三方库&lt;/a&gt;  &lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;🌅 效果图参考&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;🌅 效果图参考&lt;/h2&gt;&lt;h3 id=&quot;h3-u5E74u6708&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;年月&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;年月&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040347413299.png&quot; alt=&quot;202410102040347413299.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040347413299.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u5E74u6708u65E5&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;年月日&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;年月日&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040341988755.png&quot; alt=&quot;202410102040341988755.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040341988755.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u5E74u6708u65E5u65F6u5206&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;年月日时分&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;年月日时分&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040343390247.png&quot; alt=&quot;202410102040343390247.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040343390247.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u5E74u6708u65E5u65F6u5206u79D2&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;年月日时分秒&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;年月日时分秒&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040347643031.png&quot; alt=&quot;202410102040347643031.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040347643031.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u65F6u5206u79D2&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;时分秒&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;时分秒&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040346728980.png&quot; alt=&quot;202410102040346728980.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040346728980.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u65F6u5206&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;时分&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;时分&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040344384330.png&quot; alt=&quot;202410102040344384330.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040344384330.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u5206u79D2&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;分秒&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;分秒&lt;/h3&gt;&lt;p&gt;&lt;img title=&quot;202410102040344988892.png&quot; alt=&quot;202410102040344988892.png&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040344988892.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;🎗️接口说明&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;🎗️接口说明&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;DateTimePicker({
    // 主要配置项
    config: {
      format: DateTimeFormat.YmdHm, // 时间格式
      start: '1900-01-01 00:00',  // 开始时间
      end: '2099-12-31 23:59', // 结束时间
      selected: DateUtil.getTodayStr('yyyy-MM-dd HH:mm') // 初始选中时间
    },
    // 后缀模式：独立和非独立模
    // 独立模式：后缀是独立的Text控件，不可滚动
    // 非独立模式：每个数据项都带有后缀，跟随数据项滚动
    suffixMode: SuffixMode.Together, 
    suffixes: {  // 后缀文字
      year: '年',
      month: '月',
      day: '日',
      hour: '时',
      minute: '分',
      second: '秒'
    },
    suffixTextStyle: { // 后缀文字样式，仅在后缀模式为独立模式时生效
      font: {
        size: 15
      },
      color: Color.Black
    },
    // 以下三个样式属性参见TextPicker
    selectedTextStyle: {
      font: {
        size: 15
      },
      color: Color.Black
    },
    disappearTextStyle: {
      font: {
        size: 13
      },
      color: Color.Black
    },
    textStyle: {
      font: {
        size: 13
      }
    },
    
    // 选择回调
    onSelectedCallback: (selected) =&amp;gt; {
      this.selected = selected
    },
  
    // 循环模式：Auto，Enable，Disable
    // Auto: 数据项大于3项时可以循环滚动
    // Enable: 无论数据项多少都可以循环滚动
    // Disable: 无论数据项多少都不可以循环滚动
    loopMode: LoopMode.Auto
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;✍️ 使用示例&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;✍️ 使用示例&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { DateTimeFormat, DateTimePicker, DateUtil, SuffixMode } from 'date-time-picker'
import { DateTime, DateTimePickerConfig } from 'date-time-picker/src/main/ets/components/DateTimePicker'

@Entry
@Component
struct Index {
  @State @Watch('conConfigChanged') config : DateTimePickerConfig = {
    format: DateTimeFormat.YmdHm,
    start: '1900-01-01 00:00',
    end: '2099-12-31 23:59',
    selected: DateUtil.getTodayStr('yyyy-MM-dd HH:mm')
  }
  @State suffixMode: SuffixMode = SuffixMode.Together
  @State selected: DateTime = new DateTime(this.config.format, this.config.selected)

  conConfigChanged() {
    this.selected = new DateTime(this.config.format, this.config.selected)
  }

  build() {
    Column() {
      DateTimePicker({
        config: this.config,
        suffixMode: this.suffixMode,
        selectedTextStyle: {
          font: {
            size: 15
          },
          color: Color.Black
        },
        textStyle: {
          font: {
            size: 13
          }
        },
        suffixTextStyle: {
          font: {
            size: 15
          },
          color: Color.Black
        },
        onSelectedCallback: (selected) =&amp;gt; {
          this.selected = selected
        }
      }).width('100%')

      Blank()
      Text(`当前选择：${this.selected.format()}`)
      Blank()

      Row() {
        Button('年月', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.Ym,
              start : '1900-08',
              end : '2100-08',
              selected : '2024-08'
            }
          })

        Button('年月日', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.Ymd,
              start : '1900-08-29',
              end : '2100-08-29',
              selected : '2024-08-29'
            }
          })

        Button('年月日时分', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.YmdHm,
              start : '1900-08-29 12:00',
              end : '2100-08-29 12:00',
              selected : '2024-08-29 12:00'
            }
          })
      }.width('100%')
      .height(52)
      .justifyContent(FlexAlign.SpaceAround)

      Row() {
        Button('年月日时分秒', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.YmdHms,
              start : '1900-08-29 12:00:00',
              end : '2100-08-29 12:00:00',
              selected : '2024-08-29 12:00:00'
            }
          })
        Button('时分秒', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.Hms,
              start : '00:00:00',
              end : '12:00:00',
              selected : '06:00:00'
            }
          })
        Button('时分', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.Hm,
              start : '00:00',
              end : '12:00',
              selected : '06:00'
            }
          })
      }.width('100%')
      .height(52)
      .justifyContent(FlexAlign.SpaceAround)

      Row() {
        Button('分秒', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.config = {
              format : DateTimeFormat.Ms,
              start : '00:00',
              end : '12:00',
              selected : '06:00'
            }
          })
        Button('后缀独立', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.suffixMode = SuffixMode.Separated
          })
        Button('后缀非独立', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .onClick(() =&amp;gt; {
            this.suffixMode = SuffixMode.Together
          })
      }.width('100%')
      .height(52)
      .justifyContent(FlexAlign.SpaceAround)

    }.width('100%')
    .height('100%')
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;📱 更多&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;📱 更多&lt;/h2&gt;&lt;p&gt;我开发的其他鸿蒙库：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://ohpm.openharmony.cn/#/cn/detail/%40xinyansoft%2Foh-crop&quot;&gt;oh-crop&lt;/a&gt;: OpenHarmony/HarmonyOS上的简单的图片剪裁库，可用于头像剪裁等常见场景。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ohpm.openharmony.cn/#/cn/detail/%40xinyansoft%2Foh-topic-editor&quot;&gt;oh-topic-editor&lt;/a&gt;: OpenHarmony &amp;amp; HarmonyOS平台上基于RichEditor实现的支持添加话题、@用户的文本编辑组件。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;我的公众号：程序员吹白  &lt;/p&gt;
&lt;p&gt;&lt;img title=&quot;202410102040348889598.jpg&quot; alt=&quot;202410102040348889598.jpg&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410102040348889598.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;鸿蒙开发交流QQ群：546723002&lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;🌐 开源协议&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;🌐 开源协议&lt;/h2&gt;&lt;p&gt;Apache License Version 2.0&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Thu, 10 Oct 2024 20:37:41 +0800</pubDate></item><item><title>oh-topic-editor: OpenHarmony &amp;amp; HarmonyOS平台上基于RichEditor实现的支持添加话题、@用户的文本编辑组件</title><link>https://blog.xinyanruanjian.com/post/13.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;h2 id=&quot;h2-u9700u6C42&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;需求&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;需求&lt;/h2&gt;&lt;p&gt;在App开发中，我们常常会遇到发布文章、评论的时候需要添加话题或者@用户的需求，就像微博那样。这在Android、iOS或者其他平台上都有现成的组件可供使用，但是HarmonyOS NEXT作为一个新兴平台，三方库实在匮乏，连微博鸿蒙版App本身都没完全实现这个功能。要想实现这个功能，那就必须手搓轮子了。撸起袖子，一把梭，干就完了。  &lt;/p&gt;
&lt;h2 id=&quot;h2-u6548u679Cu56FE&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;效果图&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;效果图&lt;/h2&gt;&lt;p&gt;&lt;img title=&quot;效果图&quot; alt=&quot;效果图&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/10/202410052252394622822.png&quot;&gt;  &lt;/p&gt;
&lt;h2 id=&quot;h2-u5B9Eu73B0u539Fu7406&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;实现原理&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;实现原理&lt;/h2&gt;&lt;p&gt;ArkUI组件中，文本输入组件主要有：TextInput、TextArea和RichEditor。TextInput是单行文本输入框，TextArea是多行文本输入框，两者都不支持多Span、也不支持多种样式的文本。故而我们只能选用RichEditor富文本编辑组件来实现需求。  &lt;/p&gt;
&lt;p&gt;RichEditor支持TextSpan、ImageSpan、SymbolSpan和BuilderSpan，查看相关API就知道BuilderSpan最适合实现我们的添加话题和@用户的需求。但是坑爹的是文档提到： &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; 不支持通过getSpans，getSelection，onSelect，aboutToDelete获取builderSpan信息。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们知道，富文本编辑器的操作其实十分复杂，完全由我们手动记录和维护BuilderSpan和关联的实体对象的关系会非常复杂。我们需要知道哪个BuilderSpan对应哪个用户实体或者话题实体、BiulderSpan什么时候添加了、删除等等，需要监听RichEditor的一系列回调事件，极容易出错。  &lt;/p&gt;
&lt;p&gt;对于调用者来说，最简单的无疑是通过一个getSpans接口获取到所有Span的信息，无奈官方声明不支持。实际测试中发现，RichEditorController的getSpans方法返回的数组中包含了代表BuilderSpan的&lt;strong&gt;RichEditorImageSpanResult&lt;/strong&gt;且其valueResourceStr属性为空串，只是无法获取BuilderSpan的详细信息。那么如果我们能在添加BuilderSpan的时候记录下它的信息，然后在getSpans方法中替换原来的&lt;strong&gt;RichEditorImageSpanResult&lt;/strong&gt;，问题就解决了。  &lt;/p&gt;
&lt;p&gt;那么问题就变成了&lt;strong&gt;如何把getSpans返回的RichEditorImageSpanResult替换成对应的其他对象呢&lt;/strong&gt;。虽说代表BuilderSpan的RichEditorImageSpanResult实例没有包含BuilderSpan的具体信息，但是至关重要的是它的顺序(数组下标)是按序排列的。所以我们只需要按序获取到BuilderSpan的信息，然后按序替换即可。  &lt;/p&gt;
&lt;p&gt;由于RichEditorController的addBuilderSpan方法中，如何构建BuilderSpan是由开发者自行控制的，那么我们可以给每个BuilderSpan的组件都添加一个唯一ID，在getSpans的时候再通过 &lt;strong&gt;componentUtils.getRectangleById(id)&lt;/strong&gt;  获取到各个BuilderSpan的坐标和宽高，通过组件位置判断出它们的顺序就大功告成了！&lt;/p&gt;
&lt;p&gt;实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { componentUtils } from '@kit.ArkUI'

@Component
export struct TopicEditor {
  controller: TopicEditorController = new TopicEditorController()
  /** 是否点两次删除才把TopicSpan删除 */
  @Prop doubleDelete: boolean = true
  /** 为了方便灵活地控制各种样式、设置选项，RichEditor通过BuilderParam传入，由调用者自行创建 */
  @BuilderParam richEditorBuilder: (controller: RichEditorController, aboutToDeleteCallback: Callback&amp;lt;RichEditorDeleteValue, boolean&amp;gt;) =&amp;gt; void

  /** 删除监听 */
  private aboutToDeleteCallback = (val: RichEditorDeleteValue) =&amp;gt; {
    // 处理双击删除，只有一个Span的时候才可能是删除TopicSpan
    if(this.doubleDelete &amp;amp;&amp;amp; val.richEditorDeleteSpans.length == 1) {
      let span = val.richEditorDeleteSpans[0]
      let editorController = this.controller.getRichEditorController()
      let selection = editorController.getSelection()
      // 判断是否TopicSpan以及当前是否被选中，如果选中了直接删除，未选中则选中但不删除
      if(isTopicSpan(span) &amp;amp;&amp;amp; !(selection.selection.length == 2 &amp;amp;&amp;amp; selection.selection[0] == span.spanPosition.spanRange[0] &amp;amp;&amp;amp; selection.selection[1] == span.spanPosition.spanRange[1])) {
        let spanRange: number[] = span.spanPosition.spanRange
        editorController.setSelection(spanRange[0], spanRange[1])
        return false
      }
    }
    return true
  }

  build() {
    this.richEditorBuilder(this.controller.getRichEditorController(), this.aboutToDeleteCallback)
  }
}

/** 判断是否TopicSpan */
function isTopicSpan(span: RichEditorTextSpanResult | RichEditorImageSpanResult) : boolean {
  return span['imageStyle'] != undefined &amp;amp;&amp;amp; (span['valueResourceStr'] == '' || span['valueResourceStr'] == ' ')
}

export interface TopicSpan {
  id: string
  builder: CustomBuilder
}

/**
 * TopicEditor控制器，通过它添加TopicSpan和TextSpan，以及获取最终结果
 */
export class TopicEditorController {
  private internal = new RichEditorController()
  
  private topicIds : string[] = []
  
  /** 清空 */
  clear() {
    this.topicIds = []
    this.internal.deleteSpans()
  }

  /** 暴露RichEditorController以便灵活控制RichEditor */
  getRichEditorController(): RichEditorController {
        return this.internal
  }

  /**
   * 添加TopicSpan
   * @param span 话题、用户信息，ID必须唯一
   * @param offset 添加位置
   */
  addTopicSpan(span: TopicSpan, offset?: number) {
    this.topicIds.push(span.id)
    this.internal.addBuilderSpan(span.builder, { offset })
  }

  /**
   * 同RichEditorController
   * @param value
   * @param options
   * @returns 
   */
  addTextSpan(value: string, options?: RichEditorTextSpanOptions | undefined): number {
    return this.internal.addTextSpan(value, options)
  }

  /**
   * 获取当前内容，数组每个元素代表一个Span，调用者可以根据返回结果和业务逻辑组合成最终内容
   */
  getSpans(): TopicSpanResult[] {
    let topicSpanInfos: TopicSpanInfo[] = []
    for (let id of this.topicIds) {
      let componentInfo = componentUtils.getRectangleById(id)
      
      // 宽高为0代表已经被删除了
      if(componentInfo.size.width == 0 &amp;amp;&amp;amp; componentInfo.size.height == 0) {
        continue
      }
      topicSpanInfos.push({ id, componentInfo })
    }

    // 根据组件位置排序
    topicSpanInfos = topicSpanInfos.sort((a, b) =&amp;gt; {
      if(a.componentInfo.windowOffset.y &amp;gt;= b.componentInfo.windowOffset.y + b.componentInfo.size.height) {
        return 1
      }
      if(a.componentInfo.windowOffset.y + a.componentInfo.size.height &amp;lt;= b.componentInfo.windowOffset.y) {
        return -1
      }
      return a.componentInfo.windowOffset.x - b.componentInfo.windowOffset.x
    })

    // 按序替换得到最终结果
    let results : TopicSpanResult[] = []
    let spans = this.internal.getSpans()
    let index = 0
    for (let span of spans) {
      if(isTopicSpan(span)) {
        results.push({
          value: topicSpanInfos[index++].id,
          isTopicSpan: true
        })
      } else {
        let textSpan = span as RichEditorTextSpanResult
        results.push({
          value: textSpan.value,
          isTopicSpan: false
        })
      }
    }
    return results
  }
}

interface TopicSpanInfo {
  id: string
  componentInfo : componentUtils.ComponentInfo
}

export interface TopicSpanResult {
  /** isTopicSpan为true时，这是TopicSpan的id，为false时，是TextSpan的文本 */
  value: string
  /** 此Span是否是TopicSpan */
  isTopicSpan: boolean
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;h2-u5C40u9650u6027u53CAu6CE8u610Fu4E8Bu9879&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;局限性及注意事项&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;局限性及注意事项&lt;/h2&gt;&lt;p&gt;只支持TextSpan和TopicSpan，加入其他Span可能会导致未知异常。&lt;br&gt;另外，该实现思路未得到生产环境验证。  &lt;/p&gt;
&lt;/body&gt;</description><pubDate>Sat, 05 Oct 2024 22:46:30 +0800</pubDate></item><item><title>OpenHarmony/HarmonyOS上的简单的图片剪裁库，可用于头像剪裁等常见场景</title><link>https://blog.xinyanruanjian.com/post/12.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;📚 简介&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;📚 简介&lt;/h2&gt;&lt;p&gt;oh-crop: OpenHarmony/HarmonyOS上的简单的图片剪裁库，可用于头像剪裁等常见场景。  &lt;/p&gt;
&lt;p&gt;代码仓库：&lt;a href=&quot;https://github.com/sahooz/oh-crop&quot;&gt;oh-crop&lt;/a&gt;  &lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;📚 下载安装&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;📚 下载安装&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ohpm i @xinyansoft/oh-crop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OpenHarmony ohpm 环境配置等更多内容，请参考: &lt;a href=&quot;https://ohpm.openharmony.cn/#/cn/help/downloadandinstall&quot;&gt;下载安装三方库&lt;/a&gt;  &lt;/p&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;📚 使用&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;📚 使用&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;定义CropModel对象&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;@State private model: CropModel = new CropModel();
...  
this.model.setImage(src)
      .setFrameWidth(1000)
      .setFrameRatio(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;使用CropView&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;CropView({
  model: this.model,
})
  .layoutWeight(1)
  .width('100%')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CropView仅仅包含图片的显示和手势操作、遮罩、取景框。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;剪裁&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;let pm = await this.model.crop();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;使用得到的PixelMap去实现你的业务逻辑&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id=&quot;h2--cropmodel-&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;📚 CropModel支持的配置项&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;📚 CropModel支持的配置项&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;/**
* 图片uri
* 类型判断太麻烦了，先只支持string，其他形式的需要先转换成路径
*/
src: string = '';
/**
* 图片预览
*/
previewSource: string | Resource = '';
/**
* 是否可以拖动
*/
panEnabled: boolean = true;
/**
* 是否可以缩放
*/
zoomEnabled: boolean = true;
/**
* 取景框宽度
*/
frameWidth = 1000;
/**
* 取景框宽高比
*/
frameRatio = 1;
/**
* 遮罩颜色
*/
maskColor: string = '#AA000000';
/**
* 取景框边框颜色
*/
strokeColor: string = '#FFFFFF';
/**
* 图片加载监听
*/
imageLoadEventListener: ImageLoadEventListener | null = null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CropModel设置是也支持setter链式调用。&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Fri, 12 Jul 2024 16:44:36 +0800</pubDate></item><item><title>【已解决】HarmonyOS NEXT / DevEco Studio项目错误：ArkTS:ERROR Failed to execute es2abc</title><link>https://blog.xinyanruanjian.com/post/11.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;h2 id=&quot;h2-u5F00u53D1u73AFu5883&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;开发环境&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;开发环境&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;操作系统： Mac mini M1、MacOS 14.2.1&lt;/li&gt;&lt;li&gt;IDE：DevEco Studio NEXT Developer Beta1，Build Version: 5.0.3.403&lt;/li&gt;&lt;li&gt;compatibleSdkVersion：5.0.0(12)&lt;/li&gt;&lt;/ul&gt;
&lt;h2 id=&quot;h2-u95EEu9898u63CFu8FF0&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;问题描述&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;问题描述&lt;/h2&gt;&lt;p&gt;在执行完“Clean Project”之后重新运行项目，出现错误：ArkTS:ERROR Failed to execute es2abc  &lt;/p&gt;
&lt;p&gt;&lt;img title=&quot;错误图片&quot; alt=&quot;错误图片&quot; src=&quot;https://blog.xinyanruanjian.com/zb_users/upload/2024/07/202407091334343263948.png&quot;&gt;  &lt;/p&gt;
&lt;h2 id=&quot;h2-u89E3u51B3u65B9u6CD5&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;解决方法&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;解决方法&lt;/h2&gt;&lt;p&gt;先执行“Rebuild Project”，然后重新运行项目，运行成功&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Tue, 09 Jul 2024 13:28:03 +0800</pubDate></item><item><title>我是怎么看待女生从事程序员的？</title><link>https://blog.xinyanruanjian.com/post/10.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;上个月，微博收到一个女生的私信和评论，令我有些惊讶，又有些莫名的开心。事情是这样的，2020年的时候这个女生也私信过我，问了我一个问题：“你怎么看待女生从事程序员的？”&lt;/p&gt;
&lt;p&gt;我回答她：“怎么看待男程序员，就怎么看待女程序员。就像司机一样，技术好的不是男司机，也不是女司机，而是老司机。”&lt;/p&gt;
&lt;p&gt;她：“总感觉人们对男程序员更加信任，毕竟男生从事程序员会更多、占主流，女生好像从来都不被建议这个。”&lt;/p&gt;
&lt;p&gt;我：“男生比较多未必就是职业歧视，一来对这个感兴趣的女生本来就少，二来程序员确实比较辛苦，很多人未必想干。确实也有一些人觉得女程序员不靠谱，但我身边的朋友基本都没这个想法。每个行业都有各种各样的歧视，你做好自己该做的，怕什么呢？”&lt;/p&gt;
&lt;p&gt;她：“嗯好，谢谢你。我明白了，还是要自己够优秀才是。”&lt;/p&gt;
&lt;p&gt;时间就这样过去了三年多，突然又收到她的评论和私信：“当我还在上大学的时候，我问过你怎么看待女生当程序员这件事，现在我已经是从事三年左右的女程序员，从一开始的汇编开发，现在到C#和C++开发，在面试的时候确实能感觉到选择员工性别的天平有点倾斜，无论是出差，技术还是加班都有，但是很庆幸我坚持下来了，谢谢你当时对我的点拨。当时我还是大学生，毕业以后我是我们专业唯一一个从事程序员的女生，不论别人怎么认为，我永远相信男生可以做的女生完全不差，祝我们一起在IT行业发光发热，越来越好。”&lt;/p&gt;
&lt;p&gt;原来有时候不经意间的一句话、一件小事真的可能会对别人产生或好或坏的影响。幸好三年多过去了，她还认为她的选择没有错，而我也很开心，虽然我还是一个小小的搬砖仔。&lt;/p&gt;
&lt;p&gt;仔细想想，其实现实生活中，对于女性的歧视、贴标签的行为确实不少。比如前文提到的女司机就是一个典型的标签，一旦路上开车操作不当引起交通事故的是位女性，大家总会觉得：女司机的常规操作，你懂的。同样的事情，如果肇事的是个男性，大家也许会说他是菜鸟、傻X，但绝不会说因为他是男司机所以车技不好很正常。&lt;/p&gt;
&lt;p&gt;不知道各位又是如何看待女性程序员的呢？&lt;/p&gt;&lt;/body&gt;</description><pubDate>Fri, 19 Jan 2024 12:10:22 +0800</pubDate></item><item><title>程序员创业陷阱：技术入股</title><link>https://blog.xinyanruanjian.com/post/9.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;“我们万事俱备，就差一个程序员了！”从我入行以来就不断遇到这种异想天开的“创业者”。事实上，创业是成功率很低的事情。而我们普通人，缺少资金和人脉，要想成功更是难上加难。   &lt;/p&gt;
&lt;p&gt;很多所谓创业者其实是空想家，满嘴创新和颠覆，幻想着靠一个精妙绝伦的idea一鸣惊人。他们越想越觉得这些idea真的妙不可言，越想越兴奋，眼里甚至都已经看见自己大获成功走上人生巅峰的样子了。那么，现在就剩一个问题了，那就是找个程序员把这个产品实现出来。程序员最好是技术入股，大家一起创业。如果产品做成了，咱还能亏待他不成？  &lt;/p&gt;
&lt;p&gt;事实上，往往他们觉得只有自己能想到的创意，早就有千百个人想到过了。一个好的idea确实对创业相当重要，因为它一定程度上决定了产品的市场定位和目标受众。但是光靠一个idea就能一飞冲天的时代已经过去了。资金、运营推广、渠道、人脉资源这些和对产品的深耕打磨其实更为重要。  &lt;/p&gt;
&lt;p&gt;很多创业者喜欢拿马云来比较，觉得马云那时候也是不受人待见到处碰壁，照样能白手起家。但是，马云在那个时候就能募集到50万的启动资金。而很多创业者在2023年的现在但凡能拿得出来这些钱也不至于白嫖程序员了~&lt;br&gt;所以说，创业者里口号喊得响很多，真正靠谱的很少。有的人可能会跟你说：你就技术入伙，就算做不成也没什么损失。对于我们来说，最宝贵的资源其实就是时间。不管行业如何内卷，没必要拿自己的时间为别人不靠谱的idea买单。  &lt;/p&gt;
&lt;p&gt;如果有靠谱的人和项目，决定加入创业，那么请务必注意技术以外的一些事情，比如股权架构、工商登记、公司决策权等等。公司稳定盈利后技术合伙人被一脚踢开的例子屡见不鲜！永远不要相信在利益面前，人性、道德、友情这些东西的约束力，唯一能相信的是法律！法律！法律！重要的法律说三遍。任何口头承诺都靠不住，合伙协议里任何你直觉觉得不太对劲的地方务必仔细思量，提出来确认清楚。不要担心抹不开面子。  &lt;/p&gt;
&lt;p&gt;一旦公司实现稳定盈利，技术人员是最容易被替代的角色之一。技术不是至上的，而是服务于业务。除了技术，我们也要关注运营等等各个方面，哪怕后来被踢开，我们也尽量能复制一个这种模式出来那就最好。  &lt;/p&gt;
&lt;p&gt;总的来说，创业是一个商业行为，也许它夹带着情怀、理想和其他因素，但并不影响它的商业本质。所以创业有风险，合伙需谨慎。&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Fri, 19 Jan 2024 12:07:30 +0800</pubDate></item><item><title>自由职业者有必要注册公司吗？</title><link>https://blog.xinyanruanjian.com/post/8.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;先直接了当地说说我的观点吧：不是必须但是注册一个会比较方便。自由职业者要不要注册公司，主要基于以下几方面来考虑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;缴纳社保；&lt;/li&gt;&lt;li&gt;开具发票；&lt;/li&gt;&lt;li&gt;App上架、接支付、广告SDK；&lt;/li&gt;&lt;li&gt;税务和风险。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;接下来就分别就这4点来谈一谈我个人的看法吧，仅供参考。  &lt;/p&gt;
&lt;h3 id=&quot;h3-u7F34u7EB3u793Eu4FDD&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;缴纳社保&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;缴纳社保&lt;/h3&gt;&lt;p&gt;社保，在当代生活中的重要性无需多言了，买房、入户、小孩上学、各种办事很多都要求社保连续缴够多少年，可以说社保一旦断缴麻烦事不断。而社保挂靠已经明确为“违法行为”，最近一段时间各地人社局更是纷纷表示将严查社保挂靠/代缴的行为。&lt;br&gt;一个好消息是近年来，各地也陆续支持灵活就业人员自行参保，无需是企业职工。但是各地政策未必完全一样，另外，灵活就业人员参保险种等一些方面和企业职工社保有所区别。具体可参考当地人社局网站或向人社局咨询。所以，注册公司不是缴纳社保的必要条件了。&lt;br&gt;如果注册了公司，那就可以正常按照职工参保，否则就按灵活就业人员参保。  &lt;/p&gt;
&lt;h3 id=&quot;h3-u5F00u53D1u7968&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;开发票&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;开发票&lt;/h3&gt;&lt;p&gt;自由职业者需要面对的第二个问题是开发票的问题。很多客户需要对公账户付款，财务流程也需要发票凭证。没有公司，自然人也可以申请税务局代开发票。以前自然人申请代开发票需要去办税服务厅，还需要准备各种资料，比较麻烦。随着电子政务和电子发票越来越流行，很多地方都开始支持网上申请，可以通过官方网站、App和小程序等网络渠道申请代开发票，相对来说方便了很多。但是，个人体验上，还是公司开票比较方便。  &lt;/p&gt;
&lt;h3 id=&quot;h3-app-sdk&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;App上架、对接支付、广告SDK&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;App上架、对接支付、广告SDK&lt;/h3&gt;&lt;p&gt;自由职业的程序员大佬们，大多都不希望一直靠接外包、出卖时间等方式来赚辛苦钱、血汗钱。独立开发自己的作品，靠收会员费或者广告费来获得收入的方式非常常见。我们开发的App如果要接入国内支付宝或者微信支付，那就需要企业资质了。另外，一些大的广告平台比如腾讯的优量汇等等，也需要企业资质。&lt;br&gt;如果独立开发作品是App，那还需要解决上架的问题。国内Android应用市场要求参差不齐，要求的资料也不近相同，有些资料需要企业申请，有些应用市场不对个人开发者开放。&lt;/p&gt;
&lt;h3 id=&quot;h3-u7A0Eu52A1&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;税务&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;税务&lt;/h3&gt;&lt;p&gt;作为一个奉公守法的好公民，税务问题也是必须要正视的一个问题。如果是从第三方平台产生的收入，有的平台会代扣税费，到咱们手上的就是税后收入，这种最省事。但是有的平台需要税务自理，和其他渠道所得的收入一样。&lt;br&gt;前面提到，自然人和公司都可以开票，开票都需要纳税。自然人按劳务收入申报纳税的话，税率20%，还是比较高的，而且你的支出成本也没法抵扣。公司运作的话，税种相对复杂，但是可以享受小微企业的各种优化政策，并且可以把一些差旅费、餐饮接待费等等纳入经营成本降低利润和税费。  &lt;/p&gt;
&lt;h3 id=&quot;h3-u98CEu9669&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;风险&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;风险&lt;/h3&gt;&lt;p&gt;公司还有一个重要的作用是风险处理。我们在接洽项目的时候，一般来说会签订开发合同，有的合同会规定一些赔偿条款，这就涉及到风险的问题。&lt;br&gt;假设我们做的项目因为各种原因导致了意外情况，触发了赔偿条款，赔偿金额又特别巨大，这种情况下：如果你是有限公司名义签订的合同，公司资产不足以偿还，可以考虑破产；如果是个人名义或者无限责任类型企业，那就需要用个人财产来偿还。  &lt;/p&gt;
&lt;h3 id=&quot;h3-u516Cu53F8u6210u672C&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;公司成本&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;公司成本&lt;/h3&gt;&lt;p&gt;注册公司也需要考虑公司带来的成本。一般来说，公司成本主要有几个方面：注册成本(材料、印章、数字证书等)、场地租金、银行公帐年费、代记账年费和税费。税费上面已经提到，这里就不说了。&lt;br&gt;银行公帐年费不同银行不尽相同，几百块到几千块不等。&lt;br&gt;注册成本也不高，几百块钱就可以搞定。财务如果采用代记账的方式，有些代记账公司可以代注册公司，自己只要参与其中一些必要步骤就可以了，比如去工商局登记。代记账一般一年一千多到几千块，可以线下找代记账公司，也可以去万能的某宝。&lt;br&gt;场地租金差别就比较大了，如果自己有房产可以注册公司，那就最好，无需成本。如果租写字楼，不同城市不同位置租金差别很大。当然，这个其实也可以交给代记账财务公司帮你搞定。  &lt;/p&gt;
&lt;h3 id=&quot;h3-u603Bu7ED3&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;总结&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;总结&lt;/h3&gt;&lt;p&gt;自由职业者要不要注册公司需要结合自己的实际情况来决定，毕竟注册公司也是有成本的，而且占用一定的精力来处理公司相关的事情，其中耗费我时间最多的就是统计成本和税务处理。虽然代记账公司可以帮你处理记账凭证、制作账本和报税，但是这方面还是会占用一定的时间和精力。&lt;br&gt;另外，注册了公司之后，你的信息就被公开了，可能会有很多垃圾营销电话，不胜其烦…&lt;br&gt;以上只是我自己这些年来的一些经验，不一定准确，希望给小伙伴们一点参考。也欢迎老司机向我提供宝贵的经验，一起交流～&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Fri, 19 Jan 2024 12:05:18 +0800</pubDate></item><item><title>自由职业程序员接单，哪些单子需要特别注意？</title><link>https://blog.xinyanruanjian.com/post/7.html</link><description>&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;众所周知，国内各行各业都在卷，程序员更是卷王里的佼佼者！所以在接单这方面也是严重的僧多粥少，其中还有很大一部分是不靠谱的单子，那么那些单子需要特别注意呢？这里给大家分享一下我这几年来总结的一些经验，希望对大家有一点帮助。&lt;/p&gt;
&lt;h3 id=&quot;h3-u4F4Eu4EF7u5355&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;低价单&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;低价单&lt;/h3&gt;&lt;p&gt;首先要注意的是低价单。无论再怎么卷，请大家务必注意那些价格低到离谱的单子。价格低不仅仅代表我们的把事情做完之后收到的钱很少，更代表着甲方要么没有意识到项目的难度和工作量，要么就觉得程序员的劳动并不值得尊重。无论是那一种情况都很可能导致开发过程中或者开发完成后双方不断扯皮，甲方需求不断增加又不愿意加钱等等一系列麻烦事。是的，“钱少”往往“事多”。相比于接这种单子，建议有时间躺着刷会抖音更舒心。&lt;/p&gt;
&lt;h3 id=&quot;h3-u56FDu5916u5355&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;国外单&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;国外单&lt;/h3&gt;&lt;p&gt;其次要注意的是各种需要去某些指定的聊天软件沟通或者通过虚拟货币交易的单子。这类单子往往是违法的，而且对方很可能人在国外，一旦有问题，倒霉的只能是我们自己，轻则拘留罚款，重则判刑。曾经有一个人在东南亚的哥们找我开发，还言之凿凿的说咨询过律师，只开发不运营就没事，我果断拒绝❌&lt;/p&gt;
&lt;h3 id=&quot;h3-u7070u8272u5355&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;灰色单&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;灰色单&lt;/h3&gt;&lt;p&gt;还有一种是针对某些软件的脚本、逆向、爬虫、抢购类。这类大家往往觉得它是“灰色”，但是法律可不会跟你讲灰色，合法就是合法，违法就是违法。技术无罪，但是不正当地利用技术，可能有罪，最常见的就是破坏信息系统罪。也许有很多人做了，也没出什么事，但是我不想做，我想晚上睡个安稳觉！&lt;/p&gt;
&lt;h3 id=&quot;h3-u6025u5355&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;急单&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;急单&lt;/h3&gt;&lt;p&gt;有些客户一上来就火急火燎的，需求可能都没有沟通清楚就要你赶紧开始的，这种一定要慎重再慎重。首先急单，时间上本来就很容易失控，不然也就不是“急”单了。其次前期需求，没有梳理清楚很可能几天开发完，然后花一个月甚至更长的时间去扯皮，最后不欢而散。还有一种情况是，对方找别人做过了，结果没搞好，deadline马上要到了。这种情况更是碰都不要碰，一来别人搞不好的东西，很可能咱们也搞不了，这里面可能有咱们不知道的坑，二来有可能是这个客户自己多次变动需求导致前一个开发者合作终止。总而言之，急单需慎重，慎之又慎的慎重！&lt;/p&gt;
&lt;h3 id=&quot;h3-u505Au5B8Cu518Du4ED8u6B3Eu7684u5355&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;做完再付款的单&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;做完再付款的单&lt;/h3&gt;&lt;p&gt;还有一种需要特别注意的，就是要求全部做完再付款的或者大部分款项得做完之后才付款的，这种对于我们来说风险太大。一旦遇到甲方中途变卦、项目终止，那么我们吭哧吭哧搞得头秃到头来钱没有到。哪怕代码没有交付，我们的时间白白损失了。就算你们签订了正式的合同，去诉讼、维权也会耗费你大量精力，结果怎样也是个未知数。这种情况可以找一个靠谱的平台，通过平台托管款项，降低一点风险。&lt;/p&gt;
&lt;h3 id=&quot;h3-u603Bu7ED3&quot;&gt;&lt;a class=&quot;reference-link&quot; name=&quot;总结&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;总结&lt;/h3&gt;&lt;p&gt;总结下来，好像就是这也不能做，那也不能做。是的，这年头靠谱的项目和靠谱的人都不好找，全靠缘分～。所以没有足够的心理准备和稳定获客渠道，不要轻易尝试自由职业。更推荐大家找个稳定的班上，业余时间可以做一下自己的小作品，接入广告或者收会员费都是可以的。如果有稳定收益了，再考虑全职维护。这也是我接下来的计划之一，后续有什么情况再来和小伙伴们分享。&lt;br&gt;外包有风险，接单需谨慎。纯属个人观点，如有不对，欢迎指正～&lt;/p&gt;
&lt;/body&gt;</description><pubDate>Fri, 19 Jan 2024 11:40:35 +0800</pubDate></item></channel></rss>