v9.6.2017.0808   重构正向工程,基于映射表查找数据库字段类型到实体类型的映射
大石头 编写于 2017-08-08 21:38:06
X
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if __CORE__
#else
using System.Web;
using NewLife.Log;
#endif
using NewLife.Security;
using NewLife.Serialization;

namespace NewLife.Web
{
    /// <summary>OAuth 2.0</summary>
    public class OAuthClient
    {
        #region 属性
        /// <summary>应用Key</summary>
        public String Key { get; set; }

        /// <summary>安全码</summary>
        public String Secret { get; set; }

        /// <summary>验证地址</summary>
        public String AuthUrl { get; set; }

        /// <summary>访问令牌地址</summary>
        public String AccessUrl { get; set; }
        #endregion

        #region 返回参数
        /// <summary>授权码</summary>
        public String Code { get; private set; }

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

        /// <summary>刷新令牌</summary>
        public String RefreshToken { get; private set; }

        /// <summary>统一标识</summary>
        public String OpenID { get; private set; }

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

        /// <summary>访问项</summary>
        public IDictionary<String, String> Items { get; private set; }
        #endregion

        #region QQ专属
        /// <summary>设置为QQ专属地址</summary>
        public void SetQQ()
        {
            var url = "https://graph.qq.com/oauth2.0/";
            AuthUrl = url + "authorize?response_type=code&client_id={key}&state={state}&redirect_uri={redirect}";
            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
        }
        #endregion

        #region 跳转验证
        private String _redirect;
        private String _state;

        /// <summary>跳转验证</summary>
        /// <param name="redirect">验证完成后调整的目标地址</param>
        /// <param name="state">用户状态数据</param>
        public virtual void Authorize(String redirect, String state = null)
        {
            if (redirect.IsNullOrEmpty()) throw new ArgumentNullException(nameof(redirect));

            if (Key.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Key), "未设置应用标识");
            if (Secret.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Secret), "未设置应用密钥");

            if (state.IsNullOrEmpty()) state = Rand.Next().ToString();

            // 如果是相对路径,自动加上前缀。需要考虑反向代理的可能,不能直接使用Request.Url
            if (redirect.StartsWith("~/")) redirect = HttpRuntime.AppDomainAppVirtualPath.EnsureEnd("/") + redirect.Substring(2);
            if (redirect.StartsWith("/"))
            {
                // 从Http请求头中取出原始主机名和端口
                var req = HttpContext.Current.Request;
                var uri = new Uri("{0}://{1}:{2}".F(req.Url.Scheme, req.ServerVariables["SERVER_NAME"], req.ServerVariables["Server_Port"]));
                uri = new Uri(uri, redirect);
                redirect = uri.ToString();
            }

            if (redirect.Contains("/")) redirect = HttpUtility.UrlEncode(redirect);

            _redirect = redirect;
            _state = state;

            var url = GetUrl(AuthUrl);
            WriteLog("Authorize {0}", url);

#if !__CORE__
            HttpContext.Current.Response.Redirect(url);
#endif
        }

        /// <summary>根据授权码获取访问令牌</summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public virtual async Task<String> GetAccessToken(String code)
        {
            if (code.IsNullOrEmpty()) throw new ArgumentNullException(nameof(code), "未设置授权码");

            Code = code;

            var url = GetUrl(AccessUrl);
            WriteLog("GetAccessToken {0}", url);

            var html = await Request(url);
            if (html.IsNullOrEmpty()) return null;

            html = html.Trim();

            IDictionary<String, String> dic = null;
            if (html.StartsWith("{") && html.EndsWith("}"))
            {
                dic = new JsonParser(html).Decode().ToDictionary().ToDictionary(e => e.Key.ToLower(), e => e.Value + "");
            }
            else if (html.Contains("=") && html.Contains("&"))
            {
                dic = html.SplitAsDictionary("=", "&").ToDictionary(e => e.Key.ToLower(), e => e.Value);
            }
            if (dic != null)
            {
                if (dic.ContainsKey("access_token")) AccessToken = dic["access_token"].Trim();
                if (dic.ContainsKey("expires_in")) Expire = DateTime.Now.AddSeconds(dic["expires_in"].Trim().ToInt());
                if (dic.ContainsKey("refresh_token")) RefreshToken = dic["refresh_token"].Trim();
                if (dic.ContainsKey("openid")) OpenID = dic["openid"].Trim();
            }
            Items = dic;

            return html;
        }
        #endregion

        #region 辅助
        /// <summary>替换地址模版参数</summary>
        /// <param name="url"></param>
        /// <returns></returns>
        protected virtual String GetUrl(String url)
        {
            url = url
               .Replace("{key}", Key)
               .Replace("{secret}", Secret)
               .Replace("{token}", AccessToken)
               .Replace("{code}", Code)
               .Replace("{redirect}", _redirect)
               .Replace("{state}", _state);

            return url;
        }

        private WebClientX _Client;
        /// <summary>创建客户端</summary>
        /// <param name="url">路径</param>
        /// <returns></returns>
        protected virtual async Task<String> Request(String url)
        {
            if (_Client == null)
            {
                _Client = new WebClientX();
                _Client.Log = Log;
            }

            return await _Client.DownloadStringAsync(url);
        }
        #endregion

        #region 日志
        /// <summary>日志</summary>
        public ILog Log { get; set; } = Logger.Null;

        /// <summary>写日志</summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void WriteLog(String format, params Object[] args)
        {
            Log?.Info(format, args);
        }
        #endregion
    }
}