聊一下最近做的副业项目

  最近有段时间没有更新博客了,正好最近公司和手上都有好几个项目,因此比较忙,难得抽出点时间,简单的聊下最近做的几个副业项目的一点点感触。

小小的感触

  很多人可能会觉得做个副业就是小打小闹,或者会耽误工作,或者平时工作忙好不容易有个休息的时间,还要干活,比较累。我问了下我周围的朋友,总结了可能就是下面几个原因让大家望而却步:

1、没有合适的渠道接触到副业
2、需要各种对接或者处理人际关系,不擅长
3、耽误工作,没有时间
4、技术不匹配

  首先说说副业的可能性,经营副业和你上班打工会是两种完全不同的思考方式。引入副业后,最直观的就是明显对个人的财务状况带了很大的提升,降低了生活中的房贷车贷的风险,提高了生活的质量;一方面拓展客户,锻炼了自己的能力和技术,另一方面可以了解市场需要的技术要求,从客户的案例中提取重复的需求,将其模块化、产品化,从而提高自己的核心竞争力。

  然后说说副业的渠道,确实,作为一名程序猿,身边认识的人有限,社交网络无外乎亲戚、朋友、同学、同事,或者外加上对象的亲戚、朋友、同学、同事。因此能够额外的拓展一些关系人脉,对我们是有很大的帮助的。比如我们一般都会有很多的技术群,可以在技术群里面咨询一些大佬,或者加一些志同道合的朋友,看看有没有可以一起合作的项目等。

  这里推荐大家可以阅读几本非常不错的书,帮助大家提高沟通的技巧,《沟通的艺术》《关键对话》

沟通的艺术

  其次是对接中的收款方案,一般笔者接触到的项目都是按照项目的结算的,因此可以设定按照项目的完成时间节点收取费用。

  一般第一次合作的项目,笔者会预收10%~20%的费用作为项目订金,项目完成后,可以给用户部署到我们的测试服务器进行项目的演示,这时候可以收取到总费用的80%-90%,最后客户提出修改要求后可以收取最后的尾款。这样既帮助我们规避了款项收不回来的风险,又赢得客户的信任。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  一般前期将价格谈妥后,可以将这套付款方案和客户沟通协商,基本都会认可的。后期合作次数增加,有一定信任基础后,可以适当的放宽方案。

  是的,如果手头有一台公网IP的服务器,可以帮助我们做很多的事情。手头宽裕一些的同学,可以直接上阿里云服务器,新用户轻量级服务器50元/年;想要白嫖的可以使用免费的NATAPP进行内网穿透,免费隧道1M的小水管勉强凑合;动手能力强的的童鞋可以自己跟电信运营商申请公网IP,家里部署一台服务器,不过需要一定的Linux运维能力。

  项目免不了会有改动,前期再怎么容易,客户形容得多么简单的项目,后面就越是会有改动;如果客户有好说话一点,协商一下就能加点钱。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

加钱

  如果协商不好就容易谈崩,而且大部分客户对自己的项目需求其实都只是一个大概的描述,很多都是描述一个简单的目标,但其中需要你对内部的技术细节和各种可能的坑都能了如指掌。因此这个时候就需要我们既能做程序员又能做产品经理了,前期根据客户的需求制定一个详细的产品规划,有哪些页面,每个页面需要什么功能;这样做的目的既是帮助客户将需求具体化,明确下来,尤其是避免后续的纠纷;又能够方便我们评估项目,下面给项目进行报价。

  最后我想聊一下如何给项目报价,相信这也是让很多新手同学十分头疼的问题,常常不知道如何定价;报高了怕吓跑客户,也没有底气;报低了吧又怕自己吃亏,不划算;因此如何来报价就是一门平衡的艺术,需要经过多次实践之后才能对这门技艺熟练掌握。

  我们根据项目的工作繁杂程度进行一个简单的划分,当然每个人可能会有不同的适用范围(大神可以忽略):

  • 简单的项目:低于6K
  • 中等复杂的项目:6K~1.2W
  • 复杂的项目:大于1.2W

  再繁杂的项目,大于2W、3W的,除非人家对你有很深厚的合作基础和信任了解,不然一般不会偏向找个人开发者了;我们可以通过一个简单的公式进行粗略估算:

