Electron从零开始——介绍 - 知乎

mikel阅读(785)

来源: Electron从零开始——介绍 – 知乎

其实在 Electron 出现之前,如果你问我做桌面应用需要什么,我的第一个想法是 CC++C#Java 以及微软的 Visual Basic 等等语言(Sorry 我不是个苹果党,第一时间想不起 Object-C),外加体积巨大的 IDE(比如我到今天依然不喜欢的 Visual Studio 和曾经为了玩 Minecraft Mod 开发装的 Eclipse 等等),当然也还有各种编辑器 + 编译器的组合,比如 Notepad++  GCC……

我认识 Electron 项目应该是比较晚的了,是因为突然有一天微软发布了 Visual Studio Code 这个跨平台、开源、免费的编辑器,然后在浏览这个应用的文档时,我突然发现这个东东居然用的是 TypeScript + Electron 开发出来的,而 TypeScript 其实就是 JavaScript 的超集……这个冲击了我的世界观,要知道直接使用 JavaScript 开发桌面应用以前不是没有过,但是能够提供像是 VS Code 这么好用且顺滑的感觉,在之前是不敢想象的,甚至为了一个小小的工具,我都要在 VS 里面先做窗口界面,然后用 C# 在那里搞来搞去,可能是对桌面端应用的不熟悉,我做这种工具总是没有很顺手的感觉(对,这方面我的确很渣);而 Electron 的出现,让我有了可以使用自己熟悉的语言以及简单的编辑工具,就可以做出一个小桌面应用的希望。

当然认真学了一阵时间之后,我发现它其实就是把 V8 引擎单独包装起来,使之成为可以在桌面运行的类浏览器平台,从而使得我们写的 SPA 独立运行在系统桌面段;又因为它其实是基于 SPA 的,所以跨平台就成为了其最基础的能力,除了在调用系统功能和最终打包的时候需要考虑到不同平台的差异,其他方面完全不用考虑,使得同一版本的应用在不同系统上也可以保持一致功能,而不需要重新开发一个不同内核、不同细节项、不同技术栈的应用了,对于个人开发者,小团队乃至大型团队的中小型项目,都是个非常非常棒的特性。

我的作品本身其实也很渣,技术层面并没有什么特别的地方,甚至最初的两个小应用,还是用了 JQuery 作为 DOM 操作技术栈,其中一个基本没有在 Github 上面更新过,就单纯的是写出来玩的,一个读书应用(这个就不截图了,太渣自己看了都不好意思);另一个有持续更新一段时间,但是也没有完成,因为最近在大改,从 JQuery 改为 Vue,所以可以看看以前的样子:


这是一个投资理财的辅助分析工具,包括了三个小模块:股票、指数基金以及可转债,因为是辅助分析,所以有些部分是需要自己已经了解理论才能一眼看懂的,其界面如下(没做菜单,因为按钮对于不是很了解电脑的人更直观)。

主界面:

股票模块主界面(图表这里就不截图了,因为在修改,目前不太好看):

股票模块——白马组合(以防有人说我荐股,这里把股票名称和代码隐藏了):

股票模块——便宜组合(关键信息已隐藏):

股票模块——券商股:

指数基金模块(这里主要是行业指数的博格公式,长投温度考虑到可以很容易的获取,所以没有做进来):

点击查看博格公式,可以看到计算结果:

因为上述这些都是共有的信息,而且无论是否明白博格公式,我们也不可能参照旧数据来进行投资,所以这里没有隐藏;这里应用做的事情,其实就是简单的把需要手工计算的东西自动计算并汇总,所以没什么技术含量的。

可转债模块——可转债组合(关键信息已隐藏):

行情简表和待发转债按钮会直接打开集思录:

关于界面:

以上就是我这个简单的小工具的演示,当然或许做成移动端更好,毕竟现在使用电脑进行投资的人不多了,不过在实际使用的过程中,我学习到的投资理财工具有很多地方还是需要计算和分析,使用电脑明显优于移动端的体验,所以第一步先做了桌面端的应用;外加我现在算是转了行,而且最近正在培训阶段,精力也不太好分配,所以改动技术栈还是需要很久的(一天可能就只剩下了2个小时左右的闲暇,我还要更新喜马拉雅上面的有声小说,所以只有周末是比较好的空暇时间了),这样我打算和这个专栏一样,慢慢的来,正好可以重新梳理自己对 Electron 的理解,并且让自己的应用“升级”为正式版。

