前言:
本系列文章主要為對所學 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
本章內容:後臺模型建立以及資料庫的初始化,ASP.NET Core,獲取縮圖,獲取視訊某一幀圖片,ffmpeg,Image,SHA256,加密,鹽,EntityFramework 唯一性約束
一、模型分析
二、建立模型以及資料庫
使用者登入資訊:
通過新增修飾 [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();
執行程式:
至此資料庫初始化工作完成。