thingsboard 官方演示版本也登录不进去 唉
看错误应该是数据库出问题了
vuepress 是一款 vue 驱动的静态网站生成器,可用于产品使用手册的用途!
https://github.com/vuejs/vuepress
在开发目录下(\vuepress-master\packages\docs\docs)安装 webpack,命令为:
npm install webpack-dev-server --save-dev
进入开发目录下(\vuepress-master\packages\docs\docs)执行:
npm install
npm run dev
npm run-script build
位置:\vuepress-master\vuepress
将一下目录文件上传至服务器即可访问
# install
yarn global add vuepress
# or npm install -g vuepress
# create a markdown file
echo '# hello vuepress' > readme.md
# start writing
vuepress dev docs
# build to static files
vuepress build docs
如果想设置中文为默认国际化语言,只需要修改图中两个文件,并把 zh 目录下面的文件放到 docs 根目录,把根目录下的文件放到 en 目录即可:
config.js
const { fs, path } = require('@vuepress/shared-utils')
module.exports = ctx => ({
dest: '../../vuepress',
locales: {
'/': {
lang: 'zh-cn',
title: 'vuepress',
description: 'vue 驱动的静态网站生成器'
},
'/en/': {
lang: 'en-us',
title: 'vuepress',
description: 'vue-powered static site generator'
}
},
head: [
['link', { rel: 'icon', href: `/logo.png` }],
['link', { rel: 'manifest', href: '/manifest.json' }],
['meta', { name: 'theme-color', content: '#3eaf7c' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }],
['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }],
['meta', { name: 'msapplication-tileimage', content: '/icons/msapplication-icon-144x144.png' }],
['meta', { name: 'msapplication-tilecolor', content: '#000000' }]
],
theme: '@vuepress/vue',
themeconfig: {
repo: 'vuejs/vuepress',
editlinks: true,
docsdir: 'packages/docs/docs',
// #697 provided by the official algolia team.
algolia: ctx.isprod ? ({
apikey: '3a539aab83105f01761a137c61004d85',
indexname: 'vuepress'
}) : null,
smoothscroll: true,
locales: {
'/': {
label: '简体中文',
selecttext: '选择语言',
arialabel: '选择语言',
editlinktext: '在 github 上编辑此页',
lastupdated: '上次更新',
nav: require('./nav/zh'),
sidebar: {
'/api/': getapisidebar(),
'/guide/': getguidesidebar('指南', '深入'),
'/plugin/': getpluginsidebar('插件', '介绍', '官方插件'),
'/theme/': getthemesidebar('主题', '介绍')
}
},
'/en/': {
label: 'english',
selecttext: 'languages',
arialabel: 'select language',
editlinktext: 'edit this page on github',
lastupdated: 'last updated',
nav: require('./nav/en'),
sidebar: {
'/en/api/': getapisidebar(),
'/en/guide/': getguidesidebar('guide', 'advanced'),
'/en/plugin/': getpluginsidebar('plugin', 'introduction', 'official plugins'),
'/en/theme/': getthemesidebar('theme', 'introduction')
}
}
}
},
plugins: [
['@vuepress/back-to-top', true],
['@vuepress/pwa', {
serviceworker: true,
updatepopup: true
}],
['@vuepress/medium-zoom', true],
['@vuepress/google-analytics', {
ga: 'ua-128189152-1'
}],
['container', {
type: 'vue',
before: '<pre class="vue-container"><code>',
after: '</code></pre>'
}],
['container', {
type: 'upgrade',
before: info => `<upgradepath title="${info}">`,
after: '</upgradepath>'
}],
['flowchart']
],
extrawatchfiles: [
'.vuepress/nav/en.js',
'.vuepress/nav/zh.js'
]
})
function getapisidebar () {
return [
'cli',
'node'
]
}
function getguidesidebar (groupa, groupb) {
return [
{
title: groupa,
collapsable: false,
children: [
'',
'getting-started',
'directory-structure',
'basic-config',
'assets',
'markdown',
'using-vue',
'i18n',
'deploy'
]
},
{
title: groupb,
collapsable: false,
children: [
'frontmatter',
'permalinks',
'markdown-slot',
'global-computed'
]
}
]
}
const officalplugins = fs
.readdirsync(path.resolve(__dirname, '../plugin/official'))
.map(filename => 'official/' filename.slice(0, -3))
.sort()
function getpluginsidebar (plugintitle, pluginintro, officialplugintitle) {
return [
{
title: plugintitle,
collapsable: false,
children: [
['', pluginintro],
'using-a-plugin',
'writing-a-plugin',
'life-cycle',
'option-api',
'context-api'
]
},
{
title: officialplugintitle,
collapsable: false,
children: officalplugins
}
]
}
function getthemesidebar (groupa, introductiona) {
return [
{
title: groupa,
collapsable: false,
sidebardepth: 2,
children: [
['', introductiona],
'using-a-theme',
'writing-a-theme',
'option-api',
'default-theme-config',
'blog-theme',
'inheritance'
]
}
]
}
.vuepress\nav\zh.js
module.exports = [
{
text: '指南',
link: '/guide/'
},
{
text: '配置',
link: '/config/'
},
{
text: '插件',
link: '/plugin/'
},
{
text: '主题',
link: '/theme/'
},
{
text: '了解更多',
arialabel: '了解更多',
items: [
{
text: 'api',
items: [
{
text: 'cli',
link: '/api/cli.html'
},
{
text: 'node',
link: '/api/node.html'
}
]
},
{
text: '开发指南',
items: [
{
text: '本地开发',
link: '/miscellaneous/local-development.html'
},
{
text: '设计理念',
link: '/miscellaneous/design-concepts.html'
},
{
text: 'faq',
link: '/faq/'
},
{
text: '术语',
link: '/miscellaneous/glossary.html'
}
]
},
{
text: '其他',
items: [
{
text: '从 0.x 迁移',
link: '/miscellaneous/migration-guide.html'
},
{
text: 'changelog',
link: 'https://github.com/vuejs/vuepress/blob/master/changelog.md'
}
]
}
]
},
{
text: '0.x',
link: 'https://v0.vuepress.vuejs.org/'
}
]
.vuepress\nav\en.js
module.exports = [
{
text: 'guide',
link: '/en/guide/'
},
{
text: 'config reference',
link: '/en/config/'
},
{
text: 'plugin',
link: '/en/plugin/'
},
{
text: 'theme',
link: '/en/theme/'
},
{
text: 'learn more',
arialabel: 'learn more',
items: [
{
text: 'api',
items: [
{
text: 'cli',
link: '/en/api/cli.html'
},
{
text: 'node',
link: '/en/api/node.html'
}
]
},
{
text: 'contributing guide',
items: [
{
text: 'local development',
link: '/en/miscellaneous/local-development.html'
},
{
text: 'design concepts',
link: '/en/miscellaneous/design-concepts.html'
},
{
text: 'faq',
link: '/en/faq/'
},
{
text: 'glossary',
link: '/en/miscellaneous/glossary.html'
}
]
},
{
text: 'miscellaneous',
items: [
{
text: 'migrate from 0.x',
link: '/en/miscellaneous/migration-guide.html'
},
{
text: 'changelog',
link: 'https://github.com/vuejs/vuepress/blob/master/changelog.md'
}
]
}
]
},
{
text: '0.x',
link: 'https://v0.vuepress.vuejs.org/'
}
]
如果低于 11,或报错:
oauth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。
这个标准比较抽象,使用了很多术语,初学者不容易理解。其实说起来并不复杂,下面我就通过一个简单的类比,帮助大家轻松理解,oauth 2.0 到底是什么。
我住在一个大型的居民小区。
小区有门禁系统。
进入的时候需要输入密码。
我经常网购和外卖,每天都有快递员来送货。我必须找到一个办法,让快递员通过门禁系统,进入小区。
如果我把自己的密码,告诉快递员,他就拥有了与我同样的权限,这样好像不太合适。万一我想取消他进入小区的权力,也很麻烦,我自己的密码也得跟着改了,还得通知其他的快递员。
有没有一种办法,让快递员能够自由进入小区,又不必知道小区居民的密码,而且他的唯一权限就是送货,其他需要密码的场合,他都没有权限?
于是,我设计了一套授权机制。
第一步,门禁系统的密码输入器下面,增加一个按钮,叫做"获取授权"。快递员需要首先按这个按钮,去申请授权。
第二步,他按下按钮以后,屋主(也就是我)的手机就会跳出对话框:有人正在要求授权。系统还会显示该快递员的姓名、工号和所属的快递公司。
我确认请求属实,就点击按钮,告诉门禁系统,我同意给予他进入小区的授权。
第三步,门禁系统得到我的确认以后,向快递员显示一个进入小区的令牌(access token)。令牌就是类似密码的一串数字,只在短期内(比如七天)有效。
第四步,快递员向门禁系统输入令牌,进入小区。
有人可能会问,为什么不是远程为快递员开门,而要为他单独生成一个令牌?这是因为快递员可能每天都会来送货,第二天他还可以复用这个令牌。另外,有的小区有多重门禁,快递员可以使用同一个令牌通过它们。
我们把上面的例子搬到互联网,就是 oauth 的设计了。
首先,居民小区就是储存用户数据的网络服务。比如,微信储存了我的好友信息,获取这些信息,就必须经过微信的"门禁系统"。
其次,快递员(或者说快递公司)就是第三方应用,想要穿过门禁系统,进入小区。
最后,我就是用户本人,同意授权第三方应用进入小区,获取我的数据。
简单说,oauth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。
(1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
(2)令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
(3)令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 oauth 2.0 的优点。
注意,只要知道了令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。 这也是为什么令牌的有效期,一般都设置得很短的原因。
来源:
近几年,工业物联网技术日趋成熟,各种服务商、集成商如雨后春笋不断涌现,都先后进军工业物联网领域。将物联网应用于工业领域后,工业产品生产效率得到进一步提升,设备运维变得更加方便,传统工业进入了快速发展时期。那么,发展工业物联网,到底会给传统工业带来哪些影响呢?
就本质而言,在工业环境下,借助工业物联网能够实现生产设备和产品的连接。在具体的操作过程中,通过将工业生产过程的每一个环节、设备变成数据终端,全方位采集底层基础数据,并对这些数据进行深层面的分析与挖掘,工业生产效率就得以提升、工业运营模式也达到优化。
在传统工业自动化环境下,一切都只是发生在工厂自己的系统里,与外部世界连接的情况非常少见。而这一切,随着物联网、云计算、大数据等技术的快速发展,已经发生了巨大的改变。实际上,工业化以太网连接、无线局域网等系统已经在工厂运行多年,物联网在此基础上,可以为系统之间的互联互通 “牵线搭桥”,使单一的、封闭的系统有机联系起来。
近年来,在政策扶持力度不断加大、民间融资、投资等活动日趋频繁的背景下,工业物联网技术加快成熟,并被应用于工业生产设备预测性维护、可视化供应链监控、生产自动化等多个环节。在工业生产环节,工业物联网在全面分析设备的运作状况、提升工业生产效率方面所起到的作用也越来越重要。
如今,以大数据、云计算为代表的新一轮工业革命正在进行,许多工业企业为适应新时代工业发展的实际需要,纷纷开始借助云计算、物联网等技术进行生产模式调整和生产方式革新。在多种技术中,工业物联网以其显著的技术优势,在提升工业产品生产效率、帮助企业实现自动化、智能化生产方面的商用价值也得到了诸多业内人士的认可。
具体来讲,对于工业生产而言,工业物联网在分析工业设备运作效率、工业设备自动化运作的商用价值尤其值得重视。例如,工业设备生产效率分析。工业产品制造商通过使用工业物联网技术,可以及时收集和分析来自多个工业生产设备的数据,进而较为全面的了解工业设备的运作状况,以此 采取相应的措施,提升工业产品的生产效率。
又如,工业物联网技术可以为工业设备的自动化运作提供有力的技术支撑。通过运用相应的工业物联网技术及其设备,工业生产的机械化运作效率得以进一步提升。即使是在没有人为干预的情况下,工业生产设备也可以实现自动化、高效化运作。这样的方式,在规模化工业生产活动中能提升工业生产效率,并使产品质量得到新的提升。
就目前而言,我国许多地区已经开始将工业物联网等技术应用于工业领域,在这些新技术的推动下,我国工业发展速度不断加快,总体营收稳步增长。与此同时,一些问题也逐渐暴露出来。对于一些中小型工业企业而言,工业物联网建设成本过高、技术引进困难等都对工业物联网的应用推广造成了一定的阻碍。
尽管如此,业内人士对于我国工业物联网的发展前景仍然充满信心。今后,随着我国物联网生态环境不断成熟,物联网在工业领域的应用需求将更加强烈。在 2016 年时,我国工业物联网规模已经达到 1896 亿元,在整体物联网产业中的占比超过了 17%。预计在政策推动以及应用需求带动下,到 2020 年,我国工业物联网在整体物联网产业中的占比将达到 26%,规模将突破 4500 亿元。
在此过程中,传感器技术、5g 技术的不断进步,将大大增加物联网澳门人威尼斯3966的解决方案在工业领域的应用,物联网澳门人威尼斯3966的解决方案也将进一步提高工业企业的运营效率,增加工业制造企业的收入来源并激发工业产业创新活力,使我国工业发展取得许多新成果,各种高质量的工业产品也将出现在市场上。
祝社区越办越好!!
[warning] npm warn optional skipping optional dependency: fsevents@1.2.11 (node_modules\fsevents):
[info] added 1585 packages from 933 contributors in 492.245s
[warning] npm warn notsup skipping optional dependency: unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
[error]
[info]
[info] --- frontend-maven-plugin:1.0:npm (npm build) @ ui ---
[info] running 'npm run build' in f:\workspace\thingsboard\thingsboard-2.5\ui
[info]
[info] > thingsboard@2.5.0 build f:\workspace\thingsboard\thingsboard-2.5\ui
[info] > cross-env node_options=--max_old_space_size=4096 node_env=production webpack
[info]
[error] the rule `angular/service-name` will be split up to different rules in the next version. please read the docs for more information
上面这个错误有什么影响吗?我已经成功编译了
failed to execute goal org.apache.maven.plugins:maven-clean-plugin:2.5:clean (default-clean) on project http: failed to clean project: failed to delete f:\workspace\thingsboard\thingsboard-2.5\transport\http\target\tb-http-transport.rpm
thingsboard http transport service ................. failure 编译到这一步报错
求 thingsboard 规则引擎使用文档,谢谢!
应公司要求,将 thingsboard 数据库整改为 mysql,有大神弄过吗?
求 thingsboard 中文视频教程
求 thingsboard 连接摄像头的部件,可走萤石云
thingsboard thingsboard-gateway
我一直在尝试在本地安装 raspberry pi 3 上的 tb 网关与部署在虚拟机上的服务器之间建立连接,我遵循了 thingsboard 官方文档中的步骤,但是我一直从日志文件中获取以下错误:
“ 2019-11-12 09:50:09,759 [main] 错误 otgsgateway.mqttgatewayservice-无法连接到 thingsboard。[10000] 毫秒后连接超时”
有没有人可以为我解答,谢谢
我在传感器节点上使用 6lowpan 进行网关通信,以将传感器数据发送到本地物联网服务器,该服务器安装在 pc 上的虚拟机中。 使用此设置,我能够将数据发送到 thingsbaord 远程服务器(demo.thingsboard.io:80),但是我的本地主机(2a02:908:d41:cb60:d811:9baa:caa8:4246 端口:8080)无法获取数据。 我在终端端子 yat 中看到插座连接,并且传感器数据在一段时间后关闭。 我对两个系统(在 vm 中的主窗口和带有 ipv4 和 ipv6 地址的虚拟机中的 windows)相互执行 ping 操作,这是成功的。 我已经附加了远程服务器的终端输出映像和本地服务器的连接尝试。
我设法使用 attributeservice 在 widgetcontext 中获取遥测。
但是,我无法弄清楚 attributeservice.savetimeseries()的属性是什么?
正在 attributeservice = widgetcontext。$ scope。$ injector.get('attributeservice');
如何推动遥测?
我最近尝试了 x.509 基于证书的身份验证提供的材料。 我已经通过 ssl 设置了服务器环境,如文档 thingsboard mqtt。尽管整个进度(客户端证书,客户端 python 示例代码)始终有效,但我无法弄清楚为什么将服务器证书作为 client.keygen.sh 导入到 jks 文件后又没有任何将来的用途?谢谢。
echo "importing server public key to $client_file_prefix.jks"
keytool --importcert \
-file $server_file_prefix.cer \
-keystore $client_file_prefix.jks \
-alias $server_key_alias \
-keypass $server_key_password \
-storepass $client_keystore_password \
-noprompt
安装时,遇到这个问题了,导致编译失败
求 thingsboard pe 版本源码,有人买过吗,听说买了就会是开源的。
官方解释:“规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。”
thingsboard 解释:thingsboard 平台的核心处理引擎,好比一台车子的发动机,是基于事件构建的工作流,是一个高度可定制的框架,用于复杂事件的处理。
1、在设备数据(实时数据/属性数据)保存到数据库之前,支持对接收的遥测数据或属性数据进行拦截验证和修改;
2、将设备遥测数据(实时数据)或者设备属性从设备复制到相关资产,以便可以汇总遥测;
3、自定义告警规则/函数,进行设备告警触发、更新、清除;
4、根据设备生命周期事件触发操作。如设备上线、设备离线状态,创建告警事件;
5、加载所需的其他处理数据。在客户设备或租户属性中定义的设备的负载温度阈值;
7、发生复杂事件时发送电子邮件,并使用 “电子邮件模板” 中其他实体的属性;
8、根据定义的条件进行远程设备控制的 rpc 调用。
9、将设备实时数据、远程控制 rpc 指令,或者由规则引擎触发的告警或者事件,推送到与外部消息中间件或者第三方系统(如 kafka,mqtt,rabbitmq, spark,aws 服务等等)
简单的总结:能够用于接收任何事件,可以是来自设备的,设备生命周期事件、rest api 事件、rpc 请求等的传入;能够处理单个传入消息并生成一个或多个传出消息,发往不同的规则链路进行消息的路由处理,同时可以过滤,丰富,转换传入消息,执行操作或与外部系统通信。
1.规则消息(message)
作用:用于接收任何事件,可以是来自设备的,设备生命周期事件、rest api 事件、rpc 请求等的传入
特点:可以被序列化的有着规定的数据结构,表示系统中的各种消息
消息组成:消息 id(messageid),基于时间的通用唯一标识符;消息发起者(originator of the message),device,asset 或者其他 entity 标识符;消息类型(type of the message),遥测或者不活动的事件;消息负载(payload of the message),带有实际消息有效负载的 json 报文;元数据(metadata),键值对的列表以及消息有关的其他数据。
消息类型:类型太多不一一列举。如有需要请私信。文档免费共享。
2.规则节点(rule node)
是什么?规则节点是规则引擎的基本组件,它一次处理单个传入消息并生成一个或多个传出消息。规则节点是规则引擎的主要逻辑单元。规则节点可以过滤,丰富,转换传入消息,执行操作或与外部系统通信。
作用:规则节点可以过滤,丰富,转换传入消息,执行操作或与外部系统通信。提供节点自定义能力,实现数据的运算。
节点间的关系:规则节点可能与其他规则节点相关。每个关系都有关系类型,这是用于标识关系的逻辑含义的标签。当规则节点生成传出消息时,它总是指定用于将消息路由到下一个节点的关系类型。
规则节点类型:
a、过滤节点(用于消息过滤和路由,过滤成功走真链、错误走假链)。示例 1:script(脚本过滤器节点)使用 javascript 条件进行消息过滤(消息 msg,metadata 消息元数据,msgtype 消息类型);示例 2:switch(交换节点)将传入消息路由到一个或多个输出链,节点执行已配置的 javascript 函数。
过滤节点:
b、属性集节点:用于更新传入消息的元数据。比如 1:消息发起方用户属性(customer attributes),将消息发起方属性信息或者遥测数据加入 metadata 元数据中。比如 2:设备属性(device attributes),将消息发起方的设备属性或者遥测数据加入 metadata。
属性集节点:
c、变换节点:用户更改创立的消息字段,比如,发起方、类型、有效负载,元数据。比如 1:脚本转换节点(script),作用:修改消息内容(msg(消息负载),msgtype(消息类型),metadata(元数据)),可增加,可改。比如 2:转换到电子邮件节点(to email),通过使用从消息元数据派生的值填充电子邮件字段,将消息转换为电子邮件消息。设置 “ send_email” 输出消息类型,以后可以被 “ 发送电子邮件节点” 接受。可以将所有电子邮件字段配置为使用元数据中的值。
d、动作节点:根据传入的消息执行各种动作。
比如 1:create alarm(创建告警),通过过滤节点中的过滤脚本判断后,对满足条件的消息进行告警的触发。
比如 2:log(创建日志),对于系统中的关键系统进行日志输出,比如 3:rpc call request(远程 rpc 调用),监控系统 rpc 请求,下发控制命令请求。
e、外部节点:提供将消息及数据路由到外部中间件,或者其他第三方云平台中。用于与外部系统进行交互。
实例 1:kafka(kafka 消息中间件),mqtt(外部 mqtt 代理),rabbitmq,支持将系统中的数据发布到 kafka/mqtt 代理/rabbitmq 中,供第三方消费者订阅数据。
实例 2:send email(向外部发送邮件)。
实例 3:aws sns:将消息发布到 aws sns(亚马逊简单消息通知服务,是一种发布\订阅模式的消息收发服务)。
是什么?规则链是规则节点及其关系的逻辑组。接收来自节点的出站消息将其发送至下一个节点。
用法:租户管理员可以定义一个 “ 根” 规则链,还可以定义多个其他规则链。根规则链处理所有传入的消息,并将其转发到其他规则链以进行其他处理。其他规则链也可以将消息转发到不同的规则链。
组态化拖拽式布局编辑
每个规则节点可以具有取决于规则节点实现的特定配置参数。例如,“过滤器 - 脚本” 规则节点可通过处理传入数据的自定义 js 函数进行配置。
基于调试—事件模式的规则测试
每个规则节点可以设置为调试模式,启用之后,在事件中可以查看到入站 - 出站消息
javascript 函数测试台
一些规则节点具有特定的 ui 功能,允许用户测试 js 函数。单击 “ 测试过滤器功能” 后,您将看到 js 编辑器,可使用该编辑器替换输入参数并验证函数的输出。
规则节点可自定义:
如果 thingsboard 现有的规则处理节点,无法处理有些定制化业务场景,比如:对于数据进行分类统计,或者分组统计、或者将数据推送到外部 hadoop 生态去存储、或者将指定一段时间的数据,生成文件导出,推送到远程服务器上之类的小场景,可以通过自定义规则节点实现。官方预留扩展开发的接口,很容易构建自己的处理逻辑。
本次对于 thingsboard 的规则引擎的基本概念、可以使用的能力、规则引擎的组成,以及各个规则节点的用法、规则模型的使用特点做了简单的介绍。相信通过本次的讲解,对于规则引擎的基础、能力、用法有了大概的了解,具体的规则引擎的实战讲解,规划在设备物联接入之后,再进行演示。谢谢大家浏览,望大家持续关注,支持原创,成就更好的自己!
感谢作者:30 岁码农大叔逆袭记