ASP.NET Core Web API + Angular 仿B站(二)後臺模型建立以及資料庫的初始化

NanaseRuri發表於2019-05-07

前言:

本系列文章主要為對所學 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();

 

執行程式:

 

至此資料庫初始化工作完成。

相關文章