节点历史和节点数据,采用雪花Id
大石头 authored at 2020-09-04 00:01:20
6.21 KiB
Stardust
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using NewLife;
using NewLife.Log;
using NewLife.Web;
using Stardust.Data.Nodes;
using Stardust.Server.Common;
using XCode.Membership;

namespace Stardust.Server.Controllers
{
    public abstract class BaseController : ControllerBase, IActionFilter
    {
        #region 属性
        /// <summary></summary>
        static readonly TokenSession _session = new TokenSession();

        /// <summary>临时会话扩展信息</summary>
        public IDictionary<String, Object> Session { get; private set; }

        /// <summary>令牌</summary>
        public String Token { get; private set; }

        /// <summary>是否使用Cookie</summary>
        public Boolean UseCookies { get; set; }

        /// <summary>用户主机</summary>
        public String UserHost => HttpContext.GetUserHost();

        /// <summary>节点引用,令牌无效时使用</summary>
        protected Node _nodeForHistory;
        #endregion

        #region 校验
        /// <summary>请求处理后</summary>
        /// <param name="context"></param>
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception != null)
            {
                // 拦截全局异常,写日志
                var action = context.HttpContext.Request.Path + "";
                if (context.ActionDescriptor is ControllerActionDescriptor act) action = $"{act.ControllerName}/{act.ActionName}";

                var node =  Session?["Node"] as Node ?? _nodeForHistory;
                WriteHistory(node, action, false, context.Exception?.GetTrue() + "");
            }
        }

        /// <summary>请求处理前</summary>
        /// <param name="context"></param>
        public void OnActionExecuting(ActionExecutingContext context)
        {
            var ip = UserHost;
            ManageProvider.UserHost = ip;

            var request = context.HttpContext.Request;
            var token = request.Query["Token"] + "";
            if (token.IsNullOrEmpty()) token = (request.Headers["Authorization"] + "").TrimStart("Bearer ");
            if (token.IsNullOrEmpty()) token = request.Headers["X-Token"] + "";
            if (token.IsNullOrEmpty()) token = request.Cookies["Token"] + "";

            if (!token.IsNullOrEmpty())
            {
                Token = token;
                Session = _session.GetSession(token);
                // 考虑到可能出现的服务器切换或服务端闪断等情况
                if (Session == null) Session = RestoreSession(token);
            }
            else
            {
                CreateToken(null);
            }
        }
        #endregion

        #region 令牌
        private static TokenProvider _tokenProvider;
        private static TokenProvider GetTokenProvider(Boolean create)
        {
            if (_tokenProvider != null) return _tokenProvider;

            var provider = new TokenProvider();
            provider.ReadKey("../keys/token.prvkey", create);
            if (provider.Key.IsNullOrEmpty()) throw new ApplicationException("缺失私钥,无法创建令牌");

            return _tokenProvider = provider;
        }

        /// <summary>刷新令牌</summary>
        /// <param name="newTokedn"></param>
        protected void RefreshToken(String newToken)
        {
            Session = _session.CopySession(Token, newToken);
            Token = newToken;
        }

        /// <summary>创建token同时刷新token有效期</summary>
        /// <param name="code"></param>
        protected void CreateToken(String code)
        {
            var set = Setting.Current;
            var expire = set.TokenExpire;

            var provider = GetTokenProvider(true);

            var token = provider.Encode($"{code}#", DateTime.Now.AddSeconds(expire));
            if (Token.IsNullOrEmpty())
            {
                // 创建新的token
                Session = _session.CreateSession(token);
                Token = token;
            }
            else
            {
                RefreshToken(token);
            }

            if (UseCookies) Response.Cookies.Append("Token", Token);
        }

        /// <summary>由token恢复session</summary>
        /// <param name="token"></param>
        private IDictionary<String, Object> RestoreSession(String token)
        {
            if (token.IsNullOrEmpty()) return null;

            var str = token.Trim().Substring(null, ".")?.ToBase64().ToStr();
            var rlist = str?.Split('#', ',');
            if (rlist == null)
            {
                XTrace.WriteLine($"Token 解析失败!:{token}");
                return null;
            }

            var code = rlist[0];
            var node = Node.FindByCode(code) ?? new Node { Code = code };
            _nodeForHistory = node;

            var provider = GetTokenProvider(false);

            // token解码失败
            if (!provider.TryDecode(token, out var result, out var dt))
            {
                var msg = $"签名错误:{result ?? "null"} {dt:yyyy-MM-dd HH:mm:ss} {token}";
                XTrace.WriteLine(msg);
                WriteHistory(node, "签名错误", false, msg);

                return null;
            }

            // token 过期
            if (dt < DateTime.Now)
            {
                var msg = $"令牌过期:{result ?? "null"} {dt:yyyy-MM-dd HH:mm:ss} {token}";
                XTrace.WriteLine(msg);
                WriteHistory(node, "令牌过期", false, msg);

                return null;
            }

            XTrace.WriteLine("借助令牌复活 {0}/{1}", code, token);

            var dic = _session.CreateSession(token);
            if (dic == null) XTrace.WriteLine($"借助令牌复活 CreateSession null!");

            dic["Node"] = node;

            WriteHistory(node, "令牌复活", true, $"[{code}]{token}");

            return dic;
        }
        #endregion

        #region 日志
        protected virtual void WriteHistory(Node node, String action, Boolean success, String remark)
        {
            NodeHistory.Create(node, action, success, remark, Environment.MachineName, UserHost);
        }
        #endregion
    }
}