Browse Source

WIP infrastructure services

HOME 3 years ago
parent
commit
c53e0f4bcf
36 changed files with 891 additions and 148 deletions
  1. 6 0
      VCommonCoreExample.sln
  2. 12 5
      VCommonCoreExample/AppServices/Basic/DbAppServiceBase.cs
  3. 20 0
      VCommonCoreExample/AppServices/Platform/Dto/TenantCreateInput.cs
  4. 14 0
      VCommonCoreExample/AppServices/Platform/Dto/TenantDto.cs
  5. 11 0
      VCommonCoreExample/AppServices/Platform/Dto/TenantListRequest.cs
  6. 16 0
      VCommonCoreExample/AppServices/Platform/Dto/TenantUpdateInput.cs
  7. 112 0
      VCommonCoreExample/AppServices/Platform/TenantService.cs
  8. 2 2
      VCommonCoreExample/AppServices/Session/Dto/SessionOutput.cs
  9. 6 9
      VCommonCoreExample/AppServices/Session/SessionService.cs
  10. 14 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleCreateInput.cs
  11. 13 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleDto.cs
  12. 17 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleFormPrepOutput.cs
  13. 20 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleListOutput.cs
  14. 9 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleListRequest.cs
  15. 10 0
      VCommonCoreExample/AppServices/System/Roles/Dto/RoleUpdateInput.cs
  16. 117 0
      VCommonCoreExample/AppServices/System/Roles/RoleService.cs
  17. 93 0
      VCommonCoreExample/AppServices/System/Users/Dto/Dto.cs
  18. 154 0
      VCommonCoreExample/AppServices/System/Users/UserService.cs
  19. 14 0
      VCommonCoreExample/AppServices/UserProfiles/UserProfileService.cs
  20. 8 8
      VCommonCoreExample/Authorization/PermissionCodes.cs
  21. 34 45
      VCommonCoreExample/Authorization/PermissionManager.cs
  22. 79 0
      VCommonCoreExample/Caching/AuthCache.cs
  23. 1 0
      VCommonCoreExample/Caching/CacheDbs.cs
  24. 13 0
      VCommonCoreExample/Caching/Models/RoleCacheModel.cs
  25. 19 0
      VCommonCoreExample/Caching/Models/UserCacheModel.cs
  26. 5 14
      VCommonCoreExample/Caching/SessionCacheManager.cs
  27. 37 10
      VCommonCoreExample/DataStore/UserStore.cs
  28. 1 3
      VCommonCoreExample/Entity/Role.cs
  29. 5 5
      VCommonCoreExample/Entity/Tenant.cs
  30. 9 2
      VCommonCoreExample/Entity/User.cs
  31. 1 0
      VCommonCoreExample/EntityFrameworkCore/ExampleDbContext.cs
  32. 4 15
      VCommonCoreExample/Migrations/20210717041707_CreateDatabase.Designer.cs
  33. 4 15
      VCommonCoreExample/Migrations/20210717041707_CreateDatabase.cs
  34. 3 14
      VCommonCoreExample/Migrations/ExampleDbContextModelSnapshot.cs
  35. 7 1
      VCommonCoreExample/Startup.cs
  36. 1 0
      VCommonCoreExample/VCommonCoreExample.csproj

+ 6 - 0
VCommonCoreExample.sln

@@ -35,6 +35,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VCommon.Logging", "..\VComm
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VCommon.Caching", "..\VCommonCore\VCommon.Caching\VCommon.Caching.csproj", "{CE5D7DFA-6C8F-4878-9C45-0AD46E177954}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "@", "@", "{F285AF9C-0F95-4026-9904-FB7D854CD8B9}"
+	ProjectSection(SolutionItems) = preProject
+		.gitignore = .gitignore
+		README.md = README.md
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU

+ 12 - 5
VCommonCoreExample/AppServices/Basic/DbAppServiceBase.cs

@@ -1,8 +1,6 @@
-using VCommon.Ioc;
-using VCommon.VApplication;
+using VCommon.Linq.Expressions.Predicate;
 using VCommon.VApplication.EntityFrameworkCore;
 using VCommon.VEntity;
-using VCommonCoreExample.Audit;
 using VCommonCoreExample.EntityFrameworkCore;
 
 namespace VCommonCoreExample.AppServices.Basic
@@ -14,6 +12,15 @@ namespace VCommonCoreExample.AppServices.Basic
 
     public class DbTableAppServiceBase<TEntity> : AppServiceBase where TEntity : VEntityBase
     {
-        protected VAppEfRepository<ExampleDbContext, TEntity> GetRepository() => new();
+        protected static readonly PredicateBuilder<TEntity> PredicateBuilder = PredicateBuilder<TEntity>.Instance;
+
+        protected VAppEfRepository<ExampleDbContext, TEntity> GetRepository(string disableFilter = null) => GetRepository<TEntity>();
+
+        protected VAppEfRepository<ExampleDbContext, TOtherEntity> GetRepository<TOtherEntity>(string disableFilter = null) where TOtherEntity : VEntityBase
+        {
+            var repo = IocManager.Resolve<VAppEfRepository<ExampleDbContext, TOtherEntity>>();
+            if (disableFilter != null) repo.DisableFilter(disableFilter);
+            return repo;
+        }
     }
-}
+}

+ 20 - 0
VCommonCoreExample/AppServices/Platform/Dto/TenantCreateInput.cs

@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using VCommon.VApplication.Dto;
+
+namespace VCommonCoreExample.AppServices.Platform.Dto
+{
+    public class TenantCreateInput : IHaveName
+    {
+        [Required]
+        public string Code { get; set; }
+
+        [Required]
+        public string Name { get; set; }
+
+        [Required]
+        public string AdminPassword { get; set; }
+
+        public bool IsEnable { get; set; }
+    }
+}

+ 14 - 0
VCommonCoreExample/AppServices/Platform/Dto/TenantDto.cs

@@ -0,0 +1,14 @@
+using VCommon.VApplication.Dto;
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.Platform.Dto
+{
+    [AutoMapFrom(typeof(Tenant))]
+    public class TenantDto : EntityDto, IHaveName
+    {
+        public string Code { get; set; }
+        public string Name { get; set; }
+        public bool IsEnable { get; set; }
+    }
+}

+ 11 - 0
VCommonCoreExample/AppServices/Platform/Dto/TenantListRequest.cs

@@ -0,0 +1,11 @@
+using VCommon.VApplication.Dto;
+
+namespace VCommonCoreExample.AppServices.Platform.Dto
+{
+    public class TenantListRequest : PagedRequest, IStringSearchFilter, IPassiveFilter
+    {
+        public string Search { get; set; }
+        public bool? IsEnable { get; set; }
+        public DateTimeRange CreateOn { get; set; }
+    }
+}

+ 16 - 0
VCommonCoreExample/AppServices/Platform/Dto/TenantUpdateInput.cs

@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using VCommon.VApplication.Dto;
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.Platform.Dto
+{
+    [AutoMapTo(typeof(Tenant))]
+    public class TenantUpdateInput : EntityDto, IHaveName
+    {
+        [Required]
+        public string Name { get; set; }
+
+        public bool IsEnable { get; set; }
+    }
+}

