来源: (2条消息) Flutter报错:Execution failed for task ‘:app:compileFlutterBuildDebug‘_blhbay的博客-CSDN博客
一个可能解决问题的方法:
在terminal中:1) cd ./Android
2) gradlew clean

来源: (2条消息) Flutter报错:Execution failed for task ‘:app:compileFlutterBuildDebug‘_blhbay的博客-CSDN博客
一个可能解决问题的方法:
在terminal中:1) cd ./Android
2) gradlew clean

其实在 Electron 出现之前,如果你问我做桌面应用需要什么,我的第一个想法是 C、C++、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 Text、Atom(话说这个也是 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调用阿里云短信接口报错的解决 – 童年的回忆 – 博客园
调用短信接口错误如下:
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服务器,问题解决
来源: (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
我是一枚土生土长的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;
}
通常我们都会这么实现,现在假想我们要将height和weight参数设为可选参数,可选参数的实质就是令未被赋值的参数具有一个默认值,直白地处理,我们可以写成这样:
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);
假如我是age和weight为可选参数呢?
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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
来源: (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}
重点在于传值时要把变量通过”.”+变量的索引来写
来源: 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
共享打印机突然出现错误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
来源: (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 – 博客园
钉钉是阿里推出的企业移动OA平台,本身提供了丰富的通用应用,同时其强大的后台API接入能力让企业接入自主开发的应用成为可能,可以让开发者实现几乎任何需要的功能。
近期因为工作需要研究了一下钉钉的接入,发现其接入文档、SDK都是基于java编写的,而我们的企业网站使用ASP.NET MVC(C#)开发,所以接入只能从头自己做SDK。
接入主要包括免登、获取数据、修改数据等接口。
首先需要理解一下钉钉的免登流程,借用官方文档的图片:

是不是很熟悉?是的,基本是按照OAUTH的原理来的,版本嘛,里面有计算签名的部分,我觉得应该是OAUTH1.0。
有的读者会问,那第一步是不是应该跳转到第三方认证页面啊。我觉得“魔法”就藏在用来打开页面的钉钉内置浏览器里,在dd.config()这一步里,“魔法”就生效了。
其实简单来说,主要分为五步:
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免登流程图片,修正一处错误。