好了,这篇文章少见的扯了一大堆废话,接下来先好好的了解一下 Electron 究竟是个什么东东。


就像我上面提到过的,Electron 可以让我们使用纯 JavaScript 调用丰富的原生 API 来创建桌面应用;可以把它看作是专注于桌面应用而不是 web 服务器的 io.js 的一个变体。

当然这不意味着 Electron 是绑定了 GUI 库的 JavaScript;相反,Electron 使用 web 页面作为它的 GUI,所以可以把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器——从这个角度来理解,我们就明白了,所有和系统的交互功能,Electron 这个平台已经搞定了,我们所需要的就是创建一个自己喜欢的界面,一个或多个核心的功能,以及调用它提供给我们的 API 就好。

因为 Electron 本身其实也是 Node.js 的一个第三方项目,所以在开发的时候,我们首先需要已经安装了 Node.js,以及包管理器 NPM,外加一个顺手的编辑器:我个人用 VS Code 很顺手,当然如果喜欢用别的编辑器也没问题,比如前文提到的 Notepad++,或者著名的 Sublime TextAtom(话说这个也是 Electron 做的)、Komodo Edit……等等等等,都可以;基本准备就完成了。

接下来,用 NPM 在本地安装一个 Electron 的副本即可,可以选择全局安装或者针对项目单独安装:

npm i -g electron

# 或者
npm i --save-dev electron
当然我这里因为已经安装过,所以相当于升了个级

一个基础的 Electron 项目结果如下:

my-electron-app/
├── package.json
├── main.js
└── index.html

因为 Node.js 对项目结构以及命名还是有点要求的,所以我个人推荐用一些 Electron Cli 工具来进行项目启动和初始化,这样会相对省一些事,比如我之前使用过的 electron-forge,或者直接搭配 Vue 来建立的 electron-vue 都可以;具体的 cli 工具我们可以按照自己的喜好选择,如果更倾向用原生 JS 或者想要自己指定框架的,electron-forge 是个好选择:

我们也可以注意到,因为它是用了很多比较老的第三方库,所以有不少警告信息

这里稍稍演示一下通过 electron-forge 来建立新项目的过程,其实很简单,只要新建一个文件夹,定位到文件夹之后通过 init 命令指定当前文件夹为新项目:

electron-forge init

此时 electron-forge 会自动检查当前环境,并安装对应的模块:

这里要留意,如果 NPM 的映像是指向淘宝的话,整个过程会快很多。

完成后,初始配置就结束了:

此时,我们就可以打开 src 目录下的 index.js 来查看初始代码了:

import { app, BrowserWindow } from 'electron';

// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
  app.quit();
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

const createWindow = () => {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
  });

  // and load the index.html of the app.
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // Open the DevTools.
  mainWindow.webContents.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

这样我们就可以在这些初始代码上面,进行自己想要的修改了,当然我们下一章节会开始看看,这些基础的代码究竟是个什么意思;不过目前来看,应该已经很清晰了,毕竟 electron-forge 生成的初始代码,已经有了详尽的注释帮助我们理解了。

其实这个初始代码已经可以运行了,只要运行如下命令:

electron-forge start

可以看到,初始的界面是包括了菜单栏、主界面以及控制台的,而控制台的出现就分明体现了它本质上是个浏览器的特点,这也使得我们在本地及时的进行调整、修复和新功能测试变得非常简单。

PHP调用阿里云短信接口报错的解决 - 童年的回忆 - 博客园

mikel阅读(1175)

来源: PHP调用阿里云短信接口报错的解决 – 童年的回忆 – 博客园

调用短信接口错误如下:

cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://dysmsapi.aliyuncs.com/?Action=SendSms&Format=json&Version=2017-05-25&Timestamp=2021-07-31T03%3A45%3A33Z&…….

这是因为没有配置信任的服务器HTTPS验证。默认情况下,cURL被设为不信任任何CAs,因此浏览器无法通过HTTPs访问你服务器。

到下面站点下最新的pem文件

https://curl.se/docs/caextract.html

 

复制这个文件到php的安装目录下

打开php.ini文件,搜索curl.cainfo  去掉前面的#注释   填上该文件的绝对路径,如下图所示:

 

注意:openssl扩展需要开启

重启nginx/Apapche服务器,问题解决

 Android Studio编辑器中间竖线的去除_芸香大官人的博客-CSDN博客_android studio 中间竖线