+ 112 - 0
VCommonCoreExample/AppServices/Platform/TenantService.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Linq;
+using Microsoft.AspNetCore.Identity;
+using VCommon.VApplication.Authorization;
+using VCommon.VApplication.Dto;
+using VCommon.VApplication.Linq.Expressions;
+using VCommon.VAutoMapper;
+using VCommon.VEntityFrameworkCore;
+using VCommon.VOpenApi.VAspNetCore;
+using VCommonCoreExample.AppServices.Basic;
+using VCommonCoreExample.AppServices.Platform.Dto;
+using VCommonCoreExample.Authorization;
+using VCommonCoreExample.Caching;
+using VCommonCoreExample.Configuration;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.Platform
+{
+    [VServiceAuthorize(PermissionCodes.Platform.Tenant.EntryTenant)]
+    public interface ITenantService
+    {
+        [VServiceAuthorize(PermissionCodes.Platform.Tenant.Query)]
+        IPagedResult<TenantDto> List(TenantListRequest input);
+
+        [VServiceAuthorize(PermissionCodes.Platform.Tenant.Create)]
+        Guid Create(TenantCreateInput input);
+
+        [VServiceAuthorize(PermissionCodes.Platform.Tenant.Update)]
+        void Update(TenantUpdateInput input);
+
+        [VServiceAuthorize(PermissionCodes.Platform.Tenant.Delete)]
+        void Delete(DeleteInput input);
+    }
+
+    public class TenantService : DbTableAppServiceBase<Tenant>, ITenantService
+    {
+        private readonly AuthCache _authCache;
+
+        public TenantService(AuthCache authCache)
+        {
+            _authCache = authCache;
+        }
+
+        public IPagedResult<TenantDto> List(TenantListRequest input)
+        {
+            var exp = PredicateBuilder.FilterByEnable(input)
+                      & PredicateBuilder.DateTimeBetween(p => p.CreationTime, input.CreateOn)
+                      & (PredicateBuilder.SearchByName(input) | PredicateBuilder.SearchBy(p => p.Code, input));
+
+            using var repo = GetRepository(VEfDataFilters.Passive);
+            var db = repo.QueryNoTracking(exp);
+            var count = db.Count();
+            var items = db.OrderByCreateTimeDescAndPageBy(input).ProjectionToArray<Tenant, TenantDto>();
+
+            return new PagedResult<TenantDto>(count, items);
+        }
+
+        public Guid Create(TenantCreateInput input)
+        {
+            using var tRepo = GetRepository(VEfDataFilters.Passive);
+            if (tRepo.CheckExist(p => p.Code == input.Code)) throw new VFriendlyException("租户代码已存在");
+
+            //create tenant
+            var tenant = new Tenant
+            {
+                Code = input.Code,
+                Name = input.Name,
+                IsEnable = input.IsEnable
+            };
+
+            tRepo.Add(tenant);
+            tRepo.SaveChanges();
+
+            //create admin user
+            using var uRepo = GetRepository<User>();
+            var admin = new User
+            {
+                TenantId = tenant.Id,
+                LoginName = ProjectConst.BuildInAdminUserName,
+                Name = ProjectConst.BuildInAdminUserName,
+                IsStaticUser = true,
+                IsEnable = true,
+                Roles = "[]",
+            };
+            admin.Password = new PasswordHasher<User>().HashPassword(admin, input.AdminPassword);
+            uRepo.Add(admin);
+
+            uRepo.SaveChanges();
+
+            return tenant.Id;
+        }
+
+        public void Update(TenantUpdateInput input)
+        {
+            using var repo = GetRepository(VEfDataFilters.Passive);
+            var tenant = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到租户"); ;
+            input.MapTo(tenant);
+            repo.SaveChanges();
+            _authCache.Clear(tenant.Id);
+        }
+
+        public void Delete(DeleteInput input)
+        {
+            using var repo = GetRepository(VEfDataFilters.Passive);
+            var tenant = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到租户");
+            tenant.Code += "_deleted_" + tenant.Id;
+            repo.Delete(tenant);
+            repo.SaveChanges();
+            _authCache.Clear(tenant.Id);
+        }
+    }
+}

+ 2 - 2
VCommonCoreExample/AppServices/Session/Dto/SessionOutput.cs

@@ -8,7 +8,7 @@ namespace VCommonCoreExample.AppServices.Session.Dto
         public Guid? TenantId { get; set; }
         public Guid UserId { get; set; }
         public string UserName { get; set; }
-        public string[] Permissions { get; set; }
+        public IReadOnlyCollection<string> Permissions { get; set; }
         public IReadOnlyDictionary<string, object> Setting { get; set; }
     }
-}
+}

+ 6 - 9
VCommonCoreExample/AppServices/Session/SessionService.cs

@@ -29,12 +29,14 @@ namespace VCommonCoreExample.AppServices.Session
     public class SessionService : DbAppServiceBase, ISessionService
     {
         private readonly UserStore _userStore;
-        private readonly SessionCacheManager _sessionCache;
+        private readonly SessionCache _sessionCache;
+        private readonly IPermissionManager _permissionManager;
 
-        public SessionService(UserStore userStore, SessionCacheManager sessionCache)
+        public SessionService(UserStore userStore, SessionCache sessionCache, IPermissionManager permissionManager)
         {
             _userStore = userStore;
             _sessionCache = sessionCache;
+            _permissionManager = permissionManager;
         }
 
         public string Login(SessionLoginInput input)
@@ -93,21 +95,16 @@ namespace VCommonCoreExample.AppServices.Session
 
             var usr = db.Users
                 .Where(p => p.IsAbolish == false && p.TenantId == tid && p.Id == uid)
-                .Select(p => new { p.Name, p.RoleId }).FirstOrDefault();
+                .Select(p => new { p.Name, p.Roles }).FirstOrDefault();
 
             if (usr == null) throw new VFriendlyException("找不到用户");
 
-            //TODO: Permission
-            //var role = db.Roles
-            //    .Where(p => p.IsAbolish == false && p.TenantId == tid && p.Id == usr.RoleId)
-            //    .Select(p => p.PermissionSet).FirstOrDefault();
-
             return new SessionOutput
             {
                 TenantId = tid,
                 UserId = uid,
                 UserName = usr.Name,
-                //Permissions = WebCaching.UserInfo.GetPermissions(VSession.GetTenantId(), VSession.GetUserId()),
+                Permissions = _permissionManager.GetUserPermissionCodes(VSession.TenantId, VSession.GetUserId()),
                 //Setting = new Dictionary<string, object>(), //TODO: UserProfileService Setting
             };
         }

+ 14 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleCreateInput.cs

@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+using VCommon.VAutoMapper;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleCreateInput
+    {
+        [Required]
+        public string Name { get; set; }
+
+        [AutoMapJsonConvert]
+        public string[] PermissionSet { get; set; }
+    }
+}

+ 13 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleDto.cs

@@ -0,0 +1,13 @@
+using VCommon.VApplication.Dto;
+using VCommon.VAutoMapper;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleDto : EntityDto, IHaveName
+    {
+        public string Name { get; set; }
+
+        [AutoMapJsonConvert]
+        public string[] PermissionSet { get; set; }
+    }
+}

+ 17 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleFormPrepOutput.cs

@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using VCommon.VApplication.Authorization;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleFormPrepOutput
+    {
+        public IReadOnlyCollection<PermissionNodeOutput> AvailablePermissions { get; }
+
+        public RoleDto Output { get; set; }
+
+        public RoleFormPrepOutput(IReadOnlyCollection<PermissionNodeOutput> availablePermission)
+        {
+            AvailablePermissions = availablePermission;
+        }
+    }
+}

