<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DingTalkLogin.aspx.cs" Inherits="biaoyang_DingTalkLogin" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>钉钉微应用</title>
<script src='https://g.alicdn.com/code/npm/@ali/dingtalk-h5-remote-debug/0.1.3/index.js'></script>
<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/2.10.3/dingtalk.open.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, "Microsoft Yahei", Tahoma, Simsun, sans-serif;
background: linear-gradient(135deg, #1e5799 0%,#207cca 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 400px;
overflow: hidden;
text-align: center;
}
.header {
background: #0086F6;
color: white;
padding: 30px 20px;
position: relative;
}
.dingtalk-logo {
width: 70px;
height: 70px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 15px;
}
.dingtalk-logo svg {
width: 40px;
height: 40px;
fill: #0086F6;
}
h1 {
font-size: 24px;
font-weight: 500;
margin-bottom: 10px;
}
.subtitle {
font-size: 14px;
opacity: 0.9;
}
.content {
padding: 30px 25px;
}
.user-info {
background: #f8fafc;
border-radius: 12px;
padding: 25px;
margin: 20px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.user-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #0086F6;
margin: 0 auto 15px;
background-color: #e0e7ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: #4f46e5;
}
.user-name {
font-size: 20px;
font-weight: 600;
color: #1e293b;
margin-bottom: 5px;
}
.user-id {
color: #64748b;
font-size: 13px;
margin-bottom: 15px;
}
.loading-container {
padding: 30px 0;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(0, 134, 246, 0.2);
border-top: 5px solid #0086F6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
.loading-text {
color: #64748b;
font-size: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
color: #ef4444;
background: #fef2f2;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
}
.footer {
padding: 20px;
color: #64748b;
font-size: 12px;
border-top: 1px solid #e2e8f0;
}
.btn {
background: #0086F6;
color: white;
border: none;
border-radius: 8px;
padding: 12px 25px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: inline-block;
margin-top: 15px;
}
.btn:hover {
background: #0073d4;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 134, 246, 0.3);
}
.btn:active {
transform: translateY(0);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="dingtalk-logo">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0c282.77 0 512 229.23 512 512s-229.23 512-512 512S0 794.77 0 512 229.23 0 512 0zm0 64C264.96 64 64 264.96 64 512s200.96 448 448 448 448-200.96 448-448S759.04 64 512 64zm-64 256h128v256H448V320zm256 0h128v256H704V320zm-384 0h128v256H320V320zm192 320h128v128H512V640z"/>
</svg>
</div>
<h1>钉钉微应用</h1>
<div class="subtitle">欢迎使用企业服务</div>
</div>
<div class="content">
<div id="pnlLoading" class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">正在获取用户信息...</div>
</div>
<div id="pnlUserInfo" class="user-info" style="display:none;">
<div class="user-avatar" id="userAvatar"></div>
<div class="user-name" id="userName"></div>
<div class="user-id">用户ID: <span id="userId"></span></div>
<p>您已成功登录企业服务系统</p>
</div>
<div id="pnlError" class="error-message" style="display:none;"></div>
<button id="btnRefresh" class="btn" style="display:none;">重新获取</button>
</div>
<div class="footer">
© 2023 钉钉微应用 | 企业服务
</div>
</div>
<script>
// 初始化钉钉JSAPI
dd.ready(function() {
console.log('钉钉JSAPI已准备就绪');
// 获取免登授权码
dd.runtime.permission.requestAuthCode({
corpId: '<%= GetCorpId() %>',
onSuccess: function(result) {
console.log('获取到免登授权码:', result.code);
// 发送授权码到后端获取用户信息
getUserInfo(result.code);
},
onFail: function(err) {
showError('获取授权码失败: ' + JSON.stringify(err));
}
});
});
dd.error(function(error) {
console.error('钉钉JSAPI错误:', error);
showError('钉钉环境初始化失败: ' + JSON.stringify(error));
});
// 获取用户信息
function getUserInfo(authCode) {
fetch('Default.aspx/GetUserInfo', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code: authCode })
})
.then(response => response.json())
.then(data => {
if (data.d && data.d.success) {
displayUserInfo(data.d.userInfo);
} else {
showError(data.d.error || '获取用户信息失败');
}
})
.catch(error => {
showError('请求失败: ' + error);
});
}
// 显示用户信息
function displayUserInfo(userInfo) {
document.getElementById('pnlLoading').style.display = 'none';
document.getElementById('pnlUserInfo').style.display = 'block';
document.getElementById('userName').innerText = userInfo.name;
document.getElementById('userId').innerText = userInfo.userid || userInfo.unionid;
// 显示用户头像首字母
const avatarElement = document.getElementById('userAvatar');
if (userInfo.name) {
avatarElement.innerText = userInfo.name.charAt(0).toUpperCase();
}
}
// 显示错误信息
function showError(message) {
document.getElementById('pnlLoading').style.display = 'none';
const errorPanel = document.getElementById('pnlError');
errorPanel.style.display = 'block';
errorPanel.innerText = message;
// 显示刷新按钮
document.getElementById('btnRefresh').style.display = 'inline-block';
}
// 刷新页面
document.getElementById('btnRefresh').addEventListener('click', function() {
location.reload();
});
// 检测是否在钉钉环境中
function isInDingTalk() {
return navigator.userAgent.toLowerCase().indexOf('dingtalk') !== -1;
}
// 如果不在钉钉环境中显示提示
if (!isInDingTalk()) {
document.addEventListener('DOMContentLoaded', function() {
showError('请在钉钉客户端内打开此页面');
});
}
</script>
</body>
</html>
using System;
using System.Web.Services;
using System.Web.UI;
using System.Net.Http;
using Newtonsoft.Json;
using System.Web.Configuration;
using Newtonsoft.Json.Linq;
using System.Configuration;
using System.Web.Script.Services;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public partial class biaoyang_DingTalkLogin : System.Web.UI.Page
{
// 从配置中获取钉钉应用信息
private string appKey = WebConfigurationManager.AppSettings["DingTalkAppKey"];
private string appSecret = WebConfigurationManager.AppSettings["DingTalkAppSecret"];
private string corpId = WebConfigurationManager.AppSettings["DingTalkCorpId"];
private string agentId = WebConfigurationManager.AppSettings["DingTalkAgentId"];
protected void Page_Load(object sender, EventArgs e)
{
}
// 前端调用此方法获取企业ID
public string GetCorpId()
{
return corpId;
}
// WebMethod 供前端AJAX调用
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static object GetUserInfo(string code)
{
var page = new biaoyang_DingTalkLogin();
return page.GetUserInfoInternal(code);
}
private object GetUserInfoInternal(string code)
{
try
{
// 1. 获取访问令牌
string accessToken = GetAccessToken();
if (string.IsNullOrEmpty(accessToken))
{
return new { success = false, error = "获取访问令牌失败" };
}
// 2. 获取用户ID
string userId = GetUserId(accessToken, code);
if (string.IsNullOrEmpty(userId))
{
return new { success = false, error = "获取用户ID失败" };
}
// 3. 获取用户详细信息
var userInfo = GetUserDetail(accessToken, userId);
if (userInfo == null)
{
return new { success = false, error = "获取用户详细信息失败" };
}
// 手动处理每个字段的空值情况
return new
{
success = true,
userInfo = new
{
name = GetSafeString(userInfo, "name"),
userid = GetSafeString(userInfo, "userid"),
unionid = GetSafeString(userInfo, "unionid"),
avatar = GetSafeString(userInfo, "avatar"),
mobile = GetSafeString(userInfo, "mobile"),
email = GetSafeString(userInfo, "email"),
department = GetDepartmentInfo(userInfo["department"])
}
};
}
catch (Exception ex)
{
// 使用正确的日志记录方法
WriteErrorLog("GetUserInfoInternal 错误: " + ex.ToString());
return new { success = false, error = "系统错误,请稍后再试" };
}
}
// 安全的字符串获取方法
private string GetSafeString(JObject obj, string propertyName)
{
return obj[propertyName] != null ? obj[propertyName].ToString() : null;
}
// 处理部门信息(可能是数组)
private string GetDepartmentInfo(JToken departments)
{
if (departments == null)
return string.Empty;
try
{
if (departments is JArray)
{
var deptList = new List<string>();
foreach (var item in departments)
{
deptList.Add(item.ToString());
}
return string.Join(", ", deptList);
}
return departments.ToString();
}
catch (Exception ex)
{
WriteErrorLog("GetDepartmentInfo 错误: " + ex.ToString());
return departments.ToString();
}
}
// 获取访问令牌
private string GetAccessToken()
{
// 使用 string.Format 代替字符串插值
string url = string.Format(
"https://oapi.dingtalk.com/gettoken?appkey={0}&appsecret={1}",
appKey, appSecret);
using (HttpClient client = new HttpClient())
{
try
{
var response = client.GetAsync(url).Result;
var content = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
if (result != null && result.ContainsKey("errcode") && Convert.ToInt32(result["errcode"]) == 0)
{
return result["access_token"].ToString();
}
// 记录错误信息
string errorMsg = result != null && result.ContainsKey("errmsg")
? string.Format("获取访问令牌失败: 错误代码: {0}, 错误信息: {1}", result["errcode"], result["errmsg"])
: "无法解析钉钉API响应";
WriteErrorLog(errorMsg);
}
catch (Exception ex)
{
WriteErrorLog("GetAccessToken 错误: " + ex.ToString());
}
}
return null;
}
// 使用免登授权码获取用户ID
private string GetUserId(string accessToken, string code)
{
// 使用 string.Format 代替字符串插值
string url = string.Format(
"https://oapi.dingtalk.com/user/getuserinfo?access_token={0}&code={1}",
accessToken, code);
using (HttpClient client = new HttpClient())
{
try
{
var response = client.GetAsync(url).Result;
var content = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
if (result != null && result.ContainsKey("errcode") && Convert.ToInt32(result["errcode"]) == 0)
{
if (result.ContainsKey("userid"))
return result["userid"].ToString();
if (result.ContainsKey("unionid"))
return result["unionid"].ToString();
}
// 记录错误信息
string errorMsg = result != null && result.ContainsKey("errmsg")
? string.Format("获取用户ID失败: 错误代码: {0}, 错误信息: {1}", result["errcode"], result["errmsg"])
: "无法解析钉钉API响应";
WriteErrorLog(errorMsg);
}
catch (Exception ex)
{
WriteErrorLog("GetUserId 错误: " + ex.ToString());
}
}
return null;
}
// 获取用户详细信息
private JObject GetUserDetail(string accessToken, string userId)
{
// 使用 string.Format 代替字符串插值
string url = string.Format(
"https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}",
accessToken, userId);
using (HttpClient client = new HttpClient())
{
try
{
var response = client.GetAsync(url).Result;
var content = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<JObject>(content);
if (result != null && result["errcode"] != null && result["errcode"].Value<int>() == 0)
{
return result;
}
// 记录错误信息
string errorMsg = result != null && result["errmsg"] != null
? string.Format("获取用户详情失败: 错误代码: {0}, 错误信息: {1}", result["errcode"], result["errmsg"])
: "无法解析钉钉API响应";
WriteErrorLog(errorMsg);
}
catch (Exception ex)
{
WriteErrorLog("GetUserDetail 错误: " + ex.ToString());
}
}
return null;
}
// 统一的错误日志记录方法 - 修复 TraceError 问题
private void WriteErrorLog(string message)
{
// 使用正确的日志记录方法
if (System.Diagnostics.Trace.Listeners.Count > 0)
{
System.Diagnostics.Trace.TraceError(message);
}
else
{
// 备选日志方案
try
{
// 写入事件日志
if (!EventLog.SourceExists("DingTalkLogin"))
{
EventLog.CreateEventSource("DingTalkLogin", "Application");
}
EventLog.WriteEntry("DingTalkLogin", message, EventLogEntryType.Error);
}
catch
{
// 最后备选方案 - 写入文件
try
{
string logPath = Server.MapPath("~/App_Data/error.log");
System.IO.File.AppendAllText(logPath, DateTime.Now.ToString() + ": " + message + Environment.NewLine);
}
catch { /* 忽略所有错误 */ }
}
}
}
}
打开页面后提示欢迎使用企业服务
请求失败: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON