首页> 标签> 小程序
"小程序"
共 5210 条结果
全部 问答 文章 公开课 课程 电子书 技术圈 体验
ModelScope体验:自然语言推理模型应用
操作步骤参考模型文档和快速开始环境准备ModelScope提供了本地(参考环境配置和模型下载)和远程环境两种模式,这里选择了使用ModelScope提供的远程环境,即Notebook进行开发,更加便捷。通过个人中心进入到实例启动页面,这里选择CPU环境启动Notebook:进入到如图所示的交互页面,选择以Notebook方式运行即可进入正常的Notebook页面:模型调试通过如下命令,即可在Notebook环境下自动下载所需推理模型。这里通过pipeline()函数,以一行代码实现了模型定义,其中第一个参数为任务名称,第二个参数为模型地址,这里选用了达摩自然语言推理模型(base),该模型为在structbert-base-chinese预训练模型的基础上,用CMNLI、OCNLI两个数据集(45.8w条数据)微调训练出来的自然语言推理模型。。from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks semantic_cls = pipeline(Tasks.nli, 'damo/nlp_structbert_nli_chinese-base')在定义好模型调用函数后,即可输入句子对实现模型调用:semantic_cls(input=[('商务职业学院和财经职业学院哪个好?', '商务职业学院商务管理在哪个校区?')])得到返回结果:返回输入句子对可能的三种关系contradiction, entailment和neutral的概率,如上所示,该输入句子对之间最有可能为neutral关系,基本符合我们的人工判断,说明模型效果还是不错的。除了输入单对句子,也可以将多对句子作为模型输入,以列表输入为例:sen_list1 = ['商务职业学院和财经职业学院哪个好?', '我爱中国'] sen_list2 = ['商务职业学院商务管理在哪个校区?', '我很爱中国'] sen_pairs = [] for k in range(len(sen_list1)): sen_pairs.append((sen_list1[k], sen_list2[k])) semantic_cls(input=(sen_pairs))从而得到如下的两组返回结果,可见第一个句子对结果与上述相同,第二个句子对则更有可能为entailment关系,也基本符合我们的判断。下面,我们展示如何通过一个web ui将该功能封装起来,形成一个小的app。引入web ui安装gradio:pip install gradio运行以下代码:import gradio as gr semantic_cls = pipeline(Tasks.nli, 'damo/nlp_structbert_nli_chinese-base') def natural_language_inference(text1, text2): res = semantic_cls(input=(text1, text2)) return res server = gr.Interface( fn=natural_language_inference, inputs=[ gr.Textbox(lines=1, placeholder="请输入文本1"), gr.Textbox(lines=2, placeholder="请输入文本2") ], outputs="text" ) server.launch(share="true")注意,最后server.launch的参数share=“true”表示该应用可以在Public URL上访问,否则只能在Local URL上访问。最终,该应用呈现如下:体验优化建议当前模型文档还比较简单,仅有单句子对输入示例,可以考虑加入多句子对和数据文件作为输入的示例,以提高用户使用效率。当前结果输出为按候选类别概率从小到大排列,可以考虑按照从大到小排列,更符合阅读习惯。
文章
自然语言处理  ·  小程序
2022-08-11
浅谈-大数据工程师面临的困境和要学习的技术
读书的时候,语文老师总会让同学看看作者的生平简介,谈谈作者为什么会写出这篇文章,文章诞生的背景是什么背景,一方面是让同学理解文章,另外一方面是让同学感同身受。鄙人,不是大厂,也不算外包,算是靠在阿里系的一家创业公司的交付部门的小小大数据工程师,心比天高,命比纸薄。当然,也和上学没有好好学习有关系,怨不得其他人。回到正题,咋们先从我的个人经历聊一下大数据工程师现在面临的困境和我的一些解决思路。数据的不整齐什么是数据的不整齐,这是我自己定义的一个概念,将不符合数据质量以及元数据模糊的数据定义为不整齐的数据。当我刚开始工作的时候,需要抽出数据用quickbi做一个bi看板,整体的逻辑相对来说很简单,根据商品名称的维度,日周月的维度划分出不同粒度的销售金额,退款金额等数据。听起来很容易,可到了正式实践的时候,发现问题来了。数据提供方根本没有办法提供需要的数据,就说商品金额和商品名称,没有对应的商品信息维表,订单中存储的商品名称会根据活动的不同增加不同的前缀,商品的金额也会因为优惠的波动而波动,不要说算roi,就按百分比算个成本价,一个商品可以出四五个不同的成本价,更不要说还有0的数据。当时我和其他同事拟定,选取订单中价格最小,但不是0的数据作为商品本身的价格,暂时将bi做了出来,不过真的有人在用吗?至于元数据的模糊,这个问题是大数据绕不开的问题,就单说订单表中商品数量一个字段,这里的商品数量指的是一个订单的购买的总的商品数量,还是一个订单下一个商品的购买数量?最近在看一本叫dama数据管理的书,里面有一大章专门讲元数据治理,感觉要想解决这个问题,必须是公司内有一个领导可以拍板说做数据治理,才能解决这个问题,不然越往后,治理难度会更大。也可能是我目前接触过的业务仅仅是零售行业,感觉行业内的数据有点一拍脑门的感觉,金额数据中写“按照第一季度金额走”,字段写的是coupon_id,注释里面写的是项目id,前者看到的时候除了暂时用rlike匹配正则把数据清除掉,只能跟产品反映,产品再跟对接人反映,对接人再跟业务反映;后者更是找不到对应的负责人,只能自己看数据确定关联关系。这就引出了新的问题-业务还是数据?业务的刁难业务是永远的痛。数据本身是没有价值的,这句话在零售行业一点也没有出错,零售的大数据想创造价值,那必须是和业务紧密对接,而我目前处理的需求,也基本上都是业务的需求。可业务需要什么样的数据呢?业务本身知道,但是他们不会主动将自己的想法说出来,而是需要技术来揣摩,接触的久了,就不会发现不了,如果不是有了固定模板的数据,业务需要的数据其实只是看起来正常的数据。比如,我去年关注小程序的用户是一千万,那明年关注小程序的用户是十万,这数据肯定就不对,这个时候就需要技术来查,是不是计算逻辑出了问题,最后查到是不是源数据里面没有数据。这个故事听起来还算靠谱,但有时候需求真的过分不合理,开发一张大屏,临近尾声,节点都已经从开发提交到生产,忽然提出质疑,这里的数据不对,一查,是数据源是刚上线不久,之前的数据都是线下数据。但也不可否认,业务确实在督促技术进步。技术的进步从业务督促我进步的例子举几个,首先我接触的数据基本都在dataworks,而业务很多时候都需要抽出超过一万条的数据,我去之前,其他同事都是导出到oss,再从oss下载,可为了偷懒,当然也是在官方文档看多了,我用idea配置conf,就可以在idea搭建一个maxcomputer studio,直接下载数据,避免二次导出。再比如,有一张表里面的数据,如果有两个不同的维度,而这两个维度还互不相干,应该如何分组呢?写两个sql吗?或者抽明细数据在数据集等看板里面再聚合?也是在那个时候,我学会了cube,roullup和grouping up,简单的sql处理就可以实现复杂的逻辑。业务发现问题会来和产品唠嗑,产品又会找我唠嗑,为了提前他们发现问题,学会使用dataworks的强弱规则,真的没有话说,每天早上起来看一眼短信就可以,尤其是针对递增的数据,同周期的波动率让我更快一步。当然,还有的需求,需要将错误数据抽离出来,dataworks自己带的python节点,利用opds配合钉钉机器人,每天将错误信息打包成markdown格式报给业务。技术的局限不知道有没有人看到这里,如果看了上面,不难发现,我的每天工作似乎都在dataworks上,我承认这上面的东西很多,数据集成,离线开发,实时开发,hologres,function studio,数不清的好东西,可权限就那么多,我甚至连hologres的权限都没有。每天做的更多的是odps离线的开发,最开始还好,技术的成长很快,但几个月后,sql的基本能力就到了瓶颈期,没有大的项目,很难再有突破。最让我难受的是,一次和业务沟通,业务忽然问我,为什么用trino,而不是使用implama,我感觉自己如果再这么下去,很可能真的一辈子是个sqlboy,三十岁前可能就得找个零售行业干业务了。我不能接受这样的我,那应该怎么学习呢?自我的进步首先是发掘目前使用的工具的潜能,就拿dataworks来说,进不去function studio,我就进idea,在idea开发自定义函数,从最简单的udf到后面的udtf和udaf,再到mapreduce。传统的sql写腻歪了,那我就尝试sql组件节点,尝试赋值节点和分支节点。再就是学习阿里的训练营和定期的活动,都说人需要好的反馈来刺激自己继续努力,没有比这些小活动更好的激励方式,而且千万不要认为学习的东西是没有用的,触类旁通的道理武侠小说里面都有写。就拿我最近正在看的polardb,polardbx和polardb for postgre的课程来说,我从里面看到了数据库ap查询和tp查询的区别,理解大数据的存储大多都以列存储是为了更好的压缩和查询,掌握了docker的部分知识,自己去思考数据库和大数据的本质区别。干活可能没有那么多,但是希望诸君共勉。
文章
SQL  ·  存储  ·  DataWorks  ·  小程序  ·  大数据  ·  关系型数据库  ·  BI  ·  数据库  ·  对象存储  ·  PolarDB
2022-08-11
关于我ESC的初体验
我是一位就读于计算机科学与技术专业的大二学生,通过同学介绍在阿里云通过学生认证可以免费领取一台2核2G阿里云ESC,于是一路找到这里,通过一系列的训练后成功领取。 在领取前需要通过阿里云的训练营,通过训练让我很好的明白了使用ESC的步骤和注意事项,对我帮助非常大,并且解释的非常详细清楚。让我明白了,如何在控制台管理ESC,如何查看ESC相关信息,如何重置ESC登录密码等。本人需要阿里云ESC服务器主要是因为参加的项目组中我制作的微信小程序部署服务器并且上线,了解到需要服务器和域名后,于是在阿里云购买了域名。通过在学习如何配置服务器后,于是逐步配置xshell和xftp,后将服务器与域名连接,在期间也遇到了很多问题,例如配置时操作不成功,没找到端口等,在搜索解决办法时也发现阿里云网有很多我遇到的问题的有效解决办法,简直是雪中送炭,按方法步骤操作后,问题都一步一步解决,而小程序部署的后续操作也还在学习和进行中。 阿里云ESC对我来说最有帮助的就是可以进行重装,对于我这种这方面知识较薄弱的同学帮助很大,可以让我多次试验操作,不断学习。此外,阿里云ESC学习机非常方便,相较于其他方法操作简单,并且不需要占用电脑内存,满足了我的学习需求。总而言之,阿里云ESC很好的照顾到了我这种第一次接触服务器的同学,贴心及详细,对于还是学生的我掌握并加强这方面的知识非常重要,后续我也将继续深入学习,并且利用好ESC服务器,实现其价值。期待在阿里云ESC的帮助下能让我学习到更多知识!
文章
小程序  ·  数据安全/隐私保护
2022-08-11
利用APICloud studio3可视化开发工具开发企业管理app
利用 APICloud studio3 可以帮助我们快速创建并运行一个简单的应用,内容涉及开发工具使用、代码管理、应用预览、调试等。现在我们来通过 APICloud studio3 快速创建一个企业管理 app,帮助企业提供信息展示、案例展示、加盟申请等。方便用来展示产品、案例、宣传企业文化、资讯动态等。1. 准备工作下载并安装最新版本开发工具 APICloud Studio 3,当前支持 Windows、macOS、Linux 系统。下载地址 https://www.apicloud.com/studio3 2. 创建应用当前提供了两种创建应用的方式,可以在 APICloud App 开发控制台 或 APICloud Studio 3 中创建应用,我们以在 APICloud Studio 3 中创建应用为例。打开后点击窗口右上角的 “未登录” 按钮,使用 APICloud 账号登录,若无账号可以点击 “立即注册” 按钮进行注册。顶部菜单选择 “项目” - “新建项目”,然后填写 “应用名称”,“应用说明”,应用框架处内置了几种模板,这里我们选择 MX App“企业展示”,点击 “完成”,然后选择项目保存的路径,完成创建。在应用类型选择上 1. MX App: 一次开发多端应用,同时生成 iOS、Android、app 小程序等多端应用。2。Native App:Native App 适合功能强大、性能卓越的 APP 开发,如果您仅有 App 需求,应选择 Native App 模式。3. App Clip: 即 iOS 轻 App,是苹果公司在 iOS14 最新推出的无需下载、即搜即用的全功能 App,属于主 App 的一部分,可以理解为 “苹果小程序”。3.App 页面编辑编辑器为可视化编辑器,深度捆绑 avm 的多端引擎,只有拓展名是 stml 的才具备可视化的能力,打开 pages\main 会看到一个 stml 后缀的文件,与传统的 html 不同的是,它最终可以生成多端应用。编辑器集成了实时预览功能功能,可以通过实时预览,将页面以 H5 渲染展示在编辑器中,可以点击实时预览(左侧小眼睛)可以看到页面预览效果,也可以复制链接在浏览器中查看效果。进入可视化页面后,可以通过组件拖拽的方式对页面进行简单的修改,单击右侧属性。还便可以对所选组件进行尺寸颜色等修改,同时在右侧事件处,可以对当前函数新建一个函数,个性化制定相关功能。4.App 调试为了提高开发效率,我们提供了 WiFi 真机同步功能,设备安装 AppLoader 或者自定义 loader 应用后,可以将 APICloud Studio 中的项目代码通过 WiFi 同步到 loader 中进行预览。点击左侧的真机同步按钮,选择通过 WiFi 连接设备,扫一扫连接,连接时要保证手机与 studio 位于同一局域网段,连接后进行 WiFi 同步便可以在手机上正常显示了。(尽量开启所有权限)5.App 预览点击左侧的小云朵,通过云编译打包一个正式的安装包,选择应用类别、平台,然后点击下面的 “云编译” 按钮开始编译。等待编译完成后扫描二维码即可安装。6. 修改修改完内容后,我们将内容同步到云端,代码传输云端后选择 WiFi 全量同步就可以在手机端看到更新内容最后我们就可以在手机端下载安装软件,体验 app 啦。APICloud-专业APP开发、app定制服务商,提供一站式移动应用解决方案,满足您的各类需求,欢迎免费评估需求和获取报价。
文章
移动开发  ·  数据可视化  ·  小程序  ·  搜索推荐  ·  Linux  ·  开发工具  ·  Android开发  ·  iOS开发  ·  MacOS  ·  Windows
2022-08-11
实例|APICloud AVM框架开发视频会议APP
APP开发采用的APICloud平台的AVM多端应用开发框架,使用 avm.js 一个技术栈可同时开发 Android & iOS 原生 App、小程序和 iOS 轻 App,且多端渲染效果统一;全新的 App 引擎 3.0 不依赖 webView,提供百分百的原生渲染,保障 App 性能和体验与原生 App 一致;现有 api 直接映射兼容小程序接口,延续已有开发习惯;后台使用的PHP的thinkphp框架,通过composer集成各类插件。功能介绍1.创建会议,确认会议时间、参会人员、会议主题、确定会议主持人(默认为发起人)可开启会议;同时会通过应用消息和短信通知参会人员。2.加入会议,可通过会议大厅找的会议列表直接加入,也可通过输入会议编号加入会议;加入会议的前提是会议已在进行中。3.快速会议,可直接确认会议人员然后发起实时视频会议,参会人员实时接收应用消息或短信,快速进入会议。3.历史会议,分为我主持的会议、我参与的会议。4.会议大厅,列表显示今天需要参加的会议。5.会议纪要,会议结束后,会议主持人可通过APP或后台系统,把会议纪要整理发布到相关会议中,参会人员可在会议详情中查看会议纪要。6.会议附件,主持人员可在会议详情中,把会议相关的附件上传至相关会议中,参与人员可在会议详情中下载附件。7.通讯录,展示系统内的联系人,在创建会议时,会议中邀请人的时候会用到。应用模块项目目录应用展示 开发介绍应用导航使用的是tabLayout布局作为应用的导航。系统首页使用tabLayout,可以将相关参数配置在JSON文件中,再在config.xml中将content的 值设置成该JSON文件的路径。如果底部导航没有特殊需求这里强烈建议大家使用tabLayout为APP进行布局,官方已经将各类手机屏幕及不同的分辨率进行了适配,免去了很多关于适配方面的问题。{    "name": "root",    "hideNavigationBar": true,    "navigationBar": {      "background": "#ffffff",      "color": "#333333",      "shadow": "#ffffff",      "hideBackButton": true    },    "tabBar": {      "scrollEnabled": false,      "background": "#fff",      "shadow": "#dddddd",      "color": "#aaaaaa",      "selectedColor": "#333333",      "index":0,      "preload": 0,      "frames": [{        "name": "home",        "url": "pages/main/home.stml",        "title": "会议"      }, {        "name": "classify-index",        "url": "pages/classify/classify-index.stml",        "title": "消息"      }, {        "name": "shopping-index",        "url": "pages/shopping/shopping-index.stml",        "title": "文档"      }, {        "name": "my-index",        "url": "pages/my/my-index.stml",        "title": "我的"      }],      "list": [{        "text": "会议",        "iconPath": "image/tabbar/meeting.png",        "selectedIconPath": "image/tabbar/meeting-o.png",        "scale":3      }, {        "text": "消息",        "iconPath": "image/tabbar/message.png",        "selectedIconPath": "image/tabbar/message-o.png",        "scale":3      }, {        "text": "文档",        "iconPath": "image/tabbar/doc.png",        "selectedIconPath": "image/tabbar/doc-o.png",        "scale":3      }, {        "text": "我的",        "iconPath": "image/tabbar/user.png",        "selectedIconPath": "image/tabbar/user-o.png",        "scale":3      }]    }  }动态权限   安卓10之后,对应用的权限要求提高,不在像老版本一样配置上就会自动获取,必须进行提示。依据官方给出的教程进行了动态权限的设置。1.添加 mianfest.xml文件<?xml version="1.0" encoding="UTF-8"?><manifest>    <application name="targetSdkVersion" value="30"/></manifest>具体的使用说明,在官方论坛中有专门的帖子,APP动态权限及Android平台targetSdkVersion设置 在系统主页进行动态权限获取,也可在特殊页面的中获取本页面所需的权限,这个可根据具体的业务需求进行处理。本系统涉及到了文件存储、摄像头、麦克风的获取,具体的获取方式见如下代码,因为本系统的初始化页面时home.stml,所以在本页面的apiready()中进行权限验证。            apiready(){                let limits=[];//获取权限var resultList = api.hasPermission({list: ['storage', 'camera', 'microphone']});if (resultList[0].granted) {// 已授权,可以继续下一步操作} else {limits.push(resultList[0].name);}if (resultList[1].granted) {// 已授权,可以继续下一步操作} else {limits.push(resultList[1].name);}if (resultList[2].granted) {// 已授权,可以继续下一步操作} else {limits.push(resultList[2].name);}if(limits.length>0){api.requestPermission({list: limits,}, (res) => {});}            }    WebSocket用于即时通话的时候,监听用户在线状态,可通知用户加入会议。具体的通讯原理步骤是:会议发起人发起会议-》通过websocket给参会人员发送消息指令-》参会人员接收发送的websocket消息,通过监听触发进入会议房间,同时给会议发起人发送进入会议房间的消息-》会议发起人收到有人进入了会议房间消息后,通过监听触发进入会议房间的操作。 这种流程是会议发起人不必先进入回房间进行等待,不用启用RTC模块,只有当有其他人员收到提醒进入会议房间后才会启用RTC模块进入房间。可以有效的避免资源浪费。还有一中简易模式,会议发起人发起会议,并启用RTC模块,进入会议房间进行等待(判断等待时间,比如超过3分钟没有其他人员加入房间,自动退出会议房间结束会议)-》通过websocket给参会人员发送消息指令-》参会人员接收发送的websocket消息,通过监听触发进入会议房间。这种模式如果其他参会人员不及时参加会议的时候会造成部分资源的浪费。进入会议后其他后续的操作,就可以通过tencnetTRTC模块中的方法进行处理。websocket的目的就是即时的通知参会人员有会议要参加,因为RTC模块本身没有集成这个功能。这部分操作是在进入会议房间之前的操作。本APP用的是websocket模块,本模块可配置全局变量,方便实用。当然也可以尝试其他的websocket模块。 AVM框架里官方就集成了websocket。使用说明文档apiready(){//链接websocketvar webSocket = api.require('webSocket');//消息监听,可以监听连接,断开,接收消息等事件webSocket.addEventListener((ret, err) => {console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));//断开重连if(ret.evenType=='Closed'){webSocket.open({url : 'ws://192.168.1.5:8888/socket'}, (ret, err) => {console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));});}//收到消息if(ret.evenType=='ReturnData'){//解析data中的内容,获取会议房间ID进入会议}});//获取当前的websocket链接状态var webSocketStatus = webSocket.getConnectState();//未链接则进行链接,如果已链接则无效操作if(webSocketStatus.State =='CLOSED'){webSocket.open({url : 'ws://192.168.1.5:8888/socket'}, (ret, err) => {console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));});}},视频通话 RTC 使用的是tencnetTRTC模块,查看模块文档 首先需要去申请腾讯云 SDKAppId,进入腾讯云实时音视频控制台 创建应用,即可看到 SDKAppId。为什要用tencnetTRTC呢,因为tencnetTRTC模块不会把SDKAppId与应用进行绑定,这样就可以使用一个SDKAppId来实现两个不同的APP之间的视频通话了,共用腾讯云的通话时长。而且tencnetTRTC的接口相比较其他RTC模块更丰富,可以更好的满足一些个性化的需求。消息事件通过sendEvent把事件广播出去,然后在其他页面通过addEventListener监听事件,通过事件名和附带的参数进行其他操作。API对象说明文档 举例说明1.当创建会议成功之后,需要发送一个会议创建成功的事件;在会议列表或者其他展示会议的页面,需要监听此事件,然后在监听成功的回调中做刷新的操作。2.当会议开始或者结束之后,需要发送相应的事件,在会议列表或者其他展示会议的页面,需要监听此类事件,在监听成功的回调中做刷新列表或者更改会议状态的操作。消息推送ajpush模块封装了极光推送平台的SDK,使用此模块可实现接收推送通知和透传消息功能。关于模块使用及注意事项,请仔细阅读模块说明文档//初始化JpushSDKinitJpush(){var jpush = api.require('ajpush');jpush.init((ret, err)=>{if(ret && ret.status){//绑定别名if(api.getPrefs({sync: true,key: 'userid'})){jpush.bindAliasAndTags({alias:api.getPrefs({sync: true,key: 'userid'}),tags:['APPUSER']}, (ret, err)=>{if(ret.statusCode==0){api.toast({ msg: '推送服务初始化成功'});}else{api.toast({ msg: '绑定别名失败'});}});} //监听消息jpush.setListener((ret) => {// var content = ret.content;api.toast({ msg: ret.content});});}else{api.toast({ msg: '推送服务初始化失败'});}});api.addEventListener({name:'pause'}, function(ret,err) {jpush.onResume();//监听应用进入后台,通知jpush暂停事件}) api.addEventListener({name:'resume'}, function(ret,err) {jpush.onResume();//监听应用恢复到前台,通知jpush恢复事件})},短信验证码用户注册的时候需要通过手机短信验证码进行校验,以保证手机号真实有效,能够正常接收应用推送的各类短信通知提醒。本应用中使用的是AVM模块库中的verification-code-input组件,可自定义验证码长度和再次获取时间间隔,自动校验验证码有效性。 示例代码<template><view class="page"><safe-area></safe-area><verification-code-input :limitSecond={seconds} :limitCode={codeLen} onsetCode="getCode"></verification-code-input></view></template><script>import '../../components/verification-code-input.stml'export default {name: 'demo-verification-code-input',apiready(){ },data() {return{code:'',seconds:60,codeLen:4}},methods: {getCode(e){// console.log(JSON.stringify(e.detail));this.data.code = e.detail;}}}</script>关于验证码的有效时间,是通过后台进行设定的,通过session缓存每个手机号的验证码,并设置缓存有效时间,表单提交的时候通过session去获取验证码,如果session失效,则无法获取验证码,接口可直接返回验证码失效提示。 清空缓存首先通过getCacheSize获取应用的缓存数量,并在标签中显示,然后给标签添加点击事件,在事件中通过clearCache清除应用缓存。计算当前应用的缓存大小,保留以为小数。 apiready(){//获取APP缓存 异步返回结果:api.getCacheSize((ret) => {this.data.cache = parseInt(ret.size/1024/1024).toFixed(1);});},执行清除缓存,并提示信息。 clearCache(){api.clearCache(() => {this.data.cache=0.0;api.toast({msg:'清除完成'})});}AVM组件使用项目中使用了很多的AVM组件,其中包括视频通话组件、通讯录组件、滑动单元格组件、日期时间Picker组件、数字键盘组件等等。   其中视频通话组件(easy-video-call、easy-voice-communication、multi-person-video-call)用的是声网的SDK,这里借用了样式,把模块换成了TencentRTC。消息列表列表中使用了easy-swiper-cell滑动单元格组件,来实现滑动操作已读。时期和时间选择用到了time-picker、date-picker组件。通讯录使用的是address-book组件。在通过会议编号进入会议时,由于会议编号全是数字,这里使用了number-keyboard数组键盘组件。文档下载、图片浏览会议结束后会上传会议纪要,会议相关文件等各类文档,主要包括doc、excel、pdf和图片。对于doc、excel、pdf这类文件使用的是docReader模块。方式是先通过api.download方法下载文,然后在回调中通过docReader模块唤醒三方工具进行文件浏览。//下载、浏览附件    loadfile(url){      api.download({          url: url,          // savePath: 'fs://appDownload/',//不选自动创建路径          report: true,          cache: true,          allowResume: true      }, (ret, err)=> {          if (ret.state == 1) {              //下载成功              api.hideProgress();              var path=ret.savePath;              // alert('下载成功,文件路径:'+ret.savePath);              var docReader = api.require('docReader');              docReader.open({                  path: path,                  autorotation: false              }, (ret, err) => {                  if (!ret.status) {                      if(err.code=='1'){                        alert('打开文件错误,请自行查找文件打开,路径:'+path);                      }                      else if(err.code=='2'){                        alert('文件格式错误,请自行查找文件打开,路径:'+path);                      }                  }              });          }          else if(ret.state == 0){            api.showProgress({              title: '努力下载中...',              text: ret.percent+'%',              modal: false            });          }          else if(ret.state == 2) {              api.hideProgress();              alert('下载失败,请重试。');          }      });    } 图片使用的是photoBrowser模块进行浏览picturePreview(e){let images = e.currentTarget.dataset.list;//预览图片var photoBrowser = api.require('photoBrowser');photoBrowser.open({images: images,bgColor: '#000'}, function(ret, err) {if(ret.eventType=='click'){photoBrowser.close();}});}单设备登陆本APP做了单一设备登陆的限制,具体实现方式是,通过api.deviceId可以获取到收的设备ID,用户登陆成功之后进行设备绑定;APP初始化的时候进行设备验证,先通过接口获取数据库中记录的用户上次登录的设备ID,然后与本机设备ID进行比对,如果设备ID不一致则跳转登陆页面。//登记设备    setDeviceID(){var data={secret:'',userid:api.getPrefs({sync: true,key: 'userid'}),deviceid:api.deviceId};api.showProgress();POST('updatedeviceid',data,{}).then(ret =>{// console.log(JSON.stringify(ret));if(ret.flag=='Success'){api.toast({msg:'设备登记成功'})}api.hideProgress();}).catch(err =>{api.toast({msg:JSON.stringify(err)})})}//验证设备checkDeviceID(){var data={secret:'',userid:api.getPrefs({sync: true,key: 'userid'})};api.showProgress();POST('querydeviceidbynew',data,{}).then(ret =>{// console.log(JSON.stringify(api.deviceId));if(ret.flag=='Success'){if(ret.data.deviceid != api.deviceId){api.toast({msg:'您的设备已在其他设备上登录,请重新登录。'})$util.openWin({name: 'login',url: 'widget://pages/seeting/login.stml',title: '',hideNavigationBar:true});}}api.hideProgress();}).catch(err =>{api.toast({msg:'设备登陆异常,请重新登陆。'})$util.openWin({name: 'login',url: 'widget://pages/seeting/login.stml',title: '',hideNavigationBar:true});})}接口调用封装了 req.js进行接口调用,采用了ES6语法中的Promise是异步编程的一种解决方案(比传统的回调函数更加合理、强大),用同步操作将异步流程表达出来。避免层层嵌套回调。promise 对象提供统一接口,使得控制异步操作更加容易。有兴趣的同学可以多研究一下Promise。const config = {    schema: 'http',    host: '192.168.1.5',    path: 'index.php/Home/api/',    secret:'1f3ef6ac********6deecd990f'} function req(options) {    const baseUrl = `${config.schema}://${config.host}/${config.path}/`;    options.url = baseUrl + options.url;    return new Promise((resolve, reject) => {        api.ajax(options,  (ret, err) => {            console.log('[' + options.method + '] ' + options.url + ' [' + api.winName + '/' + api.frameName + ']\n' + JSON.stringify({                ...options, ret, err            }))            if (ret) {                resolve(ret);                api.hideProgress();            } else {                reject(err);                 api.hideProgress();            }        });    })}/** * GET请求快捷方法 * @constructor * @param url {string} 地址 * @param options {Object} 附加参数 */function GET(url, options = {}) {    return req({        ...options, url, method: 'GET'    });} /** * POST 请求快捷方法 * @param url * @param data * @param options {Object} 附加参数 * @returns {Promise<Object>} * @constructor */function POST(url, data, options = {}) {    data.secret = config.secret;    return req({        ...options, url, method: 'POST', data: {            values: data        }    });} export {    req, GET, POST, config}在stml页面中,首先要引用封装好的req.js,目前只封装了POST、GET两种方式,如果接口中有其他的方式,可以在此基础上进行封装。下面以登录页为例,展示具体的使用。<template>    <scroll-view class="page"><safe-area></safe-area><view class="top"><text class="top-title">登录</text><text class="top-sub-title">欢迎使用逍遥自在云视频会议,让您从此无忧工作!</text></view><view class="input-box"><image class="item-ico" src='../../image/user.png' mode="widthFix"></image><input class="item-input" placeholder="请输入账号" v-model="username"/></view><view class="input-box"><image class="item-ico" src='../../image/psw.png' mode="widthFix"></image><input class="item-input" type="password" placeholder="请输入密码" v-model="password"/></view><view class="btn-box"><button class="btn" onclick={this.login}>确定</button></view>    </scroll-view></template><script>import {POST} from '../../script/req.js'export default {name: 'login',apiready(){//监听返回  双击退出程序api.setPrefs({key: 'time_last',value: '0'});api.addEventListener({name : 'keyback'}, function(ret, err) {var time_last = api.getPrefs({sync: true,key: 'time_last'});var time_now = Date.parse(new Date());if (time_now - time_last > 2000) {api.setPrefs({key:'time_last',value:time_now});api.toast({msg : '再按一次退出APP',duration : 2000,location : 'bottom'});} else {api.closeWidget({silent : true});}});},data() {return{username:'',password:''}},methods: {login(){if (!this.data.username) {this.showToast("姓名不能为空");return;}if (!this.data.password) {this.showToast("密码不能为空");return;} var data={secret:'',user:this.data.username,psw:this.data.password};api.showProgress();POST('loginuser',data,{}).then(ret =>{// console.log(JSON.stringify(ret));if(ret.flag=='Success'){api.setPrefs({key:'username',value:ret.data.username});api.setPrefs({key:'userid',value:ret.data.id});api.setPrefs({key:'deviceid',value:ret.data.deviceid});api.setPrefs({key:'phone',value:ret.data.phone}); //登记设备this.setDeviceID(); api.sendEvent({name: 'loginsuccess',});api.closeWin();}else{api.toast({msg:'登录失败!请稍后再试。'})}api.hideProgress();}).catch(err =>{api.toast({msg:JSON.stringify(err)})})},//登记设备setDeviceID(){var data={secret:'',userid:api.getPrefs({sync: true,key: 'userid'}),deviceid:api.deviceId};api.showProgress();POST('updatedeviceid',data,{}).then(ret =>{// console.log(JSON.stringify(ret));if(ret.flag=='Success'){api.setPrefs({key:'deviceid',value:api.deviceid});api.toast({msg:'设备登记成功'})}api.hideProgress();}).catch(err =>{api.toast({msg:JSON.stringify(err)})})}}}</script><style>    .page {        height: 100%;background-color:#ffffff;    }.top{margin-top: 50px;margin-left: 20px;margin-bottom: 100px;}.top-title{font-size: 25px;font-weight: bold;}.top-sub-title{font-size: 13px;font-weight: bold;}.input-box{margin: 20px;border-bottom: 1px solid #ccc;padding-bottom: 5px;flex-flow: row nowrap;align-items: center;}.item-input{width: auto;border: 0;font-size: 18px;margin-left: 10px;}.item-ico{width: 35px;}.btn-box{margin-top: 50px;margin-left: 10px;margin-right: 10px;}.btn{background-color: #256fff;color: #ffffff;font-size: 20px;border-radius: 20px;padding: 10px 0;font-weight: bold;}</style>后台代码代码示例<?phpnamespace Home\Controller;require 'vendor/autoload.php';    // 注意位置一定要在 引入ThinkPHP入口文件 之前 use Think\Controller;use JPush\Client as JPushClient;use AlibabaCloud\Client\AlibabaCloud;use AlibabaCloud\Client\Exception\ClientException;use AlibabaCloud\Client\Exception\ServerException;class ApiController extends Controller {    public function index(){        $this->show('');    }    //用户登录    public function loginuser(){        checkscret('secret');//验证授权码        checkdataPost('user');//账号        checkdataPost('psw');//密码          $map['username']=$_POST['user'];        $map['password']=$_POST['psw'];        $map['zt']='T';                $releaseInfo=M()->table('user')->field('id,username,phone,deviceid,role')->where($map)->find();          if($releaseInfo){            returnApiSuccess('登录成功',$releaseInfo);          }          else{            returnApiError( '登录失败,请稍后再试');            exit();          }      }        //记录登录设备ID      public function updatedeviceid(){        checkscret('secret');//验证授权码        checkdataPost('userid');//用户ID        checkdataPost('deviceid');//设备ID         $userid=$_POST['userid'];        $deviceid=$_POST['deviceid'];         $map['id']=$userid;         $data['deviceid']=$deviceid;         $releaseInfo=M()->table('user')->where($map)->save($data);         if($releaseInfo){          returnApiSuccess('登记成功',$releaseInfo);        }        else{          returnApiError( '登记失败,请稍后再试');          exit();        }    }     //获取最新的登录用户设备ID    public function querydeviceidbynew(){        checkscret('secret');//验证授权码        checkdataPost('userid');//用户ID         $userid=$_POST['userid'];         $map['id']=$userid;         $releaseInfo=M()->table('user')->field('deviceid')->where($map)->find();         if($releaseInfo){          returnApiSuccess('查询成功',$releaseInfo);        }        else{          returnApiError( '查询失败,请稍后再试');          exit();        }    }     //APP修改密码    public function updatepassword(){        checkscret('secret');//验证授权码        checkdataPost('userid');//用户ID         checkdataPost('password');//密码            $userid=$_POST['userid'];        $password=$_POST['password'];          $map['id']=$userid;         $data['password']=$password;         $releaseInfo=M()->table('user')->where($map)->save($data);        if($releaseInfo){            returnApiSuccess('修改成功',$releaseInfo);        }        else{            returnApiError( '修改失败,请稍后再试');            exit();        }      }      //新增会议    public function addhuiyi(){        checkscret('secret');//验证授权码        checkdataPost('userid');//ID         $userid=$_POST['userid'];        $title=$_POST['title'];        $content=$_POST['content'];        $users=$_POST['users'];        $hysj=$_POST['hysj'];        $hylx=$_POST['hylx'];          $data['title']=$title;        $data['content']=$content;        $data['fqr']=$userid;        $data['cyr']=$users;        $data['hysj']=$hysj;        $data['flag']='01';//未开始        $data['cjsj']=time();        $data['type']=$hylx;         $data['txsj']=date('Y-m-d H:i:s',strtotime("$hysj-10 minute"));        $data['istip']='01';         $arruser=explode(',',$users);               $releaseInfo=M()->table('meeting')->data($data)->add();        if($releaseInfo){                 //发送消息            $this->setmessage($users,'您有一个视频会议需要参加,时间:'.$hysj);            //发送短信通知            //$this->pushmsgbyusers($users,$hysj);            //极光推送            try{              $jpush = new JPushClient(C('JPUSH_APP_KEY'), C('JPUSH_MASTER_SECRET'));              $response = $jpush->push()                  ->setPlatform('all')  //机型 IOS ANDROID                  ->addAlias($arruser)                  ->androidNotification($content)                  ->iosNotification($content,'',0,true)                  ->options(array(                      'apns_production' => true,                  ))                  ->send();                        returnApiSuccess('添加成功');              }              catch(\Exception $e){                returnApiSuccess('添加成功');                exit();              }                 }        else{          returnApiError('添加失败,请稍后再试!');          exit();        }    }     //查询会议大厅    public function querymeeting(){      checkscret('secret');//验证授权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条       $userid=$_POST['userid'];       $where['fqr']=$userid;      $where['_string']='find_in_set('.$userid.',cyr)';      $where['_logic']='or';      $map['_complex']=$where;       $map['flag']=array('neq','03');               $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }       $releaseInfo=M()->table('meeting')->field('id,title,flag,hysj,sjzd(type,\'会议类型\') hylx,cyr,fqr,type')->where($map)->limit($skip,$limit)->order('hysj desc')->select();         if($releaseInfo){        returnApiSuccess('查询成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }     //设置会议状态    public function setmeeting(){      checkscret('secret');//验证授权码      checkdataPost('id');//会议ID      checkdataPost('flag');//会议状态       $id=$_POST['id'];      $flag=$_POST['flag'];       $map['id']=$id;       $data['flag']=$flag;       if($flag=='02'){        $data['start']=time();      }      else if($flag=='03'){        $data['end']=time();      }       $releaseInfo=M()->table('meeting')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('更新成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }      //上传会议纪要    public function addhyjy(){      checkscret('secret');//验证授权码      checkdataPost('id');//会议ID      checkdataPost('hyjy');//会议纪要       $id=$_POST['id'];      $hyjy=$_POST['hyjy'];       $map['id']=$id;       $data['jiyao']=$hyjy;        $releaseInfo=M()->table('meeting')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('上传成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }     //查询历史会议    public function queryhistory(){      checkscret('secret');//验证授权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条       $userid=$_POST['userid'];       $where['fqr']=$userid;      $where['_string']='find_in_set('.$userid.',cyr)';      $where['_logic']='or';      $map['_complex']=$where;       $map['flag']=array('eq','03');               $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }       $releaseInfo=M()->table('meeting')->field('id,title,hysj')->where($map)->limit($skip,$limit)->order('hysj desc')->select();         if($releaseInfo){        returnApiSuccess('查询成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }     //查询会议详情    public function queryhistoryinfo(){      checkscret('secret');//验证授权码      checkdataPost('id');//会议ID            $id=$_POST['id'];       $map['id']=$id;       $releaseInfo=M()->table('meeting')->field('id,title,hysj,content,getusers(cyr) users,sjzd(type,\'会议类型\') type,jiyao,getmeetinglong(id) sc')->where($map)->find();         if($releaseInfo){        returnApiSuccess('查询成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }     //发送消息通知    function setmessage($users,$content){      $arruser=explode(',',$users);      foreach ($arruser as $item) {        $data['user']=$item;        $data['content']=$content;        $data['shijian']=time();        $data['sfyd']='01';         $info=M()->table('sp_message')->data($data)->add();      }    }        //查询消息    public function querymessage(){      checkscret('secret');//验证授权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条       $userid=$_POST['userid'];       $map['user']=$userid;             $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }       $releaseInfo=M()->table('message')->field('id,content,sfyd,from_unixtime(shijian,\'%Y-%m-%d %H:%i:%s\') sj')->where($map)->limit($skip,$limit)->order('sj desc')->select();         if($releaseInfo){        returnApiSuccess('查询成功',$releaseInfo);      }      else{        returnApiError( '没有查询到任何数据');        exit();      }    }     //设置消息已读    public function setxxyd(){      checkscret('secret');//验证授权码      checkdataPost('id');//ID       $id=$_POST['id'];       $map['id']=$id;       $data['sfyd']='02';       $releaseInfo=M()->table('message')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('设置成功',$data);      }      else{        returnApiError( '设置失败,请稍后再试');        exit();      }          }   //推送用户短信提醒  function pushmsgbyusers($users,$shijian){    $map['_string']='find_in_set(id,\''.$users.'\')';     $data=M()->table('user')->field('group_concat(trim(phone)) phones')->where($map)->find();     if($data){        $phones=$data['phones'];        //发送验证码               AlibabaCloud::accessKeyClient(C('accessKeyId'), C('accessSecret'))                          ->regionId('cn-beijing')                          ->asDefaultClient();        try {            $param = array("datetime"=>$shijian);            $result = AlibabaCloud::rpc()                      ->product('Dysmsapi')                      // ->scheme('https') // https | http                      ->version('2017-05-25')                      ->action('SendSms')                      ->method('POST')                      ->host('dysmsapi.aliyuncs.com')                      ->options([                            'query' => [                            'RegionId' => "cn-beijing",                            'PhoneNumbers' =>$phones,                            'SignName' => "****有限公司",                            'TemplateCode' => "SMS_****",                            'TemplateParam' => json_encode($param),                          ],                      ])                      ->request();        }catch (ClientException $e) {                  }         return $result;    }  }   //获取腾讯视频RTC usersig  public function getQQrtcusersig(){    checkscret('secret');//验证授权码    checkdataPost('userid');//用户ID     $sdkappid=C('sdkappid');    $key=C('usersig_key');     $userid=$_POST['userid'];    require 'vendor/autoload.php';    $api = new \Tencent\TLSSigAPIv2($sdkappid, $key);    $sig = $api->genSig($userid);        if($sig){      returnApiSuccess('查询成功',$sig);    }    else{      returnApiError( '查询失败,请稍后再试');      exit();    }  }}插件引用用到了阿里短信插件、极光推送插件、腾讯RTC签名插件;通过composer安装。composer.json文件{"config": {          "secure-http": false      },"require": {"jpush/jpush": "^3.6","tencent/tls-sig-api-v2": "1.0","alibabacloud/client": "^1.5"}}
文章
缓存  ·  开发框架  ·  前端开发  ·  小程序  ·  API  ·  PHP  ·  Android开发  ·  数据安全/隐私保护  ·  iOS开发  ·  数据格式
2022-08-11
程序员需要了解的硬核知识之操作系统入门
操作系统环境程序中包含着运行环境这一内容,可以说 运行环境 = 操作系统 + 硬件 ,操作系统又可以被称为软件,它是由一系列的指令组成的。我们不介绍操作系统,我们主要来介绍一下硬件的识别。我们肯定都玩儿过游戏,你玩儿游戏前需要干什么?是不是需要先看一下自己的笔记本或者电脑是不是能肝的起游戏?下面是一个游戏的配置(怀念一下 wow)图中的主要配置如下操作系统版本:说的就是应用程序运行在何种系统环境,现在市面上主要有三种操作系统环境,Windows 、Linux 和 Unix ,一般我们玩儿的大型游戏几乎都是在 Windows 上运行,可以说 Windows 是游戏的天堂。Windows 操作系统也会有区分,分为32位操作系统和64位操作系统,互不兼容。处理器:处理器指的就是 CPU,你的电脑的计算能力,通俗来讲就是每秒钟能处理的指令数,如果你的电脑觉得卡带不起来的话,很可能就是 CPU 的计算能力不足导致的。想要加深理解,请阅读博主的另一篇文章:程序员需要了解的硬核知识之CPU显卡:显卡承担图形的输出任务,因此又被称为图形处理器(Graphic Processing Unit,GPU),显卡也非常重要,比如我之前玩儿的剑灵开五档(其实就是图像变得更清晰)会卡,其实就是显卡显示不出来的原因。内存:内存即主存,就是你的应用程序在运行时能够动态分析指令的这部分存储空间,它的大小也能决定你电脑的运行速度,想要加深理解,请阅读博主的另一篇文章 程序员需要了解的硬核知识之内存存储空间:存储空间指的就是应用程序安装所占用的磁盘空间,由图中可知,此游戏的最低存储空间必须要大于 5GB,其实我们都会遗留很大一部分用来安装游戏。从程序的运行环境这一角度来考量的话,CPU 的种类是特别重要的参数,为了使程序能够正常运行,必须满足 CPU 所需的最低配置。CPU 只能解释其自身固有的语言。不同的 CPU 能解释的机器语言的种类也是不同的。机器语言的程序称为 本地代码(native code),程序员用 C 等高级语言编写的程序,仅仅是文本文件。文本文件(排除文字编码的问题)在任何环境下都能显示和编辑。我们称之为源代码。通过对源代码进行编译,就可以得到本地代码。下图反映了这个过程。Windows 操作系统克服了CPU以外的硬件差异计算机的硬件并不仅仅是由 CPU 组成的,还包括用于存储程序指令的数据和内存,以及通过 I/O 连接的键盘、显示器、硬盘、打印机等外围设备。在 WIndows 软件中,键盘输入、显示器输出等并不是直接向硬件发送指令。而是通过向 Windows 发送指令实现的。因此,程序员就不用注意内存和 I/O 地址的不同构成了。Windows 操作的是硬件而不是软件,软件通过操作 Windows 系统可以达到控制硬件的目的。不同操作系统的 API 差异性接下来我们看一下操作系统的种类。同样机型的计算机,可安装的操作系统类型也会有多种选择。例如:AT 兼容机除了可以安装 Windows 之外,还可以采用 Unix 系列的 Linux 以及 FreeBSD (也是一种Unix操作系统)等多个操作系统。当然,应用软件则必须根据不同的操作系统类型来专门开发。CPU 的类型不同,所对应机器的语言也不同,同样的道理,操作系统的类型不同,应用程序向操作系统传递指令的途径也不同。应用程序向系统传递指令的途径称为 API(Application Programming Interface)。Windows 以及 Linux 操作系统的 API,提供了任何应用程序都可以利用的函数组合。因为不同操作系统的 API 是有差异的。所以,如何要将同样的应用程序移植到另外的操作系统,就必须要覆盖应用所用到的 API 部分。键盘输入、鼠标输入、显示器输出、文件输入和输出等同外围设备进行交互的功能,都是通过 API 提供的。这也就是为什么 Windows 应用程序不能直接移植到 Linux 操作系统上的原因,API 差异太大了。在同类型的操作系统下,不论硬件如何,API 几乎相同。但是,由于不同种类 CPU 的机器语言不同,因此本地代码也不尽相同。FreeBSD Port 帮你轻松使用源代码不知道你有没有这个想法:“既然 CPU 不同会导致本地代码不同,那为何不将源代码直接发送给程序呢?”这确实是一种解决办法,Unix 系列的 FreeBSD 操作系统就使用了这种方式。Unix 系列操作系统 FreeBSD 中,存在一种名为 Ports 的机制。该机制能够结合当前运行环境的硬件环境来编译应用的源代码,进而得到可以运行的本地代码。如果目标应用的源代码在硬件上找不到,Ports 就会自动使用 FTP 连接到相应站点下载代码。全球有很多站点都提供适用于 FreeBSD 的应用源代码。通过使用 Ports 可以利用的程序源代码,大约有 16000 种。根据不同的领域进行分类,可以随时使用。FreeBSD 上应用的源代码,大部分是用 C 语言来标注的,C 编译器可以结合 FreeBSD 的运行环境来生成合适的本地代码。“FTP( File Transfer Protocol) 是连接到互联网上的计算机之间的传送文件的协议。可以使用虚拟机获取其他环境即使不通过应用程序的移植,在同一个操作系统上仍然可以使用其他的操作系统,那就是使用 虚拟机软件。虚拟机(Virtual Machine)指通过软件的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。在实体计算机中能够完成的工作在虚拟机中都能够实现。提供相同运行环境的 Java 虚拟机总算是提到大 Java 了, Java 大法好,除了虚拟机的方法之外,还有一种方法能够提供不依赖于特定硬件和操作系统的程序运行环境,那就是 Java。大家说的 Java 其实有两层意思,一种是作为编程语言的 Java;一种是作为程序运行环境的 Java。Java 与其他语言相同,都是通过源代码编译后运行的。不过,编译后生成的不是特定 CPU 使用的本地代码,而是名为字节代码 的程序。直接代码的运行环境就称为 Java 虚拟机(Java Virtual Machine)。Java 虚拟机是一边把 Java 字节代码逐一转换为本地代码一边在运行着。程序运行时,将编译后的字节代码转换为本地代码,这样的操作看上去有些迂回,但由此可以实现相同的字节码可以在不同的操作系统环境下运行。想象一下,你开发完成的应用部署到 Linux 环境下,是不是什么都不用管?Windows 有专门的 Windows 虚拟机,Macintosh 有 Macintosh 专门的虚拟机。从操作系统来看,Java虚拟机就是一个应用,从运行环境上来看,Java 虚拟机就是运行环境。BIOS 和引导最后对一些比较基础的部分做一些补充说明。程序的运行环境,存在着名为 BIOS(Basic Input/Output System)的系统。BIOS 存储在 ROM 中,是预先内置在计算机主机内部的程序。BIOS 除了键盘、磁盘和显卡等基本控制外,还有引导程序的功能。引导程序是存储在启动驱动器启示区域的小程序。操作系统的启动驱动器一般硬盘。不过有时也可能是 CD-ROM 或软盘。电脑开机后,BIOS 会确认硬件是否正常运行,没有异常的话会直接启动引导程序。引导程序的功能是把在硬盘等记录的 OS 加载到内存中运行。虽然启动应用是 OS 的功能,但 OS 不能启动自己,是通过引导程序来启动的。 </div>
文章
存储  ·  小程序  ·  Java  ·  Unix  ·  程序员  ·  Linux  ·  编译器  ·  API  ·  C语言  ·  Windows
2022-08-11
程序员需要了解的硬核知识之CPU
CPU是什么CPU 的全称是 Central Processing Unit,它是你的电脑中最硬核的组件,这种说法一点不为过。CPU 是能够让你的计算机叫计算机的核心组件,但是它却不能代表你的电脑,CPU 与计算机的关系就相当于大脑和人的关系。它是一种小型的计算机芯片,它嵌入在台式机、笔记本电脑或者平板电脑的主板上。通过在单个计算机芯片上放置数十亿个微型晶体管来构建 CPU。 这些晶体管使它能够执行运行存储在系统内存中的程序所需的计算,也就是说 CPU 决定了你电脑的计算能力。CPU 实际做什么CPU 的核心是从程序或应用程序获取指令并执行计算。此过程可以分为三个关键阶段:提取,解码和执行。CPU从系统的 RAM 中提取指令,然后解码该指令的实际内容,然后再由 CPU 的相关部分执行该指令。RAM : 随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与 CPU 直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储介质CPU 的内部结构说了这么多 CPU 的重要性,那么 CPU 的内部结构是什么呢?又是由什么组成的呢?下图展示了一般程序的运行流程(以 C 语言为例),可以说了解程序的运行流程是掌握程序运行机制的基础和前提。在这个流程中,CPU 负责的就是解释和运行最终转换成机器语言的内容。CPU 主要由两部分构成:控制单元 和 算术逻辑单元(ALU)控制单元:从内存中提取指令并解码执行算数逻辑单元(ALU):处理算数和逻辑运算CPU 是计算机的心脏和大脑,它和内存都是由许多晶体管组成的电子部件。它接收数据输入,执行指令并处理信息。它与输入/输出(I / O)设备进行通信,这些设备向 CPU 发送数据和从 CPU 接收数据。从功能来看,CPU 的内部由寄存器、控制器、运算器和时钟四部分组成,各部分之间通过电信号连通。寄存器是中央处理器内的组成部分。它们可以用来暂存指令、数据和地址。可以将其看作是内存的一种。根据种类的不同,一个 CPU 内部会有 20 - 100个寄存器。控制器负责把内存上的指令、数据读入寄存器,并根据指令的结果控制计算机运算器负责运算从内存中读入寄存器的数据时钟 负责发出 CPU 开始计时的时钟信号接下来简单解释一下内存,为什么说 CPU 需要讲一下内存呢,因为内存是与 CPU 进行沟通的桥梁。计算机所有程序的运行都是在内存中运行的,内存又被称为主存,其作用是存放 CPU 中的运算数据,以及与硬盘等外部存储设备交换的数据。只要计算机在运行中,CPU 就会把需要运算的数据调到主存中进行运算,当运算完成后CPU再将结果传送出来,主存的运行也决定了计算机的稳定运行。主存通过控制芯片与 CPU 进行相连,由可读写的元素构成,每个字节(1 byte = 8 bits)都带有一个地址编号,注意是一个字节,而不是一个位。CPU 通过地址从主存中读取数据和指令,也可以根据地址写入数据。注意一点:当计算机关机时,内存中的指令和数据也会被清除。CPU 是寄存器的集合体在 CPU 的四个结构中,我们程序员只需要了解寄存器就可以了,其余三个不用过多关注,为什么这么说?因为程序是把寄存器作为对象来描述的。说到寄存器,就不得不说到汇编语言,我大学是学信息管理与信息系统的,我就没有学过汇编这门课(就算有这门课也不会好好学hhhh),出来混总是要还的,要想作为一个硬核程序员,不能不了解这些概念。说到汇编语言,就不得不说到高级语言,说到高级语言就不得不牵扯出语言这个概念。计算机语言我们生而为人最明显的一个特征是我们能通过讲话来实现彼此的交流,但是计算机听不懂你说的话,你要想和他交流必须按照计算机指令来交换,这就涉及到语言的问题,计算机是由二进制构成的,它只能听的懂二进制也就是机器语言,但是普通人是无法看懂机器语言的,这个时候就需要一种电脑既能识别,人又能理解的语言,最先出现的就是汇编语言。但是汇编语言晦涩难懂,所以又出现了像是 C,C++,Java 的这种高级语言。所以计算机语言一般分为两种:低级语言(机器语言,汇编语言)和高级语言。使用高级语言编写的程序,经过编译转换成机器语言后才能运行,而汇编语言经过汇编器才能转换为机器语言。汇编语言首先来看一段用汇编语言表示的代码清单mov eax, dword ptr [ebp-8] /* 把数值从内存复制到 eax */ add eax, dword ptr [ebp-0Ch] /* 把 eax 的数值和内存的数值相加 */ mov dword ptr [ebp-4], eax /* 把 eax 的数值(上一步的结果)存储在内存中*/这是采用汇编语言(assembly)编写程序的一部分。汇编语言采用 助记符(memonic) 来编写程序,每一个原本是电信号的机器语言指令会有一个与其对应的助记符,例如 mov,add 分别是数据的存储(move)和相加(addition)的简写。汇编语言和机器语言是一一对应的。这一点和高级语言有很大的不同,通常我们将汇编语言编写的程序转换为机器语言的过程称为 汇编;反之,机器语言转化为汇编语言的过程称为 反汇编。汇编语言能够帮助你理解计算机做了什么工作,机器语言级别的程序是通过寄存器来处理的,上面代码中的 eax,ebp 都是表示的寄存器,是 CPU 内部寄存器的名称,所以可以说 CPU 是一系列寄存器的集合体。在内存中的存储通过地址编号来表示,而寄存器的种类则通过名字来区分。不同类型的 CPU ,其内部寄存器的种类,数量以及寄存器存储的数值范围都是不同的。不过,根据功能的不同,可以将寄存器划分为下面这几类种类功能累加寄存器存储运行的数据和运算后的数据。标志寄存器用于反应处理器的状态和运算结果的某些特征以及控制指令的执行。程序计数器程序计数器是用于存放下一条指令所在单元的地址的地方。基址寄存器存储数据内存的起始位置变址寄存器存储基址寄存器的相对地址通用寄存器存储任意数据指令寄存器储存正在被运行的指令,CPU内部使用,程序员无法对该寄存器进行读写栈寄存器存储栈区域的起始位置其中程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器都只有一个,其他寄存器一般有多个。程序计数器程序计数器(Program Counter)是用来存储下一条指令所在单元的地址。程序执行时,PC的初值为程序第一条指令的地址,在顺序执行程序时,控制器首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将PC的值加1指向下一条要执行的指令。我们还是以一个事例为准来详细的看一下程序计数器的执行过程这是一段进行相加的操作,程序启动,在经过编译解析后会由操作系统把硬盘中的程序复制到内存中,示例中的程序是将 123 和 456 执行相加操作,并将结果输出到显示器上。由于使用机器语言难以描述,所以这是经过翻译后的结果,实际上每个指令和数据都可能分布在不同的地址上,但为了方便说明,把组成一条指令的内存和数据放在了一个内存地址上。地址 0100 是程序运行的起始位置。Windows 等操作系统把程序从硬盘复制到内存后,会将程序计数器作为设定为起始位置 0100,然后执行程序,每执行一条指令后,程序计数器的数值会增加1(或者直接指向下一条指令的地址),然后,CPU 就会根据程序计数器的数值,从内存中读取命令并执行,也就是说,程序计数器控制着程序的流程。条件分支和循环机制我们都学过高级语言,高级语言中的条件控制流程主要分为三种:顺序执行、条件分支、循环判断三种,顺序执行是按照地址的内容顺序的执行指令。条件分支是根据条件执行任意地址的指令。循环是重复执行同一地址的指令。顺序执行的情况比较简单,每执行一条指令程序计数器的值就是 + 1。条件和循环分支会使程序计数器的值指向任意的地址,这样一来,程序便可以返回到上一个地址来重复执行同一个指令,或者跳转到任意指令。下面以条件分支为例来说明程序的执行过程(循环也很相似)程序的开始过程和顺序流程是一样的,CPU 从0100处开始执行命令,在0100和0101都是顺序执行,PC 的值顺序+1,执行到0102地址的指令时,判断0106寄存器的数值大于0,跳转(jump)到0104地址的指令,将数值输出到显示器中,然后结束程序,0103 的指令被跳过了,这就和我们程序中的 if() 判断是一样的,在不满足条件的情况下,指令会直接跳过。所以 PC 的执行过程也就没有直接+1,而是下一条指令的地址。标志寄存器条件和循环分支会使用到 jump(跳转指令),会根据当前的指令来判断是否跳转,上面我们提到了标志寄存器,无论当前累加寄存器的运算结果是正数、负数还是零,标志寄存器都会将其保存(也负责溢出和奇偶校验)溢出(overflow):是指运算的结果超过了寄存器的长度范围奇偶校验(parity check):是指检查运算结果的值是偶数还是奇数CPU 在进行运算时,标志寄存器的数值会根据当前运算的结果自动设定,运算结果的正、负和零三种状态由标志寄存器的三个位表示。标志寄存器的第一个字节位、第二个字节位、第三个字节位各自的结果都为1时,分别代表着正数、零和负数。CPU 的执行机制比较有意思,假设累加寄存器中存储的 XXX 和通用寄存器中存储的 YYY 做比较,执行比较的背后,CPU 的运算机制就会做减法运算。而无论减法运算的结果是正数、零还是负数,都会保存到标志寄存器中。结果为正表示 XXX 比 YYY 大,结果为零表示 XXX 和 YYY 相等,结果为负表示 XXX 比 YYY 小。程序比较的指令,实际上是在 CPU 内部做减法运算。函数调用机制接下来,我们继续介绍函数调用机制,哪怕是高级语言编写的程序,函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。函数执行跳转指令后,必须进行返回处理,单纯的指令跳转没有意义,下面是一个实现函数跳转的例子图中将变量 a 和 b 分别赋值为 123 和 456 ,调用 MyFun(a,b) 方法,进行指令跳转。图中的地址是将 C 语言编译成机器语言后运行时的地址,由于1行 C 程序在编译后通常会变为多行机器语言,所以图中的地址是分散的。在执行完 MyFun(a,b)指令后,程序会返回到 MyFun(a,b) 的下一条指令,CPU 继续执行下面的指令。函数的调用和返回很重要的两个指令是 call 和 return 指令,再将函数的入口地址设定到程序计数器之前,call 指令会把调用函数后要执行的指令地址存储在名为栈的主存内。函数处理完毕后,再通过函数的出口来执行 return 指令。return 指令的功能是把保存在栈中的地址设定到程序计数器。MyFun 函数在被调用之前,0154 地址保存在栈中,MyFun 函数处理完成后,会把0154的地址保存在程序计数器中。这个调用过程如下在一些高级语言的条件或者循环语句中,函数调用的处理会转换成 call 指令,函数结束后的处理则会转换成 return 指令。通过地址和索引实现数组接下来我们看一下基址寄存器和变址寄存器,通过这两个寄存器,我们可以对主存上的特定区域进行划分,来实现类似数组的操作,首先,我们用十六进制数将计算机内存上的 00000000 - FFFFFFFF 的地址划分出来。那么,凡是该范围的内存地址,只要有一个 32 位的寄存器,便可查看全部地址。但如果想要想数组那样分割特定的内存区域以达到连续查看的目的的话,使用两个寄存器会更加方便。例如,我们用两个寄存器(基址寄存器和变址寄存器)来表示内存的值这种表示方式很类似数组的构造,数组是指同样长度的数据在内存中进行连续排列的数据构造。用数组名表示数组全部的值,通过索引来区分数组的各个数据元素,例如: a[0] - a[4],[]内的 0 - 4 就是数组的下标。CPU 指令执行过程那么 CPU 是如何执行一条条的指令的呢?几乎所有的冯·诺伊曼型计算机的CPU,其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数、结果写回。取指令阶段是将内存中的指令读取到 CPU 中寄存器的过程,程序寄存器用于存储下一条指令所在的地址指令译码阶段,在取指令完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。执行指令阶段,译码完成后,就需要执行这一条指令了,此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。访问取数阶段,根据指令的需要,有可能需要从内存中提取数据,此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。结果写回阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据“写回”到某种存储形式:结果数据经常被写到CPU的内部寄存器中,以便被后续的指令快速地存取;总结本篇文章我们主要讲述了CPU 是什么,CPU 的重要性,CPU 执行程序的过程还讲述了 CPU 的内部结构,它的组成部分提到了汇编语言和高级语言提到了CPU 与 寄存器的关系提到了主要的寄存器的功能,程序计数器,标志寄存器,基址寄存器和变址寄存器还提到了函数调用机制是怎样的。CPU 指令的执行过程如果有任何疑问或者想找我交流的,欢迎关注个人二维码或者我的个人微信号 </div>
文章
存储  ·  小程序  ·  Java  ·  程序员  ·  C语言  ·  芯片  ·  C++  ·  索引  ·  Windows
2022-08-11
钉钉小程序手机端请求不成功
后台服务器正常运行,小程序开发者工具连接服务器,一切正常后发布,开放平台的发布管理中选择最新版本也发布了。手机端,一样向服务端发起请求,却没有进入服务器。
问答
小程序
2022-08-11
高校学生参加飞天加速计划
我是一名来自四川的大四学生,用阿里云服务器已经一年多了,平时能够在阿里云服务器上部署一些自己的小项目,不仅能够学习,还能满足自己的一些成就感。阿里云的很多活动对学生非常友好,最开始看到阿里云的学生服务器仅需9元一月,就直接下手了一年,服务器配置能够满足我的基本需求,平时学习Linux和部署一些项目在上面拿去参赛等。 阿里云非常的人性,在开通服务器时会介绍很多知识,能够让小白快速上手。开通服务器后,就安装了宝塔面板,这是一个非常好的服务器运维面板,提供了很多便捷的工具。阿里云服务器ECS可以搭建个人网站、小程序等,安全性也很高,不需要担心安全问题。 飞天加速计划是个非常好的活动,它为我在家中学习的大学生提供了一个稳定性强、容易使用、安全度高的云服务器,可以通过学习,做出属于自己的网页,可以和同学一起开发小程序、微信公众号等等。还搭建了小程序服务端,在比赛中还获了奖。阿里云将其在、大数据、AI、低代码、数据智能等方面,提供给开发者便利。宣布升级到2.0的"飞天加速计划",助力开发者应用创新。其中包括:面向高校师生,提供30亿小时免费资源,提供教育基金,用于学生教育与实践;面向开发者,提供专属特价产品及免费学习资源、认证服务和比赛基金,给我们很多优惠以及很多方便。 最后,对计算机感兴趣的,特别是喜欢开发的,一定要拥有一台自己的云服务器,刚好阿里云的活动就能够满足大部分的学生用户,刚开始还有免费的ECS领取,也希望更多的大学生加入到这一队列来,为自己增加一个新的技能!我希望阿里云能够不断创新,给广大应用人员提供更多的方便。
文章
弹性计算  ·  人工智能  ·  运维  ·  小程序  ·  安全  ·  大数据  ·  Linux  ·  云计算  ·  开发者
2022-08-11
“飞天加速计划-高校学生在家实践”感想
我是大学三年级的一名学生,现在就读软件工程专业。由于自己在学习thinphp和微信小程序结合部署小程序的时候要用到服务器ECS,在多方考查之后,所以选择了最好的阿里云服务器ECS。拥有了一台阿里云服务器ECS后,我不禁想到如何才能够利用好这台服务器,给我的项目更多便捷和乐趣以及如何部署我的后台。带着这个目标,我开始广泛地了解服务器的原理和应用。在这个过程中,我发现阿里云服务器的各方面都非常的好用,它与宝塔等控制台的搭配使得站点的部署,框架的部署简捷了许多。 因为是第一次使用阿里云服务器ECS,对使用过程不太熟悉,遇到了很多的困难,在使用之前我多次使用了阿里云官方模拟进行训练,经过模拟训练我对阿里云ECS服务器有了进一步的了解和熟悉,逐渐对服务器有了个初步的认识。在使用过程中,让我难忘的就是创建一个实例,搭建thinkphp后端的过程,因为第一次使用,所以很多的功能没有了解,所以这个过程挺艰难的,但是按照网上的教程一步一步来就能完成的。在” 高校学生在家实践”之前,我还不会搭建服务器环境和部署项目的,但有了这次的活动,我也掌握了一项了。 关于收获和总结。在使用了服务器这一段时间后,我还是收获了不少,至少懂得了怎么进行简单的配置服务器。“飞天加速计划·高校学生在家实践”活动很好,帮助了许多高校学生,学习到了相关的知识,我也会在今后继续学习,完成我心中所想,搭建一个属于自己设计的网站。感谢阿里云带我成功入门,领略了服务器世界的好玩与精彩。
文章
弹性计算  ·  小程序  ·  PHP
2022-08-11
1 2 3 4 5 6 7 8 9
...
20
跳转至:
钉钉开发者社区
50013 人关注 | 422 讨论 | 98 内容
+ 订阅
  • DingTalk「开发者说」钉钉连接平台,OA审批场景下如何实现系统互通
  • DingTalk「开发者说」钉钉连接平台,快速实现企业系统互联互通
  • DingTalk「开发者说」钉钉工作台的能力开放
查看更多 >
开发与运维
5311 人关注 | 127527 讨论 | 212257 内容
+ 订阅
  • 进程和线程的区别
  • 【分布式集群】微服务电商应用系统的集群构建
  • ECS使用体验
查看更多 >
大数据
185130 人关注 | 24555 讨论 | 59663 内容
+ 订阅
  • Kubernetes 故障排查 eBPF
  • Kubernetes Cilium 展示
  • kubernetes 日常命令
查看更多 >
安全
1089 人关注 | 23378 讨论 | 59373 内容
+ 订阅
  • Kubernetes 信息安全
  • Kubernetes 网络模型
  • Kubernetes Cilium 展示
查看更多 >
人工智能
2647 人关注 | 10122 讨论 | 71324 内容
+ 订阅
  • Kubernetes 信息安全
  • Kubernetes 故障排查 eBPF
  • Kubernetes 网络模型
查看更多 >