+ 20 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleListOutput.cs

@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using VCommon.VApplication.Auditing.DataAnnotations;
+using VCommon.VApplication.Dto;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleListOutput : IPagedResult<RoleDto>
+    {
+        public RoleListOutput(int totalRecord, IReadOnlyList<RoleDto> items)
+        {
+            TotalRecord = totalRecord;
+            Items = items;
+        }
+
+        public int TotalRecord { get; }
+
+        [SimplifyAuditingLog]
+        public IReadOnlyList<RoleDto> Items { get; }
+    }
+}

+ 9 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleListRequest.cs

@@ -0,0 +1,9 @@
+using VCommon.VApplication.Dto;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleListRequest : PagedRequest, IStringSearchFilter
+    {
+        public string Search { get; set; }
+    }
+}

+ 10 - 0
VCommonCoreExample/AppServices/System/Roles/Dto/RoleUpdateInput.cs

@@ -0,0 +1,10 @@
+using System;
+using VCommon.VApplication.Dto;
+
+namespace VCommonCoreExample.AppServices.System.Roles.Dto
+{
+    public class RoleUpdateInput : RoleCreateInput, IEntityDto
+    {
+        public Guid Id { get; set; }
+    }
+}

+ 117 - 0
VCommonCoreExample/AppServices/System/Roles/RoleService.cs

@@ -0,0 +1,117 @@
+using System;
+using VCommon.VApplication.Authorization;
+using VCommon.VApplication.Dto;
+using VCommon.VApplication.Linq.Expressions;
+using VCommon.VAutoMapper;
+using VCommon.VOpenApi.VAspNetCore;
+using VCommonCoreExample.AppServices.Basic;
+using VCommonCoreExample.AppServices.System.Roles.Dto;
+using VCommonCoreExample.Authorization;
+using VCommonCoreExample.Caching;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.System.Roles
+{
+    [VServiceAuthorize(PermissionCodes.System.Role.EntryRole)]
+    public interface IRoleService
+    {
+        [VServiceAuthorize(PermissionCodes.System.Role.Query)]
+        IPagedResult<RoleDto> List(RoleListRequest input);
+
+        [VServiceAuthorize(
+            PermissionCodes.System.Role.Create,
+            PermissionCodes.System.Role.Update)]
+        RoleFormPrepOutput FormPrep(Guid? input);
+
+        [VServiceAuthorize(PermissionCodes.System.Role.Create)]
+        Guid Create(RoleCreateInput input);
+
+        [VServiceAuthorize(PermissionCodes.System.Role.Update)]
+        void Update(RoleUpdateInput input);
+
+        [VServiceAuthorize(PermissionCodes.System.Role.Delete)]
+        void Delete(DeleteInput input);
+    }
+
+    public class RoleService : DbTableAppServiceBase<Role>, IRoleService
+    {
+        private readonly IPermissionManager _permissionManager;
+        private readonly AuthCache _authCache;
+
+        public RoleService(IPermissionManager permissionManager,AuthCache authCache)
+        {
+            _permissionManager = permissionManager;
+            _authCache = authCache;
+        }
+
+        public IPagedResult<RoleDto> List(RoleListRequest input)
+        {
+            using var repo = GetRepository();
+
+            var exp = PredicateBuilder.SearchByName(input);
+
+            var count = repo.Count(exp);
+
+            var items = repo.QueryNoTracking(exp)
+                .OrderByCreateTimeDescAndPageBy(input)
+                .ProjectionToArray<Role, RoleDto>();
+
+            return new RoleListOutput(count, items);
+        }
+
+        public RoleFormPrepOutput FormPrep(Guid? input)
+        {
+            var result = new RoleFormPrepOutput(_permissionManager.GetPermissionTreeOutput(VSession.Side));
+            if (input.HasValue)
+            {
+                using var repo = GetRepository();
+                result.Output = repo.QueryProjection<RoleDto>(input.Value);
+            }
+            return result;
+        }
+
+        public Guid Create(RoleCreateInput input)
+        {
+            foreach (var s in input.PermissionSet)
+            {
+                if (false == _permissionManager.ValidPermission(s)) throw new VFriendlyException($"无效的权限代码:{s}");
+            }
+
+            var repo = GetRepository();
+            if (repo.CheckExist(p => p.Name == input.Name)) throw new VFriendlyException("角色名称已经存在");
+
+            var entity = input.MapTo<Role>();
+            repo.Add(entity);
+            repo.SaveChanges();
+
+            return entity.Id;
+        }
+
+        public void Update(RoleUpdateInput input)
+        {
+            foreach (var s in input.PermissionSet)
+            {
+                if (false == _permissionManager.ValidPermission(s))  throw new VFriendlyException($"无效的权限代码:{s}");
+            }
+
+            var repo = GetRepository();
+            if(repo.CheckExist(p => p.Id != input.Id && p.Name == input.Name)) throw new VFriendlyException("角色名称已经存在");
+
+            var entity = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到角色");
+            input.MapTo(entity);
+            repo.SaveChanges();
+
+            _authCache.ClearRole(VSession.TenantId,entity.Id);
+        }
+
+        public void Delete(DeleteInput input)
+        {
+            var repo = GetRepository();
+            var entity = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到角色");
+            repo.Delete(entity);
+            repo.SaveChanges();
+
+            _authCache.ClearRole(VSession.TenantId, input.Id);
+        }
+    }
+}

+ 93 - 0
VCommonCoreExample/AppServices/System/Users/Dto/Dto.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using VCommon.VApplication.Auditing.DataAnnotations;
+using VCommon.VApplication.Dto;
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.System.Users.Dto
+{
+    public class UserListRequest : PagedRequest, IStringSearchFilter, IPassiveFilter
+    {
+        public string Search { get; set; }
+        public bool? IsEnable { get; set; }
+        public DateTimeRange CreateOn { get; set; }
+    }
+
+    [AutoMapFrom(typeof(User))]
+    public class UserDto : EntityDto, IHaveName
+    {
+        [AutoMapJsonConvert()]
+        public Guid[] Roles { get; set; }
+
+        public string Name { get; set; }
+
+        public string LoginName { get; set; }
+
+        public bool IsEnable { get; set; }
+    }
+
+    public class UserListOutput : IPagedResult<UserDto>
+    {
+        public IReadOnlyCollection<NamedEntityDto> AvailableRoles { get; set; }
+
+        public UserListOutput(int totalRecord, IReadOnlyList<UserDto> items)
+        {
+            TotalRecord = totalRecord;
+            Items = items;
+        }
+
+        public int TotalRecord { get; }
+
+        [SimplifyAuditingLog]
+        public IReadOnlyList<UserDto> Items { get; }
+    }
+
+    public class UserFormPrepOutput
+    {
+        public UserDto Output { get; set; }
+
+        public IReadOnlyCollection<NamedEntityDto> AvailableRoles { get; }
+
+        public UserFormPrepOutput(IReadOnlyCollection<NamedEntityDto> availableRoles)
+        {
+            AvailableRoles = availableRoles;
+        }
+    }
+
+    public class UserCreateInput
+    {
+        [Required]
+        [AutoMapJsonConvert]
+        public Guid[] Roles { get; set; }
+
+        [Required]
+        public string Name { get; set; }
+
+        [Required]
+        public string LoginName { get; set; }
+
+        [Required]
+        public string Password { get; set; }
+
+        public bool IsEnable { get; set; }
+    }
+
+    public class UserUpdateInput : EntityDto
+    {
+        [Required]
+        [AutoMapJsonConvert]
+        public Guid[] Roles { get; set; }
+
+        [Required]
+        public string Name { get; set; }
+
+        [Required]
+        public string LoginName { get; set; }
+
+        public string Password { get; set; }
+
+        public bool IsEnable { get; set; }
+    }
+}

