前言:
本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿。
本系列文章主要参考资料:
微软文档:
https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows
<https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows>
Angular 文档: https://angular.cn/tutorial <https://angular.cn/tutorial>
Typescript 文档: https://www.typescriptlang.org/docs/home.html
<https://www.typescriptlang.org/docs/home.html>
此系列皆使用 C#+Typescript+Angular+EF Core 作为开发环境,使用 VSCode 对 Angular
进行开发同时作为命令行启动器,使用 VS2017 对 ASP.NET Core 进行开发。如果有什么问题或者意见欢迎在留言区进行留言。
如果图片看不清的话请在新窗口打开图片或保存本地查看。
项目 github 地址:https://github.com/NanaseRuri/FakeBilibili
<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/ <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();
运行程序:
至此数据库初始化工作完成。
热门工具 换一换