ASP.NET Core Web API + Angular 仿B站(二)後臺模型創建以及數據庫的初始化
前言:
本系列文章主要為對所學 Angular 框架的一次微小的實踐,對 b站頁面作簡單的模仿。
本系列文章主要參考資料:
微軟文檔: https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows
Angular 文檔: https://angular.cn/tutorial
Typescript 文檔: https://www.typescriptlang.org/docs/home.html
此系列皆使用 C#+Typescript+Angular+EF Core 作為開發環境,使用 VSCode 對 Angular 進行開發同時作為命令行啟動器,使用 VS2017 對 ASP.NET Core 進行開發。如果有什麽問題或者意見歡迎在留言區進行留言。
如果圖片看不清的話請在新窗口打開圖片或保存本地查看。
項目 github 地址:https://github.com/NanaseRuri/FakeBilibili
本章內容:後臺模型分析以及建立數據庫
一、模型分析
二、創建模型以及數據庫
用戶登錄信息:
通過添加修飾 [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)] 可以手動設置 Id 屬性插入數據庫:
增加一個 salt 用來接受隨機字符串以進行加密。
1 public class UserIdentity 2 { 3 [Key] 4 [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)] 5 public int Id { get; set; } 6 7 [Required] 8 public string UserName { get; set; } 9 10 [Required]11 [EmailAddress] 12 public string Email { get; set; } 13 14 [Required] 15 public string Password { get; set; } 16 17 [Required] 18 public string Salt { get; set; } 19 }
用戶登錄信息數據庫,通過創建索引的方式為用戶名和郵箱添加唯一約束:
1 public class UserIdentityDbContext : DbContext 2 { 3 public UserIdentityDbContext(DbContextOptions<UserIdentityDbContext> options):base(options) 4 { } 5 6 public DbSet<UserIdentity> Users { get; set; } 7 8 protected override void OnModelCreating(ModelBuilder modelBuilder) 9 { 10 modelBuilder.Entity<UserIdentity>().HasIndex(u => new { u.Email }).IsUnique(true); 11 modelBuilder.Entity<UserIdentity>().HasIndex(u => new { u.UserName }).IsUnique(true); 12 } 13 }
在 appsetting.json 中添加數據庫連接字符串:
1 "ConnectionStrings": { 2 "UserIdentityDbContext": "Server=(localdb)\\mssqllocaldb;Database=UserIdentityDbContext;Trusted_Connection=True;MultipleActiveResultSets=true", 3 "UserAndVideoDbContext": "Server=(localdb)\\mssqllocaldb;Database=UserAndVideoDbContext;Trusted_Connection=True;MultipleActiveResultSets=true" 4 },
VS2017 PM控制臺中添加遷移:
add-migration UserIdentityDb -c FakeBilibili.Data.UserIdentityDbContext
添加數據庫:
update-database -c FakeBilibili.Data.UserIdentityDbContext
查看數據庫中 User 表的定義,已添加唯一約束:
用戶信息類:
1 public class User 2 { 3 [Key] 4 [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)] 5 public int Id { get; set; } 6 7 [EmailAddress] 8 [Required] 9 public string Email { get; set; } 10 11 [Required] 12 public string UserName { get; set; } 13 14 public string AvatarLocation { get; set; } 15 16 /// <summary> 17 /// 作品 18 /// </summary> 19 public ICollection<Video> Works { get; set; } 20 21 /// <summary> 22 /// 關註,內部用 / 分隔 23 /// </summary> 24 public string Follows { get; set; } 25 26 /// <summary> 27 /// 粉絲 28 /// </summary> 29 public string Fans { get; set; } 30 }
首先新建枚舉用於確定視頻分類:
1 public enum Category 2 { 3 動畫, 4 番劇, 5 音樂, 6 數碼, 7 遊戲, 8 科技 9 }
視頻信息類:
1 public class Video 2 { 3 [Key] 4 public int Id { get; set; } 5 6 [Required] 7 public string Title { get; set; } 8 9 [Required] 10 public User Author { get; set; } 11 12 [Required] 13 public string VideoLocation { get; set; } 14 15 [Required] 16 public string ThumbnailLocation { get; set; } 17 18 [Required] 19 public TimeSpan Duration { get; set; } 20 21 [Required] 22 public DateTime PublishDateTime { get; set; } 23 24 /// <summary> 25 /// 類別 26 /// </summary> 27 [Required] 28 public Category Category { get; set; } 29 30 /// <summary> 31 /// 標簽 32 /// </summary> 33 public string Tag { get; set; } 34 35 /// <summary> 36 /// 觀看數 37 /// </summary> 38 [Required] 39 public int VideoView { get; set; } 40 }
創建用戶信息和視頻的數據庫,並通過創建索引的方式為用戶名和郵箱添加唯一約束:
1 public class UserAndVideoDbContext:DbContext 2 { 3 public UserAndVideoDbContext(DbContextOptions<UserAndVideoDbContext> options):base(options) { } 4 5 public DbSet<User> Users { get; set; } 6 public DbSet<Video> Videos { get; set; } 7 8 protected override void OnModelCreating(ModelBuilder modelBuilder) 9 { 10 modelBuilder.Entity<User>().HasIndex(u => new { u.Email}).IsUnique(true); 11 modelBuilder.Entity<User>().HasIndex(u => new { u.UserName }).IsUnique(true); 12 } 13 }
運行遷移和更新命令:
1 add-migration UserAndVideoDb -c FakeBilibili.Data.UserAndVideoDbContext 2 update-database -c FakeBilibili.Data.UserAndVideoDbContext
三、數據庫初始化
為了方便添加鹽值,添加一個 SaltGenerator 類:
默認生成一個 8 位的隨機字符串:
1 public class SaltGenerator 2 { 3 public static string GenerateSalt() 4 { 5 return GenerateSalt(8); 6 } 7 8 public static string GenerateSalt(int length) 9 { 10 if (length<=0) 11 { 12 return String.Empty; 13 } 14 15 StringBuilder salt = new StringBuilder(); 16 Random random=new Random(); 17 StringBuilder saltCharList=new StringBuilder(); 18 //將小寫字母加入到列表中 19 for (int i = 97; i <= 122; i++) 20 { 21 saltCharList.Append((char) i); 22 } 23 //將大寫字母加入到列表中 24 for (int i = 65; i <=90; i++) 25 { 26 saltCharList.Append((char) i); 27 } 28 saltCharList.Append(0123456789); 29 30 for (int saltIndex = 0; saltIndex < length; saltIndex++) 31 { 32 salt.Append(saltCharList[random.Next(61)]); 33 } 34 35 return salt.ToString(); 36 } 37 }
為了方便對密碼進行加密,在此創建一個 Encryptor 類:
1 public class Encryptor : IEncrypt 2 { 3 private readonly SHA256 sha256; 4 5 public Encryptor() 6 { 7 sha256 = SHA256.Create(); 8 } 9 10 public string Encrypt(string password, string salt) 11 { 12 byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password + salt)); 13 StringBuilder hashPassword = new StringBuilder(); 14 foreach (var hashByte in hashBytes) 15 { 16 hashPassword.Append(hashByte); 17 } 18 return hashPassword.ToString(); 19 } 20 }
分別創建三個類進行登錄用用戶,用戶以及視頻的初始化:
對登錄用用戶進行初始化,使用 SHA256 算法對密碼進行加密:
1 public class UserIdentityInitiator 2 { 3 public static async Task Initial(IServiceProvider provider) 4 { 5 UserIdentityDbContext context = provider.GetRequiredService<UserIdentityDbContext>(); 6 Encryptor encryptor = new Encryptor(); 7 if (!context.Users.Any()) 8 { 9 for (int i = 0; i < 20; i++) 10 { 11 string salt = SaltGenerator.GenerateSalt(); 12 UserIdentity user = new UserIdentity() 13 { 14 UserName = $"User{i+1}", 15 Password = encryptor.Encrypt($"User{i+1}",salt), 16 Salt = salt, 17 Id = i+1, 18 Email = $"User{i + 1}@cnblog.com" 19 }; 20 await context.Users.AddAsync(user); 21 await context.SaveChangesAsync(); 22 } 23 } 24 } 25 }
然後在 Configure 方法最後一行中添加代碼:
UserIdentityInitiator.Initial(app.ApplicationServices).Wait();
運行程序後查看本地數據庫:
對用戶進行初始化:
在此添加了一個 Avatar 文件夾並放入幾張圖片備用,對用戶頭像進行初始化:
1 public class UserInitiator 2 { 3 public static async Task Initial(IServiceProvider serviceProvider) 4 { 5 UserAndVideoDbContext context = serviceProvider.GetRequiredService<UserAndVideoDbContext>(); 6 if (!context.Users.Any()) 7 { 8 string currentDirectory = Directory.GetCurrentDirectory(); 9 int pictureSerial = 0; 10 11 for (int i = 0; i < 20; i++) 12 { 13 pictureSerial = i % 4; 14 User user = new User() 15 { 16 AvatarLocation = Path.Combine(currentDirectory,"Avatar",$"{pictureSerial}.jpg"), 17 UserName = $"User{i+1}", 18 Id = i+1, 19 Email = $"User{i+1}@cnblog.com" 20 }; 21 22 await context.Users.AddAsync(user); 23 await context.SaveChangesAsync(); 24 } 25 } 26 } 27 }
在 Configure 方法最後一行添加方法:
UserInitiator.InitialUsers(app.ApplicationServices).Wait();
對視頻進行初始化:
新建一個 Video 文件夾放置若幹視頻:
在.net core 中為了生成縮略圖,需要引用 System.Drawing.Common 庫:
為了方便地獲取視頻封面,可以引用 Xabe.FFmpeg 庫:
由於 Xabe.FFmpeg 是 .NET 平臺上對 FFmpeg 封裝,基本功能依靠於 FFmpeg 實現,因此需要下載 FFmpeg:https://ffmpeg.zeranoe.com/builds/
解壓文件後將文件夾添加到環境變量中:
新建一個類用以在本地保存縮略圖:
1 public class PictureTrimmer 2 { 3 public static string GetLocalTrimmedPicture(string fileName) 4 { 5 string newLocation = fileName.Insert(fileName.LastIndexOf(".")+1, "min."); 6 Image.FromFile(fileName).GetThumbnailImage(320, 180, (() => false), IntPtr.Zero).Save(newLocation); 7 return newLocation; 8 } 9 }
初始化視頻:
為 FFmpeg.ExecutablesPath 賦值,設置成 FFmpeg 解壓後的位置:
由於 Xabe.FFmpeg 創建圖片時使用 FileStream 指定 FileMode 為 NewCreate,所以存在已有文件時會拋出異常,需保證創建的圖片不與現有文件重名。
在此全部視頻由 ID 為1的用戶上傳:
1 public class VideoInitiator 2 { 3 public static async Task Initial(IServiceProvider provider) 4 { 5 FFmpeg.ExecutablesPath = @"D:\office softwares\FFMpeg"; 6 7 UserAndVideoDbContext context = provider.GetRequiredService<UserAndVideoDbContext>(); 8 string videoDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Video"); 9 10 User author = context.Users.Include(u => u.Works).FirstOrDefault(u => u.Id == 1); 11 12 if (!context.Videos.Any()) 13 { 14 for (int i = 1; i <= 6; i++) 15 { 16 string videoPath = Path.Combine(videoDirectory, $"{i}.mp4"); 17 string picPath = Path.Combine(videoDirectory, $"{i}.jpg"); 18 19 if (File.Exists(picPath)) 20 { 21 File.Delete(picPath); 22 } 23 24 //獲取視頻信息 25 IMediaInfo mediaInfo = await MediaInfo.Get(videoPath); 26 //以 0 秒時的畫面作為封面圖並保存在本地 27 Conversion.Snapshot(videoPath, picPath, 28 TimeSpan.FromSeconds(0)).Start().Wait(); 29 30 Video video = new Video() 31 { 32 Title = $"輕音少女 第{i}集", 33 Author = context.Users.FirstOrDefault(u => u.Id == 0), 34 Category = Category.番劇, 35 VideoLocation = videoPath, 36 Duration = mediaInfo.Duration, 37 PublishDateTime = DateTime.Now, 38 ThumbnailLocation = PictureTrimmer.GetLocalTrimmedPicture(picPath), 39 Tag = "輕音少女", 40 VideoView = 0 41 }; 42 author.Works.Add(video); 43 await context.Videos.AddAsync(video); 44 await context.SaveChangesAsync(); 45 } 46 47 } 48 } 49 }
在 Configure 方法最後一行調用初始化方法:
VideoInitiator.Initial(app.ApplicationServices).Wait();
運行程序:
至此數據庫初始化工作完成。
ASP.NET Core Web API + Angular 仿B站(二)後臺模型創建以及數據庫的初始化