mikel阅读(812)

来源: (2条消息) Android Studio编辑器中间竖线的去除_芸香大官人的博客-CSDN博客_android studio 中间竖线

题外(多次没能记住竖线的去除,第一次写博客,突然觉得,人类文明得以长存,在于文字的保存。)

本方法基于Android Studio版本:Android Studio 3.3.2
进入【File】=>【Setting】=>【Editor】=>【General】=>【Appearance】
去除勾选“Show hard wrap guide(configured in Code Style options)”
点击【Apply】或者【OK】,如图所示:

————————————————
版权声明:本文为CSDN博主「芸香大官人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/RoseChilde/article/details/88586684

关于JS的可选参数,该知道的都在这里了 - 简书

mikel阅读(1544)

来源: 关于JS的可选参数,该知道的都在这里了 – 简书

前言

我是一枚土生土长的iOS程序猿,之所以会写关于JS的文章,主要是因为我最近在负责组里的部分运营需求,所以写前端也逐渐比较多,于是乎学习JS势在必行,所以就开写了。

正文

我们都知道函数是js里的一等公民,并且在js里,你声明一个函数——你可以定制多个参数,与此同时,你在调用该函数的时候不需要传入所有的参数,它就能正常执行——只不过这些参数默认就是undefined而已。所以似乎js的函数天生就带有可选参数这个功能,只不过在你不进行定制的时候它们都具有一个“统一”的值罢了。
所以,我们也知道,除非函数里就实现了针对某个参数为undefined时的行为,让参数为undefined是比较危险的。

该如何实现函数内的可选参数,我们将用js里的构造方法来举例(假如我们要实现一个Person类):

function Person(name, age, height, weight) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
}

通常我们都会这么实现,现在假想我们要将heightweight参数设为可选参数,可选参数的实质就是令未被赋值的参数具有一个默认值,直白地处理,我们可以写成这样:

function Person(name, age, height, weight) {
    var nHeight = height || 0;
    var nWeight = weight || 0; 
    this.name = name;
    this.age = age;
    this.height = nHeight ;
    this.weight = nWeight ;
}

但是因为我们这里的参数只是简单的赋值给属性,所以我们可以这么写:

function Person(name, age, height, weight) {
    this.name = name;
    this.age = age;
    this.height = height || 0;
    this.weight = weight || 0;
}

面对这样的实现,不难发现它还有点问题——这种实现永远只能把可选参数连续地声明在函数的末端,必要的参数必须得放前面,因为它只能这样生成:

var person = new Person("Turtle", 23);

假如我是ageweight为可选参数呢?

function Person(name, age, height, weight) {
    this.name = name;
    this.age = age || 0;
    this.height = height;
    this.weight = weight || 0;
}
// 我就不能这样生成Person对象了
var person = new Person("Turtle", "170cm");

因为这样子赋值,没法让170cm赋到height属性上,只会赋到age属性上,这显然不是我们想要的。

一种简便的解决方法是不定义这么多的参数赋值,而统一使用一个对象来进行赋值:

function Person(options) {
    this.name = options.name;
    this.age = options.age || 0;
    this.height = options.height;
    this.weight = options.weight || 0;
}

var options = {
    name: "Turtle",
    height: "170cm",
};
var person = new Person(options);
//or
var person2 = new Person({
    name: "Turtle",
    height: "170cm",
});

而在es6里,它支持了为参数提供默认值,所以你可以这么干:

function Person({name, age = 0, height, weight = 0} = {}) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
}
// 效果和上面一致

作者:turtleeeee
链接:https://www.jianshu.com/p/55fc2be7e0f0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 tp中U方法在传值变量时的运用_chevinswift的博客-CSDN博客

mikel阅读(742)

来源: (3条消息) tp中U方法在传值变量时的运用_chevinswift的博客-CSDN博客

U方法用于完成对URL地址的组装,特点在于可以自动根据当前的URL模式和设置生成对应的URL地址,格式为:

U(‘地址’,’参数’,’伪静态’,’是否跳转’,’显示域名’);

1 //比如操作成功跳转到Store模块下的Ump控制器中的lists方法
2 $this->success('新增成功',U('Strore/Ump/lists'));
1 //跳转时带着参数的话
2 $this->success('新增成功',U('Store/Ump/lists','type=1&id=1'));

