合并冲突
xiyunfei 编写于 2021-08-15 10:21:33
NewLife.Cube
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NewLife.Caching;
using NewLife.Cube.Web.Models;
using NewLife.Remoting;
using XCode.Membership;

namespace NewLife.Web.OAuth
{
    /// <summary>企业微信身份验证提供者</summary>
    public class QyWeiXin : OAuthClient
    {
        #region 属性
        /// <summary>企业Ids</summary>
        public String CorpId { get => Key; set => Key = value; }

        /// <summary>应用凭证</summary>
        public String CorpSecret { get => Secret; set => Secret = value; }

        /// <summary>应用Id</summary>
        public String AgentId { get; set; }

        ///// <summary>访问令牌</summary>
        //public String AccessToken { get; set; }

        ///// <summary>过期时间</summary>
        //public DateTime Expire { get; set; }

        ///// <summary>性能跟踪</summary>
        //public ITracer Tracer { get; set; } = DefaultTracer.Instance;

        //private HttpClient _client;
        private readonly String _uri = "https://qyapi.weixin.qq.com/";
        private ICache _cache;
        #endregion

        #region 构造
        /// <summary>实例化</summary>
        public QyWeiXin()
        {
            Server = "https://open.weixin.qq.com/connect/oauth2/";

            AuthUrl = "authorize?response_type={response_type}&appid={key}&redirect_uri={redirect}&state={state}&scope={scope}#wechat_redirect";

            var qyapi = "https://qyapi.weixin.qq.com/cgi-bin/";
            AccessUrl = qyapi + "gettoken?corpid={key}&corpsecret={secret}";
            UserUrl = qyapi + "user/getuserinfo?access_token={token}&code={code}";

            Scope = "snsapi_base";

            _cache = Cache.Default ?? new MemoryCache();
        }

        /// <summary>是否支持指定用户端,也就是判断是否在特定应用内打开,例如QQ/DingDing/WeiXin</summary>
        /// <param name="userAgent"></param>
        /// <returns></returns>
        public override Boolean Support(String userAgent) => !userAgent.IsNullOrEmpty()  && userAgent.Contains(" wxwork/");

        /// <summary>针对指定客户端进行初始化</summary>
        /// <param name="userAgent"></param>
        public override void Init(String userAgent)
        {
            var key = CorpId;
            if (!key.IsNullOrEmpty() && key.Contains("#"))
            {
                CorpId = key.Substring(null, "#");
                AgentId = key.Substring("#", null);
            }

            // 钉钉内打开时,自动切换为应用内免登
            if (Support(userAgent))
            {
                Scope = "snsapi_base";
            }
            else
            {
                Scope = null;
                AuthUrl = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid={key}&agentid={agentid}&redirect_uri={redirect}&state={state}".Replace("{agentid}", AgentId);
            }
        }
        #endregion

        #region 令牌
        /// <summary>获取令牌</summary>
        /// <returns></returns>
        public async Task<String> GetToken()
        {
            if (CorpId.IsNullOrEmpty()) throw new ArgumentNullException(nameof(CorpId));
            if (CorpSecret.IsNullOrEmpty()) throw new ArgumentNullException(nameof(CorpSecret));

            //var url = $"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={CorpId}&corpsecret={CorpSecret}";

            var client = GetClient();
            //_client = Tracer.CreateHttpClient();
            //_client.BaseAddress = new Uri("https://qyapi.weixin.qq.com");
            var rs = await client.GetAsync<IDictionary<String, Object>>(_uri + "cgi-bin/gettoken", new { corpid = CorpId, corpsecret = CorpSecret });

            AccessToken = rs["access_token"] as String;
            var exp = rs["expires_in"].ToInt(-1);
            if (exp > 0) Expire = DateTime.Now.AddSeconds(exp);

            return AccessToken;
        }

        /// <summary>获取访问令牌</summary>
        /// <returns></returns>
        protected async Task<String> GetAccessToken()
        {
            if (!String.IsNullOrEmpty(AccessToken) && Expire > DateTime.Now.AddMinutes(1)) return AccessToken;

            return await GetToken();
        }
        #endregion

        #region 通信录
        /// <summary>获取部门列表</summary>
        /// <returns></returns>
        public async Task<DepartmentInfo[]> GetDepartments()
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<DepartmentInfo[]>(HttpMethod.Get, _uri + "cgi-bin/department/list", new { access_token = token }, null, "department");

            return rs;
        }

        /// <summary>获取用户</summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<UserInfo> GetUser(String userId)
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<UserInfo>(HttpMethod.Get, _uri + "cgi-bin/user/get", new
            {
                userid = userId,
                access_token = token
            }, null, "");