预估工时(天) * 自己每天的工资 + 预留修改费用

  一个项目的开发往往不是一个人可以独立完成的,涉及到UI、后台等,我们也可以让合作的小伙伴报价后算上这些费用。

  除此之外,还有一些额外隐形的费用我们在充分调研后,需要和客户在报价中进行详细的说明,让客户了解;比如客户需要我们部署网站和域名,那每年的服务器费用就需要额外列出。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  再或者在客户的需求中,有语音会议或者发送短信验证码等功能,这个我们自己肯定是不能开发的,也需要找额外的企业解决方案;好在很多的平台,阿里云腾讯云华为云等,都会有相应的解决方案,我们可以去这些平台调研,根据文档API来找到适合自己的方案。

  下面就谈谈我接触的几个项目。

Chrome插件

  Chrome插件的项目也是笔者第一次接触的这样的需求,开发网站开发APP都普遍,开发插件确实不太常见,这样的需求也是少之又少。客户的需求描述也是比较简单,抓取某网站的数据,通过插件上传后关闭网站。

  当时最最主要的难点就在于如何通过插件来关闭tab页,首先第一反应肯定是通过window.close()方法来关闭,但是测试后失效了,根本没有反应,控制台还给出以下警告:

Scripts may close only the windows that were opened by them.

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  网上找了相关资料后,浏览器有相关规定:只有通过window.open(url)打开的窗口,才能够由window.close()关闭,为的是阻止恶意的脚本终止用户的浏览器。

  又查了资料,可以先去一个空白的页面:

1
2
window.location.href = 'about:blank'
window.close()

  这样页面确实是没有了,但是跳转到了一个空白的页面,好像和客户的需求不太符合啊。于是我就放弃了window.close这条路,转而查找Chrome插件的API。

  终于,在文档中找到了chrome.tabs.remove,可以移除tab标签页,使用chrome.tabs.query查找所有的标签页,第一个就是选中激活的标签页:

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

1
2
3
4
5
6
// background.js
chrome.tabs.query({}, function (tabs) {
if(tabs.length){
chrome.tabs.remove(tabs[0].id);
}
});

  在开发中还有比较坑的就是谷歌升级了插件的版本,V2版本的插件不再支持,建议升级到V3版本:

插件报错

  在V2中,我们使用background参数配置background.js在后台运行,并且设置persistent来让脚本一直在后台运行,但是这样会占用系统资源;所以在V3中,改用service_worker来智能化启动脚本。

V3中不能使用关键字persistent。

  由于background的这些改动,因此我们的background.js脚本变得不可靠了,对于一些定制任务,只能使用alarms来实现。

小程序

  小程序是当前被大家广泛接受和需要的,因此在做副业项目时也是接触比较多的。个人感觉小程序其实技术上没有什么复杂的,棘手的就是需要查找各种文档以及小程序兼容性问题。

  小程序一般都需要用户登录和授权,登录和授权其实是两个不同的流程。整个技术难点在于整个流程步骤比较多,我们首先跟随官方给出的流程图,简单看下步骤(流程图会在下面反复被用到,大家可以在心里默记下来):

小程序登录流程图

  说到小程序的登录,绕不开的就是openid和unionid,这两者的区别也是面试时喜欢问的,简单来形容一下就是,unionid相当于你的身份证,在不同地方都是唯一的,而openid相当于你在公司和小区的唯一通行证,凭借这个才能进出。

openid

  首先来看下openid,它是微信提供给开发者的用户唯一标识,虽然称为唯一,但它在不同应用下是不一样的。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

同一个用户在不同的公众号或小程序下的openid是不同的。

  比如我在同一个公众平台的账号下面既开发了一个公众号,又维护了一个小程序,今天有一个人分别打开了这两个应用,但是我在两个应用的后端获取到这个人对应的openid是两个不同的值。

  那么我们如何来获取openid呢?这个问题的回答就需要用到上面的登录授权流程图了,整个流程图其实就是获取openid的过程,没有记下来的小伙伴可以再回去看下。

  我们结合后台接口来看下整个过程,首先在小程序中,我们调用wx.login获取到一个临时的code,这个步骤一般放到app.js的onLaunch中,判断没有token的情况下去进行登录操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onLaunch() {
const _this = this;
const { hasLogin } = this.globalData;
if (!hasLogin) {
wx.login({
success(res) {
const { errMsg, code } = res;
if (errMsg === "login:ok") {
// code todo
}
},
});
}
}

临时登录凭证code只能使用一次

  拿到这个临时code后,我们就要发送给后端,让后端去调用微信的接口了:

1
2
3
4
5
if (errMsg === "login:ok") {
fetchUserLogin({ code }).then((res) => {
// todo 处理接口返回的token数据
});
}

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  后端拿到code后,来到了第二步,需要找微信服务器换取openid,我们看下node的简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const APP_ID = "APP_ID"
const APP_SCRET = "APP_SCRET"
const { code } = req.body;

const resp = await axios.get(
`https://api.weixin.qq.com/sns/jscode2session`, {
params: {
appid: APP_ID,
secret: APP_SCRET,
js_code: code,
grant_type: "authorization_code",
},
});
const { session_key, openid } = resp.data;

  后端拿到openid,存入数据库,唯一绑定用户,然后返回一个token给小程序,小程序拿到token后存入storage,发起业务在请求头携带就可以了。

unionid

  说完了openid,我们来看看unionid,我们看下官方文档对unionid机制的描述:

如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  简单来说,unionid可以唯一确定一个用户的身份,相当于用户的身份证信息。

同一个用户在不同的公众号或小程序下的unionid是相同的。

  如果应用只限于小程序内,则不需要unionid,直接通过openid,如果要跨应用,则需要unionid作为身份标识。那么unionid如何来获取呢?若当前小程序已绑定到微信开放平台帐号下,后台调用jscode2session接口时就能获取到unionid,需要注意的是:

  很多小伙伴就会有疑问了,既然unionid比openid更具有唯一性,那我直接用unionid不就完了,还要openid干嘛呢?

  我们看完这两者的获取方式后会发现,unionid的获取其实有更多的条件,需要绑定微信开放平台,如果用户取消绑定了就获取不到了,多用于多个终端应用账号的打通。而openid不用授权弹框,静默的方式就获取了,更多可以看这篇文章:openid有什么用?为什么不直接用unionid?

用户授权

  上面我们拿到了用户的openid和unionid可以确定用户的身份了,但是为了给用户更好的体验,很多时候我们想要拿到用户的基本信息,比如昵称、头像,在用户详情页展示,或者拿到用户的手机号,避免用户验证时重复填写手机号,直接带入即可。

  用户授权最重要的就是wx.getUserInfoapi了,在小程序登录、用户信息相关接口调整说明这篇官方公告中,对getUserInfo进行了调整,很多开发者一进入小程序就直接调用getUserInfo唤起弹框,这种方式让微信官方很恼火,因此对这个api进行了调整。

  调整后,如果我们只是简单的展示用户头像昵称,可以使用<open-data />组件:

1
2
3
4
5
6
7
8
<open-data 
class="avatar"
type="userAvatarUrl">
</open-data>
<open-data
class="nick-name"
type="userNickName">
</open-data>

  我们也可以给这个组件加个类名,控制样式。如果我们需要存储用户信息,使用button让用户主动触发弹框授权来获取:

1
2
3
4
5
<button 
open-type="getUserInfo"
bindgetuserinfo="bindGetUserInfo">
授权登录
</button>
1
2
3
4
5
6
7
8
bindGetUserInfo(e) {
const { userInfo } = e.detail;
const {
nickName,
avatarUrl,
gender
} = userInfo;
}

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  在调用wx.getUserInfo前需要进行授权信息的判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onLoad() {
wx.getSetting({
success(res) {
if (res.authSetting["scope.userInfo"]) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称
wx.getUserInfo({
success: function (res) {
const { userInfo } = res;
},
});
}
},
});
}

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里


本网所有内容文字和图片,版权均属谢小飞所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。如需转载请关注公众号【前端壹读】后回复【转载】。