添加项目文件。

This commit is contained in:
heiye111
2026-05-19 21:55:48 +08:00
parent 4dfb6cd5ae
commit b3e57cd13c
24 changed files with 3074 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore;
using WebAppServer1.Models;
namespace WebAppServer1.ApplicationDbContext
{
public class AppDbContext :DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 好友关系双向约束
modelBuilder.Entity<Friend>()
.HasOne(f => f.User)
.WithMany(u => u.Friends)
.HasForeignKey(f => f.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany()
.HasForeignKey(f => f.FriendId)
.OnDelete(DeleteBehavior.Restrict);
// 群成员约束
modelBuilder.Entity<GroupMember>()
.HasOne(gm => gm.Group)
.WithMany(g => g.Members)
.HasForeignKey(gm => gm.GroupId);
modelBuilder.Entity<GroupMember>()
.HasOne(gm => gm.User)
.WithMany()
.HasForeignKey(gm => gm.UserId);
// 消息约束
modelBuilder.Entity<Message>()
.HasOne(m => m.Sender)
.WithMany(u => u.Messages)
.HasForeignKey(m => m.SenderId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Message>()
.HasOne(m => m.Receiver)
.WithMany()
.HasForeignKey(m => m.ReceiverId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Message>()
.HasOne(m => m.Group)
.WithMany()
.HasForeignKey(m => m.GroupId);
}
public DbSet<User> Users { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }
public DbSet<Message> Messages { get; set; }
public DbSet<Notification> Notifications { get; set; }
public DbSet<FileEntity> Files { get; set; }
public DbSet<Tokens> Tokens { get; set; }
public DbSet<UserProfileHistory> userProfileHistories { get; set; }
public DbSet<LoginRecord> LoginRecords { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace WebAppServer1.Authentication
{
public class Authentication
{
}
}

View File

@@ -0,0 +1,87 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebAppServer1.ApplicationDbContext;
using WebAppServer1.Models;
namespace WebAppServer1.Authentication
{
public class TokenService
{
private readonly IConfiguration _config;
private readonly AppDbContext pgSql;
public TokenService(IConfiguration config, AppDbContext appDbContext)
{
_config = config;
pgSql = appDbContext;
}
public string GenerateAccessToken(string username, int userid)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, username),
//new Claim(ClaimTypes.Email, username),
new Claim(ClaimTypes.NameIdentifier, userid.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(_config["Jwt:AccessTokenExpirationMinutes"])),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public async Task<string> GenerateRefreshToken(int userid, string username)
{
var refreshToken = Guid.NewGuid().ToString("N");
var refreshTokenExpiry = DateTime.UtcNow.AddDays(Convert.ToDouble(_config["Jwt:RefreshTokenExpirationDays"]));
var tokens = new Tokens
{
UserId = userid,
UserName = username,
RefreshToken = refreshToken,
IsRevoked = false,
IssuedAt = DateTime.UtcNow,
ExpiresAt = refreshTokenExpiry,
};
pgSql.Add(tokens);
await pgSql.SaveChangesAsync();
return refreshToken;
}
public async Task<bool> ValidateRefreshToken(string refreshToken)
{
var exists = await pgSql.Tokens.AnyAsync(t => t.RefreshToken == refreshToken);
if (!exists) { return false; }
var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken);
if (token == null || token.IsRevoked ) { return false; }
if (token.ExpiresAt < DateTime.UtcNow)
{
token.IsRevoked = true;
await pgSql.SaveChangesAsync();
return false;
}
return true;
}
public async Task RevokeRefreshToken(string refreshToken)
{
var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken);
pgSql.Tokens.Remove(token!);
await pgSql.SaveChangesAsync();
}
}
}

249
Chat/Chat.cs Normal file
View File

@@ -0,0 +1,249 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity.Data;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;
using System.Diagnostics;
using WebAppServer1.ApplicationDbContext;
using WebAppServer1.Authentication;
using WebAppServer1.Models;
using WebAppServer1.Tool;
namespace WebAppServer1.Chat
{
public class Chat : Hub
{
private readonly ILogger<Chat> logger;
private readonly IConnectionMultiplexer _redis;
private readonly AppDbContext pgSql;
private readonly TokenService jwtService;
public Chat(ILogger<Chat> _logger, IConnectionMultiplexer redis, AppDbContext _pgsql,TokenService _jwtService)
{
logger = _logger;
_redis = redis;
pgSql = _pgsql;
jwtService = _jwtService;
}
//wss子端点API
public override async Task OnConnectedAsync()
{
var db = _redis.GetDatabase();
await db.SetAddAsync("AllConnections", Context.ConnectionId);
await BroadcastOnlineCount();
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var db = _redis.GetDatabase();
await db.SetRemoveAsync("AllConnections", Context.ConnectionId);
// 遍历所有群组,移除该连接
var server = _redis.GetServer(_redis.GetEndPoints().First());
foreach (var key in server.Keys(pattern: "Group:*"))
{
await db.SetRemoveAsync(key, Context.ConnectionId);
var groupName = key.ToString().Replace("Group:", "");
await BroadcastGroupCount(groupName);
}
await BroadcastOnlineCount();
await base.OnDisconnectedAsync(exception);
}
//简单广播消息
[Authorize]
public async Task SendMessage(string user, string message)
{
logger.LogWarning("广播消息");
//测试发送
var exuser = Context.User?.Identity?.Name ?? "匿名";
await Clients.All.SendAsync("ReceiveMessage", exuser, message);
}
//群聊分组消息
public async Task JoinGroup(string groupName)
{
var db = _redis.GetDatabase();
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await db.SetAddAsync($"Group:{groupName}", Context.ConnectionId);
await BroadcastGroupCount(groupName);
logger.LogWarning($"用户: {Context.ConnectionId}加入了{groupName}群组!");
await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, "系统", $"{Context.ConnectionId} 加入了群组");
}
public async Task LeaveGroup(string groupName)
{
var db = _redis.GetDatabase();
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await db.SetRemoveAsync($"Group:{groupName}", Context.ConnectionId);
await BroadcastGroupCount(groupName);
logger.LogInformation($"用户{Context.ConnectionId}---离开了---{groupName}群组");
await Clients.Group(groupName).SendAsync("ReceiveMessage",groupName, "系统", $"{Context.ConnectionId} 离开了群聊 {groupName}");
}
[Authorize]
public async Task SendMessageToGroup(string groupName, string user, string message)
{
logger.LogInformation($"用户{user}发来的的消息:{message}");
await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, user, message);
}
//发送已读回执
public async Task SendReadReceipt(int messageId)
{
var update = await pgSql.Messages.FindAsync(messageId);
if (update == null) { return; };
update.IsRead = true;
pgSql.Messages.Update(update);
await pgSql.SaveChangesAsync();
await Clients.User(update.SenderId.ToString()).SendAsync("SendReadReceipt",messageId);
}
//发送私聊消息
[Authorize]
public async Task SendPrivateMessage(string userid, string message)
{
var receiver = await pgSql.Users.FindAsync(int.Parse(userid));
if (receiver == null)
{
await Clients.Caller.SendAsync("SendPrivateMessage", false, "用户不存在", Context.UserIdentifier);
return;
}
var newMesssage = new Message
{
SenderId = int.Parse(Context.UserIdentifier!),
ReceiverId = receiver.Id,
Content = message,
CreatedAt = DateTime.UtcNow,
MessageType = MessageType.Text,
IsDeleted = false,
IsRead = false
};
pgSql.Messages.Add(newMesssage);
await pgSql.SaveChangesAsync();
await Clients.User(userid).SendAsync("SendPrivateMessage", true, message,Context.UserIdentifier,newMesssage.Id);
await Clients.Caller.SendAsync("SendPrivateMessage", false, message, Context.UserIdentifier,newMesssage.Id);
}
//搜索好友
public async Task<SearchFriendsResultResponse> SearchFriends(string username)
{
User? user = null;
if (int.TryParse(username, out int userId))
{
user = await pgSql.Users.FindAsync(userId);
Console.WriteLine("按 ID 查询: " + userId);
}
if (user == null)
{
user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username);
}
return user == null
? new SearchFriendsResultResponse { success = false }
: new SearchFriendsResultResponse
{
success = true,
userid = user.Id,
username = user.Nickname
};
}
//获取用户表单测试
public async Task<List<object>> GetFormData()
{
var users = await pgSql.Users
.Select(u => new {
Id = u.Id,
Name = u.Username,
Password = u.PasswordHash,
CreatedDate = u.CreatedAt,
LastModifiedDate = u.LastActive
})
.ToListAsync();
return users.Cast<object>().ToList();
}
//刷新Token
// 刷新 Token
public async Task RefreshAccessToken(string refreshToken)
{
var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken);
if (token == null || token.IsRevoked || token.ExpiresAt < DateTime.UtcNow)
{
await Clients.Caller.SendAsync("RefreshFailed", "Refresh token invalid or expired");
return;
}
var newAccessToken = jwtService.GenerateAccessToken(token.UserName, token.UserId);
var newRefreshToken = await jwtService.GenerateRefreshToken(token.UserId, token.UserName);
token.IsRevoked = true;
await pgSql.SaveChangesAsync();
await Clients.Caller.SendAsync("ReceiveNewAccessToken", newAccessToken, newRefreshToken);
}
//登录认证
public async Task LoginAuthentication(string username, string password)
{
var exists = await pgSql.Users.AnyAsync(a => a.Username == username && a.PasswordHash == password);
if (!exists)
{
await Clients.Client(Context.ConnectionId).SendAsync("LoginResult",false,"用户名或密码错误!");
return;
}
var user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username);
if (user == null) { return; }
var accessToken = jwtService.GenerateAccessToken(user.Username,user.Id);
var refreshToken = await jwtService.GenerateRefreshToken(user.Id, username);
await Clients.Client(Context.ConnectionId).SendAsync("LoginResult", true, accessToken, refreshToken);
logger.LogInformation($"用户:{username}正在进行登录认证!密码:{password}===accessToken:{accessToken}");
}
//新用户注册
public async Task Register()
{
var newUser = new User
{
Username = "user_" + Guid.NewGuid().ToString("N").Substring(0, 8),
Nickname = GenerateTool.GenerateRandomNickname(),
PasswordHash = GenerateTool.GeneratePassword(),
CreatedAt = DateTime.UtcNow,
LastActive = DateTime.UtcNow
};
pgSql.Add(newUser);
await pgSql.SaveChangesAsync();
logger.LogInformation($"新用户:{newUser.Username}正在进行登录注册操作!密码:{newUser.Nickname}");
await Clients.Client(Context.ConnectionId).SendAsync("RegisterResult", true,"恭喜注册成功!");
await LoginAuthentication(newUser.Username,newUser.PasswordHash);
}
//统计在线人数
private async Task BroadcastOnlineCount()
{
var db = _redis.GetDatabase();
var count = await db.SetLengthAsync("AllConnections");
await Clients.All.SendAsync("OnlineCount", count);
}
//统计群组在线人数
private async Task BroadcastGroupCount(string groupName)
{
var db = _redis.GetDatabase();
var count = await db.SetLengthAsync($"Group:{groupName}");
await Clients.Group(groupName).SendAsync("GroupCount", groupName, count);
}
}
}