+ 154 - 0
VCommonCoreExample/AppServices/System/Users/UserService.cs

@@ -0,0 +1,154 @@
+using Microsoft.AspNetCore.Identity;
+using System;
+using System.Linq;
+using VCommon;
+using VCommon.VApplication.Authorization;
+using VCommon.VApplication.Dto;
+using VCommon.VApplication.Linq.Expressions;
+using VCommon.VAutoMapper;
+using VCommon.VEntityFrameworkCore;
+using VCommon.VOpenApi.VAspNetCore;
+using VCommonCoreExample.AppServices.Basic;
+using VCommonCoreExample.AppServices.System.Users.Dto;
+using VCommonCoreExample.Authorization;
+using VCommonCoreExample.Caching;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.AppServices.System.Users
+{
+    [VServiceAuthorize(PermissionCodes.System.User.EntryUser)]
+    public interface IUserService
+    {
+        [VServiceAuthorize(PermissionCodes.System.User.Query)]
+        UserListOutput List(UserListRequest input);
+
+        [VServiceAuthorize(
+            PermissionCodes.System.User.Create,
+            PermissionCodes.System.User.Update)]
+        UserFormPrepOutput FormPrep(Guid? input);
+
+        [VServiceAuthorize(PermissionCodes.System.User.Create)]
+        Guid Create(UserCreateInput input);
+
+        [VServiceAuthorize(PermissionCodes.System.User.Update)]
+        void Update(UserUpdateInput input);
+
+        [VServiceAuthorize(PermissionCodes.System.User.Delete)]
+        void Delete(DeleteInput input);
+    }
+
+    public class UserService : DbTableAppServiceBase<User>, IUserService
+    {
+        private readonly AuthCache _authCache;
+
+        public UserService(AuthCache authCache)
+        {
+            _authCache = authCache;
+        }
+
+        public UserListOutput List(UserListRequest input)
+        {
+            var exp = PredicateBuilder.FilterByEnable(input)
+                  & PredicateBuilder.DateTimeBetween(p => p.CreationTime, input.CreateOn)
+                  & (PredicateBuilder.SearchByName(input) | PredicateBuilder.SearchBy(p => p.LoginName, input));
+
+            var repo = GetRepository(VEfDataFilters.Passive);
+            var source = repo.QueryNoTracking(exp);
+            var count = source.Count();
+            var items = source.OrderByCreateTimeDescAndPageBy(input).ProjectionToArray<User, UserDto>();
+
+            var result = new UserListOutput(count, items);
+
+            var roleIds = result.Items.SelectMany(x => x.Roles).Distinct().ToArray();
+
+            if (roleIds.Length > 0)
+            {
+                var rolRepo = repo.ForkRepository<Role>();
+                var queryNoTracking = rolRepo.QueryNoTracking(x => roleIds.Contains(x.Id));
+                var namedEntityDtos = queryNoTracking.Select(x => new NamedEntityDto { Id = x.Id, Name = x.Name });
+                result.AvailableRoles = namedEntityDtos.ToArray();
+            }
+            else
+            {
+                result.AvailableRoles = new NamedEntityDto[0];
+            }
+
+            return result;
+        }
+
+        public UserFormPrepOutput FormPrep(Guid? input)
+        {
+            UserFormPrepOutput result;
+
+            using (var ctx = GetRepository<Role>())
+            {
+                var items = ctx.QueryNoTracking().Select(p => new NamedEntityDto { Id = p.Id, Name = p.Name }).ToArray();
+                result = new UserFormPrepOutput(items);
+            }
+
+            if (input.HasValue)
+            {
+                using var ctx = GetRepository(VEfDataFilters.Passive);
+                result.Output = ctx.QueryProjection<UserDto>(input.Value);
+            }
+
+            return result;
+        }
+
+        public Guid Create(UserCreateInput input)
+        {
+            ValidRoleIds(input.Roles);
+            var repo = GetRepository(VEfDataFilters.Passive);
+            if (repo.CheckExist(p => p.LoginName == input.LoginName)) throw new VFriendlyException("登录名已存在");
+
+            var entity = input.MapTo<User>();
+            entity.Password = new PasswordHasher<User>().HashPassword(entity, input.Password);
+            repo.Add(entity);
+            repo.SaveChanges();
+
+            return entity.Id;
+        }
+
+        private void ValidRoleIds(Guid[] roleIds)
+        {
+            using var ctx = GetRepository<Role>();
+            var fromDb = ctx.QueryNoTracking().Select(p => p.Id).ToArray();
+            if (roleIds.Except(fromDb).Any()) throw new VFriendlyException("意外的角色编号");
+        }
+
+        public void Update(UserUpdateInput input)
+        {
+            ValidRoleIds(input.Roles);
+
+            var repo = GetRepository(VEfDataFilters.Passive);
+
+            if (repo.CheckExist(p => p.Id != input.Id && p.LoginName == input.LoginName))
+                throw new VFriendlyException("登录名已经存在");
+
+            var entity = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到用户");
+
+            input.Password = input.Password.IsNullOrEmpty()
+                ? entity.Password
+                : new PasswordHasher<User>().HashPassword(entity, input.Password);
+
+            input.MapTo(entity);
+            repo.SaveChanges();
+
+            _authCache.ClearUser(VSession.TenantId, entity.Id);
+        }
+
+        public void Delete(DeleteInput input)
+        {
+            var repo = GetRepository(VEfDataFilters.Passive);
+
+            var entity = repo.GetEntityOrDefault(input.Id) ?? throw new VFriendlyException("找不到用户");
+            if (entity.IsStaticUser) throw new VFriendlyException("不能删除内置用户");
+            
+            entity.LoginName += "_deleted_" + entity.Id;
+            repo.Delete(entity);
+            repo.SaveChanges();
+
+            _authCache.ClearUser(VSession.TenantId, entity.Id);
+        }
+    }
+}

+ 14 - 0
VCommonCoreExample/AppServices/UserProfiles/UserProfileService.cs

@@ -0,0 +1,14 @@
+using VCommon.VApplication.Authorization;
+
+namespace VCommonCoreExample.AppServices.UserProfiles
+{
+    [VServiceAuthorize]
+    public interface IUserProfileService
+    {
+        //TODO: change my password
+    }
+
+    public class UserProfileService : IUserProfileService
+    {
+    }
+}

+ 8 - 8
VCommonCoreExample/Authorization/PermissionCodes.cs

@@ -16,15 +16,15 @@
                 public const string Delete = EntryTenant + "." + nameof(Delete);
             }
 
-            public static class License
-            {
-                public const string EntryLicense = nameof(EntryPlatform) + "." + nameof(License);
+            //public static class License
+            //{
+            //    public const string EntryLicense = nameof(EntryPlatform) + "." + nameof(License);
 
-                public const string Query = EntryLicense + "." + nameof(Query);
-                public const string Create = EntryLicense + "." + nameof(Create);
-                public const string Update = EntryLicense + "." + nameof(Update);
-                public const string Delete = EntryLicense + "." + nameof(Delete);
-            }
+            //    public const string Query = EntryLicense + "." + nameof(Query);
+            //    public const string Create = EntryLicense + "." + nameof(Create);
+            //    public const string Update = EntryLicense + "." + nameof(Update);
+            //    public const string Delete = EntryLicense + "." + nameof(Delete);
+            //}
         }
 
         public static class System

