Merge pull request '更新 📘 接口响应 Code 设计文档.md' (#7) from test into main
Reviewed-on: code/Chat#7
This commit is contained in:
commit
52f5ad8eeb
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
30
backend/IM_API/.dockerignore
Normal file
30
backend/IM_API/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
||||||
3
backend/IM_API/.gitignore
vendored
Normal file
3
backend/IM_API/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
.vs/
|
||||||
33
backend/IM_API/Controllers/WeatherForecastController.cs
Normal file
33
backend/IM_API/Controllers/WeatherForecastController.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace IM_API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class WeatherForecastController : ControllerBase
|
||||||
|
{
|
||||||
|
private static readonly string[] Summaries = new[]
|
||||||
|
{
|
||||||
|
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly ILogger<WeatherForecastController> _logger;
|
||||||
|
|
||||||
|
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet(Name = "GetWeatherForecast")]
|
||||||
|
public IEnumerable<WeatherForecast> Get()
|
||||||
|
{
|
||||||
|
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||||
|
{
|
||||||
|
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||||
|
TemperatureC = Random.Shared.Next(-20, 55),
|
||||||
|
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
backend/IM_API/Dockerfile
Normal file
30
backend/IM_API/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。
|
||||||
|
|
||||||
|
# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
|
||||||
|
# 此阶段用于生成服务项目
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["IM_API.csproj", "."]
|
||||||
|
RUN dotnet restore "./IM_API.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/."
|
||||||
|
RUN dotnet build "./IM_API.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
# 此阶段用于发布要复制到最终阶段的服务项目
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./IM_API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "IM_API.dll"]
|
||||||
26
backend/IM_API/IM_API.csproj
Normal file
26
backend/IM_API/IM_API.csproj
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>3f396849-59bd-435f-a0cb-351ec0559e70</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerfileContext>.</DockerfileContext>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.21">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.21">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
9
backend/IM_API/IM_API.csproj.user
Normal file
9
backend/IM_API/IM_API.csproj.user
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ActiveDebugProfile>http</ActiveDebugProfile>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
6
backend/IM_API/IM_API.http
Normal file
6
backend/IM_API/IM_API.http
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@IM_API_HostAddress = http://localhost:5202
|
||||||
|
|
||||||
|
GET {{IM_API_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
25
backend/IM_API/IM_API.sln
Normal file
25
backend/IM_API/IM_API.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36408.4 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IM_API", "IM_API.csproj", "{F001642C-3E66-4C2C-9A25-1AE5EE76CE97}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F001642C-3E66-4C2C-9A25-1AE5EE76CE97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F001642C-3E66-4C2C-9A25-1AE5EE76CE97}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F001642C-3E66-4C2C-9A25-1AE5EE76CE97}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F001642C-3E66-4C2C-9A25-1AE5EE76CE97}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {EC36B0B9-6176-4BC7-BF88-33F923E3E777}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
58
backend/IM_API/Models/Admin.cs
Normal file
58
backend/IM_API/Models/Admin.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("admins")]
|
||||||
|
[Index("RoleId", Name = "RoleId")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Admin
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Username { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 密码
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Password { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int RoleId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态(0:正常,2:封禁)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Updated { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("RoleId")]
|
||||||
|
[InverseProperty("Admins")]
|
||||||
|
public virtual Role Role { get; set; } = null!;
|
||||||
|
}
|
||||||
57
backend/IM_API/Models/Conversation.cs
Normal file
57
backend/IM_API/Models/Conversation.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("conversations")]
|
||||||
|
[Index("Userid", Name = "Userid")]
|
||||||
|
[Index("LastMessageId", Name = "lastMessageId")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Conversation
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对方ID(群聊为群聊ID,单聊为单聊ID)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Targetid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息类型(同Messages.MsgType)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int MsgType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后一条消息ID
|
||||||
|
/// </summary>
|
||||||
|
[Column("lastMessageId", TypeName = "int(11)")]
|
||||||
|
public int LastMessageId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未读消息数
|
||||||
|
/// </summary>
|
||||||
|
[Column("unreadCount", TypeName = "int(11)")]
|
||||||
|
public int UnreadCount { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("LastMessageId")]
|
||||||
|
[InverseProperty("Conversations")]
|
||||||
|
public virtual Message LastMessage { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("Userid")]
|
||||||
|
[InverseProperty("Conversations")]
|
||||||
|
public virtual User User { get; set; } = null!;
|
||||||
|
}
|
||||||
41
backend/IM_API/Models/Device.cs
Normal file
41
backend/IM_API/Models/Device.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("devices")]
|
||||||
|
[Index("Userid", Name = "Userid")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Device
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备所属用户
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备类型(
|
||||||
|
/// 0:Android,1:Ios,2:PC,3:Pad,4:未知)
|
||||||
|
/// </summary>
|
||||||
|
[Column("DType", TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Dtype { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后一次登录
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime LastLogin { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Userid")]
|
||||||
|
[InverseProperty("Devices")]
|
||||||
|
public virtual User User { get; set; } = null!;
|
||||||
|
}
|
||||||
59
backend/IM_API/Models/File.cs
Normal file
59
backend/IM_API/Models/File.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("files")]
|
||||||
|
[Index("Messageld", Name = "Messageld")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class File
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件储存URL
|
||||||
|
/// </summary>
|
||||||
|
[Column("URL")]
|
||||||
|
[StringLength(100)]
|
||||||
|
public string Url { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件大小(单位:KB)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件类型
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(10)]
|
||||||
|
public string Type { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联消息ID
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Messageld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Messageld")]
|
||||||
|
[InverseProperty("Files")]
|
||||||
|
public virtual Message MessageldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
53
backend/IM_API/Models/Friend.cs
Normal file
53
backend/IM_API/Models/Friend.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("friends")]
|
||||||
|
[Index("Id", Name = "ID")]
|
||||||
|
[Index("Userld", "Friendld", Name = "Userld")]
|
||||||
|
[Index("Friendld", Name = "用户2id")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Friend
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户ID
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户2ID
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Friendld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前好友关系状态
|
||||||
|
/// (0:待通过,1:已添加,2:已拒绝,3:已拉黑)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 好友关系创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Friendld")]
|
||||||
|
[InverseProperty("FriendFriendldNavigations")]
|
||||||
|
public virtual User FriendldNavigation { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("Userld")]
|
||||||
|
[InverseProperty("FriendUserldNavigations")]
|
||||||
|
public virtual User UserldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
57
backend/IM_API/Models/Friendrequest.cs
Normal file
57
backend/IM_API/Models/Friendrequest.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("friendrequest")]
|
||||||
|
[Index("RequestUser", Name = "RequestUser")]
|
||||||
|
[Index("ResponseUser", Name = "ResponseUser")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Friendrequest
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请人
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int RequestUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被申请人
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int ResponseUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请附言
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte State { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("RequestUser")]
|
||||||
|
[InverseProperty("FriendrequestRequestUserNavigations")]
|
||||||
|
public virtual User RequestUserNavigation { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("ResponseUser")]
|
||||||
|
[InverseProperty("FriendrequestResponseUserNavigations")]
|
||||||
|
public virtual User ResponseUserNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
73
backend/IM_API/Models/Group.cs
Normal file
73
backend/IM_API/Models/Group.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("groups")]
|
||||||
|
[Index("GroupMaster", Name = "GroupMaster")]
|
||||||
|
[Index("Id", Name = "ID")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Group
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊名称
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(20)]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群主
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int GroupMaster { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群权限
|
||||||
|
/// (0:需管理员同意,1:任意人可加群,2:不允许任何人加入)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Auhority { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全员禁言(0允许发言,2全员禁言)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte AllMembersBanned { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊状态
|
||||||
|
/// (1:正常,2:封禁)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群公告
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string? Announcement { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("GroupMaster")]
|
||||||
|
[InverseProperty("Groups")]
|
||||||
|
public virtual User GroupMasterNavigation { get; set; } = null!;
|
||||||
|
|
||||||
|
[InverseProperty("Group")]
|
||||||
|
public virtual ICollection<Groupinvite> Groupinvites { get; set; } = new List<Groupinvite>();
|
||||||
|
|
||||||
|
[InverseProperty("Group")]
|
||||||
|
public virtual ICollection<Grouprequest> Grouprequests { get; set; } = new List<Grouprequest>();
|
||||||
|
}
|
||||||
63
backend/IM_API/Models/Groupinvite.cs
Normal file
63
backend/IM_API/Models/Groupinvite.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("groupinvite")]
|
||||||
|
[Index("GroupId", Name = "GroupId")]
|
||||||
|
[Index("InviteUser", Name = "InviteUser")]
|
||||||
|
[Index("InvitedUser", Name = "InvitedUser")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Groupinvite
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊编号
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int GroupId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被邀请用户
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int? InvitedUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邀请用户
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int? InviteUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前状态(0:待被邀请人同意
|
||||||
|
/// 1:被邀请人已同意)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte? State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime? Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("GroupId")]
|
||||||
|
[InverseProperty("Groupinvites")]
|
||||||
|
public virtual Group Group { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("InviteUser")]
|
||||||
|
[InverseProperty("GroupinviteInviteUserNavigations")]
|
||||||
|
public virtual User? InviteUserNavigation { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("InvitedUser")]
|
||||||
|
[InverseProperty("GroupinviteInvitedUserNavigations")]
|
||||||
|
public virtual User? InvitedUserNavigation { get; set; }
|
||||||
|
}
|
||||||
52
backend/IM_API/Models/Groupmember.cs
Normal file
52
backend/IM_API/Models/Groupmember.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("groupmember")]
|
||||||
|
[Index("Groupld", Name = "Groupld")]
|
||||||
|
[Index("Id", Name = "ID")]
|
||||||
|
[Index("Userld", Name = "Userld")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Groupmember
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户编号
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊编号
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Groupld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成员角色(0:普通成员,1:管理员,2:群主)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Role { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加入群聊时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Groupld")]
|
||||||
|
[InverseProperty("GroupmemberGroupldNavigations")]
|
||||||
|
public virtual User GroupldNavigation { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("Userld")]
|
||||||
|
[InverseProperty("GroupmemberUserldNavigations")]
|
||||||
|
public virtual User UserldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
57
backend/IM_API/Models/Grouprequest.cs
Normal file
57
backend/IM_API/Models/Grouprequest.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("grouprequest")]
|
||||||
|
[Index("GroupId", Name = "GroupId")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Grouprequest
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 群聊编号
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int GroupId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请人
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 申请状态(0:待管理员同意,1:已拒绝,2:已同意)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 入群附言
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string Description { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("GroupId")]
|
||||||
|
[InverseProperty("Grouprequests")]
|
||||||
|
public virtual Group Group { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("GroupId")]
|
||||||
|
[InverseProperty("Grouprequests")]
|
||||||
|
public virtual User GroupNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
343
backend/IM_API/Models/IMDbContext.cs
Normal file
343
backend/IM_API/Models/IMDbContext.cs
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
public partial class IMDbContext : DbContext
|
||||||
|
{
|
||||||
|
public IMDbContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMDbContext(DbContextOptions<IMDbContext> options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual DbSet<Admin> Admins { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Conversation> Conversations { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Device> Devices { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<File> Files { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Friend> Friends { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Friendrequest> Friendrequests { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Group> Groups { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Groupinvite> Groupinvites { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Groupmember> Groupmembers { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Grouprequest> Grouprequests { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<LoginLog> LoginLogs { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Message> Messages { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Notification> Notifications { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Permission> Permissions { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Permissionarole> Permissionaroles { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<Role> Roles { get; set; }
|
||||||
|
|
||||||
|
public virtual DbSet<User> Users { get; set; }
|
||||||
|
|
||||||
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
|
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
|
||||||
|
=> optionsBuilder.UseMySql("server=192.168.5.100;port=3306;database=IM;user=root;password=768788Dyw", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.44-mysql"));
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.UseCollation("latin1_swedish_ci")
|
||||||
|
.HasCharSet("latin1");
|
||||||
|
|
||||||
|
modelBuilder.Entity<Admin>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间 ");
|
||||||
|
entity.Property(e => e.Password).HasComment("密码");
|
||||||
|
entity.Property(e => e.RoleId).HasComment("角色");
|
||||||
|
entity.Property(e => e.State).HasComment("状态(0:正常,2:封禁) ");
|
||||||
|
entity.Property(e => e.Updated).HasComment("更新时间 ");
|
||||||
|
entity.Property(e => e.Username).HasComment("用户名");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.Role).WithMany(p => p.Admins)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("admins_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Conversation>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.LastMessageId).HasComment("最后一条消息ID ");
|
||||||
|
entity.Property(e => e.MsgType).HasComment("消息类型(同Messages.MsgType) ");
|
||||||
|
entity.Property(e => e.Targetid).HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) ");
|
||||||
|
entity.Property(e => e.UnreadCount).HasComment("未读消息数 ");
|
||||||
|
entity.Property(e => e.Userid).HasComment("用户");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.LastMessage).WithMany(p => p.Conversations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("conversations_ibfk_2");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("conversations_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Device>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Dtype).HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)");
|
||||||
|
entity.Property(e => e.LastLogin).HasComment("最后一次登录 ");
|
||||||
|
entity.Property(e => e.Userid).HasComment("设备所属用户 ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.User).WithMany(p => p.Devices)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("devices_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<File>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间 ");
|
||||||
|
entity.Property(e => e.Messageld).HasComment("关联消息ID ");
|
||||||
|
entity.Property(e => e.Name).HasComment("文件名 ");
|
||||||
|
entity.Property(e => e.Size).HasComment("文件大小(单位:KB) ");
|
||||||
|
entity.Property(e => e.Type).HasComment("文件类型 ");
|
||||||
|
entity.Property(e => e.Url).HasComment("文件储存URL ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.MessageldNavigation).WithMany(p => p.Files)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("files_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Friend>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("好友关系创建时间");
|
||||||
|
entity.Property(e => e.Friendld).HasComment("用户2ID");
|
||||||
|
entity.Property(e => e.Status).HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)");
|
||||||
|
entity.Property(e => e.Userld).HasComment("用户ID");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.FriendldNavigation).WithMany(p => p.FriendFriendldNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("用户2id");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.UserldNavigation).WithMany(p => p.FriendUserldNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("用户id");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Friendrequest>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("申请时间 ");
|
||||||
|
entity.Property(e => e.Description).HasComment("申请附言 ");
|
||||||
|
entity.Property(e => e.RequestUser).HasComment("申请人 ");
|
||||||
|
entity.Property(e => e.ResponseUser).HasComment("被申请人 ");
|
||||||
|
entity.Property(e => e.State).HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.RequestUserNavigation).WithMany(p => p.FriendrequestRequestUserNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("friendrequest_ibfk_1");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.ResponseUserNavigation).WithMany(p => p.FriendrequestResponseUserNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("friendrequest_ibfk_2");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Group>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.AllMembersBanned).HasComment("全员禁言(0允许发言,2全员禁言)");
|
||||||
|
entity.Property(e => e.Announcement).HasComment("群公告");
|
||||||
|
entity.Property(e => e.Auhority).HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)");
|
||||||
|
entity.Property(e => e.Created).HasComment("群聊创建时间");
|
||||||
|
entity.Property(e => e.GroupMaster).HasComment("群主");
|
||||||
|
entity.Property(e => e.Name).HasComment("群聊名称");
|
||||||
|
entity.Property(e => e.Status).HasComment("群聊状态\r\n(1:正常,2:封禁)");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.GroupMasterNavigation).WithMany(p => p.Groups)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("groups_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Groupinvite>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间");
|
||||||
|
entity.Property(e => e.GroupId).HasComment("群聊编号");
|
||||||
|
entity.Property(e => e.InviteUser).HasComment("邀请用户");
|
||||||
|
entity.Property(e => e.InvitedUser).HasComment("被邀请用户");
|
||||||
|
entity.Property(e => e.State).HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.Group).WithMany(p => p.Groupinvites)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("groupinvite_ibfk_2");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.InviteUserNavigation).WithMany(p => p.GroupinviteInviteUserNavigations).HasConstraintName("groupinvite_ibfk_1");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.InvitedUserNavigation).WithMany(p => p.GroupinviteInvitedUserNavigations).HasConstraintName("groupinvite_ibfk_3");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Groupmember>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created)
|
||||||
|
.HasDefaultValueSql("'1970-01-01 00:00:00'")
|
||||||
|
.HasComment("加入群聊时间");
|
||||||
|
entity.Property(e => e.Groupld).HasComment("群聊编号");
|
||||||
|
entity.Property(e => e.Role).HasComment("成员角色(0:普通成员,1:管理员,2:群主)");
|
||||||
|
entity.Property(e => e.Userld).HasComment("用户编号");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.GroupldNavigation).WithMany(p => p.GroupmemberGroupldNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("groupmember_ibfk_2");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.UserldNavigation).WithMany(p => p.GroupmemberUserldNavigations)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("groupmember_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Grouprequest>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间");
|
||||||
|
entity.Property(e => e.Description).HasComment("入群附言");
|
||||||
|
entity.Property(e => e.GroupId).HasComment("群聊编号\r\n");
|
||||||
|
entity.Property(e => e.State).HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)");
|
||||||
|
entity.Property(e => e.UserId).HasComment("申请人 ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.Group).WithMany(p => p.Grouprequests)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("grouprequest_ibfk_1");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.GroupNavigation).WithMany(p => p.Grouprequests)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("grouprequest_ibfk_2");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<LoginLog>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Dtype).HasComment("设备类型(通Devices/DType) ");
|
||||||
|
entity.Property(e => e.Logined).HasComment("登录时间 ");
|
||||||
|
entity.Property(e => e.State).HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) ");
|
||||||
|
entity.Property(e => e.Userld).HasComment("登录用户 ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.UserldNavigation).WithMany(p => p.LoginLogs)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("login_log_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Message>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.ChatType).HasComment("聊天类型\r\n(0:私聊,1:群聊)");
|
||||||
|
entity.Property(e => e.Content).HasComment("消息内容 ");
|
||||||
|
entity.Property(e => e.Created).HasComment("发送时间 ");
|
||||||
|
entity.Property(e => e.MsgType).HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)");
|
||||||
|
entity.Property(e => e.Recipient).HasComment("接收者(私聊为用户ID,群聊为群聊ID) ");
|
||||||
|
entity.Property(e => e.Sender).HasComment("发送者 ");
|
||||||
|
entity.Property(e => e.State).HasComment("消息状态(0:已发送,1:已撤回) ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.SenderNavigation).WithMany(p => p.Messages)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("messages_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Notification>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Content).HasComment("通知内容");
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间");
|
||||||
|
entity.Property(e => e.Ntype).HasComment("通知类型(0:文本)");
|
||||||
|
entity.Property(e => e.Title).HasComment("通知标题");
|
||||||
|
entity.Property(e => e.Userld).HasComment("接收人(为空为全体通知)");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.UserldNavigation).WithMany(p => p.Notifications)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("notifications_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Permission>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Code).HasComment("权限编码 ");
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间 ");
|
||||||
|
entity.Property(e => e.Name).HasComment("权限名称 ");
|
||||||
|
entity.Property(e => e.Ptype).HasComment("权限类型(0:增,1:删,2:改,3:查) ");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Permissionarole>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Id).ValueGeneratedNever();
|
||||||
|
entity.Property(e => e.Permissionld).HasComment("权限 ");
|
||||||
|
entity.Property(e => e.Roleld).HasComment("角色 ");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.PermissionldNavigation).WithMany(p => p.Permissionaroles)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("permissionarole_ibfk_2");
|
||||||
|
|
||||||
|
entity.HasOne(d => d.RoleldNavigation).WithMany(p => p.Permissionaroles)
|
||||||
|
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||||
|
.HasConstraintName("permissionarole_ibfk_1");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Role>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created).HasComment("创建时间 ");
|
||||||
|
entity.Property(e => e.Description).HasComment("角色描述 ");
|
||||||
|
entity.Property(e => e.Name).HasComment("角色名称 ");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<User>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.Property(e => e.Created)
|
||||||
|
.HasDefaultValueSql("'1970-01-01 00:00:00'")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
entity.Property(e => e.IsDeleted).HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除");
|
||||||
|
entity.Property(e => e.NickName).HasComment("用户昵称");
|
||||||
|
entity.Property(e => e.OlineStatus).HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线");
|
||||||
|
entity.Property(e => e.Password).HasComment("密码");
|
||||||
|
entity.Property(e => e.Status)
|
||||||
|
.HasDefaultValueSql("'1'")
|
||||||
|
.HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)");
|
||||||
|
entity.Property(e => e.Updated).HasComment("修改时间");
|
||||||
|
entity.Property(e => e.Username).HasComment("唯一用户名");
|
||||||
|
});
|
||||||
|
|
||||||
|
OnModelCreatingPartial(modelBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||||
|
}
|
||||||
46
backend/IM_API/Models/LoginLog.cs
Normal file
46
backend/IM_API/Models/LoginLog.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("login_log")]
|
||||||
|
[Index("Userld", Name = "Userld")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class LoginLog
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备类型(通Devices/DType)
|
||||||
|
/// </summary>
|
||||||
|
[Column("DType", TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Dtype { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 登录时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Logined { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 登录用户
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 登录状态(0:登陆成功,1:未验证,2:已被拒绝)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte State { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Userld")]
|
||||||
|
[InverseProperty("LoginLogs")]
|
||||||
|
public virtual User UserldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
72
backend/IM_API/Models/Message.cs
Normal file
72
backend/IM_API/Models/Message.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("messages")]
|
||||||
|
[Index("Sender", Name = "Sender")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Message
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天类型
|
||||||
|
/// (0:私聊,1:群聊)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte ChatType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息类型
|
||||||
|
/// (0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte MsgType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息内容
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string Content { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送者
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Sender { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收者(私聊为用户ID,群聊为群聊ID)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Recipient { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息状态(0:已发送,1:已撤回)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty("LastMessage")]
|
||||||
|
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
|
||||||
|
|
||||||
|
[InverseProperty("MessageldNavigation")]
|
||||||
|
public virtual ICollection<File> Files { get; set; } = new List<File>();
|
||||||
|
|
||||||
|
[ForeignKey("Sender")]
|
||||||
|
[InverseProperty("Messages")]
|
||||||
|
public virtual User SenderNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
52
backend/IM_API/Models/Notification.cs
Normal file
52
backend/IM_API/Models/Notification.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("notifications")]
|
||||||
|
[Index("Userld", Name = "Userld")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Notification
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收人(为空为全体通知)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Userld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知类型(0:文本)
|
||||||
|
/// </summary>
|
||||||
|
[Column("NType", TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Ntype { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知标题
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(40)]
|
||||||
|
public string Title { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知内容
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string Content { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Userld")]
|
||||||
|
[InverseProperty("Notifications")]
|
||||||
|
public virtual User UserldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
44
backend/IM_API/Models/Permission.cs
Normal file
44
backend/IM_API/Models/Permission.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("permissions")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Permission
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限类型(0:增,1:删,2:改,3:查)
|
||||||
|
/// </summary>
|
||||||
|
[Column("PType", TypeName = "int(11)")]
|
||||||
|
public int Ptype { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限名称
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限编码
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty("PermissionldNavigation")]
|
||||||
|
public virtual ICollection<Permissionarole> Permissionaroles { get; set; } = new List<Permissionarole>();
|
||||||
|
}
|
||||||
39
backend/IM_API/Models/Permissionarole.cs
Normal file
39
backend/IM_API/Models/Permissionarole.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("permissionarole")]
|
||||||
|
[Index("Permissionld", Name = "Permissionld")]
|
||||||
|
[Index("Roleld", Name = "Roleld")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Permissionarole
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Roleld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "int(11)")]
|
||||||
|
public int Permissionld { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("Permissionld")]
|
||||||
|
[InverseProperty("Permissionaroles")]
|
||||||
|
public virtual Permission PermissionldNavigation { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey("Roleld")]
|
||||||
|
[InverseProperty("Permissionaroles")]
|
||||||
|
public virtual Role RoleldNavigation { get; set; } = null!;
|
||||||
|
}
|
||||||
41
backend/IM_API/Models/Role.cs
Normal file
41
backend/IM_API/Models/Role.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("roles")]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class Role
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名称
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(20)]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色描述
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "text")]
|
||||||
|
public string Description { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty("Role")]
|
||||||
|
public virtual ICollection<Admin> Admins { get; set; } = new List<Admin>();
|
||||||
|
|
||||||
|
[InverseProperty("RoleldNavigation")]
|
||||||
|
public virtual ICollection<Permissionarole> Permissionaroles { get; set; } = new List<Permissionarole>();
|
||||||
|
}
|
||||||
117
backend/IM_API/Models/User.cs
Normal file
117
backend/IM_API/Models/User.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace IM_API.Models;
|
||||||
|
|
||||||
|
[Table("users")]
|
||||||
|
[Index("Id", Name = "ID")]
|
||||||
|
[Index("Username", Name = "Username", IsUnique = true)]
|
||||||
|
[MySqlCharSet("utf8mb4")]
|
||||||
|
[MySqlCollation("utf8mb4_general_ci")]
|
||||||
|
public partial class User
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("ID", TypeName = "int(11)")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一用户名
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Username { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 密码
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string Password { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户昵称
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(50)]
|
||||||
|
public string NickName { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户在线状态
|
||||||
|
/// 0(默认):不在线
|
||||||
|
/// 1:在线
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte OlineStatus { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改时间
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "datetime")]
|
||||||
|
public DateTime? Updated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 账户状态
|
||||||
|
/// (0:未激活,1:正常,2:封禁)
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 软删除标识
|
||||||
|
/// 0:账号正常
|
||||||
|
/// 1:账号已删除
|
||||||
|
/// </summary>
|
||||||
|
[Column(TypeName = "tinyint(4)")]
|
||||||
|
public sbyte IsDeleted { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty("User")]
|
||||||
|
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
|
||||||
|
|
||||||
|
[InverseProperty("User")]
|
||||||
|
public virtual ICollection<Device> Devices { get; set; } = new List<Device>();
|
||||||
|
|
||||||
|
[InverseProperty("FriendldNavigation")]
|
||||||
|
public virtual ICollection<Friend> FriendFriendldNavigations { get; set; } = new List<Friend>();
|
||||||
|
|
||||||
|
[InverseProperty("UserldNavigation")]
|
||||||
|
public virtual ICollection<Friend> FriendUserldNavigations { get; set; } = new List<Friend>();
|
||||||
|
|
||||||
|
[InverseProperty("RequestUserNavigation")]
|
||||||
|
public virtual ICollection<Friendrequest> FriendrequestRequestUserNavigations { get; set; } = new List<Friendrequest>();
|
||||||
|
|
||||||
|
[InverseProperty("ResponseUserNavigation")]
|
||||||
|
public virtual ICollection<Friendrequest> FriendrequestResponseUserNavigations { get; set; } = new List<Friendrequest>();
|
||||||
|
|
||||||
|
[InverseProperty("InviteUserNavigation")]
|
||||||
|
public virtual ICollection<Groupinvite> GroupinviteInviteUserNavigations { get; set; } = new List<Groupinvite>();
|
||||||
|
|
||||||
|
[InverseProperty("InvitedUserNavigation")]
|
||||||
|
public virtual ICollection<Groupinvite> GroupinviteInvitedUserNavigations { get; set; } = new List<Groupinvite>();
|
||||||
|
|
||||||
|
[InverseProperty("GroupldNavigation")]
|
||||||
|
public virtual ICollection<Groupmember> GroupmemberGroupldNavigations { get; set; } = new List<Groupmember>();
|
||||||
|
|
||||||
|
[InverseProperty("UserldNavigation")]
|
||||||
|
public virtual ICollection<Groupmember> GroupmemberUserldNavigations { get; set; } = new List<Groupmember>();
|
||||||
|
|
||||||
|
[InverseProperty("GroupNavigation")]
|
||||||
|
public virtual ICollection<Grouprequest> Grouprequests { get; set; } = new List<Grouprequest>();
|
||||||
|
|
||||||
|
[InverseProperty("GroupMasterNavigation")]
|
||||||
|
public virtual ICollection<Group> Groups { get; set; } = new List<Group>();
|
||||||
|
|
||||||
|
[InverseProperty("UserldNavigation")]
|
||||||
|
public virtual ICollection<LoginLog> LoginLogs { get; set; } = new List<LoginLog>();
|
||||||
|
|
||||||
|
[InverseProperty("SenderNavigation")]
|
||||||
|
public virtual ICollection<Message> Messages { get; set; } = new List<Message>();
|
||||||
|
|
||||||
|
[InverseProperty("UserldNavigation")]
|
||||||
|
public virtual ICollection<Notification> Notifications { get; set; } = new List<Notification>();
|
||||||
|
}
|
||||||
36
backend/IM_API/Program.cs
Normal file
36
backend/IM_API/Program.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
namespace IM_API
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
backend/IM_API/Properties/launchSettings.json
Normal file
52
backend/IM_API/Properties/launchSettings.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5202"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7157;http://localhost:5202"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTPS_PORTS": "8081",
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:39786",
|
||||||
|
"sslPort": 44308
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
backend/IM_API/WeatherForecast.cs
Normal file
13
backend/IM_API/WeatherForecast.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace IM_API
|
||||||
|
{
|
||||||
|
public class WeatherForecast
|
||||||
|
{
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
backend/IM_API/appsettings.Development.json
Normal file
8
backend/IM_API/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
backend/IM_API/appsettings.json
Normal file
9
backend/IM_API/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@ -1,141 +1,141 @@
|
|||||||
# IM 系统消息存储与推送策略文档
|
# IM 系统消息存储与推送策略文档
|
||||||
|
|
||||||
## 1. 概述
|
## 1. 概述
|
||||||
|
|
||||||
本策略文档定义了 **消息在系统中的存储、读取和推送流程**,目标是:
|
本策略文档定义了 **消息在系统中的存储、读取和推送流程**,目标是:
|
||||||
|
|
||||||
- 保证 **消息实时性**
|
- 保证 **消息实时性**
|
||||||
- 支持 **离线消息存储与同步**
|
- 支持 **离线消息存储与同步**
|
||||||
- 支持 **多端登录同步**
|
- 支持 **多端登录同步**
|
||||||
- 支持 **单聊、群聊及系统消息**
|
- 支持 **单聊、群聊及系统消息**
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 2. 消息存储策略
|
## 2. 消息存储策略
|
||||||
|
|
||||||
### 2.1 消息表设计
|
### 2.1 消息表设计
|
||||||
|
|
||||||
表结构参考前期设计:
|
表结构参考前期设计:
|
||||||
|
|
||||||
| 表名 | 作用 |
|
| 表名 | 作用 |
|
||||||
| ------------ | ------------------------------------------------------------ |
|
| ------------ | ------------------------------------------------------------ |
|
||||||
| Messages | 存储所有聊天消息(单聊/群聊) |
|
| Messages | 存储所有聊天消息(单聊/群聊) |
|
||||||
| Conversation | 缓存用户最近会话信息(last_message_id, target_id, unread_count) |
|
| Conversation | 缓存用户最近会话信息(last_message_id, target_id, unread_count) |
|
||||||
| Files | 附件 / 图片 / 语音存储URL |
|
| Files | 附件 / 图片 / 语音存储URL |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 2.2 消息存储规则
|
### 2.2 消息存储规则
|
||||||
|
|
||||||
1. **单聊消息**
|
1. **单聊消息**
|
||||||
- 写入 `Messages` 表
|
- 写入 `Messages` 表
|
||||||
- 更新发送者和接收者 `Conversation` 表
|
- 更新发送者和接收者 `Conversation` 表
|
||||||
- 更新 `UnreadCount`
|
- 更新 `UnreadCount`
|
||||||
2. **群聊消息**
|
2. **群聊消息**
|
||||||
- 写入 `Messages` 表
|
- 写入 `Messages` 表
|
||||||
- 更新群成员对应的 `Conversation` 表(except 发送者)
|
- 更新群成员对应的 `Conversation` 表(except 发送者)
|
||||||
- 更新每个成员的 `UnreadCount`
|
- 更新每个成员的 `UnreadCount`
|
||||||
3. **文件消息**
|
3. **文件消息**
|
||||||
- 文件存储到对象存储(OSS/S3/MinIO)
|
- 文件存储到对象存储(OSS/S3/MinIO)
|
||||||
- `Messages.Content` 存文件 URL + metadata
|
- `Messages.Content` 存文件 URL + metadata
|
||||||
4. **消息撤回**
|
4. **消息撤回**
|
||||||
- 消息允许撤回时,修改 `message.status = 1
|
- 消息允许撤回时,修改 `message.status = 1
|
||||||
- 更新 `Conversation.LastMessageId`(如撤回的是最后一条消息)
|
- 更新 `Conversation.LastMessageId`(如撤回的是最后一条消息)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 3. 消息推送策略
|
## 3. 消息推送策略
|
||||||
|
|
||||||
### 3.1 推送原则
|
### 3.1 推送原则
|
||||||
|
|
||||||
- **实时性**:在线用户立即通过 WebSocket 推送
|
- **实时性**:在线用户立即通过 WebSocket 推送
|
||||||
- **可靠性**:离线用户存储消息,登录时同步
|
- **可靠性**:离线用户存储消息,登录时同步
|
||||||
- **顺序保证**:消息按 `timestamp` 或 `messageId` 顺序发送
|
- **顺序保证**:消息按 `timestamp` 或 `messageId` 顺序发送
|
||||||
- **幂等性**:客户端可根据 `messageId` 去重
|
- **幂等性**:客户端可根据 `messageId` 去重
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.2 单聊推送流程
|
### 3.2 单聊推送流程
|
||||||
|
|
||||||
1. 发送者通过 WebSocket 或 HTTP API 发送消息
|
1. 发送者通过 WebSocket 或 HTTP API 发送消息
|
||||||
2. 服务端写入 `Messages` 表
|
2. 服务端写入 `Messages` 表
|
||||||
3. 查询接收者是否在线
|
3. 查询接收者是否在线
|
||||||
- **在线**:通过 WebSocket 推送
|
- **在线**:通过 WebSocket 推送
|
||||||
- **离线**:存储到 Redis 或 `Conversation.UnreadCount`
|
- **离线**:存储到 Redis 或 `Conversation.UnreadCount`
|
||||||
4. 接收者收到消息后发送 `MESSAGE_ACK`
|
4. 接收者收到消息后发送 `MESSAGE_ACK`
|
||||||
5. //暂不要求:更新消息状态(已送达 / 已读)
|
5. //暂不要求:更新消息状态(已送达 / 已读)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.3 群聊推送流程
|
### 3.3 群聊推送流程
|
||||||
|
|
||||||
1. 发送者发送群消息
|
1. 发送者发送群消息
|
||||||
2. 服务端写入 `Message`s 表
|
2. 服务端写入 `Message`s 表
|
||||||
3. 查询群成员列表(`GroupMember` 表)
|
3. 查询群成员列表(`GroupMember` 表)
|
||||||
4. 遍历成员:
|
4. 遍历成员:
|
||||||
- **在线成员**:WebSocket 推送
|
- **在线成员**:WebSocket 推送
|
||||||
- **离线成员**:增加 `UnreadCount`,保存在 Redis/数据库
|
- **离线成员**:增加 `UnreadCount`,保存在 Redis/数据库
|
||||||
5. //暂不要求:接收者回 ACK 后更新 `message_receipt`(已读)
|
5. //暂不要求:接收者回 ACK 后更新 `message_receipt`(已读)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.4 离线消息处理
|
### 3.4 离线消息处理
|
||||||
|
|
||||||
- 离线消息存储位置:
|
- 离线消息存储位置:
|
||||||
1. 数据库 `Messages` 表(长期保存)
|
1. 数据库 `Messages` 表(长期保存)
|
||||||
2. Redis 缓存(短期加速推送)
|
2. Redis 缓存(短期加速推送)
|
||||||
- 客户端上线时:
|
- 客户端上线时:
|
||||||
1. 请求 `/syncMessages` 接口
|
1. 请求 `/syncMessages` 接口
|
||||||
2. 返回未读消息 + 未读计数
|
2. 返回未读消息 + 未读计数
|
||||||
- 消息同步完成后清除缓存或更新状态
|
- 消息同步完成后清除缓存或更新状态
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.5 多端同步策略
|
### 3.5 多端同步策略
|
||||||
|
|
||||||
- 每个设备维护独立的 `deviceId`
|
- 每个设备维护独立的 `deviceId`
|
||||||
- WebSocket 推送时:
|
- WebSocket 推送时:
|
||||||
- 排除发送设备
|
- 排除发送设备
|
||||||
- 推送给同账号其他设备
|
- 推送给同账号其他设备
|
||||||
- //暂不要求:消息回执:
|
- //暂不要求:消息回执:
|
||||||
- 每端发送 ACK
|
- 每端发送 ACK
|
||||||
- 服务端更新 `Voncers` 和 `message_receipt`
|
- 服务端更新 `Voncers` 和 `message_receipt`
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 4. 消息可靠性保障
|
## 4. 消息可靠性保障
|
||||||
|
|
||||||
| 场景 | 解决方案 |
|
| 场景 | 解决方案 |
|
||||||
| ------------------ | ---------------------------------- |
|
| ------------------ | ---------------------------------- |
|
||||||
| 消息丢失 | 发送端生成 `requestId`,服务端去重 |
|
| 消息丢失 | 发送端生成 `requestId`,服务端去重 |
|
||||||
| 消息顺序错乱 | 按 `messageId` 或 `timestamp` 排序 |
|
| 消息顺序错乱 | 按 `messageId` 或 `timestamp` 排序 |
|
||||||
| WebSocket 异常断开 | 客户端重连后同步离线消息 |
|
| WebSocket 异常断开 | 客户端重连后同步离线消息 |
|
||||||
| 群聊大消息量 | 异步推送 + 批量 ACK |
|
| 群聊大消息量 | 异步推送 + 批量 ACK |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 5. //暂不要求:高性能优化策略
|
## 5. //暂不要求:高性能优化策略
|
||||||
|
|
||||||
1. **消息表索引**:`(chat_type, to_id, created_at)`
|
1. **消息表索引**:`(chat_type, to_id, created_at)`
|
||||||
2. **会话表缓存**:`conversation` 表避免全表查询
|
2. **会话表缓存**:`conversation` 表避免全表查询
|
||||||
3. **Redis 缓存**:用户在线状态、未读消息数
|
3. **Redis 缓存**:用户在线状态、未读消息数
|
||||||
4. **分表/分库**:按月或按用户分表
|
4. **分表/分库**:按月或按用户分表
|
||||||
5. **异步推送队列**:消息通过 MQ(Kafka/RabbitMQ)推送,保证高并发
|
5. **异步推送队列**:消息通过 MQ(Kafka/RabbitMQ)推送,保证高并发
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 6. 消息撤回与删除策略
|
## 6. 消息撤回与删除策略
|
||||||
|
|
||||||
1. **撤回条件**:超时限制( 2 分钟内可撤回)
|
1. **撤回条件**:超时限制( 2 分钟内可撤回)
|
||||||
2. **撤回操作**:
|
2. **撤回操作**:
|
||||||
- 更新 `message.status = 1`
|
- 更新 `message.status = 1`
|
||||||
- 更新 `Conversation.LastMessageId`
|
- 更新 `Conversation.LastMessageId`
|
||||||
- 推送撤回事件到在线用户
|
- 推送撤回事件到在线用户
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 7. 系统消息与通知策略
|
## 7. 系统消息与通知策略
|
||||||
|
|
||||||
- 系统消息(好友申请、群邀请、公告)走 **同样的消息推送流程**
|
- 系统消息(好友申请、群邀请、公告)走 **同样的消息推送流程**
|
||||||
- 保留在 `Notification` 表
|
- 保留在 `Notification` 表
|
||||||
- 支持离线同步
|
- 支持离线同步
|
||||||
@ -1,115 +1,115 @@
|
|||||||
# IM 系统鉴权与 Token 安全规范文档
|
# IM 系统鉴权与 Token 安全规范文档
|
||||||
|
|
||||||
## 1. 概述
|
## 1. 概述
|
||||||
|
|
||||||
本规范用于确保系统用户身份验证、消息安全和多端同步安全。
|
本规范用于确保系统用户身份验证、消息安全和多端同步安全。
|
||||||
鉴权体系采用 **Token(JWT 或自定义) + HTTPS/WebSocket** 方式。
|
鉴权体系采用 **Token(JWT 或自定义) + HTTPS/WebSocket** 方式。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 2. 鉴权方式选择
|
## 2. 鉴权方式选择
|
||||||
|
|
||||||
| 方法 |
|
| 方法 |
|
||||||
| -------------------------------------- |
|
| -------------------------------------- |
|
||||||
| JWT(JSON Web Token)+ Redis黑名单机制 |
|
| JWT(JSON Web Token)+ Redis黑名单机制 |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 3. Token 生成规则
|
## 3. Token 生成规则
|
||||||
|
|
||||||
### 3.1 Token 内容结构(JWT 示例)
|
### 3.1 Token 内容结构(JWT 示例)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"userId": 1001, // 用户ID
|
"userId": 1001, // 用户ID
|
||||||
"iat": 1700000000, // 签发时间(Unix时间戳)
|
"iat": 1700000000, // 签发时间(Unix时间戳)
|
||||||
"exp": 1700003600, // 过期时间
|
"exp": 1700003600, // 过期时间
|
||||||
"deviceId": "uuid-xxxx", // 设备ID,用于多端区分
|
"deviceId": "uuid-xxxx", // 设备ID,用于多端区分
|
||||||
"role": "user" // 角色
|
"role": "user" // 角色
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **签名算法**:HMAC-SHA256 或 RSA
|
- **签名算法**:HMAC-SHA256 或 RSA
|
||||||
- **签名秘钥**:服务端统一管理,不暴露给客户端
|
- **签名秘钥**:服务端统一管理,不暴露给客户端
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.2 Token 生成流程
|
### 3.2 Token 生成流程
|
||||||
|
|
||||||
1. 用户登录(用户名/密码)
|
1. 用户登录(用户名/密码)
|
||||||
2. 验证用户名与密码正确
|
2. 验证用户名与密码正确
|
||||||
3. 生成 Token,写入 Redis(可选)
|
3. 生成 Token,写入 Redis(可选)
|
||||||
4. 返回 Token 给客户端
|
4. 返回 Token 给客户端
|
||||||
|
|
||||||
**响应示例**:
|
**响应示例**:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "登录成功",
|
"message": "登录成功",
|
||||||
"data": {
|
"data": {
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 4. Token 使用
|
## 4. Token 使用
|
||||||
|
|
||||||
### 4.1 HTTP 接口鉴权
|
### 4.1 HTTP 接口鉴权
|
||||||
|
|
||||||
- 客户端请求带上 Header:
|
- 客户端请求带上 Header:
|
||||||
|
|
||||||
```
|
```
|
||||||
Authorization: Bearer <token>
|
Authorization: Bearer <token>
|
||||||
```
|
```
|
||||||
|
|
||||||
- 后端解析 Token:
|
- 后端解析 Token:
|
||||||
1. 校验签名
|
1. 校验签名
|
||||||
2. 校验 exp 是否过期
|
2. 校验 exp 是否过期
|
||||||
3. 校验 Redis 黑名单(可选)
|
3. 校验 Redis 黑名单(可选)
|
||||||
- 不通过返回 401 / code 1006
|
- 不通过返回 401 / code 1006
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 4.2 WebSocket 鉴权
|
### 4.2 WebSocket 鉴权
|
||||||
|
|
||||||
- 建立连接时通过 Query 或 Header 传 Token:
|
- 建立连接时通过 Query 或 Header 传 Token:
|
||||||
|
|
||||||
```
|
```
|
||||||
ws://example.com/ws?token=xxxx&deviceId=uuid-001
|
ws://example.com/ws?token=xxxx&deviceId=uuid-001
|
||||||
```
|
```
|
||||||
|
|
||||||
- 握手阶段:
|
- 握手阶段:
|
||||||
1. 服务器验证 Token
|
1. 服务器验证 Token
|
||||||
2. 成功返回 AUTH_SUCCESS
|
2. 成功返回 AUTH_SUCCESS
|
||||||
3. 失败返回 AUTH_FAIL 并关闭连接
|
3. 失败返回 AUTH_FAIL 并关闭连接
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 5. Token 过期策略
|
## 5. Token 过期策略
|
||||||
|
|
||||||
| 类型 | 建议值 | 说明 |
|
| 类型 | 建议值 | 说明 |
|
||||||
| ------------------ | ---------------------- | ---------------------------------------- |
|
| ------------------ | ---------------------- | ---------------------------------------- |
|
||||||
| 短期 Token | 30 分钟 ~ 1 小时 | 防止长时间泄露 |
|
| 短期 Token | 30 分钟 ~ 1 小时 | 防止长时间泄露 |
|
||||||
| 长期 Refresh Token | 7 ~ 30 天 | 用于获取新 Token,安全性高 |
|
| 长期 Refresh Token | 7 ~ 30 天 | 用于获取新 Token,安全性高 |
|
||||||
| WebSocket 长连接 | Token 与短期有效期一致 | 客户端定期刷新 Token(心跳或重连时验证) |
|
| WebSocket 长连接 | Token 与短期有效期一致 | 客户端定期刷新 Token(心跳或重连时验证) |
|
||||||
|
|
||||||
### 5.1 Token 刷新流程
|
### 5.1 Token 刷新流程
|
||||||
|
|
||||||
1. 客户端 Token 快过期时,调用刷新接口
|
1. 客户端 Token 快过期时,调用刷新接口
|
||||||
2. 服务端验证 Refresh Token
|
2. 服务端验证 Refresh Token
|
||||||
3. 返回新 Token,更新 Redis / 黑名单
|
3. 返回新 Token,更新 Redis / 黑名单
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 6. 多端登录处理
|
## 6. 多端登录处理
|
||||||
|
|
||||||
- **每个设备对应一个 deviceId**
|
- **每个设备对应一个 deviceId**
|
||||||
- Token 中绑定 deviceId
|
- Token 中绑定 deviceId
|
||||||
- 多端策略:
|
- 多端策略:
|
||||||
1. **允许多端同时登录**:每端单独维护 Token
|
1. **允许多端同时登录**:每端单独维护 Token
|
||||||
2. **限制单端登录**:新登录覆盖旧设备 Token
|
2. **限制单端登录**:新登录覆盖旧设备 Token
|
||||||
3. **设备列表管理**:可查看在线设备并强制下线
|
3. **设备列表管理**:可查看在线设备并强制下线
|
||||||
|
|
||||||
@ -1,401 +1,401 @@
|
|||||||
# 接口文档(REST API) — 聊天系统
|
# 接口文档(REST API) — 聊天系统
|
||||||
|
|
||||||
> 统一响应格式(JSON)
|
> 统一响应格式(JSON)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "请求成功",
|
"message": "请求成功",
|
||||||
"data": {}
|
"data": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `code`:参照响应 Code 规范(0 成功,非 0 为错误)。
|
- `code`:参照响应 Code 规范(0 成功,非 0 为错误)。
|
||||||
- `message`:提示文本。
|
- `message`:提示文本。
|
||||||
- `data`:返回主体。
|
- `data`:返回主体。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 通用约定
|
## 通用约定
|
||||||
|
|
||||||
- 所有需要登录的接口必须在 Header 中带 `Authorization: Bearer <token>`。
|
- 所有需要登录的接口必须在 Header 中带 `Authorization: Bearer <token>`。
|
||||||
- 时间戳统一使用 Unix 秒。
|
- 时间戳统一使用 Unix 秒。
|
||||||
- 分页统一采用 `page`(第几页,从1开始)与 `limit`(每页大小)或基于时间/消息ID的 `afterMessageId` / `beforeTimestamp`。
|
- 分页统一采用 `page`(第几页,从1开始)与 `limit`(每页大小)或基于时间/消息ID的 `afterMessageId` / `beforeTimestamp`。
|
||||||
- 幂等性:对于可能重试的写操作,请求体中带 `requestId`。
|
- 幂等性:对于可能重试的写操作,请求体中带 `requestId`。
|
||||||
- Content-Type: `application/json`(文件上传除外)。
|
- Content-Type: `application/json`(文件上传除外)。
|
||||||
- 返回错误码请参考前面的响应 Code 文档。
|
- 返回错误码请参考前面的响应 Code 文档。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
1. 鉴权(Auth)
|
1. 鉴权(Auth)
|
||||||
2. 用户(User)
|
2. 用户(User)
|
||||||
3. 好友(Friend)
|
3. 好友(Friend)
|
||||||
4. 会话(Conversation)
|
4. 会话(Conversation)
|
||||||
5. 消息(Message)
|
5. 消息(Message)
|
||||||
6. 文件/上传(File)
|
6. 文件/上传(File)
|
||||||
7. 群组(Group)
|
7. 群组(Group)
|
||||||
8. 通知(Notification)
|
8. 通知(Notification)
|
||||||
9. 管理后台(Admin)
|
9. 管理后台(Admin)
|
||||||
10. 常见错误与限流
|
10. 常见错误与限流
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 1. 鉴权(Auth)
|
## 1. 鉴权(Auth)
|
||||||
|
|
||||||
### 1.1 注册
|
### 1.1 注册
|
||||||
|
|
||||||
- URL: `POST /api/v1/auth/register`
|
- URL: `POST /api/v1/auth/register`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"username": "alice",
|
"username": "alice",
|
||||||
"password": "password123",
|
"password": "password123",
|
||||||
"phone": "13800000000",
|
"phone": "13800000000",
|
||||||
"email":"admin@admin.com",
|
"email":"admin@admin.com",
|
||||||
"nickname":"测试用户",
|
"nickname":"测试用户",
|
||||||
"avatar": "https://cdn.example.com/avatar/1001.png",
|
"avatar": "https://cdn.example.com/avatar/1001.png",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 成功响应:
|
- 成功响应:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "注册成功",
|
"message": "注册成功",
|
||||||
"data": { "userId": 1001 }
|
"data": { "userId": 1001 }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.2 登录(返回 Token)
|
### 1.2 登录(返回 Token)
|
||||||
|
|
||||||
- URL: `POST /api/v1/auth/login`
|
- URL: `POST /api/v1/auth/login`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"username": "alice",
|
"username": "alice",
|
||||||
"password": "password123",
|
"password": "password123",
|
||||||
"deviceId": "uuid-device-001"
|
"deviceId": "uuid-device-001"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 成功响应:
|
- 成功响应:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "登录成功",
|
"message": "登录成功",
|
||||||
"data": {
|
"data": {
|
||||||
"token": "eyJ....",
|
"token": "eyJ....",
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
"refreshToken": "rft-xxxx"
|
"refreshToken": "rft-xxxx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.3 刷新 Token
|
### 1.3 刷新 Token
|
||||||
|
|
||||||
- URL: `POST /api/v1/auth/refresh`
|
- URL: `POST /api/v1/auth/refresh`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "refreshToken": "rft-xxxx", "deviceId": "uuid-device-001" }
|
{ "refreshToken": "rft-xxxx", "deviceId": "uuid-device-001" }
|
||||||
```
|
```
|
||||||
|
|
||||||
- 响应同登录(返回新 token)。
|
- 响应同登录(返回新 token)。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 2. 用户(User)
|
## 2. 用户(User)
|
||||||
|
|
||||||
### 2.1 获取当前用户信息
|
### 2.1 获取当前用户信息
|
||||||
|
|
||||||
- URL: `GET /api/v1/user/me`
|
- URL: `GET /api/v1/user/me`
|
||||||
- Header: `Authorization: Bearer <token>`
|
- Header: `Authorization: Bearer <token>`
|
||||||
- 响应 data 示例:
|
- 响应 data 示例:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"id": 1001,
|
"id": 1001,
|
||||||
"username": "alice",
|
"username": "alice",
|
||||||
"nickname": "Alice",
|
"nickname": "Alice",
|
||||||
"avatar": "https://cdn.example.com/avatar/1001.png",
|
"avatar": "https://cdn.example.com/avatar/1001.png",
|
||||||
"status": 1
|
"status": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 修改用户资料
|
### 2.2 修改用户资料
|
||||||
|
|
||||||
- URL: `PUT /api/v1/user/profile`
|
- URL: `PUT /api/v1/user/profile`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"nickname": "小艾",
|
"nickname": "小艾",
|
||||||
"olinestatus": "0",
|
"olinestatus": "0",
|
||||||
"avatar": "https://..."
|
"avatar": "https://..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.3 根据 id 查询用户(用于搜索/加好友)
|
### 2.3 根据 id 查询用户(用于搜索/加好友)
|
||||||
|
|
||||||
- URL: `GET /api/v1/user/{userId}`
|
- URL: `GET /api/v1/user/{userId}`
|
||||||
- 响应含基本公开信息(不含敏感字段)。
|
- 响应含基本公开信息(不含敏感字段)。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 3. 好友(Friend)
|
## 3. 好友(Friend)
|
||||||
|
|
||||||
### 3.1 发送好友申请
|
### 3.1 发送好友申请
|
||||||
|
|
||||||
- URL: `POST /api/v1/friend/request`
|
- URL: `POST /api/v1/friend/request`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"toUserId": 1002,
|
"toUserId": 1002,
|
||||||
"Description": "我们在项目中认识,申请加好友",
|
"Description": "我们在项目中认识,申请加好友",
|
||||||
"requestId": "uuid-req-001" // 幂等字段
|
"requestId": "uuid-req-001" // 幂等字段
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 成功返回 `requestId` 或新记录 id。
|
- 成功返回 `requestId` 或新记录 id。
|
||||||
|
|
||||||
### 3.2 列出好友申请(收/发)
|
### 3.2 列出好友申请(收/发)
|
||||||
|
|
||||||
- URL: `GET /api/v1/friend/requests?type=received|sent&page=1&limit=20`
|
- URL: `GET /api/v1/friend/requests?type=received|sent&page=1&limit=20`
|
||||||
|
|
||||||
### 3.3 处理好友申请(同意/拒绝)
|
### 3.3 处理好友申请(同意/拒绝)
|
||||||
|
|
||||||
- URL: `POST /api/v1/friend/request/{requestId}/handle`
|
- URL: `POST /api/v1/friend/request/{requestId}/handle`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "action": "accept" } // accept | reject
|
{ "action": "accept" } // accept | reject
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.4 获取好友列表
|
### 3.4 获取好友列表
|
||||||
|
|
||||||
- URL: `GET /api/v1/friend/list?page=1&limit=50`
|
- URL: `GET /api/v1/friend/list?page=1&limit=50`
|
||||||
- 返回:好友数组(id, nickname, avatar, remark)
|
- 返回:好友数组(id, nickname, avatar, remark)
|
||||||
|
|
||||||
### 3.5 删除好友 / 拉黑
|
### 3.5 删除好友 / 拉黑
|
||||||
|
|
||||||
- URL: `DELETE /api/v1/friend/{friendId}`
|
- URL: `DELETE /api/v1/friend/{friendId}`
|
||||||
- URL: `POST /api/v1/friend/{friendId}/block`
|
- URL: `POST /api/v1/friend/{friendId}/block`
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 4. 会话(Conversation)
|
## 4. 会话(Conversation)
|
||||||
|
|
||||||
### 4.1 获取会话列表(聊天列表)
|
### 4.1 获取会话列表(聊天列表)
|
||||||
|
|
||||||
- URL: `GET /api/v1/conversations?page=1&limit=50`
|
- URL: `GET /api/v1/conversations?page=1&limit=50`
|
||||||
- 返回每条会话示例:
|
- 返回每条会话示例:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"targetId": 1002,
|
"targetId": 1002,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"lastMessage": { "messageId": 50001, "contentType":"text", "content":"你好", "timestamp": 1700000000 },
|
"lastMessage": { "messageId": 50001, "contentType":"text", "content":"你好", "timestamp": 1700000000 },
|
||||||
"unreadCount": 3,
|
"unreadCount": 3,
|
||||||
"updatedAt": 1700000000
|
"updatedAt": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 删除会话(清空会话/历史)
|
### 4.2 删除会话(清空会话/历史)
|
||||||
|
|
||||||
- URL: `DELETE /api/v1/conversation/{chatType}/{targetId}`
|
- URL: `DELETE /api/v1/conversation/{chatType}/{targetId}`
|
||||||
- 注:chatType 为 `single` 或 `group`。删除会影响客户端显示/未读计数,历史消息视策略保留或软删除。
|
- 注:chatType 为 `single` 或 `group`。删除会影响客户端显示/未读计数,历史消息视策略保留或软删除。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 5. 消息(Message)
|
## 5. 消息(Message)
|
||||||
|
|
||||||
> 说明:即时消息优先通过 WebSocket 发送/接收;REST 接口用于历史消息读取、离线发送(备用)、ACK、撤回等。
|
> 说明:即时消息优先通过 WebSocket 发送/接收;REST 接口用于历史消息读取、离线发送(备用)、ACK、撤回等。
|
||||||
|
|
||||||
### 5.1 发送消息(HTTP 版备用)
|
### 5.1 发送消息(HTTP 版备用)
|
||||||
|
|
||||||
- URL: `POST /api/v1/message/send`
|
- URL: `POST /api/v1/message/send`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"requestId": "uuid-msg-001",
|
"requestId": "uuid-msg-001",
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 1002,
|
"to": 1002,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"contentType": "text",
|
"contentType": "text",
|
||||||
"content": "你好",
|
"content": "你好",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 响应:
|
- 响应:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "code": 0, "data": { "messageId": 50001, "status": "sent" } }
|
{ "code": 0, "data": { "messageId": 50001, "status": "sent" } }
|
||||||
```
|
```
|
||||||
|
|
||||||
- 注意:若用户在线,后端可同时通过 WebSocket 推送到接收端。
|
- 注意:若用户在线,后端可同时通过 WebSocket 推送到接收端。
|
||||||
|
|
||||||
### 5.2 拉取历史消息(分页或基于消息ID)
|
### 5.2 拉取历史消息(分页或基于消息ID)
|
||||||
|
|
||||||
- URL: `GET /api/v1/messages/history?chatType=single&targetId=1002&beforeMessageId=50000&limit=50`
|
- URL: `GET /api/v1/messages/history?chatType=single&targetId=1002&beforeMessageId=50000&limit=50`
|
||||||
- 返回消息数组(按时间倒序或正序,双方约定)。
|
- 返回消息数组(按时间倒序或正序,双方约定)。
|
||||||
|
|
||||||
### 5.3 同步未读/离线消息(登录/重连时)
|
### 5.3 同步未读/离线消息(登录/重连时)
|
||||||
|
|
||||||
- URL: `GET /api/v1/messages/sync?since=1700000000` 或 `afterMessageId=xxxxx`
|
- URL: `GET /api/v1/messages/sync?since=1700000000` 或 `afterMessageId=xxxxx`
|
||||||
- 返回:所有未读/未同步消息(或给定时间段内消息)。
|
- 返回:所有未读/未同步消息(或给定时间段内消息)。
|
||||||
|
|
||||||
### 5.4 消息已读/送达回执(HTTP)
|
### 5.4 消息已读/送达回执(HTTP)
|
||||||
|
|
||||||
- URL: `POST /api/v1/message/ack`
|
- URL: `POST /api/v1/message/ack`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"messageId": 50001,
|
"messageId": 50001,
|
||||||
"status": "read", // delivered | read
|
"status": "read", // delivered | read
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"from": 1002, // ack 发送者(接收方)
|
"from": 1002, // ack 发送者(接收方)
|
||||||
"to": 1001
|
"to": 1001
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.5 撤回消息
|
### 5.5 撤回消息
|
||||||
|
|
||||||
- URL: `POST /api/v1/message/{messageId}/recall`
|
- URL: `POST /api/v1/message/{messageId}/recall`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "requestId": "uuid-recall-001" }
|
{ "requestId": "uuid-recall-001" }
|
||||||
```
|
```
|
||||||
|
|
||||||
- 响应成功后,服务器会向相关在线端推送 `MESSAGE_RECALL` 事件,更新 message.status。
|
- 响应成功后,服务器会向相关在线端推送 `MESSAGE_RECALL` 事件,更新 message.status。
|
||||||
|
|
||||||
### 5.6 删除单条消息(客户端侧删除/服务端删除)
|
### 5.6 删除单条消息(客户端侧删除/服务端删除)
|
||||||
|
|
||||||
- URL: `DELETE /api/v1/message/{messageId}`
|
- URL: `DELETE /api/v1/message/{messageId}`
|
||||||
- 注意区分“仅自己删除”与“全局删除(撤回)”。
|
- 注意区分“仅自己删除”与“全局删除(撤回)”。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 6. 文件/上传(File)
|
## 6. 文件/上传(File)
|
||||||
|
|
||||||
### 6.1 上传文件(图片/语音/文档)
|
### 6.1 上传文件(图片/语音/文档)
|
||||||
|
|
||||||
- URL: `POST /api/v1/file/upload`
|
- URL: `POST /api/v1/file/upload`
|
||||||
- Content-Type: `multipart/form-data`
|
- Content-Type: `multipart/form-data`
|
||||||
- 字段:`file`,可选 `type`、`attachedMessageRequestId`
|
- 字段:`file`,可选 `type`、`attachedMessageRequestId`
|
||||||
- 成功响应:
|
- 成功响应:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"data": {
|
"data": {
|
||||||
"fileId": 9001,
|
"fileId": 9001,
|
||||||
"fileUrl": "https://oss.example.com/xxx.jpg",
|
"fileUrl": "https://oss.example.com/xxx.jpg",
|
||||||
"fileName": "xxx.jpg",
|
"fileName": "xxx.jpg",
|
||||||
"fileSize": 12345
|
"fileSize": 12345
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 建议:文件先上传到对象存储(OSS/S3/MinIO),返回 URL,消息发送时引用该 URL(message.content)。
|
- 建议:文件先上传到对象存储(OSS/S3/MinIO),返回 URL,消息发送时引用该 URL(message.content)。
|
||||||
|
|
||||||
### 6.2 下载文件
|
### 6.2 下载文件
|
||||||
|
|
||||||
- 直接访问 `fileUrl` 或通过后端代理下载(带鉴权)。
|
- 直接访问 `fileUrl` 或通过后端代理下载(带鉴权)。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 7. 群组(Group)
|
## 7. 群组(Group)
|
||||||
|
|
||||||
### 7.1 创建群
|
### 7.1 创建群
|
||||||
|
|
||||||
- URL: `POST /api/v1/group/create`
|
- URL: `POST /api/v1/group/create`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"name": "项目群",
|
"name": "项目群",
|
||||||
"ownerId": 1001,
|
"ownerId": 1001,
|
||||||
"memberIds": [1002,1003],
|
"memberIds": [1002,1003],
|
||||||
"maxMembers": 500,
|
"maxMembers": 500,
|
||||||
"needApproval": true // 加群是否需要审批
|
"needApproval": true // 加群是否需要审批
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- 响应返回 `groupId`。
|
- 响应返回 `groupId`。
|
||||||
|
|
||||||
### 7.2 获取群信息
|
### 7.2 获取群信息
|
||||||
|
|
||||||
- URL: `GET /api/v1/group/{groupId}`
|
- URL: `GET /api/v1/group/{groupId}`
|
||||||
|
|
||||||
### 7.3 邀请入群
|
### 7.3 邀请入群
|
||||||
|
|
||||||
- URL: `POST /api/v1/group/{groupId}/invite`
|
- URL: `POST /api/v1/group/{groupId}/invite`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "inviter":1001, "invitees":[1004,1005], "message":"来加入我们吧" }
|
{ "inviter":1001, "invitees":[1004,1005], "message":"来加入我们吧" }
|
||||||
```
|
```
|
||||||
|
|
||||||
- 若群需要审批,发送 `group_join_request`;否则直接加入并更新 group_member。
|
- 若群需要审批,发送 `group_join_request`;否则直接加入并更新 group_member。
|
||||||
|
|
||||||
### 7.4 加群申请(用户申请)
|
### 7.4 加群申请(用户申请)
|
||||||
|
|
||||||
- URL: `POST /api/v1/group/{groupId}/join-request`
|
- URL: `POST /api/v1/group/{groupId}/join-request`
|
||||||
- 管理员/群主处理:`POST /api/v1/group/join-request/{requestId}/handle`
|
- 管理员/群主处理:`POST /api/v1/group/join-request/{requestId}/handle`
|
||||||
|
|
||||||
### 7.5 群成员管理(踢人/设管理员/退出群)
|
### 7.5 群成员管理(踢人/设管理员/退出群)
|
||||||
|
|
||||||
- 踢人:`POST /api/v1/group/{groupId}/kick`
|
- 踢人:`POST /api/v1/group/{groupId}/kick`
|
||||||
- 退出:`POST /api/v1/group/{groupId}/leave`
|
- 退出:`POST /api/v1/group/{groupId}/leave`
|
||||||
- 设管理员:`POST /api/v1/group/{groupId}/role`
|
- 设管理员:`POST /api/v1/group/{groupId}/role`
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 8. 通知(Notification)
|
## 8. 通知(Notification)
|
||||||
|
|
||||||
### 8.1 获取通知列表
|
### 8.1 获取通知列表
|
||||||
|
|
||||||
- URL: `GET /api/v1/notifications?page=1&limit=50`
|
- URL: `GET /api/v1/notifications?page=1&limit=50`
|
||||||
- 类型包含:好友请求、群邀请、系统公告等。
|
- 类型包含:好友请求、群邀请、系统公告等。
|
||||||
|
|
||||||
### 8.2 标记通知为已读
|
### 8.2 标记通知为已读
|
||||||
|
|
||||||
- URL: `POST /api/v1/notification/{notificationId}/read`
|
- URL: `POST /api/v1/notification/{notificationId}/read`
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 9. 管理后台(Admin)
|
## 9. 管理后台(Admin)
|
||||||
|
|
||||||
> 仅管理员或具备权限的账号访问(需在 token 中包含角色或额外权限校验)
|
> 仅管理员或具备权限的账号访问(需在 token 中包含角色或额外权限校验)
|
||||||
|
|
||||||
### 9.1 管理员登录(同 auth)
|
### 9.1 管理员登录(同 auth)
|
||||||
|
|
||||||
- URL: `POST /api/v1/admin/login`
|
- URL: `POST /api/v1/admin/login`
|
||||||
|
|
||||||
### 9.2 查询用户列表
|
### 9.2 查询用户列表
|
||||||
|
|
||||||
- URL: `GET /api/v1/admin/users?page=1&limit=50&keyword=alice`
|
- URL: `GET /api/v1/admin/users?page=1&limit=50&keyword=alice`
|
||||||
|
|
||||||
### 9.3 禁用/启用用户
|
### 9.3 禁用/启用用户
|
||||||
|
|
||||||
- URL: `POST /api/v1/admin/user/{userId}/ban`
|
- URL: `POST /api/v1/admin/user/{userId}/ban`
|
||||||
- 请求:
|
- 请求:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "action": "ban", "reason": "违规传播" } // action: ban | unban
|
{ "action": "ban", "reason": "违规传播" } // action: ban | unban
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9.4 查询操作日志
|
### 9.4 查询操作日志
|
||||||
|
|
||||||
- URL: `GET /api/v1/admin/logs?page=1&limit=50`
|
- URL: `GET /api/v1/admin/logs?page=1&limit=50`
|
||||||
@ -1,220 +1,220 @@
|
|||||||
# 数据字典
|
# 数据字典
|
||||||
|
|
||||||
### 表名:Users
|
### 表名:Users
|
||||||
|
|
||||||
#### 表说明:储存用户个人信息
|
#### 表说明:储存用户个人信息
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ----------- | ----------- | -------- | -------- | ------- | --------- | ------------------------------------------------ | --------- |
|
| ----------- | ----------- | -------- | -------- | ------- | --------- | ------------------------------------------------ | --------- |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| Username | VARCHAR(50) | 是 | / | / | 唯一 | 唯一用户名 | admin |
|
| Username | VARCHAR(50) | 是 | / | / | 唯一 | 唯一用户名 | admin |
|
||||||
| Password | VARCHAR(50) | 是 | / | / | / | 用户密码 | 123456 |
|
| Password | VARCHAR(50) | 是 | / | / | / | 用户密码 | 123456 |
|
||||||
| NickName | VARCHAR(50) | 是 | / | / | / | 用户昵称 | / |
|
| NickName | VARCHAR(50) | 是 | / | / | / | 用户昵称 | / |
|
||||||
| OlineStatus | TINYINT | 是 | 0 | / | / | 用户在线状态<br />0(默认):不在线<br />1:在线 | 0 |
|
| OlineStatus | TINYINT | 是 | 0 | / | / | 用户在线状态<br />0(默认):不在线<br />1:在线 | 0 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 账户创建时间 | 2025/9/29 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 账户创建时间 | 2025/9/29 |
|
||||||
| Updated | DATETIME | 否 | / | / | / | 账户修改时间 | 2024/9/29 |
|
| Updated | DATETIME | 否 | / | / | / | 账户修改时间 | 2024/9/29 |
|
||||||
| Status | TINYINT | 是 | 1 | / | / | 账户状态<br />(0:未激活,1:正常,2:封禁) | 1 |
|
| Status | TINYINT | 是 | 1 | / | / | 账户状态<br />(0:未激活,1:正常,2:封禁) | 1 |
|
||||||
| IsDeleted | TINYINT | 是 | 0 | / | / | 软删除标识<br />0:账号正常<br />1:账号已删除 | 0 |
|
| IsDeleted | TINYINT | 是 | 0 | / | / | 软删除标识<br />0:账号正常<br />1:账号已删除 | 0 |
|
||||||
|
|
||||||
### 表名:Friends
|
### 表名:Friends
|
||||||
|
|
||||||
#### 表说明:好友关系映射
|
#### 表说明:好友关系映射
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| -------- | -------- | -------- | -------- | ---------------- | --------- | ------------------------------------------------------------ | --------- |
|
| -------- | -------- | -------- | -------- | ---------------- | --------- | ------------------------------------------------------------ | --------- |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户ID | 1 |
|
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户ID | 1 |
|
||||||
| FriendId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户2ID | 2 |
|
| FriendId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户2ID | 2 |
|
||||||
| Status | TINYINT | 是 | 0 | / | / | 当前好友关系状态<br />(0:待通过,1:已添加,2:已拒绝,3:已拉黑) | 0 |
|
| Status | TINYINT | 是 | 0 | / | / | 当前好友关系状态<br />(0:待通过,1:已添加,2:已拒绝,3:已拉黑) | 0 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 好友关系创建时间 | 2025/9/29 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 好友关系创建时间 | 2025/9/29 |
|
||||||
|
|
||||||
### 表名:Groups
|
### 表名:Groups
|
||||||
|
|
||||||
#### 表说明:群聊
|
#### 表说明:群聊
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ---------------- | ------------- | -------- | -------- | ---------------- | --------- | ------------------------------------------------------------ | ------------------ |
|
| ---------------- | ------------- | -------- | -------- | ---------------- | --------- | ------------------------------------------------------------ | ------------------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| Name | VARCHAT(20) | 是 | / | / | / | 群聊名称 | 测试群聊1 |
|
| Name | VARCHAT(20) | 是 | / | / | / | 群聊名称 | 测试群聊1 |
|
||||||
| GroupMaster | INT | 是 | / | 外键(Users.Id) | 索引 | 群主 | 1 |
|
| GroupMaster | INT | 是 | / | 外键(Users.Id) | 索引 | 群主 | 1 |
|
||||||
| Auhority | TINYINT | 是 | 0 | / | / | 群权限<br />(0:需管理员同意,1:任意人可加群,2:不允许任何人加入) | 0 |
|
| Auhority | TINYINT | 是 | 0 | / | / | 群权限<br />(0:需管理员同意,1:任意人可加群,2:不允许任何人加入) | 0 |
|
||||||
| AllMembersBanned | TINYINT | 是 | 0 | / | / | 全员禁言(0允许发言,2全员禁言) | 0 |
|
| AllMembersBanned | TINYINT | 是 | 0 | / | / | 全员禁言(0允许发言,2全员禁言) | 0 |
|
||||||
| Status | TINYINT | 是 | 1 | / | / | 群聊状态<br />(1:正常,2:封禁) | 1 |
|
| Status | TINYINT | 是 | 1 | / | / | 群聊状态<br />(1:正常,2:封禁) | 1 |
|
||||||
| Announcement | TEXT | 否 | null | / | / | 群公告 | 这是一条测试群公告 |
|
| Announcement | TEXT | 否 | null | / | / | 群公告 | 这是一条测试群公告 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 群聊创建时间 | 2025/9/29 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 群聊创建时间 | 2025/9/29 |
|
||||||
|
|
||||||
### 表名:GroupMember
|
### 表名:GroupMember
|
||||||
|
|
||||||
#### 表说明:群成员
|
#### 表说明:群成员
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------- | -------- | -------- | -------- | --------------- | --------- | -------------------------------------- | -------- |
|
| ------- | -------- | -------- | -------- | --------------- | --------- | -------------------------------------- | -------- |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户编号 | 1 |
|
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户编号 | 1 |
|
||||||
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
||||||
| Role | TINYINT | 是 | 0 | / | / | 成员角色(0:普通成员,1:管理员,2:群主) | 1 |
|
| Role | TINYINT | 是 | 0 | / | / | 成员角色(0:普通成员,1:管理员,2:群主) | 1 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 加入群聊时间 | 1970/1/1 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 加入群聊时间 | 1970/1/1 |
|
||||||
|
|
||||||
### 表名:GroupInvite
|
### 表名:GroupInvite
|
||||||
|
|
||||||
#### 表说明:群聊邀请
|
#### 表说明:群聊邀请
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ----------- | -------- | -------- | -------- | --------------- | --------- | ------------------------------------------------ | -------- |
|
| ----------- | -------- | -------- | -------- | --------------- | --------- | ------------------------------------------------ | -------- |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
||||||
| InvitedUser | INT | 是 | / | 外键(Users.Id) | 索引 | 被邀请用户 | 1 |
|
| InvitedUser | INT | 是 | / | 外键(Users.Id) | 索引 | 被邀请用户 | 1 |
|
||||||
| InviteUser | INT | 是 | / | 外键(Users.Id) | 索引 | 邀请用户 | 1 |
|
| InviteUser | INT | 是 | / | 外键(Users.Id) | 索引 | 邀请用户 | 1 |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 当前状态(0:待被邀请人同意<br />1:被邀请人已同意) | 1 |
|
| State | TINYINT | 是 | 0 | / | / | 当前状态(0:待被邀请人同意<br />1:被邀请人已同意) | 1 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | 1970/1/1 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | 1970/1/1 |
|
||||||
|
|
||||||
### 表名:GroupRequest
|
### 表名:GroupRequest
|
||||||
|
|
||||||
#### 表说明:群聊入群申请
|
#### 表说明:群聊入群申请
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ----------- | -------- | -------- | --------------- | --------------- | --------- | --------------------------------------------- | ------ |
|
| ----------- | -------- | -------- | --------------- | --------------- | --------- | --------------------------------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
| GroupId | INT | 是 | / | 外键(Groups.Id) | 索引 | 群聊编号 | 1 |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 申请人 | 1 |
|
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 申请人 | 1 |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 申请状态(0:待管理员同意,1:已拒绝,2:已同意) | 1 |
|
| State | TINYINT | 是 | 0 | / | / | 申请状态(0:待管理员同意,1:已拒绝,2:已同意) | 1 |
|
||||||
| Description | TEXT | 是 | xxx申请加入群聊 | / | / | 入群附言 | / |
|
| Description | TEXT | 是 | xxx申请加入群聊 | / | / | 入群附言 | / |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
||||||
|
|
||||||
### 表名:Messages
|
### 表名:Messages
|
||||||
|
|
||||||
#### 表说明:用户消息
|
#### 表说明:用户消息
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| --------- | -------- | -------- | -------- | -------------- | --------- | ------------------------------------------------------------ | ------ |
|
| --------- | -------- | -------- | -------- | -------------- | --------- | ------------------------------------------------------------ | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| ChatType | TINYINT | 是 | 0 | / | / | 聊天类型<br />(0:私聊,1:群聊) | 0 |
|
| ChatType | TINYINT | 是 | 0 | / | / | 聊天类型<br />(0:私聊,1:群聊) | 0 |
|
||||||
| MsgType | TINYINT | 是 | 0 | / | / | 消息类型<br />(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天) | 0 |
|
| MsgType | TINYINT | 是 | 0 | / | / | 消息类型<br />(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天) | 0 |
|
||||||
| Content | TEXT | 是 | / | / | / | 消息内容 | / |
|
| Content | TEXT | 是 | / | / | / | 消息内容 | / |
|
||||||
| Sender | INT | 是 | / | 外键(Users.Id) | 索引 | 发送者 | / |
|
| Sender | INT | 是 | / | 外键(Users.Id) | 索引 | 发送者 | / |
|
||||||
| Recipient | INT | 是 | / | / | / | 接收者(私聊为用户ID,群聊为群聊ID) | / |
|
| Recipient | INT | 是 | / | / | / | 接收者(私聊为用户ID,群聊为群聊ID) | / |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 消息状态(0:已发送,1:已撤回) | / |
|
| State | TINYINT | 是 | 0 | / | / | 消息状态(0:已发送,1:已撤回) | / |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 发送时间 | 、 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 发送时间 | 、 |
|
||||||
|
|
||||||
### 表名:Files
|
### 表名:Files
|
||||||
|
|
||||||
#### 表说明:文件
|
#### 表说明:文件
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| --------- | ------------ | -------- | -------- | ------------------- | --------- | -------------------- | ----------------------- |
|
| --------- | ------------ | -------- | -------- | ------------------- | --------- | -------------------- | ----------------------- |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| Name | VARCHAR(50) | 是 | / | / | / | 文件名 | 测试文件.txt |
|
| Name | VARCHAR(50) | 是 | / | / | / | 文件名 | 测试文件.txt |
|
||||||
| URL | VARCHAR(100) | 是 | / | / | / | 文件储存URL | https://baidu.com/1.txt |
|
| URL | VARCHAR(100) | 是 | / | / | / | 文件储存URL | https://baidu.com/1.txt |
|
||||||
| Size | INT | 是 | / | / | / | 文件大小(单位:KB) | 1024 |
|
| Size | INT | 是 | / | / | / | 文件大小(单位:KB) | 1024 |
|
||||||
| Type | VARCHAT(10) | 是 | / | / | / | 文件类型 | txt |
|
| Type | VARCHAT(10) | 是 | / | / | / | 文件类型 | txt |
|
||||||
| MessageId | INT | 是 | / | 外键(Messages.Id) | 索引 | 关联消息ID | 1 |
|
| MessageId | INT | 是 | / | 外键(Messages.Id) | 索引 | 关联消息ID | 1 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | 2025/9/29 |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | 2025/9/29 |
|
||||||
|
|
||||||
### 表名:Notifications
|
### 表名:Notifications
|
||||||
|
|
||||||
#### 表说明:系统通知消息
|
#### 表说明:系统通知消息
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------- | ------------ | -------- | -------- | -------------- | --------- | ------------------------ | ------ |
|
| ------- | ------------ | -------- | -------- | -------------- | --------- | ------------------------ | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| UserId | INT | 否 | / | 外键(Users.Id) | 索引 | 接收人(为空为全体通知) | 1 |
|
| UserId | INT | 否 | / | 外键(Users.Id) | 索引 | 接收人(为空为全体通知) | 1 |
|
||||||
| NType | TINYINT | 是 | 0 | / | / | 通知类型(0:文本) | 0 |
|
| NType | TINYINT | 是 | 0 | / | / | 通知类型(0:文本) | 0 |
|
||||||
| Title | NVARCHAR(20) | 是 | / | / | / | 通知标题 | 1 |
|
| Title | NVARCHAR(20) | 是 | / | / | / | 通知标题 | 1 |
|
||||||
| Content | TEXT | 是 | / | / | / | 通知内容 | 1 |
|
| Content | TEXT | 是 | / | / | / | 通知内容 | 1 |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
||||||
|
|
||||||
### 表名:Conversations
|
### 表名:Conversations
|
||||||
|
|
||||||
#### 表说明:用户会话
|
#### 表说明:用户会话
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------------- | -------- | -------- | ------ | ----------------- | --------- | ------------------------------------ | ------ |
|
| ------------- | -------- | -------- | ------ | ----------------- | --------- | ------------------------------------ | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户 | 1 |
|
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 用户 | 1 |
|
||||||
| TargetId | INT | 是 | / | / | / | 对方ID(群聊为群聊ID,单聊为单聊ID) | 1 |
|
| TargetId | INT | 是 | / | / | / | 对方ID(群聊为群聊ID,单聊为单聊ID) | 1 |
|
||||||
| MsgType | INT | 是 | / | / | / | 消息类型(同Messages.MsgType) | / |
|
| MsgType | INT | 是 | / | / | / | 消息类型(同Messages.MsgType) | / |
|
||||||
| lastMessageId | INT | 是 | / | 外键(Messages.Id) | 索引 | 最后一条消息ID | 1 |
|
| lastMessageId | INT | 是 | / | 外键(Messages.Id) | 索引 | 最后一条消息ID | 1 |
|
||||||
| unreadCount | INT | 是 | / | / | / | 未读消息数 | / |
|
| unreadCount | INT | 是 | / | / | / | 未读消息数 | / |
|
||||||
|
|
||||||
### 表名:FriendRequest
|
### 表名:FriendRequest
|
||||||
|
|
||||||
#### 表说明:好友申请
|
#### 表说明:好友申请
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------------ | -------- | -------- | ------------------- | ---------------- | --------- | ------------------------------------------- | ------ |
|
| ------------ | -------- | -------- | ------------------- | ---------------- | --------- | ------------------------------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| RequestUser | INT | 是 | / | 外键(Users.Id) | 索引 | 申请人 | / |
|
| RequestUser | INT | 是 | / | 外键(Users.Id) | 索引 | 申请人 | / |
|
||||||
| ResponseUser | INT | 是 | / | 外键(Users.Id) | 索引 | 被申请人 | / |
|
| ResponseUser | INT | 是 | / | 外键(Users.Id) | 索引 | 被申请人 | / |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 申请时间 | / |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 申请时间 | / |
|
||||||
| Description | TEXT | 否 | xxx申请添加你为好友 | / | / | 申请附言 | / |
|
| Description | TEXT | 否 | xxx申请添加你为好友 | / | / | 申请附言 | / |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) | / |
|
| State | TINYINT | 是 | 0 | / | / | 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) | / |
|
||||||
|
|
||||||
### 表名:Devices
|
### 表名:Devices
|
||||||
|
|
||||||
#### 表说明:用户设备
|
#### 表说明:用户设备
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| --------- | -------- | -------- | -------- | -------------- | --------- | ---------------------------------------------------- | ------ |
|
| --------- | -------- | -------- | -------- | -------------- | --------- | ---------------------------------------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 设备所属用户 | / |
|
| UserId | INT | 是 | / | 外键(Users.Id) | 索引 | 设备所属用户 | / |
|
||||||
| DType | TINYINT | 是 | / | / | / | 设备类型(<br />0:Android,1:Ios,2:PC,3:Pad,4:未知) | 0 |
|
| DType | TINYINT | 是 | / | / | / | 设备类型(<br />0:Android,1:Ios,2:PC,3:Pad,4:未知) | 0 |
|
||||||
| LastLogin | DATETIME | 是 | 1970/1/1 | / | / | 最后一次登录 | / |
|
| LastLogin | DATETIME | 是 | 1970/1/1 | / | / | 最后一次登录 | / |
|
||||||
|
|
||||||
### 表名:Login_Log
|
### 表名:Login_Log
|
||||||
|
|
||||||
#### 表说明:登录日志
|
#### 表说明:登录日志
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------- | -------- | -------- | -------- | -------------- | --------- | ---------------------------------------- | ------ |
|
| ------- | -------- | -------- | -------- | -------------- | --------- | ---------------------------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| DType | TINYINT | 是 | / | / | / | 设备类型(通Devices/DType) | / |
|
| DType | TINYINT | 是 | / | / | / | 设备类型(通Devices/DType) | / |
|
||||||
| Logined | DATETIME | 是 | 1970/1/1 | / | / | 登录时间 | / |
|
| Logined | DATETIME | 是 | 1970/1/1 | / | / | 登录时间 | / |
|
||||||
| UserId | INT | 是 | / | 外键(Users.Id) | / | 登录用户 | / |
|
| UserId | INT | 是 | / | 外键(Users.Id) | / | 登录用户 | / |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 登录状态(0:登陆成功,1:未验证,2:已被拒绝) | / |
|
| State | TINYINT | 是 | 0 | / | / | 登录状态(0:登陆成功,1:未验证,2:已被拒绝) | / |
|
||||||
|
|
||||||
### 表名:Admins
|
### 表名:Admins
|
||||||
|
|
||||||
#### 表说明:系统管理员
|
#### 表说明:系统管理员
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| -------- | ----------- | -------- | -------- | ---------------- | --------- | ----------------------- | ------ |
|
| -------- | ----------- | -------- | -------- | ---------------- | --------- | ----------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| Username | VARCHAR(50) | 是 | / | / | / | 用户名 | / |
|
| Username | VARCHAR(50) | 是 | / | / | / | 用户名 | / |
|
||||||
| Password | VARCHAR(50) | 是 | / | / | / | 密码 | / |
|
| Password | VARCHAR(50) | 是 | / | / | / | 密码 | / |
|
||||||
| RoleId | INT | 是 | / | 外键(Roles.Id) | 索引 | 角色 | / |
|
| RoleId | INT | 是 | / | 外键(Roles.Id) | 索引 | 角色 | / |
|
||||||
| State | TINYINT | 是 | 0 | / | / | 状态(0:正常,2:封禁) | / |
|
| State | TINYINT | 是 | 0 | / | / | 状态(0:正常,2:封禁) | / |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
||||||
| Updated | DATETIME | 是 | 1970/1/1 | / | / | 更新时间 | / |
|
| Updated | DATETIME | 是 | 1970/1/1 | / | / | 更新时间 | / |
|
||||||
|
|
||||||
### 表名:Roles
|
### 表名:Roles
|
||||||
|
|
||||||
#### 表说明:角色
|
#### 表说明:角色
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ----------- | ----------- | -------- | -------- | ------- | --------- | -------- | ------ |
|
| ----------- | ----------- | -------- | -------- | ------- | --------- | -------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| Name | VARCHAR(20) | 是 | / | / | / | 角色名称 | / |
|
| Name | VARCHAR(20) | 是 | / | / | / | 角色名称 | / |
|
||||||
| Description | TEXT | 是 | 空字符串 | / | / | 角色描述 | / |
|
| Description | TEXT | 是 | 空字符串 | / | / | 角色描述 | / |
|
||||||
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
| Created | DATETIME | 是 | 1970/1/1 | / | / | 创建时间 | / |
|
||||||
|
|
||||||
### 表名:Permissions
|
### 表名:Permissions
|
||||||
|
|
||||||
#### 表说明:权限
|
#### 表说明:权限
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------- | ----------- | -------- | ------ | ------- | --------- | ----------------------------- | ------ |
|
| ------- | ----------- | -------- | ------ | ------- | --------- | ----------------------------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| PType | INT | 是 | 0 | / | / | 权限类型(0:增,1:删,2:改,3:查) | / |
|
| PType | INT | 是 | 0 | / | / | 权限类型(0:增,1:删,2:改,3:查) | / |
|
||||||
| Name | VARCHAR(50) | 是 | / | / | / | 权限名称 | / |
|
| Name | VARCHAR(50) | 是 | / | / | / | 权限名称 | / |
|
||||||
| Code | INT | 是 | / | / | / | 权限编码 | / |
|
| Code | INT | 是 | / | / | / | 权限编码 | / |
|
||||||
| Created | DATETIME | 是 | / | / | / | 创建时间 | / |
|
| Created | DATETIME | 是 | / | / | / | 创建时间 | / |
|
||||||
|
|
||||||
### 表名:PermissionARole
|
### 表名:PermissionARole
|
||||||
|
|
||||||
#### 表说明:权限角色关联
|
#### 表说明:权限角色关联
|
||||||
|
|
||||||
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 主/外键 | 约束/索引 | 字段说明 | 示例值 |
|
||||||
| ------------ | -------- | -------- | ------ | ---------------------- | --------- | -------- | ------ |
|
| ------------ | -------- | -------- | ------ | ---------------------- | --------- | -------- | ------ |
|
||||||
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
| Id | INT | 是 | / | 主键 | 索引 | 主键自增 | 1 |
|
||||||
| RoleId | INT | 是 | / | 外键(Roles.Id) | 索引 | 角色 | / |
|
| RoleId | INT | 是 | / | 外键(Roles.Id) | 索引 | 角色 | / |
|
||||||
| PermissionId | INT | 是 | / | 外键(Permissions.Id) | 索引 | 权限 | / |
|
| PermissionId | INT | 是 | / | 外键(Permissions.Id) | 索引 | 权限 | / |
|
||||||
@ -1,64 +1,64 @@
|
|||||||
# 需求规格说明书(SRS)
|
# 需求规格说明书(SRS)
|
||||||
|
|
||||||
## 1. 项目背景
|
## 1. 项目背景
|
||||||
- #### 项目目标:
|
- #### 项目目标:
|
||||||
|
|
||||||
本项目旨在实现一个类似 QQ 的即时通讯系统,提供用户注册、好友聊天、群聊、文件传输等核心功能。
|
本项目旨在实现一个类似 QQ 的即时通讯系统,提供用户注册、好友聊天、群聊、文件传输等核心功能。
|
||||||
|
|
||||||
- 使用场景:学习练手 + 内部小团队沟通工具。
|
- 使用场景:学习练手 + 内部小团队沟通工具。
|
||||||
|
|
||||||
- #### 项目范围:
|
- #### 项目范围:
|
||||||
|
|
||||||
学习项目
|
学习项目
|
||||||
|
|
||||||
- #### 业务价值:
|
- #### 业务价值:
|
||||||
|
|
||||||
学习项目
|
学习项目
|
||||||
|
|
||||||
## 2. 用户需求
|
## 2. 用户需求
|
||||||
- 普通用户:注册、登录、聊天、加好友
|
- 普通用户:注册、登录、聊天、加好友
|
||||||
- 管理员:封禁违规账号、管理群聊
|
- 管理员:封禁违规账号、管理群聊
|
||||||
- 使用场景:1v1 聊天、群聊、发送文件、发送表情/图片等
|
- 使用场景:1v1 聊天、群聊、发送文件、发送表情/图片等
|
||||||
|
|
||||||
## 3. 功能需求
|
## 3. 功能需求
|
||||||
- ##### 3.1 账号系统
|
- ##### 3.1 账号系统
|
||||||
- F-1 用户注册:支持手机号/邮箱注册,需验证唯一性。
|
- F-1 用户注册:支持手机号/邮箱注册,需验证唯一性。
|
||||||
- F-2 用户登录:支持账号+密码、Token 鉴权。
|
- F-2 用户登录:支持账号+密码、Token 鉴权。
|
||||||
- F-3 用户资料:可修改头像、昵称、个性签名。
|
- F-3 用户资料:可修改头像、昵称、个性签名。
|
||||||
|
|
||||||
##### 3.2 好友系统
|
##### 3.2 好友系统
|
||||||
- F-4 添加好友:通过账号/手机号搜索并申请。
|
- F-4 添加好友:通过账号/手机号搜索并申请。
|
||||||
- F-5 好友请求:系统通知对方,同意/拒绝。
|
- F-5 好友请求:系统通知对方,同意/拒绝。
|
||||||
- F-6 删除好友、拉黑。
|
- F-6 删除好友、拉黑。
|
||||||
|
|
||||||
##### 3.3 消息系统
|
##### 3.3 消息系统
|
||||||
- F-7 单聊:支持文本、表情、图片、文件。
|
- F-7 单聊:支持文本、表情、图片、文件。
|
||||||
- F-8 群聊:支持多人实时消息。
|
- F-8 群聊:支持多人实时消息。
|
||||||
- F-9 消息状态:已发送、已送达、已读(暂时只在数据库层面标记已读,不显示在客户端)。
|
- F-9 消息状态:已发送、已送达、已读(暂时只在数据库层面标记已读,不显示在客户端)。
|
||||||
- F-10 消息管理:撤回、删除、搜索历史记录。
|
- F-10 消息管理:撤回、删除、搜索历史记录。
|
||||||
|
|
||||||
##### 3.4 群聊功能
|
##### 3.4 群聊功能
|
||||||
- F-11 创建群聊:指定群名称,邀请成员。
|
- F-11 创建群聊:指定群名称,邀请成员。
|
||||||
- F-12 群管理:踢人、设管理员、发布公告。
|
- F-12 群管理:踢人、设管理员、发布公告。
|
||||||
- F-13 群人数上限:本期 500 人。
|
- F-13 群人数上限:本期 500 人。
|
||||||
|
|
||||||
##### 3.5 系统通知
|
##### 3.5 系统通知
|
||||||
- F-14 好友申请通知。
|
- F-14 好友申请通知。
|
||||||
- F-15 群邀请通知。
|
- F-15 群邀请通知。
|
||||||
- F-16 新消息推送(WebSocket)。
|
- F-16 新消息推送(WebSocket)。
|
||||||
|
|
||||||
## 4. 非功能需求
|
## 4. 非功能需求
|
||||||
- 实时性:消息延迟 ≤ 1 秒
|
- 实时性:消息延迟 ≤ 1 秒
|
||||||
- 并发性:单群支持 ≥ 500 人在线聊天
|
- 并发性:单群支持 ≥ 500 人在线聊天
|
||||||
- 安全性:消息加密传输(WebSocket + TLS)
|
- 安全性:消息加密传输(WebSocket + TLS)
|
||||||
- 可扩展性:后端支持水平扩展(分布式 IM 服务器) ***<u>此条暂不要求</u>***
|
- 可扩展性:后端支持水平扩展(分布式 IM 服务器) ***<u>此条暂不要求</u>***
|
||||||
|
|
||||||
## 5. 约束与假设
|
## 5. 约束与假设
|
||||||
- 本期仅支持 Web 端(PC + H5),移动端后续开发
|
- 本期仅支持 Web 端(PC + H5),移动端后续开发
|
||||||
- 音视频通话仅提供基础功能,不做美颜、录屏
|
- 音视频通话仅提供基础功能,不做美颜、录屏
|
||||||
|
|
||||||
## 6.验收标准
|
## 6.验收标准
|
||||||
|
|
||||||
- 两个用户能互加好友并聊天。
|
- 两个用户能互加好友并聊天。
|
||||||
- 群聊消息在 500 人场景下稳定传递。
|
- 群聊消息在 500 人场景下稳定传递。
|
||||||
- 消息能在弱网环境下重试并成功送达。
|
- 消息能在弱网环境下重试并成功送达。
|
||||||
@ -1,285 +1,285 @@
|
|||||||
# 📘 WebSocket 通讯协议设计文档
|
# 📘 WebSocket 通讯协议设计文档
|
||||||
|
|
||||||
## 1. 概述
|
## 1. 概述
|
||||||
|
|
||||||
本协议用于实现 **即时聊天(IM)系统的实时消息传输**。
|
本协议用于实现 **即时聊天(IM)系统的实时消息传输**。
|
||||||
客户端通过 WebSocket 连接后与服务器保持长连接,实现消息推送、状态同步、群聊与单聊。
|
客户端通过 WebSocket 连接后与服务器保持长连接,实现消息推送、状态同步、群聊与单聊。
|
||||||
|
|
||||||
**支持功能**:
|
**支持功能**:
|
||||||
|
|
||||||
- 用户登录鉴权
|
- 用户登录鉴权
|
||||||
- 单聊消息发送/接收
|
- 单聊消息发送/接收
|
||||||
- 群聊消息发送/接收
|
- 群聊消息发送/接收
|
||||||
- 消息撤回、已读回执
|
- 消息撤回、已读回执
|
||||||
- 心跳保活与断线重连
|
- 心跳保活与断线重连
|
||||||
- 系统通知(好友请求、群邀请)
|
- 系统通知(好友请求、群邀请)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 2. 消息传输格式
|
## 2. 消息传输格式
|
||||||
|
|
||||||
### 2.1 基础消息结构(JSON)
|
### 2.1 基础消息结构(JSON)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE_TYPE",
|
"type": "MESSAGE_TYPE",
|
||||||
"requestId": "string", // 客户端生成的请求ID,便于幂等
|
"requestId": "string", // 客户端生成的请求ID,便于幂等
|
||||||
"from": 1001, // 发送者ID
|
"from": 1001, // 发送者ID
|
||||||
"to": 1002, // 接收者ID(单聊)或群ID(群聊)
|
"to": 1002, // 接收者ID(单聊)或群ID(群聊)
|
||||||
"chatType": "single", // "single" | "group"
|
"chatType": "single", // "single" | "group"
|
||||||
"contentType": "text", // "text" | "image" | "file" | "voice" | "system"
|
"contentType": "text", // "text" | "image" | "file" | "voice" | "system"
|
||||||
"content": "消息内容或文件URL",
|
"content": "消息内容或文件URL",
|
||||||
"timestamp": 1700000000 // Unix时间戳
|
"timestamp": 1700000000 // Unix时间戳
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 2.2 常用 type 枚举
|
### 2.2 常用 type 枚举
|
||||||
|
|
||||||
| type | 说明 |
|
| type | 说明 |
|
||||||
| -------------- | ------------------------- |
|
| -------------- | ------------------------- |
|
||||||
| AUTH | 握手鉴权 |
|
| AUTH | 握手鉴权 |
|
||||||
| HEARTBEAT | 心跳保活 |
|
| HEARTBEAT | 心跳保活 |
|
||||||
| MESSAGE | 普通聊天消息(单聊/群聊) |
|
| MESSAGE | 普通聊天消息(单聊/群聊) |
|
||||||
| MESSAGE_ACK | 消息已读/送达回执 |
|
| MESSAGE_ACK | 消息已读/送达回执 |
|
||||||
| MESSAGE_RECALL | 消息撤回 |
|
| MESSAGE_RECALL | 消息撤回 |
|
||||||
| FRIEND_REQUEST | 好友申请通知 |
|
| FRIEND_REQUEST | 好友申请通知 |
|
||||||
| GROUP_INVITE | 群邀请通知 |
|
| GROUP_INVITE | 群邀请通知 |
|
||||||
| SYSTEM_NOTICE | 系统公告/通知 |
|
| SYSTEM_NOTICE | 系统公告/通知 |
|
||||||
| ERROR | 错误消息 |
|
| ERROR | 错误消息 |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 3. 握手与鉴权
|
## 3. 握手与鉴权
|
||||||
|
|
||||||
### 3.1 客户端连接
|
### 3.1 客户端连接
|
||||||
|
|
||||||
```
|
```
|
||||||
ws://example.com/ws?token=xxxx
|
ws://example.com/ws?token=xxxx
|
||||||
```
|
```
|
||||||
|
|
||||||
- 客户端通过 Token 鉴权
|
- 客户端通过 Token 鉴权
|
||||||
- 服务器验证 Token 后,返回 AUTH_SUCCESS 或 AUTH_FAIL
|
- 服务器验证 Token 后,返回 AUTH_SUCCESS 或 AUTH_FAIL
|
||||||
|
|
||||||
### 3.2 服务端响应示例
|
### 3.2 服务端响应示例
|
||||||
|
|
||||||
**成功:**
|
**成功:**
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "AUTH",
|
"type": "AUTH",
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"userId": 1001,
|
"userId": 1001,
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**失败:**
|
**失败:**
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "AUTH",
|
"type": "AUTH",
|
||||||
"status": "fail",
|
"status": "fail",
|
||||||
"code": 1006,
|
"code": 1006,
|
||||||
"message": "Token无效或过期"
|
"message": "Token无效或过期"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 4. 心跳机制
|
## 4. 心跳机制
|
||||||
|
|
||||||
### 4.1 客户端发送
|
### 4.1 客户端发送
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "HEARTBEAT",
|
"type": "HEARTBEAT",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 服务器响应
|
### 4.2 服务器响应
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "HEARTBEAT",
|
"type": "HEARTBEAT",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **客户端**:每隔 30 秒发送一次心跳
|
- **客户端**:每隔 30 秒发送一次心跳
|
||||||
- **服务器**:若 2 倍心跳时间未收到消息,则断开连接
|
- **服务器**:若 2 倍心跳时间未收到消息,则断开连接
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 5. 消息传输
|
## 5. 消息传输
|
||||||
|
|
||||||
### 5.1 单聊消息
|
### 5.1 单聊消息
|
||||||
|
|
||||||
客户端发送:
|
客户端发送:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE",
|
"type": "MESSAGE",
|
||||||
"requestId": "uuid-001",
|
"requestId": "uuid-001",
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 1002,
|
"to": 1002,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"contentType": "text",
|
"contentType": "text",
|
||||||
"content": "你好",
|
"content": "你好",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
服务器推送给接收者:
|
服务器推送给接收者:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE",
|
"type": "MESSAGE",
|
||||||
"messageId": 50001,
|
"messageId": 50001,
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 1002,
|
"to": 1002,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"contentType": "text",
|
"contentType": "text",
|
||||||
"content": "你好",
|
"content": "你好",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 5.2 群聊消息
|
### 5.2 群聊消息
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE",
|
"type": "MESSAGE",
|
||||||
"requestId": "uuid-002",
|
"requestId": "uuid-002",
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 3001, // 群ID
|
"to": 3001, // 群ID
|
||||||
"chatType": "group",
|
"chatType": "group",
|
||||||
"contentType": "image",
|
"contentType": "image",
|
||||||
"content": "http://img.example.com/xxx.jpg",
|
"content": "http://img.example.com/xxx.jpg",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
服务器会 **推送到群成员列表(除了自己)**。
|
服务器会 **推送到群成员列表(除了自己)**。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 5.3 消息回执(MESSAGE_ACK)
|
### 5.3 消息回执(MESSAGE_ACK)
|
||||||
|
|
||||||
客户端收到消息后发送:
|
客户端收到消息后发送:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE_ACK",
|
"type": "MESSAGE_ACK",
|
||||||
"messageId": 50001,
|
"messageId": 50001,
|
||||||
"from": 1002,
|
"from": 1002,
|
||||||
"to": 1001,
|
"to": 1001,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"status": "read",
|
"status": "read",
|
||||||
"timestamp": 1700000000
|
"timestamp": 1700000000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
服务器更新消息状态,并可推送给发送方。
|
服务器更新消息状态,并可推送给发送方。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 5.4 消息撤回(MESSAGE_RECALL)
|
### 5.4 消息撤回(MESSAGE_RECALL)
|
||||||
|
|
||||||
客户端请求撤回消息:
|
客户端请求撤回消息:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE_RECALL",
|
"type": "MESSAGE_RECALL",
|
||||||
"messageId": 50001,
|
"messageId": 50001,
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 1002,
|
"to": 1002,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"timestamp": 1700000010
|
"timestamp": 1700000010
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
服务器验证是否允许撤回(时间限制、权限等),允许则推送给接收方:
|
服务器验证是否允许撤回(时间限制、权限等),允许则推送给接收方:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "MESSAGE_RECALL",
|
"type": "MESSAGE_RECALL",
|
||||||
"messageId": 50001,
|
"messageId": 50001,
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"chatType": "single",
|
"chatType": "single",
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"timestamp": 1700000010
|
"timestamp": 1700000010
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 6. 好友 / 群邀请通知
|
## 6. 好友 / 群邀请通知
|
||||||
|
|
||||||
### 6.1 好友申请(FRIEND_REQUEST)
|
### 6.1 好友申请(FRIEND_REQUEST)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "FRIEND_REQUEST",
|
"type": "FRIEND_REQUEST",
|
||||||
"requestId": "uuid-003",
|
"requestId": "uuid-003",
|
||||||
"from": 1001,
|
"from": 1001,
|
||||||
"to": 1002,
|
"to": 1002,
|
||||||
"content": "加个好友吧",
|
"content": "加个好友吧",
|
||||||
"timestamp": 1700000020
|
"timestamp": 1700000020
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6.2 群邀请(GROUP_INVITE)
|
### 6.2 群邀请(GROUP_INVITE)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "GROUP_INVITE",
|
"type": "GROUP_INVITE",
|
||||||
"inviteId": "uuid-004",
|
"inviteId": "uuid-004",
|
||||||
"groupId": 3001,
|
"groupId": 3001,
|
||||||
"inviter": 1001,
|
"inviter": 1001,
|
||||||
"invitee": 1003,
|
"invitee": 1003,
|
||||||
"content": "邀请你加入群聊",
|
"content": "邀请你加入群聊",
|
||||||
"timestamp": 1700000030
|
"timestamp": 1700000030
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 7. 错误处理(ERROR)
|
## 7. 错误处理(ERROR)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "ERROR",
|
"type": "ERROR",
|
||||||
"code": 2300,
|
"code": 2300,
|
||||||
"message": "消息发送失败",
|
"message": "消息发送失败",
|
||||||
"requestId": "uuid-001",
|
"requestId": "uuid-001",
|
||||||
"timestamp": 1700000040
|
"timestamp": 1700000040
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **code** 对应响应 Code 规范
|
- **code** 对应响应 Code 规范
|
||||||
- **requestId** 可帮助客户端确认失败的具体请求
|
- **requestId** 可帮助客户端确认失败的具体请求
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 8. 断线重连
|
## 8. 断线重连
|
||||||
|
|
||||||
- 客户端断线后,尝试每隔 5 秒重连一次
|
- 客户端断线后,尝试每隔 5 秒重连一次
|
||||||
- 重连成功后,重新发送 AUTH 消息进行鉴权
|
- 重连成功后,重新发送 AUTH 消息进行鉴权
|
||||||
- 重连后可请求 **未读消息同步**(message 表或 Redis 缓存)
|
- 重连后可请求 **未读消息同步**(message 表或 Redis 缓存)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 9. 附录:contentType 示例
|
## 9. 附录:contentType 示例
|
||||||
|
|
||||||
| contentType | content 示例 | 描述 |
|
| contentType | content 示例 | 描述 |
|
||||||
| ----------- | ---------------------------------- | ----------------- |
|
| ----------- | ---------------------------------- | ----------------- |
|
||||||
| text | "你好" | 文本消息 |
|
| text | "你好" | 文本消息 |
|
||||||
| image | "http://img.example.com/xxx.jpg" | 图片 URL |
|
| image | "http://img.example.com/xxx.jpg" | 图片 URL |
|
||||||
| file | "http://file.example.com/xxx.pdf" | 文件 URL + 文件名 |
|
| file | "http://file.example.com/xxx.pdf" | 文件 URL + 文件名 |
|
||||||
| voice | "http://audio.example.com/xxx.mp3" | 语音 URL + 时长 |
|
| voice | "http://audio.example.com/xxx.mp3" | 语音 URL + 时长 |
|
||||||
| system | "用户xxx加入群" | 系统消息 |
|
| system | "用户xxx加入群" | 系统消息 |
|
||||||
@ -1,144 +1,145 @@
|
|||||||
# 📘 接口响应 Code 设计文档
|
# 📘 接口响应 Code 设计文档
|
||||||
|
|
||||||
## 1. 响应数据结构
|
## 1. 响应数据结构
|
||||||
|
|
||||||
统一使用 JSON 格式:
|
统一使用 JSON 格式:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "请求成功",
|
"message": "请求成功",
|
||||||
"data": {}
|
"data": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **code**:数字型,业务状态码
|
- **code**:数字型,业务状态码
|
||||||
- **message**:字符串,错误或提示信息
|
- **message**:字符串,错误或提示信息
|
||||||
- **data**:对象/数组,返回的数据内容(可选)
|
- **data**:对象/数组,返回的数据内容(可选)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 2. Code 设计原则
|
## 2. Code 设计原则
|
||||||
|
|
||||||
1. **统一性**:所有接口返回结构一致。
|
1. **统一性**:所有接口返回结构一致。
|
||||||
2. **分级设计**:分为系统级错误(1xxx)、业务错误(2xxx+)。
|
2. **分级设计**:分为系统级错误(1xxx)、业务错误(2xxx+)。
|
||||||
3. **可扩展性**:预留范围,避免混乱。
|
3. **可扩展性**:预留范围,避免混乱。
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 3. Code 约定规范
|
## 3. Code 约定规范
|
||||||
|
|
||||||
### 3.1 成功类
|
### 3.1 成功类
|
||||||
|
|
||||||
| code | message | 说明 |
|
| code | message | 说明 |
|
||||||
| ---- | ------- | ------------ |
|
| ---- | ------- | ------------ |
|
||||||
| 0 | 成功 | 通用成功响应 |
|
| 0 | 成功 | 通用成功响应 |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.2 系统级错误(1000 ~ 1999)
|
### 3.2 系统级错误(1000 ~ 1999)
|
||||||
|
|
||||||
| code | message | 说明 |
|
| code | message | 说明 |
|
||||||
| ---- | ---------- | ------------------ |
|
| ---- | ---------- | ------------------ |
|
||||||
| 1000 | 系统错误 | 未知异常 |
|
| 1000 | 系统错误 | 未知异常 |
|
||||||
| 1001 | 服务不可用 | 服务器维护中或宕机 |
|
| 1001 | 服务不可用 | 服务器维护中或宕机 |
|
||||||
| 1002 | 请求超时 | 后端超时 |
|
| 1002 | 请求超时 | 后端超时 |
|
||||||
| 1003 | 参数错误 | 缺少或参数不合法 |
|
| 1003 | 参数错误 | 缺少或参数不合法 |
|
||||||
| 1004 | 数据库错误 | 数据库读写失败 |
|
| 1004 | 数据库错误 | 数据库读写失败 |
|
||||||
| 1005 | 权限不足 | 无权限访问 |
|
| 1005 | 权限不足 | 无权限访问 |
|
||||||
| 1006 | 认证失败 | Token 无效/过期 |
|
| 1006 | 认证失败 | Token 无效/过期 |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.3 用户相关错误(2000 ~ 2099)
|
### 3.3 用户相关错误(2000 ~ 2099)
|
||||||
|
|
||||||
| code | message | 说明 |
|
| code | message | 说明 |
|
||||||
| ---- | ---------- | ---------------- |
|
| ---- | ---------- | ---------------- |
|
||||||
| 2000 | 用户不存在 | 查询不到用户 |
|
| 2000 | 用户不存在 | 查询不到用户 |
|
||||||
| 2001 | 用户已存在 | 注册时用户已存在 |
|
| 2001 | 用户已存在 | 注册时用户已存在 |
|
||||||
| 2002 | 密码错误 | 登录密码错误 |
|
| 2002 | 密码错误 | 登录密码错误 |
|
||||||
| 2003 | 用户被禁用 | 被管理员封禁 |
|
| 2003 | 用户被禁用 | 被管理员封禁 |
|
||||||
| 2004 | 登录过期 | 需重新登录 |
|
| 2004 | 登录过期 | 需重新登录 |
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### 3.4 好友相关错误(2100 ~ 2199)
|
### 3.4 好友相关错误(2100 ~ 2199)
|
||||||
|
|
||||||
| code | message | 说明 |
|
| code | message | 说明 |
|
||||||
| ---- | -------------- | ---------- |
|
| ---- | -------------- | ---------- |
|
||||||
| 2100 | 好友申请已存在 | 重复申请 |
|
| 2100 | 好友申请已存在 | 重复申请 |
|
||||||
| 2101 | 好友关系不存在 | 不是好友 |
|
| 2101 | 好友关系不存在 | 不是好友 |
|
||||||
| 2102 | 已经是好友 | 重复添加 |
|
| 2102 | 已经是好友 | 重复添加 |
|
||||||
| 2103 | 好友请求被拒绝 | 被对方拒绝 |
|
| 2103 | 好友请求被拒绝 | 被对方拒绝 |
|
||||||
|
| 2104 | 无法申请加好友 | 被对方拉黑 |
|
||||||
------
|
|
||||||
|
------
|
||||||
### 3.5 群聊相关错误(2200 ~ 2299)
|
|
||||||
|
### 3.5 群聊相关错误(2200 ~ 2299)
|
||||||
| code | message | 说明 |
|
|
||||||
| ---- | ------------ | ------------- |
|
| code | message | 说明 |
|
||||||
| 2200 | 群不存在 | 查询不到群 |
|
| ---- | ------------ | ------------- |
|
||||||
| 2201 | 已在群中 | 不能重复加入 |
|
| 2200 | 群不存在 | 查询不到群 |
|
||||||
| 2202 | 群成员已满 | 超出限制 |
|
| 2201 | 已在群中 | 不能重复加入 |
|
||||||
| 2203 | 无加群权限 | 需要邀请/验证 |
|
| 2202 | 群成员已满 | 超出限制 |
|
||||||
| 2204 | 群邀请已过期 | 邀请链接过期 |
|
| 2203 | 无加群权限 | 需要邀请/验证 |
|
||||||
|
| 2204 | 群邀请已过期 | 邀请链接过期 |
|
||||||
------
|
|
||||||
|
------
|
||||||
### 3.6 消息相关错误(2300 ~ 2399)
|
|
||||||
|
### 3.6 消息相关错误(2300 ~ 2399)
|
||||||
| code | message | 说明 |
|
|
||||||
| ---- | ---------------- | ------------------- |
|
| code | message | 说明 |
|
||||||
| 2300 | 消息发送失败 | 发送时异常 |
|
| ---- | ---------------- | ------------------- |
|
||||||
| 2301 | 消息不存在 | 查询不到 |
|
| 2300 | 消息发送失败 | 发送时异常 |
|
||||||
| 2302 | 消息撤回失败 | 超过时间限制 |
|
| 2301 | 消息不存在 | 查询不到 |
|
||||||
| 2303 | 不支持的消息类型 | message_type 不合法 |
|
| 2302 | 消息撤回失败 | 超过时间限制 |
|
||||||
|
| 2303 | 不支持的消息类型 | message_type 不合法 |
|
||||||
------
|
|
||||||
|
------
|
||||||
### 3.7 文件相关错误(2400 ~ 2499)
|
|
||||||
|
### 3.7 文件相关错误(2400 ~ 2499)
|
||||||
| code | message | 说明 |
|
|
||||||
| ---- | -------------- | ------------ |
|
| code | message | 说明 |
|
||||||
| 2400 | 文件上传失败 | 存储服务错误 |
|
| ---- | -------------- | ------------ |
|
||||||
| 2401 | 文件不存在 | 下载时未找到 |
|
| 2400 | 文件上传失败 | 存储服务错误 |
|
||||||
| 2402 | 文件大小超限 | 超过配置限制 |
|
| 2401 | 文件不存在 | 下载时未找到 |
|
||||||
| 2403 | 文件类型不支持 | 格式不允许 |
|
| 2402 | 文件大小超限 | 超过配置限制 |
|
||||||
|
| 2403 | 文件类型不支持 | 格式不允许 |
|
||||||
------
|
|
||||||
|
------
|
||||||
### 3.8 管理后台相关错误(3000 ~ 3099)
|
|
||||||
|
### 3.8 管理后台相关错误(3000 ~ 3099)
|
||||||
| code | message | 说明 |
|
|
||||||
| ---- | ------------ | ---------------- |
|
| code | message | 说明 |
|
||||||
| 3000 | 管理员不存在 | 账号错误 |
|
| ---- | ------------ | ---------------- |
|
||||||
| 3001 | 密码错误 | 后台登录失败 |
|
| 3000 | 管理员不存在 | 账号错误 |
|
||||||
| 3002 | 角色不存在 | 角色未找到 |
|
| 3001 | 密码错误 | 后台登录失败 |
|
||||||
| 3003 | 权限不足 | 无操作权限 |
|
| 3002 | 角色不存在 | 角色未找到 |
|
||||||
| 3004 | 操作记录失败 | 后台日志写入失败 |
|
| 3003 | 权限不足 | 无操作权限 |
|
||||||
|
| 3004 | 操作记录失败 | 后台日志写入失败 |
|
||||||
------
|
|
||||||
|
------
|
||||||
## 4. 响应示例
|
|
||||||
|
## 4. 响应示例
|
||||||
### 成功示例
|
|
||||||
|
### 成功示例
|
||||||
```
|
|
||||||
{
|
```
|
||||||
"code": 0,
|
{
|
||||||
"message": "好友申请成功",
|
"code": 0,
|
||||||
"data": {
|
"message": "好友申请成功",
|
||||||
"requestId": 12345
|
"data": {
|
||||||
}
|
"requestId": 12345
|
||||||
}
|
}
|
||||||
```
|
}
|
||||||
|
```
|
||||||
### 失败示例
|
|
||||||
|
### 失败示例
|
||||||
```
|
|
||||||
{
|
```
|
||||||
"code": 2100,
|
{
|
||||||
"message": "好友申请已存在",
|
"code": 2100,
|
||||||
"data": null
|
"message": "好友申请已存在",
|
||||||
}
|
"data": null
|
||||||
|
}
|
||||||
```
|
```
|
||||||
8
frontend/web/.editorconfig
Normal file
8
frontend/web/.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
1
frontend/web/.gitattributes
vendored
Normal file
1
frontend/web/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
30
frontend/web/.gitignore
vendored
Normal file
30
frontend/web/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
6
frontend/web/.prettierrc.json
Normal file
6
frontend/web/.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
9
frontend/web/.vscode/extensions.json
vendored
Normal file
9
frontend/web/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"vitest.explorer",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
50
frontend/web/README.md
Normal file
50
frontend/web/README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# web
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
32
frontend/web/eslint.config.js
Normal file
32
frontend/web/eslint.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
import globals from 'globals'
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import pluginVitest from '@vitest/eslint-plugin'
|
||||||
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
js.configs.recommended,
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
|
||||||
|
{
|
||||||
|
...pluginVitest.configs.recommended,
|
||||||
|
files: ['src/**/__tests__/*'],
|
||||||
|
},
|
||||||
|
skipFormatting,
|
||||||
|
])
|
||||||
13
frontend/web/index.html
Normal file
13
frontend/web/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
frontend/web/jsconfig.json
Normal file
8
frontend/web/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
6334
frontend/web/package-lock.json
generated
Normal file
6334
frontend/web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
frontend/web/package.json
Normal file
37
frontend/web/package.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"lint": "eslint . --fix",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"vue": "^3.5.22",
|
||||||
|
"vue-router": "^4.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vitest/eslint-plugin": "^1.3.13",
|
||||||
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"eslint": "^9.33.0",
|
||||||
|
"eslint-plugin-vue": "~10.4.0",
|
||||||
|
"globals": "^16.3.0",
|
||||||
|
"jsdom": "^27.0.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"vite": "^7.1.7",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.2",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
frontend/web/public/favicon.ico
Normal file
BIN
frontend/web/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
11
frontend/web/src/App.vue
Normal file
11
frontend/web/src/App.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>You did it!</h1>
|
||||||
|
<p>
|
||||||
|
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
||||||
|
documentation
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
11
frontend/web/src/__tests__/App.spec.js
Normal file
11
frontend/web/src/__tests__/App.spec.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import App from '../App.vue'
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('mounts renders properly', () => {
|
||||||
|
const wrapper = mount(App)
|
||||||
|
expect(wrapper.text()).toContain('You did it!')
|
||||||
|
})
|
||||||
|
})
|
||||||
12
frontend/web/src/main.js
Normal file
12
frontend/web/src/main.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
8
frontend/web/src/router/index.js
Normal file
8
frontend/web/src/router/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
12
frontend/web/src/stores/counter.js
Normal file
12
frontend/web/src/stores/counter.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
||||||
18
frontend/web/vite.config.js
Normal file
18
frontend/web/vite.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
14
frontend/web/vitest.config.js
Normal file
14
frontend/web/vitest.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue
Block a user