引入redis服务,支持自动化单元测试
大石头 authored at 2022-03-31 22:56:30
12.58 KiB
X
using System;
using System.Collections.Generic;
using System.Threading;
using NewLife;
using NewLife.Collections;
using NewLife.Model;
using NewLife.Security;

namespace XCode.Membership
{
    /// <summary>管理提供者接口</summary>
    /// <remarks>
    /// 管理提供者接口主要提供(或统一规范)用户提供者定位、用户查找登录等功能。
    /// 只需要一个实现IManageUser接口的用户类即可实现IManageProvider接口。
    /// IManageProvider足够精简,使得大多数用户可以自定义实现;
    /// 也因为其简单稳定,大多数需要涉及用户与权限功能的操作,均可以直接使用该接口。
    /// </remarks>
    public interface IManageProvider : IServiceProvider
    {
        /// <summary>当前登录用户,设为空则注销登录</summary>
        IManageUser Current { get; set; }

        /// <summary>密码提供者</summary>
        IPasswordProvider PasswordProvider { get; set; }

        /// <summary>获取当前用户</summary>
        /// <param name="context"></param>
        /// <returns></returns>
        IManageUser GetCurrent(IServiceProvider context);

        /// <summary>设置当前用户</summary>
        /// <param name="user"></param>
        /// <param name="context"></param>
        void SetCurrent(IManageUser user, IServiceProvider context);

        /// <summary>根据用户编号查找</summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        IManageUser FindByID(Object userid);

        /// <summary>根据用户帐号查找</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        IManageUser FindByName(String name);

        /// <summary>登录</summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="rememberme">是否记住密码</param>
        /// <returns></returns>
        IManageUser Login(String username, String password, Boolean rememberme = false);

        /// <summary>注销</summary>
        void Logout();

        /// <summary>注册用户</summary>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <param name="roleid">角色</param>
        /// <param name="enable">是否启用</param>
        /// <returns></returns>
        IManageUser Register(String username, String password, Int32 roleid = 0, Boolean enable = false);

        /// <summary>修改密码</summary>
        /// <param name="username">用户名</param>
        /// <param name="newPassword">新密码</param>
        /// <param name="oldPassword">旧密码,如果未指定,则不校验</param>
        /// <returns></returns>
        IManageUser ChangePassword(String username, String newPassword, String oldPassword);

        /// <summary>获取服务</summary>
        /// <remarks>
        /// 其实IServiceProvider有该扩展方法,但是在FX2里面不方面使用,所以这里保留
        /// </remarks>
        /// <typeparam name="TService"></typeparam>
        /// <returns></returns>
        TService GetService<TService>();
    }

    /// <summary>管理提供者</summary>
    public abstract class ManageProvider : IManageProvider
    {
        #region 静态实例
        /// <summary>当前管理提供者</summary>
        public static IManageProvider Provider { get; set; }

        /// <summary>当前登录用户</summary>
        public static IUser User { get => Provider?.Current as IUser; set => Provider.Current = value as IManageUser; }

        /// <summary>菜单工厂</summary>
        public static IMenuFactory Menu => GetFactory<IMenu>() as IMenuFactory;

        private static readonly ThreadLocal<String> _UserHost = new();
        /// <summary>用户主机</summary>
        public static String UserHost { get => _UserHost.Value; set => _UserHost.Value = value; }
        #endregion

        #region IManageProvider 接口
        /// <summary>当前用户</summary>
        public virtual IManageUser Current { get => GetCurrent(); set => SetCurrent(value); }

        /// <summary>密码提供者</summary>
        public IPasswordProvider PasswordProvider { get; set; } = new SaltPasswordProvider();

        /// <summary>获取当前用户</summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public abstract IManageUser GetCurrent(IServiceProvider context = null);

        /// <summary>设置当前用户</summary>
        /// <param name="user"></param>
        /// <param name="context"></param>
        public abstract void SetCurrent(IManageUser user, IServiceProvider context = null);

        /// <summary>根据用户编号查找</summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public virtual IManageUser FindByID(Object userid) => Membership.User.FindByID((userid + "").ToInt(-1));

        /// <summary>根据用户帐号查找</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public virtual IManageUser FindByName(String name) => Membership.User.FindForLogin(name);

        /// <summary>登录</summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="rememberme">是否记住密码</param>
        /// <returns></returns>
        public virtual IManageUser Login(String username, String password, Boolean rememberme) => Current = LoginCore(username, password);