            return rs;
        }

        /// <summary>获取部门内用户列表</summary>
        /// <param name="departmentId"></param>
        /// <param name="fetchChild"></param>
        /// <returns></returns>
        public async Task<UserInfo[]> GetUsers(Int32 departmentId, Boolean fetchChild = false)
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<UserInfo[]>(HttpMethod.Get, _uri + "cgi-bin/user/list", new
            {
                department_id = departmentId,
                fetch_child = fetchChild ? 1 : 0,
                access_token = token
            }, null, "userlist");

            return rs;
        }
        #endregion

        #region 考勤
        /// <summary>获取考勤数据</summary>
        /// <param name="userIds"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public async Task<CheckInData[]> GetCheckIn(String[] userIds, DateTime start, DateTime end)
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<CheckInData[]>(HttpMethod.Post, _uri + "cgi-bin/checkin/getcheckindata?access_token=" + token, new
            {
                opencheckindatatype = 3,
                starttime = start.ToInt(),
                endtime = end.ToInt(),
                useridlist = userIds,
            }, null, "checkindata");

            return rs;
        }
        #endregion

        #region 审批
        /// <summary>获取审批数据</summary>
        /// <param name="templateId"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="cursor"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public async Task<String[]> GetApprovals(String templateId, DateTime start, DateTime end, Int32 cursor = 0, Int32 size = 100)
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<String[]>(HttpMethod.Post, _uri + "cgi-bin/oa/getapprovalinfo?access_token=" + token, new
            {
                starttime = start.ToInt(),
                endtime = end.ToInt(),
                cursor,
                size,
                filters = new[] {
                    new { key = "template_id", value = templateId },
                    new { key = "sp_status", value = "2" },
                },
            }, null, "sp_no_list");

            return rs;
        }

        /// <summary>获取审批单详情</summary>
        /// <param name="sp_no"></param>
        /// <returns></returns>
        public async Task<ApprovalInfo> GetApproval(String sp_no)
        {
            var token = await GetAccessToken();

            var client = GetClient();
            var rs = await client.InvokeAsync<ApprovalInfo>(HttpMethod.Post, _uri + "cgi-bin/oa/getapprovaldetail?access_token=" + token, new
            {
                sp_no
            }, null, "info");

            return rs;
        }
        #endregion

        #region 辅助
        /// <summary>获取配置</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static String GetConfig(String name) => Parameter.GetOrAdd(0, "QyWeiXin", name)?.Value;

        /// <summary>依据魔方字典参数的配置,创建企业微信对象</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static QyWeiXin CreateFor(String name)
        {
            return new QyWeiXin
            {
                CorpId = QyWeiXin.GetConfig("CorpId"),
                CorpSecret = QyWeiXin.GetConfig(name),
            };
        }
        #endregion

        #region 用户信息增强
        /// <summary>企业内部应用获取用户信息</summary>
        /// <returns></returns>
        public override String GetUserInfo()
        {
            var html = base.GetUserInfo();

            // 获取用户详情
            if (!UserName.IsNullOrEmpty())
            {
                var user = Task.Run(() => GetUser(UserName)).Result;
                if (user != null)
                {
                    NickName = user.Name;
                    Mobile = user.Mobile;
                    Mail = user.Mail;
                    Avatar = user.Avatar;
                    Detail = user.Alias;
                    DepartmentCode = user.MainDepartment;
                }

                // 根据部门编码,填充部门名称
                var code = DepartmentCode.ToInt();
                //if (!DepartmentCode.IsNullOrEmpty() && DepartmentName.IsNullOrEmpty())
                if (code > 0 && DepartmentName.IsNullOrEmpty())
                {
                    var key = $"sso:dps:{CorpId}";
                    var dps = _cache.Get<DepartmentInfo[]>(key);
                    if (dps == null || dps.Length == 0)
                    {
                        dps = Task.Run(() => GetDepartments()).Result;

                        _cache.Set(key, dps, 3600);
                    }

                    var dp = dps?.FirstOrDefault(e => e.Id == code);
                    if (dp != null) DepartmentName = dp.Name;
                }
            }

            return html;
        }

        /// <summary>从响应数据中获取信息</summary>
        /// <param name="dic"></param>
        protected override void OnGetInfo(IDictionary<String, String> dic)
        {
            base.OnGetInfo(dic);

            if (dic.TryGetValue("UserId", out var str)) UserName = str.Trim();
            if (dic.TryGetValue("DeviceId", out str)) DeviceId = str.Trim();
            //if (dic.TryGetValue("OpenId", out str)) OpenID = str.Trim();

            if (dic.TryGetValue("errmsg", out str) && str != "ok") throw new InvalidOperationException(str);

        }
        #endregion
    }
}