当在模板中使用U方法时,好处在于:一旦你的环境变化或者参数设置改变,你不需要更改模板中的任何代码。

在模板中的调用格式需要采用 {:U(‘地址’, ‘参数’…)} 的方式

<!--在模板中使用U方法 -->
{:U('Store/Ump/lists','type=1&id=1')}
{:U('Article/index','category='.$vo['name'])}

 

有一点重要的那就是传变量值时,例如{$news.id}

 

  1. <volist name=“news” id=“news”>
  2. <ul>
  3. <li class=“news_li”>
  4. <a href=“{:U(‘News/news_detail’,’id=’.$news[‘id’])}”>
  1. <span class=“news_content”>{$news.content}</span></a>
  2. <span class=“news_time”>{$news.time}</span>
  3. </li>
  4. </ul>
  5. </volist>

重点在于传值时要把变量通过”.”+变量的索引来写

C-Lodop打印服务没启动怎么办 - 花谢悦神 - 博客园

mikel阅读(1251)

来源: C-Lodop打印服务没启动怎么办 – 花谢悦神 – 博客园

C-Lodop作为服务,解决了高版本火狐谷歌不支持np插件问题,支持跳出来浏览器的限制,支持所有浏览器,默认是只需安装一次,以后每次开机自启动,但是如果禁止了开机启动项等问题,会造成之后突然出现没启动状况,或每次重启机器没自启动。

没启动的可能原因:
1.CLodopPrint.exe进程被人为或意外故障杀死。
2.没设置开机自启动,禁用了c-lodop开机启动项。
3.当前操作用户权限不足,无系统管理员权限。(可用管理员重装试试,直接覆盖安装即可)
4.注意安全软件禁用c-lodop. 目前常用的360及金山已进行了安全认证,其他的杀毒软件请加入白名单,或直接上报提交对应的杀毒软件服务器。
5.旧版端口被占用。(新版c-lodop是双端口,占用可能很小)
查看一下当前安装的C-Lodop版本,与lodop官方发布的最新版本对比,更新到支持双端口的版本。

C-Lodop没启动,可能造成的现象:
1.提示“CLodop云打印服务(localhost本地)未安装启动!点击这里执行安装,安装后请刷新页面。”
2.预览界面由本地预览界面变成远程预览界面。
3.写入文件,打印设计,打印维护,提示“不能远程打印设计”“不能远程打印维护”“不能远程写文件”。
4.js调试提示http://localhost:8000/CLodopFuncs.js访问不到,无法链接,请求超时等。
(https版提示无法连接到的是https://localhost:8443/CLodopFuncs.js)
Failed to load resource: net:ERR_CONNETION_REFUSED http://localhost:8000/CLodopfuncs.js?Priotity=1
Failed to load resource: net:ERR_CONNETION_REFUSED http://localhost:18000/CLodopfuncs.js?Priotity=1
Uncaught TypeError:Cannot read property ‘PRINT_INIT’ of undefined
C-Lodop不启动,获取不到CLodopFuncs.js,获取不到LODOP对象,里面的函数也就是提示undefined没有定义。

解决方法:
1.没安装,需要安装C-Lodop,可在官网下载中心下载。
2.已安装,没启动,可参考上面的 没启动的可能原因,排查一下。
3.已安装,已启动,这种情况属于已经启动却访问不到CLodopfuncs.js,很可能是因为启动端口和引用端口不一致引起的,可参考本博客另一篇专门介绍C-Lodop端口的博文。如何设置C-Lodop打印控件的端口

如何启动:
默认是开机自启动的,首先要排除上面那些项。
1.双击桌面上的C-Lodop快捷方式,可快速重启C-Lodop(也可在开始菜单找到该项重启C-Lodop)。
2.最新版(2018-09更新的最新版),修改了该提示
原提示:“CLodop云打印服务(localhost本地)未安装启动!点击这里执行安装,安装后请刷新页面。”
新版修改后的提示:“Web打印服务CLodop未安装启动,点击这里下载执行安装
(若此前已安装过,可点这里直接再次启动),成功后请刷新本页面。”
新版的提示中有“可点这里直接再次启动”,点提示的那个位置就能快速重启C-Lodop,不必再去双击桌面的C-Lodop快捷方式。

补充说明:出现远程预览界面和提示”不能远程……”,如果是集中打印等方式是正常现象,打印设计维护写文件都是 在客户端本地打印方式上可使用。
集中打印,广域网AO端桥打印,广域网AO打印,作为服务器安装的C-Lodop可以设置定时重启,在空闲时定时重启一下。

其他相关博文:
提示“Web打印服务CLodop未安装启动”的各种原因和解决方法

CLodop.js提示:has been blocked by CORS policy: The request client is not a secure context and the

CORS跨域问题:

升级谷歌浏览器最新chrome94版本后,提示Access to XMLHttpRequest at ‘http://localhost:xxxx/api‘ from origin ‘http://xxx.xxx.com:xxxx’ has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.

解决办法:

打开浏览器,进入chrome://flags/页面

搜索Block insecure private network requests

设置为Disabled,Relaunch就好了。

共享打印机突然出现错误0x00000709解决方法(亲测可用)_adiyang的博客-CSDN博客_0x00000709

mikel阅读(983)

来源: 共享打印机突然出现错误0x00000709解决方法(亲测可用)_adiyang的博客-CSDN博客_0x00000709

共享打印机突然出现错误0x00000709怎么办!

之前打印机还是好用的,恰好将我的电脑和打印机搬走用了,然后回来后重新共享就连接不上了。😭😭😭弄了好几天都没弄上。
在控制面板那里添加打印机会提示 Windows无法连接到打印机。请检查打印机名并重试。如果这是网络打印机,请确保打印机已打开,并且打印机地址正确。 这个错误;
若是通过网上邻居连接可以进入主机电脑里,但连接打印机会出现 操作无法完成(错误0x00000709)。再次检查打印机名称,并确保打印机已连接到网络。 这个错误。
首先检查了服务端的共享设置(设置没有变化),排除了设置问题,当然了设置没问题那人就懵了😣
在网上查都是再说“print spooler”服务的问题,但当我打开服务时候,它!一直是启动的状态(很好这条路也给堵死了)
不能连接打印机太憋手了,继续!有一篇文章是说要这些服务都开着
workstation
windows firewall
webclient
upnp device host
tcp/ip netbios helper
ssdt discovery
server
print spooler
ip helper
dns client
dhcp client
computer browser
好家伙,挨个开开,结果发现computer browser这个没有→控制面板 – 程序 – 程序和功能窗口中,在左侧的启用或关闭 Windows 功能中并勾选SMB1.0/CIFS 文件共享支持

当然了,并没有结束,它依然不好使!!!!!
但我发现了一篇博客完美解决了这个问题,KB50006670没有错罪魁祸首就是这个补丁,卸载后就可以正常共享了

在下面的路径找到补丁就可以卸载了。

(补充一下有的说通过IP连不上打印机但是通过电脑名可以连接打印机,但我遇到的问题是IP与名字都连接不上)
————————————————
版权声明:本文为CSDN博主「adiyang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/adiyang/article/details/121077986

css文字重叠解决_zengyonglan的博客-CSDN博客_css文字重叠

mikel阅读(730)

来源: (1条消息) css文字重叠解决_zengyonglan的博客-CSDN博客_css文字重叠

DIV+CSS布局文字较多时候重叠下一行文字内容如何解决?

出现原因是因为对li设置了css高度和宽度,当内容比较多是内容会自动换行,而又有高度,这样就造成内容溢出而与下一排内容重叠覆盖现象。

解决方法如下:

第一种,取消高度height样式设置 即可实现不重叠,但内容会换行占位。

 

第二种,使用隐藏溢出样式单词overflow:hidden

此方法是使用overflow隐藏超出对象设置宽度高度的内容,推荐使用。

 

示例代码如下:

<!DOCTYPE html>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>内容重叠解决 www.divcss5.com</title>
<style>
ul,li{ list-style:none}
ul{ width:100px}
ul li{ float:left;width:100px; height:22px;line-height:22px;
text-align:left; overflow:hidden}
/* css注释:为了便于截图,将CSS代码换行排版 */
</style>
</head>
<body>
<ul>
<li>标题一内容</li>
<li>测试文字多重叠标题二内容</li>
<li>标题三内容</li>
</ul>
</body>
</html>
————————————————
版权声明:本文为CSDN博主「时光默」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zengyonglan/article/details/52797445

钉钉企业应用C#开发笔记之一(免登) - Pleiades - 博客园

mikel阅读(1818)

来源: 钉钉企业应用C#开发笔记之一(免登) – Pleiades – 博客园

关于钉钉

钉钉是阿里推出的企业移动OA平台,本身提供了丰富的通用应用,同时其强大的后台API接入能力让企业接入自主开发的应用成为可能,可以让开发者实现几乎任何需要的功能。

近期因为工作需要研究了一下钉钉的接入,发现其接入文档、SDK都是基于java编写的,而我们的企业网站使用ASP.NET MVC(C#)开发,所以接入只能从头自己做SDK。

接入主要包括免登、获取数据、修改数据等接口。

免登流程

首先需要理解一下钉钉的免登流程,借用官方文档的图片:

钉钉免登流程图

是不是很熟悉?是的,基本是按照OAUTH的原理来的,版本嘛,里面有计算签名的部分,我觉得应该是OAUTH1.0。

有的读者会问,那第一步是不是应该跳转到第三方认证页面啊。我觉得“魔法”就藏在用来打开页面的钉钉内置浏览器里,在dd.config()这一步里,“魔法”就生效了。

 

其实简单来说,主要分为五步:

  1. 在你的Web服务器端调用api,传入CorpId和CorpSecret,获取accessToken,即访问令牌。
  2. 在服务器端调用api,传入accessToken,获取JsApiTicket,即JsApi的访问许可(门票)。
  3. 按照既定规则,在后台由JsApiTicket、NonceStr、Timestamp、本页面Url生成字符串,计算SHA1消息摘要,即签名Signature。
  4. 将AgentId、CorpId、Timestamp、NonceStr、Signature等参数传递到前台,在前台调用api,得到authCode,即授权码。
  5. 根据授权码,在前台或后台调用api,获得userId,进而再根据userId,调用api获取用户详细信息。

PS:为什么需要在后台完成一些api的调用呢?应该是因为js跨域调用的问题,我具体没有深究。

实践方法

理解了上述步骤,我对登陆过程的实现也大致有了一个设想,既然免登需要前后端一起来完成,那就添加一个专门的登陆页面,将登陆过程都在里面实现,将登陆结果写入到Session,并重定向回业务页面,即算完成。图示如下:

其中每个api的调用方式,在官方文档中都有说明。同时,我在阿里云开发者论坛找到了网友提供的SDK,有兴趣可以下载:钉钉非官方.Net SDK

另外,GitHub上还有官方的JQuery版免登开发Demo,可以参考:GitHub JQuery免登

我参考的是.Net SDK,将其中的代码,提取出了我所需要的部分,做了简化处理。基本原理就是每次调用API都是发起HttpRequest,将结果做JSON反序列化。

核心代码如下:

复制代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Web;
  5 using System.IO;
  6 using Newtonsoft.Json;
  7 using Newtonsoft.Json.Linq;
  8 using DDApi.Model;
  9 
 10 namespace DDApi
 11 {
 12     public static class DDHelper
 13     {
 14         public static string GetAccessToken(string corpId, string corpSecret)
 15         {
 16             string url = string.Format("https://oapi.dingtalk.com/gettoken?corpid={0}&corpsecret={1}", corpId, corpSecret);
 17             try
 18             {
 19                 string response = HttpRequestHelper.Get(url);
 20                 AccessTokenModel oat = Newtonsoft.Json.JsonConvert.DeserializeObject<AccessTokenModel>(response);
 21 
 22                 if (oat != null)
 23                 {
 24                     if (oat.errcode == 0)
 25                     {
 26                         return oat.access_token;
 27                     }
 28                 }
 29             }
 30             catch (Exception ex)
 31             {
 32                 throw;
 33             }
 34             return string.Empty;
 35         }
 36 
 37         /* https://oapi.dingtalk.com/get_jsapi_ticket?access_token=79721ed2fc46317197e27d9bedec0425
 38          * 
 39          * errmsg    "ok"
 40          * ticket    "KJWkoWOZ0BMYaQzWFDF5AUclJOHgO6WvzmNNJTswpAMPh3S2Z98PaaJkRzkjsmT5HaYFfNkMdg8lFkvxSy9X01"
 41          * expires_in    7200
 42          * errcode    0
 43          */
 44         public static string GetJsApiTicket(string accessToken)
 45         {
 46             string url = string.Format("https://oapi.dingtalk.com/get_jsapi_ticket?access_token={0}", accessToken);
 47             try
 48             {
 49                 string response = HttpRequestHelper.Get(url);
 50                 JsApiTicketModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<JsApiTicketModel>(response);
 51 
 52                 if (model != null)
 53                 {
 54                     if (model.errcode == 0)
 55                     {
 56                         return model.ticket;
 57                     }
 58                 }
 59             }
 60             catch (Exception ex)
 61             {
 62                 throw;
 63             }
 64             return string.Empty;
 65         }
 66 
 67         public static long GetTimeStamp()
 68         {
 69             TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
 70             return Convert.ToInt64(ts.TotalSeconds);
 71         }
 72 
 73         public static string GetUserId(string accessToken, string code)
 74         {
 75             string url = string.Format("https://oapi.dingtalk.com/user/getuserinfo?access_token={0}&code={1}", accessToken, code);
 76             try
 77             {
 78                 string response = HttpRequestHelper.Get(url);
 79                 GetUserInfoModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<GetUserInfoModel>(response);
 80 
 81                 if (model != null)
 82                 {
 83                     if (model.errcode == 0)
 84                     {
 85                         return model.userid;
 86                     }
 87                     else
 88                     {
 89                         throw new Exception(model.errmsg);
 90                     }
 91                 }
 92             }
 93             catch (Exception ex)
 94             {
 95                 throw;
 96             }
 97             return string.Empty;
 98         }
 99 
100         public static string GetUserDetailJson(string accessToken, string userId)
101         {
102             string url = string.Format("https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}", accessToken, userId);
103             try
104             {
105                 string response = HttpRequestHelper.Get(url);
106                 return response;
107             }
108             catch (Exception ex)
109             {
110                 throw;
111             }
112             return null;
113         }
114 
115         public static UserDetailInfo GetUserDetail(string accessToken, string userId)
116         {
117             string url = string.Format("https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}", accessToken, userId);
118             try
119             {
120                 string response = HttpRequestHelper.Get(url);
121                 UserDetailInfo model = Newtonsoft.Json.JsonConvert.DeserializeObject<UserDetailInfo>(response);
122 
123                 if (model != null)
124                 {
125                     if (model.errcode == 0)
126                     {
127                         return model;
128                     }
129                 }
130             }
131             catch (Exception ex)
132             {
133                 throw;
134             }
135             return null;
136         }
137 
138         public static List<DepartmentInfo> GetDepartmentList(string accessToken, int parentId = 1)
139         {
140             string url = string.Format("https://oapi.dingtalk.com/department/list?access_token={0}", accessToken);
141             if (parentId >= 0)
142             {
143                 url += string.Format("&id={0}", parentId);
144             }
145             try
146             {
147                 string response = HttpRequestHelper.Get(url);
148                 GetDepartmentListModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<GetDepartmentListModel>(response);
149 
150                 if (model != null)
151                 {
152                     if (model.errcode == 0)
153                     {
154                         return model.department.ToList();
155                     }
156                 }
157             }
158             catch (Exception ex)
159             {
160                 throw;
161             }
162             return null;
163         }
164     }
165 }
复制代码
 HttpRequestHelper View Code

其中的Model,就不再一一贴出来了,大家可以根据官方文档自己建立,这里只举一个例子,即GetAccessToken的返回结果:

复制代码
    public class AccessTokenModel
    {
        public string access_token { get; set; }

        public int errcode { get; set; }

        public string errmsg { get; set; }
    }
复制代码

我创建了一个类DDApiService,将上述方法做了封装:

 DDApiService View Code

以上是底层核心部分。登录页面的实现在控制器DDController中,代码如下:

 DDController View Code

视图View的代码:

 Login.cshtml View Code

其中nonstr理论上最好应该每次都随机,留待读者去完成吧:-)

钉钉免登就是这样,只要弄懂了就会觉得其实不难,还顺便理解了OAUTH。

后续改进

这个流程没有考虑到AccessToken、JsApiTicket的有效期时间(2小时),因为整个过程就在一个页面中都完成了。如果想要进一步扩展,多次调用api的话,需要考虑到上述有效期。

如果为了图简便每都去获取AccessToken也是可以的,但是会增加服务器负担,而且api的调用频率是有限制的(1500次/s好像),所以应当采取措施控制。例如可以将AccessToken、JsApiTicket存放在this.HttpContext.Application[“accessToken”]中,每次判断有效期是否过期,如果过期就调用api重新申请一个。

以上就是这样,感谢阅读。

 


 

20170710编辑,更新mvc免登流程图片,修正一处错误。