        /// <summary>核心登录方法</summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public virtual IManageUser LoginCore(String username, String password)
        {
            try
            {
                if (String.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username), $"用户名或密码不正确!");

                // 过滤帐号中的空格,防止出现无操作无法登录的情况
                var account = username.Trim();
                // 登录时必须从数据库查找用户,缓存中的用户对象密码字段可能为空
                var user = Membership.User.FindForLogin(account);
                if (user == null) throw new EntityException("用户名或密码不正确!");
                if (!user.Enable) throw new EntityException("账号{0}被禁用!", account);

                var prv = PasswordProvider;
                if (prv == null) throw new ArgumentNullException(nameof(PasswordProvider));

                // 数据库为空密码,任何密码均可登录
                if (!user.Password.IsNullOrEmpty())
                {
                    if (!prv.Verify(password, user.Password)) throw new EntityException("用户名或密码不正确!");

                    // 旧式密码更新为新格式,更安全
                    if (!user.Password.Contains("$"))
                        user.Password = prv.Hash(password);
                }
                else
                {
                    user.Password = prv.Hash(password);
                }

                user.SaveLoginInfo();

                Membership.User.WriteLog("登录", true, $"用户[{user}]使用[{username}]登录成功");

                return user;
            }
            catch (Exception ex)
            {
                Membership.User.WriteLog("登录", false, username + "登录失败!" + ex.Message);
                throw;
            }
        }

        /// <summary>注销</summary>
        public virtual void Logout()
        {
            if (Current is IUser user) user.Logout();

            Current = null;
        }

        /// <summary>注册用户</summary>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <param name="roleid">角色</param>
        /// <param name="enable">是否启用。某些系统可能需要验证审核</param>
        /// <returns></returns>
        public virtual IManageUser Register(String username, String password, Int32 roleid, Boolean enable)
        {
            try
            {
                // 去重判断
                var user = Membership.User.FindForLogin(username);
                if (user != null) throw new ArgumentException(nameof(username), $"用户[{username}]已存在!");

                var pass = PasswordProvider.Hash(password);

                user = new User
                {
                    Name = username,
                    Password = pass,
                    Enable = enable,
                    RoleID = roleid
                };

                user.Register();

                Membership.User.WriteLog("注册", true, $"用户[{user}]使用[{username}]注册成功");

                return user;
            }
            catch (Exception ex)
            {
                Membership.User.WriteLog("注册", false, username + "注册失败!" + ex.Message);
                throw;
            }
        }

        /// <summary>修改密码</summary>
        /// <param name="username">用户名</param>
        /// <param name="newPassword">新密码</param>
        /// <param name="oldPassword">旧密码,如果未指定,则不校验</param>
        /// <returns></returns>
        public virtual IManageUser ChangePassword(String username, String newPassword, String oldPassword)
        {
            try
            {
                if (username.IsNullOrEmpty()) throw new ArgumentNullException(nameof(username), "该帐号不存在!");

                // 过滤帐号中的空格,防止出现误操作无法登录的情况
                var account = username.Trim();
                var user = Membership.User.Find(Membership.User.__.Name, account);
                if (user == null) throw new EntityException("帐号{0}不存在!", account);
                if (!user.Enable) throw new EntityException("账号{0}被禁用!", account);

                var prv = PasswordProvider;

                // 数据库为空密码,任何密码均可登录
                if (!oldPassword.IsNullOrEmpty())
                {
                    if (!prv.Verify(oldPassword, user.Password)) throw new EntityException("用户名或密码不正确!");
                }

                user.Password = prv.Hash(newPassword);
                user.Update();

                Membership.User.WriteLog("修改密码", true, username);

                return user;
            }
            catch (Exception ex)
            {
                Membership.User.WriteLog("修改密码", false, username + "修改密码失败!" + ex.Message);
                throw;
            }
        }

        /// <summary>获取服务</summary>
        /// <typeparam name="TService"></typeparam>
        /// <returns></returns>
        public TService GetService<TService>() => (TService)GetService(typeof(TService));

        /// <summary>获取服务</summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public virtual Object GetService(Type serviceType) => null;
        #endregion

        #region 实体类扩展
        private static IDictionary<Type, IEntityFactory> _factories;
        private static void InitFactories()
        {
            if (_factories == null)
            {
                var fact = new NullableDictionary<Type, IEntityFactory>
                {
                    [typeof(IRole)] = Role.Meta.Factory,
                    [typeof(IMenu)] = Membership.Menu.Meta.Factory,
                    [typeof(ILog)] = Log.Meta.Factory,
                    [typeof(IUser)] = Membership.User.Meta.Factory
                };

                // 不想加锁,用原子操作
                //_factories = fact;
                Interlocked.CompareExchange(ref _factories, fact, null);
            }
        }

        private static void Register<TIEntity>(IEntityFactory factory)
        {
            InitFactories();
            
            _factories[typeof(TIEntity)] = factory;
        }

        /// <summary>根据实体类接口获取实体工厂</summary>
        /// <typeparam name="TIEntity"></typeparam>
        /// <returns></returns>
        internal static IEntityFactory GetFactory<TIEntity>()
        {
            InitFactories();

            return _factories[typeof(TIEntity)];
        }

        internal static T Get<T>() => (T)GetFactory<T>()?.Default;
        #endregion
    }
}