+ 34 - 45
VCommonCoreExample/Authorization/PermissionManager.cs

@@ -3,17 +3,21 @@ using System.Collections.Generic;
 using System.Linq;
 using VCommon.Ioc;
 using VCommon.VApplication.Authorization;
+using VCommonCoreExample.Caching;
+using VCommonCoreExample.Configuration;
 
 namespace VCommonCoreExample.Authorization
 {
     public class PermissionManager : ISingletonIocClass, IPermissionManager
     {
-        private readonly PermissionTree _tree;
+        private readonly AuthCache _authCache;
 
+        private readonly PermissionTree _tree;
         private readonly HashSet<string> _allPermissionCodes;
 
-        public PermissionManager()
+        public PermissionManager(AuthCache authCache)
         {
+            _authCache = authCache;
             _tree = new PermissionTree();
             _allPermissionCodes = Init();
         }
@@ -22,11 +26,11 @@ namespace VCommonCoreExample.Authorization
         {
             var nodePlatform = _tree.CreateRootNode(PermissionCodes.Platform.EntryPlatform, "平台管理", side: MultiTenancySides.Host);
             {
-                var nodeLic = nodePlatform.CreateChildNode(PermissionCodes.Platform.License.EntryLicense, "许可", side: MultiTenancySides.Host);
-                nodeLic.CreateChildNode(PermissionCodes.Platform.License.Query, "查询", side: MultiTenancySides.Host);
-                nodeLic.CreateChildNode(PermissionCodes.Platform.License.Create, "创建", side: MultiTenancySides.Host);
-                nodeLic.CreateChildNode(PermissionCodes.Platform.License.Update, "修改", side: MultiTenancySides.Host);
-                nodeLic.CreateChildNode(PermissionCodes.Platform.License.Delete, "删除", side: MultiTenancySides.Host);
+                //var nodeLic = nodePlatform.CreateChildNode(PermissionCodes.Platform.License.EntryLicense, "许可", side: MultiTenancySides.Host);
+                //nodeLic.CreateChildNode(PermissionCodes.Platform.License.Query, "查询", side: MultiTenancySides.Host);
+                //nodeLic.CreateChildNode(PermissionCodes.Platform.License.Create, "创建", side: MultiTenancySides.Host);
+                //nodeLic.CreateChildNode(PermissionCodes.Platform.License.Update, "修改", side: MultiTenancySides.Host);
+                //nodeLic.CreateChildNode(PermissionCodes.Platform.License.Delete, "删除", side: MultiTenancySides.Host);
 
                 var nodeTenant = nodePlatform.CreateChildNode(PermissionCodes.Platform.Tenant.EntryTenant, "租户管理", side: MultiTenancySides.Host);
                 nodeTenant.CreateChildNode(PermissionCodes.Platform.Tenant.Query, "查询", side: MultiTenancySides.Host);
@@ -37,7 +41,7 @@ namespace VCommonCoreExample.Authorization
 
             var nodeSystem = _tree.CreateRootNode(PermissionCodes.System.EntrySystem, "系统管理", side: MultiTenancySides.Both);
             {
-                var nodeRole = nodeSystem.CreateChildNode(PermissionCodes.System.Role.EntryRole, "岗位管理", side: MultiTenancySides.Both);
+                var nodeRole = nodeSystem.CreateChildNode(PermissionCodes.System.Role.EntryRole, "角色管理", side: MultiTenancySides.Both);
                 nodeRole.CreateChildNode(PermissionCodes.System.Role.Query, "查询", side: MultiTenancySides.Both);
                 nodeRole.CreateChildNode(PermissionCodes.System.Role.Create, "创建", side: MultiTenancySides.Both);
                 nodeRole.CreateChildNode(PermissionCodes.System.Role.Update, "修改", side: MultiTenancySides.Both);
@@ -50,10 +54,10 @@ namespace VCommonCoreExample.Authorization
                 nodeOrg.CreateChildNode(PermissionCodes.System.Org.Delete, "删除", side: MultiTenancySides.Tenant);
 
                 var nodeUser = nodeSystem.CreateChildNode(PermissionCodes.System.User.EntryUser, "用户管理", side: MultiTenancySides.Both);
-                nodeUser.CreateChildNode(PermissionCodes.System.User.Query, "查询", side: MultiTenancySides.Tenant);
-                nodeUser.CreateChildNode(PermissionCodes.System.User.Create, "创建", side: MultiTenancySides.Tenant);
-                nodeUser.CreateChildNode(PermissionCodes.System.User.Update, "修改", side: MultiTenancySides.Tenant);
-                nodeUser.CreateChildNode(PermissionCodes.System.User.Delete, "删除", side: MultiTenancySides.Tenant);
+                nodeUser.CreateChildNode(PermissionCodes.System.User.Query, "查询", side: MultiTenancySides.Both);
+                nodeUser.CreateChildNode(PermissionCodes.System.User.Create, "创建", side: MultiTenancySides.Both);
+                nodeUser.CreateChildNode(PermissionCodes.System.User.Update, "修改", side: MultiTenancySides.Both);
+                nodeUser.CreateChildNode(PermissionCodes.System.User.Delete, "删除", side: MultiTenancySides.Both);
             }
 
             return _tree.CompleteAdding();
@@ -63,42 +67,27 @@ namespace VCommonCoreExample.Authorization
 
         public bool CheckPermission(Guid? tenantId, Guid userId, params string[] hasAnyPermission)
         {
-            throw new NotImplementedException();
+            var ucm = _authCache.GetUser(tenantId, userId);
+            if (ucm == null) return false;
+            if (ucm.LoginName == ProjectConst.BuildInAdminUserName) return true;
+            var permissions = ucm.Roles.SelectMany(p => _authCache.GetRole(tenantId, p).PermissionSet).ToHashSet();
+            return hasAnyPermission.Any(permissions.Contains);
         }
 
-        //public IReadOnlyCollection<string> GetUserPermissionCodes(Guid? tenantId, Guid userId)
-        //{
-        //    throw new NotImplementedException();
-        //}
-        
-        //public IReadOnlyCollection<PermissionNode> GetAllPermissionNode(MultiTenancySides side, ICollection<string> licPermission)
-        //{
-        //    if (licPermission != null) return _tree.AllNodes.Where(p => p.TenancySide == side && te).ToArray();
-        //    return _tree.AllNodes.Where(p => p.TenancySide == side).ToArray();
-        //}
-
-        //public IReadOnlyCollection<string> GetTenantPermissionNode(ICollection<string> licPermissions)
-        //{
-        //    throw new NotImplementedException();
-        //}
-
-        //public IReadOnlyCollection<PermissionNodeOutput> GetPermissionTreeOutput(MultiTenancySides side, ICollection<string> licPermission)
-        //{
-        //    _tree.Filter();
-        //}
-
-
-
-        //IReadOnlyCollection<string> IPermissionManager.GetUserPermissionCodes()
-        //{
-        //    throw new NotImplementedException();
-        //}
-
-        //public IReadOnlyCollection<string> GetHostPermissionNode()
-        //{
-        //    throw new NotImplementedException();
-        //}
+        public IReadOnlyCollection<string> GetUserPermissionCodes(Guid? tenantId, Guid userId)
+        {
+            var ucm = _authCache.GetUser(tenantId, userId);
+            if (ucm == null) return null;
+            if (ucm.LoginName == ProjectConst.BuildInAdminUserName) return null;
+            var permissions = ucm.Roles.SelectMany(p => _authCache.GetRole(tenantId, p).PermissionSet).ToHashSet();
+            return permissions;
+        }
 
+        public IReadOnlyCollection<PermissionNodeOutput> GetPermissionTreeOutput(MultiTenancySides side, IReadOnlyCollection<string> licPermission = null)
+        {
+            return _tree.Filter(side, licPermission);
+        }
 
+      
     }
 }

+ 79 - 0
VCommonCoreExample/Caching/AuthCache.cs

@@ -0,0 +1,79 @@
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Linq;
+using VCommon.Caching;
+using VCommon.Ioc;
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Caching.Models;
+using VCommonCoreExample.Configuration;
+using VCommonCoreExample.Entity;
+using VCommonCoreExample.EntityFrameworkCore;
+
+namespace VCommonCoreExample.Caching
+{
+    public class AuthCache : ISingletonIocClass
+    {
+        //Tenant_T<TenantId>
+        //  Enable -> bool
+        //  User_<UserKey> -> {loginName,roles,password,enable}
+        //  Role_<RoleId> -> {permissions}
+
+        private const string EnableKey = "Enable";
+
+        private readonly IIocManager _iocManager;
+        private readonly FixedCacheManager _cache;
+
+        public AuthCache(IConfiguration configuration, IIocManager iocManager)
+        {
+            _iocManager = iocManager;
+            _cache = new FixedCacheManager(configuration[ProjectConst.CacheServerSettingKey], (int)CacheDbs.Auth);
+        }
+
+        private string TenantKey(Guid? tenantId) => "Tenant_T" + tenantId;
+
+        private string UserKey(Guid userId) => "User_" + userId;
+
+        private string RoleKey(Guid roleId) => "Role_" + roleId;
+
+        public void Clear(Guid? tenantId) => _cache.KeyDelete(TenantKey(tenantId));
+
+        public void ClearUser(Guid? tenantId, Guid userId) => _cache.HashFieldDelete(TenantKey(tenantId), UserKey(userId));
+
+        public void ClearRole(Guid? tenantId, Guid roleId) => _cache.HashFieldDelete(TenantKey(tenantId), RoleKey(roleId));
+
+        public bool? CheckTenantEnable(Guid tenantId)
+        {
+            return (bool?)_cache.HashFieldFetch(TenantKey(tenantId), EnableKey, () =>
+            {
+                using var db = _iocManager.Resolve<ExampleDbContext>();
+                var value = db.Tenants.Where(p => p.IsAbolish == false && p.Id == tenantId)
+                    .Select(p => (bool?)p.IsEnable).FirstOrDefault();
+                return (CacheValue)value;
+            });
+        }
+
+        public UserCacheModel GetUser(Guid? tenantId, Guid userId)
+        {
+            return _cache.HashFieldFetchJson(TenantKey(tenantId), UserKey(userId), () =>
+            {
+                using var db = _iocManager.Resolve<ExampleDbContext>();
+                var cm = db.Users
+                    .Where(p => p.IsAbolish == false && p.TenantId == tenantId && p.Id == userId)
+                    .ProjectionFirstOrDefault<User, UserCacheModel>();
+                return cm;
+            });
+        }
+
+        public RoleCacheModel GetRole(Guid? tenantId, Guid roleId)
+        {
+            return _cache.HashFieldFetchJson(TenantKey(tenantId), RoleKey(roleId), () =>
+            {
+                using var db = _iocManager.Resolve<ExampleDbContext>();
+                var cm = db.Roles
+                    .Where(p => p.IsAbolish == false && p.TenantId == tenantId && p.Id == roleId)
+                    .ProjectionFirstOrDefault<Role, RoleCacheModel>();
+                return cm;
+            });
+        }
+    }
+}

+ 1 - 0
VCommonCoreExample/Caching/CacheDbs.cs

@@ -3,5 +3,6 @@
     public enum CacheDbs
     {
         Session = 0,
+        Auth = 1,
     }
 }

+ 13 - 0
VCommonCoreExample/Caching/Models/RoleCacheModel.cs

@@ -0,0 +1,13 @@
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.Caching.Models
+{
+    [AutoMapFrom(typeof(Role))]
+    public class RoleCacheModel
+    {
+        [AutoMapJsonConvert]
+        public string[] PermissionSet { get; set; }
+
+    }
+}

+ 19 - 0
VCommonCoreExample/Caching/Models/UserCacheModel.cs

@@ -0,0 +1,19 @@
+using System;
+using VCommon.VAutoMapper;
+using VCommonCoreExample.Entity;
+
+namespace VCommonCoreExample.Caching.Models
+{
+    [AutoMapFrom(typeof(User))]
+    public class UserCacheModel
+    {
+
+        public bool IsEnable { get; set; }
+        public string LoginName { get; set; }
+
+        public string Password { get; set; }
+
+        [AutoMapJsonConvert]
+        public Guid[] Roles { get; set; }
+    }
+}

+ 5 - 14
VCommonCoreExample/Caching/SessionCacheManager.cs

@@ -7,27 +7,18 @@ using VCommonCoreExample.Configuration;
 
 namespace VCommonCoreExample.Caching
 {
-    public class SessionCacheManager : ISingletonIocClass
+    public class SessionCache : ISingletonIocClass
     {
-        private readonly IIocManager _iocManager;
-
-        // Token_<Token> -> Model.CacheModel.UserLoginCacheModel {tid,uid,password_hash,KickSign}
-        // KickSign_<UserId> -> match to UserLogin
-
-
+        // Token_<Token> -> {tid,uid,password_hash,KickSign}
+        // KickSign_<UserKey> -> match to UserLogin
 
         private readonly FixedCacheManager _cache;
 
-        public SessionCacheManager(IConfiguration configuration, IIocManager iocManager)
-        {
-            _iocManager = iocManager;
-            _cache = new FixedCacheManager(configuration[ProjectConst.CacheServerSettingKey], (int)CacheDbs.Session);
-        }
-
+        public SessionCache(IConfiguration configuration) => _cache = new FixedCacheManager(configuration[ProjectConst.CacheServerSettingKey], (int)CacheDbs.Session);
 
         private string TokenKey(string token) => "Token_" + token;
-        private string KickSignKey(Guid? tenantId, Guid userId) => "KickSign_T" + tenantId + "_U" + userId;
 
+        private string KickSignKey(Guid? tenantId, Guid userId) => "KickSign_T" + tenantId + "_U" + userId;
 
         public void Clear(string token) => _cache.KeyDelete(TokenKey(token));
 

+ 37 - 10
VCommonCoreExample/DataStore/UserStore.cs

@@ -18,16 +18,18 @@ namespace VCommonCoreExample.DataStore
         private const int NormalSessionExpireDays = 1;
         private const int RememberMeSessionExpireDays = 7;
 
-        private readonly SessionCacheManager _cache;
+        private readonly SessionCache _sessionCache;
+        private readonly AuthCache _authCache;
 
         [Dependency]
         public IIocManager IocManager { get; set; }
 
         private ExampleDbContext GetDbContext() => IocManager.Resolve<ExampleDbContext>();
 
-        public UserStore(SessionCacheManager cache)
+        public UserStore(SessionCache sessionCache, AuthCache authCache)
         {
-            _cache = cache;
+            _sessionCache = sessionCache;
+            _authCache = authCache;
         }
 
         public UserLoginModel GetLoginInfo(string tenantCode, string loginName, out string message)
@@ -60,22 +62,47 @@ namespace VCommonCoreExample.DataStore
 
         public void ValidateToken(string token, out Guid? tenantId, out Guid userId)
         {
-            var c = _cache.Get(token);
+            var c = _sessionCache.Get(token);
             if (c == null) throw new VFriendlyException("会话已失效");
-            var ek = _cache.GetKickSign(c.TenantId, c.UserId);
+            var ek = _sessionCache.GetKickSign(c.TenantId, c.UserId);
             if (c.KickSign != ek)
             {
-                _cache.Clear(token);
+                _sessionCache.Clear(token);
                 throw new VFriendlyException("会话已失效:用户已重新登录");
             }
 
-            //TODO: Check exist and tenant/user delete/disable/passChanged
+            //Check exist and tenant/user delete/disable/passChanged
 
-            tenantId = c.TenantId;
-            userId = c.UserId;
+            if (c.TenantId.HasValue && true != _authCache.CheckTenantEnable(c.TenantId.Value))
+            {
+                _sessionCache.Clear(token);
+                throw new VFriendlyException("会话已失效:租户不存在或未启用");
+            }
+
+            var ucm = _authCache.GetUser(c.TenantId, c.UserId);
+            if (null == ucm)
+            {
+                _sessionCache.Clear(token);
+                throw new VFriendlyException("会话已失效:用户不存在");
+            }
+
+            if (false == ucm.IsEnable)
+            {
+                _sessionCache.Clear(token);
+                throw new VFriendlyException("会话已失效:用户未启用");
+            }
+
+            if (c.HashedPassword != ucm.Password)
+            {
+                _sessionCache.Clear(token);
+                throw new VFriendlyException("会话已失效:密码已变更");
+            }
 
             // extend expire
-            _cache.ExtendExpire(token, tenantId, userId, c.Remember ? RememberMeSessionExpireDays : NormalSessionExpireDays);
+            _sessionCache.ExtendExpire(token, c.TenantId, c.UserId, c.Remember ? RememberMeSessionExpireDays : NormalSessionExpireDays);
+
+            tenantId = c.TenantId;
+            userId = c.UserId;
         }
     }
 }

+ 1 - 3
VCommonCoreExample/Entity/Role.cs

@@ -12,12 +12,10 @@ namespace VCommonCoreExample.Entity
         public string Name { get; set; }
 
         /// <summary>
-        /// 权限集合 JSON Array
+        /// 权限集合 JSON Array of String
         /// </summary>
         public string PermissionSet { get; set; }
 
-        public bool IsStaticRole { get; set; }
-
         public bool IsAbolish { get; set; }
         public DateTime CreationTime { get; set; }
         public Guid CreationUserId { get; set; }

+ 5 - 5
VCommonCoreExample/Entity/Tenant.cs

@@ -5,14 +5,14 @@ using VCommon.VEntity;
 
 namespace VCommonCoreExample.Entity
 {
-    [Index(nameof(LicenseId))]
+    //[Index(nameof(LicenseId))]
     [Index(nameof(Code), IsUnique = true)]
     public class Tenant : VEntityBase, ISoftDelete, IPassive, IHaveName, IFullAuditEntity
     {
-        /// <summary>
-        /// 许可Id
-        /// </summary>
-        public Guid LicenseId { get; set; }
+        ///// <summary>
+        ///// 许可Id
+        ///// </summary>
+        //public Guid LicenseId { get; set; }
 
         public bool IsEnable { get; set; }
 

+ 9 - 2
VCommonCoreExample/Entity/User.cs

@@ -1,23 +1,30 @@
 using Microsoft.EntityFrameworkCore;
 using System;
+using System.ComponentModel.DataAnnotations;
 using VCommon.VApplication.Dto;
 using VCommon.VEntity;
 
 namespace VCommonCoreExample.Entity
 {
     [Index(nameof(TenantId))]
-    [Index(nameof(RoleId))]
     [Index(nameof(TenantId), nameof(LoginName), IsUnique = true)]
     public class User : VEntityBase, IMayHaveTenant, IHaveName, IPassive, ISoftDelete, IFullAuditEntity
     {
         public Guid? TenantId { get; set; }
 
-        public Guid RoleId { get; set; }
+        /// <summary>
+        /// JSON Array of Guid
+        /// </summary>
+        [Required]
+        public string Roles { get; set; }
 
+        [Required]
         public string Name { get; set; }
 
+        [Required]
         public string LoginName { get; set; }
 
+        [Required]
         public string Password { get; set; }
 
         public bool IsStaticUser { get; set; }

+ 1 - 0
VCommonCoreExample/EntityFrameworkCore/ExampleDbContext.cs

@@ -33,6 +33,7 @@ namespace VCommonCoreExample.EntityFrameworkCore
                 Name = ProjectConst.BuildInAdminUserName,
                 IsEnable = true,
                 IsStaticUser = true,
+                Roles = "[]",
                 Password = "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A==", // 123qwe, PasswordHasher
             });
 

+ 4 - 15
VCommonCoreExample/Migrations/20210717041707_CreateDatabase.Designer.cs

@@ -9,7 +9,7 @@ using VCommonCoreExample.EntityFrameworkCore;
 namespace VCommonCoreExample.Migrations
 {
     [DbContext(typeof(ExampleDbContext))]
-    [Migration("20210717041707_CreateDatabase")]
+    [Migration("20210718094943_CreateDatabase")]
     partial class CreateDatabase
     {
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -98,9 +98,6 @@ namespace VCommonCoreExample.Migrations
                     b.Property<bool>("IsAbolish")
                         .HasColumnType("tinyint(1)");
 
-                    b.Property<bool>("IsStaticRole")
-                        .HasColumnType("tinyint(1)");
-
                     b.Property<DateTime?>("LastModificationTime")
                         .HasColumnType("datetime(6)");
 
@@ -149,9 +146,6 @@ namespace VCommonCoreExample.Migrations
                     b.Property<Guid?>("LastModificationUserId")
                         .HasColumnType("char(36)");
 
-                    b.Property<Guid>("LicenseId")
-                        .HasColumnType("char(36)");
-
                     b.Property<string>("Name")
                         .HasColumnType("longtext");
 
@@ -160,8 +154,6 @@ namespace VCommonCoreExample.Migrations
                     b.HasIndex("Code")
                         .IsUnique();
 
-                    b.HasIndex("LicenseId");
-
                     b.ToTable("Tenants");
                 });
 
@@ -200,16 +192,14 @@ namespace VCommonCoreExample.Migrations
                     b.Property<string>("Password")
                         .HasColumnType("longtext");
 
-                    b.Property<Guid>("RoleId")
-                        .HasColumnType("char(36)");
+                    b.Property<string>("Roles")
+                        .HasColumnType("longtext");
 
                     b.Property<Guid?>("TenantId")
                         .HasColumnType("char(36)");
 
                     b.HasKey("Id");
 
-                    b.HasIndex("RoleId");
-
                     b.HasIndex("TenantId");
 
                     b.HasIndex("TenantId", "LoginName")
@@ -228,8 +218,7 @@ namespace VCommonCoreExample.Migrations
                             IsStaticUser = true,
                             LoginName = "admin",
                             Name = "admin",
-                            Password = "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A==",
-                            RoleId = new Guid("00000000-0000-0000-0000-000000000000")
+                            Password = "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A=="
                         });
                 });
 

+ 4 - 15
VCommonCoreExample/Migrations/20210717041707_CreateDatabase.cs

@@ -62,7 +62,6 @@ namespace VCommonCoreExample.Migrations
                         .Annotation("MySql:CharSet", "utf8mb4"),
                     PermissionSet = table.Column<string>(type: "longtext", nullable: true)
                         .Annotation("MySql:CharSet", "utf8mb4"),
-                    IsStaticRole = table.Column<bool>(type: "tinyint(1)", nullable: false),
                     IsAbolish = table.Column<bool>(type: "tinyint(1)", nullable: false),
                     CreationTime = table.Column<DateTime>(type: "datetime(6)", nullable: false),
                     CreationUserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
@@ -80,7 +79,6 @@ namespace VCommonCoreExample.Migrations
                 columns: table => new
                 {
                     Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
-                    LicenseId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
                     IsEnable = table.Column<bool>(type: "tinyint(1)", nullable: false),
                     Code = table.Column<string>(type: "varchar(255)", nullable: true)
                         .Annotation("MySql:CharSet", "utf8mb4"),
@@ -118,7 +116,8 @@ namespace VCommonCoreExample.Migrations
                 {
                     Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
                     TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
-                    RoleId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
+                    Roles = table.Column<string>(type: "longtext", nullable: true)
+                        .Annotation("MySql:CharSet", "utf8mb4"),
                     Name = table.Column<string>(type: "longtext", nullable: true)
                         .Annotation("MySql:CharSet", "utf8mb4"),
                     LoginName = table.Column<string>(type: "varchar(255)", nullable: true)
@@ -141,8 +140,8 @@ namespace VCommonCoreExample.Migrations
 
             migrationBuilder.InsertData(
                 table: "Users",
-                columns: new[] { "Id", "CreationTime", "CreationUserId", "IsAbolish", "IsEnable", "IsStaticUser", "LastModificationTime", "LastModificationUserId", "LoginName", "Name", "Password", "RoleId", "TenantId" },
-                values: new object[] { new Guid("00000001-0002-0003-0405-060708090a0b"), new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("00000000-0000-0000-0000-000000000000"), false, true, true, null, null, "admin", "admin", "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A==", new Guid("00000000-0000-0000-0000-000000000000"), null });
+                columns: new[] { "Id", "CreationTime", "CreationUserId", "IsAbolish", "IsEnable", "IsStaticUser", "LastModificationTime", "LastModificationUserId", "LoginName", "Name", "Password", "Roles", "TenantId" },
+                values: new object[] { new Guid("00000001-0002-0003-0405-060708090a0b"), new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("00000000-0000-0000-0000-000000000000"), false, true, true, null, null, "admin", "admin", "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A==", null, null });
 
             migrationBuilder.CreateIndex(
                 name: "IX_Roles_TenantId",
@@ -156,11 +155,6 @@ namespace VCommonCoreExample.Migrations
                 unique: true);
 
             migrationBuilder.CreateIndex(
-                name: "IX_Tenants_LicenseId",
-                table: "Tenants",
-                column: "LicenseId");
-
-            migrationBuilder.CreateIndex(
                 name: "IX_UserInOrganizations_OrgId",
                 table: "UserInOrganizations",
                 column: "OrgId");
@@ -171,11 +165,6 @@ namespace VCommonCoreExample.Migrations
                 column: "UserId");
 
             migrationBuilder.CreateIndex(
-                name: "IX_Users_RoleId",
-                table: "Users",
-                column: "RoleId");
-
-            migrationBuilder.CreateIndex(
                 name: "IX_Users_TenantId",
                 table: "Users",
                 column: "TenantId");

+ 3 - 14
VCommonCoreExample/Migrations/ExampleDbContextModelSnapshot.cs

@@ -96,9 +96,6 @@ namespace VCommonCoreExample.Migrations
                     b.Property<bool>("IsAbolish")
                         .HasColumnType("tinyint(1)");
 
-                    b.Property<bool>("IsStaticRole")
-                        .HasColumnType("tinyint(1)");
-
                     b.Property<DateTime?>("LastModificationTime")
                         .HasColumnType("datetime(6)");
 
@@ -147,9 +144,6 @@ namespace VCommonCoreExample.Migrations
                     b.Property<Guid?>("LastModificationUserId")
                         .HasColumnType("char(36)");
 
-                    b.Property<Guid>("LicenseId")
-                        .HasColumnType("char(36)");
-
                     b.Property<string>("Name")
                         .HasColumnType("longtext");
 
@@ -158,8 +152,6 @@ namespace VCommonCoreExample.Migrations
                     b.HasIndex("Code")
                         .IsUnique();
 
-                    b.HasIndex("LicenseId");
-
                     b.ToTable("Tenants");
                 });
 
@@ -198,16 +190,14 @@ namespace VCommonCoreExample.Migrations
                     b.Property<string>("Password")
                         .HasColumnType("longtext");
 