View File

@@ -0,0 +1,55 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WebAppServer1.ApplicationDbContext;
#nullable disable
namespace WebAppServer1.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260516024729_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("LastModifiedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Password")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace WebAppServer1.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: true),
CreatedDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
LastModifiedDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Password = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@@ -0,0 +1,90 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WebAppServer1.ApplicationDbContext;
#nullable disable
namespace WebAppServer1.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260517061910_tokens")]
partial class tokens
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WebAppServer1.Models.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRevoked")
.HasColumnType("boolean");
b.Property<DateTime>("IssuedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("LastModifiedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace WebAppServer1.Migrations
{
/// <inheritdoc />
public partial class tokens : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Tokens",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
RefreshToken = table.Column<string>(type: "text", nullable: false),
UserId = table.Column<int>(type: "integer", nullable: false),
UserName = table.Column<string>(type: "text", nullable: false),
IssuedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
IsRevoked = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tokens", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Tokens");
}
}
}

View File

@@ -0,0 +1,509 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WebAppServer1.ApplicationDbContext;
#nullable disable
namespace WebAppServer1.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260519032425_InitialFriends")]
partial class InitialFriends
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("FileType")
.HasColumnType("integer");
b.Property<string>("FileUrl")
.IsRequired()
.HasColumnType("text");
b.Property<int>("MessageId")
.HasColumnType("integer");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<DateTime>("UploadedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UploaderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("MessageId");
b.HasIndex("UploaderId");
b.ToTable("Files");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("FriendId")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("FriendId");
b.HasIndex("UserId");
b.ToTable("Friends");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Groups");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("GroupId")
.HasColumnType("integer");
b.Property<DateTime>("JoinedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("UserId");
b.ToTable("GroupMembers");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("DeviceInfo")
.IsRequired()
.HasColumnType("text");
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsSuccess")
.HasColumnType("boolean");
b.Property<DateTime>("LoginTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("LoginRecords");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("GroupId")
.HasColumnType("integer");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("MessageType")
.HasColumnType("integer");
b.Property<int?>("ReceiverId")
.HasColumnType("integer");
b.Property<int>("SenderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("ReceiverId");
b.HasIndex("SenderId");
b.ToTable("Messages");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Notifications");
});
modelBuilder.Entity("WebAppServer1.Models.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRevoked")
.HasColumnType("boolean");
b.Property<DateTime>("IssuedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsOnline")
.HasColumnType("boolean");
b.Property<DateTime>("LastActive")
.HasColumnType("timestamp with time zone");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Signature")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ChangedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ChangedBy")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FieldName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NewValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OldValue")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("userProfileHistories");
});
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.HasOne("WebAppServer1.Models.Message", "Message")
.WithMany()
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "Uploader")
.WithMany()
.HasForeignKey("UploaderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Message");
b.Navigation("Uploader");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.HasOne("WebAppServer1.Models.User", "FriendUser")
.WithMany()
.HasForeignKey("FriendId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany("Friends")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("FriendUser");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.HasOne("WebAppServer1.Models.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany("Members")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("WebAppServer1.Models.User", "Receiver")
.WithMany()
.HasForeignKey("ReceiverId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("WebAppServer1.Models.User", "Sender")
.WithMany("Messages")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Group");
b.Navigation("Receiver");
b.Navigation("Sender");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Navigation("Members");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Navigation("Friends");
b.Navigation("Messages");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,408 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace WebAppServer1.Migrations
{
/// <inheritdoc />
public partial class InitialFriends : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Password",
table: "Users",
newName: "Username");
migrationBuilder.RenameColumn(
name: "Name",
table: "Users",
newName: "Signature");
migrationBuilder.RenameColumn(
name: "LastModifiedDate",
table: "Users",
newName: "LastActive");
migrationBuilder.RenameColumn(
name: "CreatedDate",
table: "Users",
newName: "CreatedAt");
migrationBuilder.AddColumn<string>(
name: "AvatarUrl",
table: "Users",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Email",
table: "Users",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<bool>(
name: "IsOnline",
table: "Users",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "PasswordHash",
table: "Users",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "Friends",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
FriendId = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Friends", x => x.Id);
table.ForeignKey(
name: "FK_Friends_Users_FriendId",
column: x => x.FriendId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Friends_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Groups",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
OwnerId = table.Column<int>(type: "integer", nullable: false),
AvatarUrl = table.Column<string>(type: "text", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Groups", x => x.Id);
table.ForeignKey(
name: "FK_Groups_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "LoginRecords",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
LoginTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
IpAddress = table.Column<string>(type: "text", nullable: false),
DeviceInfo = table.Column<string>(type: "text", nullable: false),
IsSuccess = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LoginRecords", x => x.Id);
table.ForeignKey(
name: "FK_LoginRecords_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Notifications",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Content = table.Column<string>(type: "text", nullable: false),
IsRead = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Notifications", x => x.Id);
table.ForeignKey(
name: "FK_Notifications_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "userProfileHistories",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
FieldName = table.Column<string>(type: "text", nullable: false),
OldValue = table.Column<string>(type: "text", nullable: false),
NewValue = table.Column<string>(type: "text", nullable: false),
ChangedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ChangedBy = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_userProfileHistories", x => x.Id);
table.ForeignKey(
name: "FK_userProfileHistories_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "GroupMembers",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
GroupId = table.Column<int>(type: "integer", nullable: false),
UserId = table.Column<int>(type: "integer", nullable: false),
Role = table.Column<int>(type: "integer", nullable: false),
JoinedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GroupMembers", x => x.Id);
table.ForeignKey(
name: "FK_GroupMembers_Groups_GroupId",
column: x => x.GroupId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_GroupMembers_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Messages",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
SenderId = table.Column<int>(type: "integer", nullable: false),
ReceiverId = table.Column<int>(type: "integer", nullable: true),
GroupId = table.Column<int>(type: "integer", nullable: true),
Content = table.Column<string>(type: "text", nullable: false),
MessageType = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
IsRead = table.Column<bool>(type: "boolean", nullable: false),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Messages", x => x.Id);
table.ForeignKey(
name: "FK_Messages_Groups_GroupId",
column: x => x.GroupId,
principalTable: "Groups",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Messages_Users_ReceiverId",
column: x => x.ReceiverId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Messages_Users_SenderId",
column: x => x.SenderId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Files",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UploaderId = table.Column<int>(type: "integer", nullable: false),
MessageId = table.Column<int>(type: "integer", nullable: false),
FileUrl = table.Column<string>(type: "text", nullable: false),
FileType = table.Column<int>(type: "integer", nullable: false),
Size = table.Column<int>(type: "integer", nullable: false),
UploadedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_Messages_MessageId",
column: x => x.MessageId,
principalTable: "Messages",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Files_Users_UploaderId",
column: x => x.UploaderId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Files_MessageId",
table: "Files",
column: "MessageId");
migrationBuilder.CreateIndex(
name: "IX_Files_UploaderId",
table: "Files",
column: "UploaderId");
migrationBuilder.CreateIndex(
name: "IX_Friends_FriendId",
table: "Friends",
column: "FriendId");
migrationBuilder.CreateIndex(
name: "IX_Friends_UserId",
table: "Friends",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_GroupMembers_GroupId",
table: "GroupMembers",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "IX_GroupMembers_UserId",
table: "GroupMembers",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Groups_OwnerId",
table: "Groups",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_LoginRecords_UserId",
table: "LoginRecords",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Messages_GroupId",
table: "Messages",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "IX_Messages_ReceiverId",
table: "Messages",
column: "ReceiverId");
migrationBuilder.CreateIndex(
name: "IX_Messages_SenderId",
table: "Messages",
column: "SenderId");
migrationBuilder.CreateIndex(
name: "IX_Notifications_UserId",
table: "Notifications",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_userProfileHistories_UserId",
table: "userProfileHistories",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Files");
migrationBuilder.DropTable(
name: "Friends");
migrationBuilder.DropTable(
name: "GroupMembers");
migrationBuilder.DropTable(
name: "LoginRecords");
migrationBuilder.DropTable(
name: "Notifications");
migrationBuilder.DropTable(
name: "userProfileHistories");
migrationBuilder.DropTable(
name: "Messages");
migrationBuilder.DropTable(
name: "Groups");
migrationBuilder.DropColumn(
name: "AvatarUrl",
table: "Users");
migrationBuilder.DropColumn(
name: "Email",
table: "Users");
migrationBuilder.DropColumn(
name: "IsOnline",
table: "Users");
migrationBuilder.DropColumn(
name: "PasswordHash",
table: "Users");
migrationBuilder.RenameColumn(
name: "Username",
table: "Users",
newName: "Password");
migrationBuilder.RenameColumn(
name: "Signature",
table: "Users",
newName: "Name");
migrationBuilder.RenameColumn(
name: "LastActive",
table: "Users",
newName: "LastModifiedDate");
migrationBuilder.RenameColumn(
name: "CreatedAt",
table: "Users",
newName: "CreatedDate");
}
}
}

View File

@@ -0,0 +1,513 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WebAppServer1.ApplicationDbContext;
#nullable disable
namespace WebAppServer1.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260519043509_addNickName")]
partial class addNickName
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("FileType")
.HasColumnType("integer");
b.Property<string>("FileUrl")
.IsRequired()
.HasColumnType("text");
b.Property<int>("MessageId")
.HasColumnType("integer");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<DateTime>("UploadedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UploaderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("MessageId");
b.HasIndex("UploaderId");
b.ToTable("Files");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("FriendId")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("FriendId");
b.HasIndex("UserId");
b.ToTable("Friends");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Groups");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("GroupId")
.HasColumnType("integer");
b.Property<DateTime>("JoinedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("UserId");
b.ToTable("GroupMembers");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("DeviceInfo")
.IsRequired()
.HasColumnType("text");
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsSuccess")
.HasColumnType("boolean");
b.Property<DateTime>("LoginTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("LoginRecords");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("GroupId")
.HasColumnType("integer");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("MessageType")
.HasColumnType("integer");
b.Property<int?>("ReceiverId")
.HasColumnType("integer");
b.Property<int>("SenderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("ReceiverId");
b.HasIndex("SenderId");
b.ToTable("Messages");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Notifications");
});
modelBuilder.Entity("WebAppServer1.Models.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRevoked")
.HasColumnType("boolean");
b.Property<DateTime>("IssuedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsOnline")
.HasColumnType("boolean");
b.Property<DateTime>("LastActive")
.HasColumnType("timestamp with time zone");
b.Property<string>("Nickname")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Signature")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ChangedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ChangedBy")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FieldName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NewValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OldValue")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("userProfileHistories");
});
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.HasOne("WebAppServer1.Models.Message", "Message")
.WithMany()
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "Uploader")
.WithMany()
.HasForeignKey("UploaderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Message");
b.Navigation("Uploader");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.HasOne("WebAppServer1.Models.User", "FriendUser")
.WithMany()
.HasForeignKey("FriendId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany("Friends")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("FriendUser");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.HasOne("WebAppServer1.Models.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany("Members")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("WebAppServer1.Models.User", "Receiver")
.WithMany()
.HasForeignKey("ReceiverId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("WebAppServer1.Models.User", "Sender")
.WithMany("Messages")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Group");
b.Navigation("Receiver");
b.Navigation("Sender");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Navigation("Members");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Navigation("Friends");
b.Navigation("Messages");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace WebAppServer1.Migrations
{
/// <inheritdoc />
public partial class addNickName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Nickname",
table: "Users",
type: "text",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Nickname",
table: "Users");
}
}
}

View File

@@ -0,0 +1,510 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WebAppServer1.ApplicationDbContext;
#nullable disable
namespace WebAppServer1.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("FileType")
.HasColumnType("integer");
b.Property<string>("FileUrl")
.IsRequired()
.HasColumnType("text");
b.Property<int>("MessageId")
.HasColumnType("integer");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<DateTime>("UploadedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UploaderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("MessageId");
b.HasIndex("UploaderId");
b.ToTable("Files");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("FriendId")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("FriendId");
b.HasIndex("UserId");
b.ToTable("Friends");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Groups");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("GroupId")
.HasColumnType("integer");
b.Property<DateTime>("JoinedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("UserId");
b.ToTable("GroupMembers");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("DeviceInfo")
.IsRequired()
.HasColumnType("text");
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsSuccess")
.HasColumnType("boolean");
b.Property<DateTime>("LoginTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("LoginRecords");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("GroupId")
.HasColumnType("integer");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("MessageType")
.HasColumnType("integer");
b.Property<int?>("ReceiverId")
.HasColumnType("integer");
b.Property<int>("SenderId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("ReceiverId");
b.HasIndex("SenderId");
b.ToTable("Messages");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRead")
.HasColumnType("boolean");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Notifications");
});
modelBuilder.Entity("WebAppServer1.Models.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsRevoked")
.HasColumnType("boolean");
b.Property<DateTime>("IssuedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarUrl")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsOnline")
.HasColumnType("boolean");
b.Property<DateTime>("LastActive")
.HasColumnType("timestamp with time zone");
b.Property<string>("Nickname")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Signature")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ChangedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ChangedBy")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FieldName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NewValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OldValue")
.IsRequired()
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("userProfileHistories");
});
modelBuilder.Entity("WebAppServer1.Models.FileEntity", b =>
{
b.HasOne("WebAppServer1.Models.Message", "Message")
.WithMany()
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "Uploader")
.WithMany()
.HasForeignKey("UploaderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Message");
b.Navigation("Uploader");
});
modelBuilder.Entity("WebAppServer1.Models.Friend", b =>
{
b.HasOne("WebAppServer1.Models.User", "FriendUser")
.WithMany()
.HasForeignKey("FriendId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany("Friends")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("FriendUser");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.HasOne("WebAppServer1.Models.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("WebAppServer1.Models.GroupMember", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany("Members")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Message", b =>
{
b.HasOne("WebAppServer1.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("WebAppServer1.Models.User", "Receiver")
.WithMany()
.HasForeignKey("ReceiverId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("WebAppServer1.Models.User", "Sender")
.WithMany("Messages")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Group");
b.Navigation("Receiver");
b.Navigation("Sender");
});
modelBuilder.Entity("WebAppServer1.Models.Notification", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b =>
{
b.HasOne("WebAppServer1.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebAppServer1.Models.Group", b =>
{
b.Navigation("Members");
});
modelBuilder.Entity("WebAppServer1.Models.User", b =>
{
b.Navigation("Friends");
b.Navigation("Messages");
});
#pragma warning restore 612, 618
}
}
}

15
Models/ResultResponse.cs Normal file
View File

@@ -0,0 +1,15 @@
using MessagePack;
namespace WebAppServer1.Models
{
[MessagePackObject]
public class SearchFriendsResultResponse
{
[Key("success")]
public bool success { get; set; }
[Key("username")]
public string username { get; set; }
[Key("userid")]
public int userid { get; set; }
};
}

13
Models/Tokens.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace WebAppServer1.Models
{
public class Tokens
{
public int Id { get; set; }
public required string RefreshToken { get; set; }
public int UserId { get; set; }
public required string UserName { get; set; }
public DateTime IssuedAt { get; set; }
public DateTime ExpiresAt { get; set; }
public bool IsRevoked { get; set; }
}
}

174
Models/Users.cs Normal file
View File

@@ -0,0 +1,174 @@
using MessagePack;
namespace WebAppServer1.Models
{
// 用户表
public class User
{
public int Id { get; set; }
public string Username { get; set; } = string.Empty;
public string Nickname { get; set; } = string.Empty; // 可修改,不唯一
public string Email { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string AvatarUrl { get; set; } = string.Empty;
public string Signature { get; set; } = string.Empty;
public DateTime LastActive { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsOnline { get; set; }
public ICollection<Friend> Friends { get; set; } = new List<Friend>();
public ICollection<Message> Messages { get; set; } = new List<Message>();
}
// 好友关系表
public class Friend
{
public int Id { get; set; }
public int UserId { get; set; }
public int FriendId { get; set; }
public FriendStatus Status { get; set; }
public DateTime CreatedAt { get; set; }
public User User { get; set; }
public User FriendUser { get; set; }
}
public enum FriendStatus
{
Pending,
Accepted,
Blocked
}
// 群聊表
public class Group
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int OwnerId { get; set; }
public string AvatarUrl { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public User Owner { get; set; }
public ICollection<GroupMember> Members { get; set; } = new List<GroupMember>();
}
// 群成员表
public class GroupMember
{
public int Id { get; set; }
public int GroupId { get; set; }
public int UserId { get; set; }
public GroupRole Role { get; set; }
public DateTime JoinedAt { get; set; }
public Group Group { get; set; }
public User User { get; set; }
}
public enum GroupRole
{
Member,
Admin,
Owner
}
// 消息表
public class Message
{
public int Id { get; set; }
public int SenderId { get; set; }
public int? ReceiverId { get; set; } // 私聊
public int? GroupId { get; set; } // 群聊
public string Content { get; set; } = string.Empty;
public MessageType MessageType { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsRead { get; set; }
public bool IsDeleted { get; set; }
public User Sender { get; set; }
public User Receiver { get; set; }
public Group Group { get; set; }
}
public enum MessageType
{
Text,
Image,
File,
Video,
Voice
}
// 通知表
public class Notification
{
public int Id { get; set; }
public int UserId { get; set; }
public NotificationType Type { get; set; }
public string Content { get; set; } = string.Empty;
public bool IsRead { get; set; }
public DateTime CreatedAt { get; set; }
public User User { get; set; }
}
public enum NotificationType
{
FriendRequest,
Message,
System
}
// 文件表
public class FileEntity
{
public int Id { get; set; }
public int UploaderId { get; set; }
public int MessageId { get; set; }
public string FileUrl { get; set; } = string.Empty;
public FileType FileType { get; set; }
public int Size { get; set; }
public DateTime UploadedAt { get; set; }
public User Uploader { get; set; }
public Message Message { get; set; }
}
public enum FileType
{
Image,
Video,
Document,
Audio
}
public class UserProfileHistory
{
public int Id { get; set; }
public int UserId { get; set; }
public string FieldName { get; set; } = string.Empty;
public string OldValue { get; set; } = string.Empty;
public string NewValue { get; set; } = string.Empty;
public DateTime ChangedAt { get; set; }
public string ChangedBy { get; set; } = string.Empty;
public User User { get; set; }
}
public class LoginRecord
{
public int Id { get; set; }
public int UserId { get; set; }
public DateTime LoginTime { get; set; }
public string IpAddress { get; set; } = string.Empty;
public string DeviceInfo { get; set; } = string.Empty;
public bool IsSuccess { get; set; }
public User User { get; set; }
}
}

152
Program.cs Normal file
View File

@@ -0,0 +1,152 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using StackExchange.Redis;
using System.Text;
using WebAppServer1.ApplicationDbContext;
using WebAppServer1.Authentication;
using WebAppServer1.Chat;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
//注册SignalR服务
builder.Services.AddSignalR(options =>
{
options.MaximumReceiveMessageSize = 1024 * 1024 * 10; // 10MB
})
.AddMessagePackProtocol(options =>
{
options.SerializerOptions = MessagePack.MessagePackSerializerOptions.Standard
.WithSecurity(MessagePack.MessagePackSecurity.UntrustedData);
});
//连接并注册Redis服务
var redis = ConnectionMultiplexer.Connect("83.229.121.44:6378,password=redis_4GG7KG,defaultDatabase=5");
builder.Services.AddSingleton<IConnectionMultiplexer>(redis);
// 服务启动时清理 Redis
//var _redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
var db = redis.GetDatabase();
var server = redis.GetServer(redis.GetEndPoints().First());
// 清理全局连接
await db.KeyDeleteAsync("AllConnections");
// 清理所有群组
foreach (var _key in server.Keys(pattern: "Group:*"))
{
await db.KeyDeleteAsync(_key);
}
//连接数据库
// 添加 DbContext 并使用 PostgreSQL
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("PostgresConnection")));
//配置跨域
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowVueApp",
policy =>
{
policy.WithOrigins("http://192.168.1.254:5173", "http://localhost:5173") // 前端地址
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
//配置jwt
// JWT 配置
var jwtSettings = builder.Configuration.GetSection("Jwt");
var keyString = jwtSettings["Key"] ?? throw new InvalidOperationException("JWT Key 未配置");
var key = Encoding.UTF8.GetBytes(keyString);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
};
// 允许 SignalR 使用 JWT
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/Chat"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddScoped<TokenService>();
var app = builder.Build();
app.UseRouting();
//启用跨域配置
app.UseCors("AllowVueApp");
//启用jwt
app.UseAuthentication();
app.UseAuthorization();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
//app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
//映射端点
app.MapHub<Chat>("/Chat");
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://0.0.0.0:5162",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

45
Tool/GenerateTool.cs Normal file
View File

@@ -0,0 +1,45 @@
using System.Security.Cryptography;
using System.Text;
namespace WebAppServer1.Tool
{
public class GenerateTool
{
public static string GeneratePassword()
{
const string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const string digits = "0123456789";
var sb = new StringBuilder();
// 生成 2 个字母
for (int i = 0; i < 2; i++)
{
int index = RandomNumberGenerator.GetInt32(letters.Length);
sb.Append(letters[index]);
}
// 生成 4 个数字
for (int i = 0; i < 4; i++)
{
int index = RandomNumberGenerator.GetInt32(digits.Length);
sb.Append(digits[index]);
}
return sb.ToString();
}
public static string GenerateRandomNickname()
{
string[] part1 = { "自由的", "快乐的", "孤独的", "勇敢的", "神秘的" };
string[] part2 = { "小鸟", "星星", "花朵", "少年", "旅人" };
string[] part3 = { "在唱歌", "在飞翔", "在微笑", "在流浪", "在思考" };
var random = new Random();
string nickname = part1[random.Next(part1.Length)]
+ part2[random.Next(part2.Length)]
+ part3[random.Next(part3.Length)];
return nickname;
}
}
}

21
WebAppServer1.csproj Normal file
View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="StackExchange.Redis" Version="2.13.1" />
</ItemGroup>
</Project>

6
WebAppServer1.http Normal file
View File

@@ -0,0 +1,6 @@
@WebAppServer1_HostAddress = http://localhost:5162
GET {{WebAppServer1_HostAddress}}/weatherforecast/
Accept: application/json
###

3
WebAppServer1.slnx Normal file
View File

@@ -0,0 +1,3 @@
<Solution>
<Project Path="WebAppServer1.csproj" />
</Solution>

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

21
appsettings.json Normal file
View File

@@ -0,0 +1,21 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"PostgresConnection": "Host=localhost;Port=5555;Database=webapp;Username=postgres;Password=123456"
},
"Jwt": {
"Key": "YourSuperSecretKeyForJwtTokenGeneration2024!@#$%^&*()",
"Issuer": "SignalRChat",
"Audience": "SignalRChatUsers",
"AccessTokenExpirationMinutes": 5,
"RefreshTokenExpirationDays": 60
},
"AllowedHosts": "*"
}