-                    b.Property<Guid>("RoleId")
-                        .HasColumnType("char(36)");
+                    b.Property<string>("Roles")
+                        .HasColumnType("longtext");
 
                     b.Property<Guid?>("TenantId")
                         .HasColumnType("char(36)");
 
                     b.HasKey("Id");
 
-                    b.HasIndex("RoleId");
-
                     b.HasIndex("TenantId");
 
                     b.HasIndex("TenantId", "LoginName")
@@ -226,8 +216,7 @@ namespace VCommonCoreExample.Migrations
                             IsStaticUser = true,
                             LoginName = "admin",
                             Name = "admin",
-                            Password = "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A==",
-                            RoleId = new Guid("00000000-0000-0000-0000-000000000000")
+                            Password = "AIk0zKJKWH+8N9+hHWFFZ5U26GVyNG0V+9vNUDmuX6P6XnWeQXhd3cTV0jcM/DIi8A=="
                         });
                 });
 

+ 7 - 1
VCommonCoreExample/Startup.cs

@@ -14,7 +14,10 @@ using VCommon.VApplication;
 using VCommon.VOpenApi.VAspNetCore;
 using VCommon.VSwaggerUI;
 using VCommon.VSwaggerUI.VAspNetCore;
+using VCommonCoreExample.AppServices.Platform;
 using VCommonCoreExample.AppServices.Session;
+using VCommonCoreExample.AppServices.System.Roles;
+using VCommonCoreExample.AppServices.System.Users;
 using VCommonCoreExample.Configuration;
 using VCommonCoreExample.EntityFrameworkCore;
 
@@ -70,7 +73,10 @@ namespace VCommonCoreExample
 
             ApiMiddleware.Init(new Dictionary<string, Type>
             {
-                {"Session",typeof(ISessionService) }
+                {"Session",typeof(ISessionService) },
+                {"SystemUser",typeof(IUserService) },
+                {"SystemRole",typeof(IRoleService) },
+                {"PlatformTenant",typeof(ITenantService) },
             }, iocManager: rootContainer, docGen: dbg, isDebuggingEnabled: dbg);
 
             app.UseMiddleware<ApiMiddleware>();

+ 1 - 0
VCommonCoreExample/VCommonCoreExample.csproj

@@ -22,6 +22,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Folder Include="AppServices\System\Organizations\" />
     <Folder Include="Migrations\" />
   </ItemGroup>