using System.Text.Json;
using System.Text.Json.Serialization;
using Dpz.Core.Entity.Base.MapperConfig;
using Dpz.Core.EnumLibrary;
using Dpz.Core.Public.Entity;
using TypeAdapterConfig = Mapster.TypeAdapterConfig;
namespace Dpz.Core.Public.ViewModel;
/// <summary>
/// 评论 出参
/// </summary>
public class CommentViewModel : IHaveCustomMapping
{
/// <summary>
/// Gets or sets Id.
/// </summary>
public required string Id { get; set; }
/// <summary>
/// 评论类型
/// </summary>
public CommentNode Node { get; set; }
/// <summary>
/// 关联
/// </summary>
public required string Relation { get; set; }
/// <summary>
/// 回复时间
/// </summary>
public DateTime PublishTime { get; set; }
/// <summary>
/// 评论人 (多态)分为匿名评论和成员评论
/// </summary>
[JsonConverter(typeof(CommenterConverter))]
public required VmCommenter Commenter { get; set; }
/// <summary>
/// 回复内容
/// </summary>
public required string CommentText { get; set; }
/// <summary>
/// 回复ID
/// </summary>
public List<string> Replies { get; set; } = [];
/// <summary>
/// 是否删除
/// </summary>
public required bool IsDelete { get; set; }
/// <summary>
/// 回复
/// </summary>
public List<CommentChildren> Children { get; set; } = [];
/// <summary>
/// Executes CreateMappings.
/// </summary>
public static void CreateMappings(TypeAdapterConfig cfg)
{
cfg.NewConfig<Comment, CommentViewModel>().MapWith(x => CreateCommentViewModel(x));
}
private static CommentViewModel CreateCommentViewModel(Comment entity)
{
var commenter = entity.Commenter;
var commentText = entity.CommentText;
if (entity.IsDelete)
{
commenter = new GuestCommenter
{
Email = "",
NickName = "该用户发言已被删除",
Site = "",
};
commentText = "该用户发言因某些原因已被删除";
}
var viewModel = new CommentViewModel
{
Id = entity.Id.ToString(),
Node = entity.Node,
Relation = entity.Relation,
PublishTime = entity.PublishTime.ToLocalTime(),
CommentText = commentText,
Replies = entity.Replies.Select(x => x.ToString()).ToList(),
IsDelete = entity.IsDelete,
Children = new List<CommentChildren>(),
Commenter = CommentMappingHelper.ToViewModelCommenter(commenter),
};
return viewModel;
}
}
/// <summary>
/// Represents the CommentChildren type.
/// </summary>
public class CommentChildren : IHaveCustomMapping
{
/// <summary>
/// Gets or sets Id.
/// </summary>
public required string Id { get; set; }
/// <summary>
/// 回复时间
/// </summary>
public DateTime PublishTime { get; set; }
/// <summary>
/// 评论人
/// </summary>
[JsonConverter(typeof(CommenterConverter))]
public required VmCommenter Commenter { get; set; }
/// <summary>
/// 回复内容
/// </summary>
public required string CommentText { get; set; }
/// <summary>
/// 回复ID
/// </summary>
public List<string> Replies { get; set; } = [];
/// <summary>
/// 是否删除
/// </summary>
public required bool IsDelete { get; set; }
/// <summary>
/// 被回复对象
/// </summary>
public CommentReplyTo? ReplyTo { get; set; }
/// <summary>
/// Executes CreateMappings.
/// </summary>
public static void CreateMappings(TypeAdapterConfig cfg)
{
cfg.NewConfig<Comment, CommentChildren>().MapWith(x => CreateCommentChildren(x));
}
private static CommentChildren CreateCommentChildren(Comment entity)
{
var commenter = entity.Commenter;
var commentText = entity.CommentText;
if (entity.IsDelete)
{
commenter = new GuestCommenter
{
Email = "",
NickName = "该用户发言已被删除",
Site = "",
};
commentText = "该用户发言因某些原因已被删除";
}
var viewModel = new CommentChildren
{
Id = entity.Id.ToString(),
PublishTime = entity.PublishTime.ToLocalTime(),
CommentText = commentText,
Replies = entity.Replies.Select(x => x.ToString()).ToList(),
IsDelete = entity.IsDelete,
Commenter = CommentMappingHelper.ToViewModelCommenter(commenter),
};
return viewModel;
}
}
internal static class CommentMappingHelper
{
internal static VmCommenter ToViewModelCommenter(Commenter commenter)
{
return commenter switch
{
MembleCommenter membleCommenter => new VmMemberCommenter
{
NickName = membleCommenter.NickName,
Avatar = membleCommenter.Avatar,
Identity = membleCommenter.Identity,
},
GuestCommenter guestCommenter => new VmGuestCommenter(guestCommenter.Email)
{
NickName = guestCommenter.NickName,
Site = guestCommenter.Site,
},
_ => throw new NotSupportedException(
$"not supported commenter type:{commenter.GetType().Name}"
),
};
}
}
/// <summary>
/// 被回复对象信息
/// </summary>
/// <param name="Id">被回复评论ID</param>
/// <param name="NickName">被回复人昵称</param>
public record CommentReplyTo(string Id, string NickName);
/// <summary>
/// Represents the VmCommenter type.
/// </summary>
public abstract class VmCommenter
{
/// <summary>
/// 昵称
/// </summary>
public required string NickName { get; set; }
}
/// <summary>
/// 登录会员评论
/// </summary>
public class VmMemberCommenter : VmCommenter
{
/// <summary>
/// 头像
/// </summary>
public required string Avatar { get; set; }
/// <summary>
/// 身份标识
/// </summary>
public required string Identity { get; set; }
}
/// <summary>
/// 匿名评论
/// </summary>
public class VmGuestCommenter(string email) : VmCommenter
{
/// <summary>
/// 网站
/// </summary>
public string? Site { get; set; }
/// <summary>
/// 邮箱MD5
/// </summary>
public string EmailMd5 => email.GenerateHashMd5();
/// <summary>
/// 获取邮箱
/// </summary>
public string GetEmail() => email;
}
/// <summary>
/// Represents the CommenterConverter type.
/// </summary>
public class CommenterConverter : JsonConverter<VmCommenter>
{
/// <summary>
/// Executes Read.
/// </summary>
public override VmCommenter? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException("JsonTokenType.StartObject not found.");
}
if (
!reader.Read()
|| reader.TokenType != JsonTokenType.PropertyName
|| reader.GetString() != "$type"
)
{
throw new JsonException("Property $type not found.");
}
if (!reader.Read() || reader.TokenType != JsonTokenType.String)
{
throw new JsonException("Value at $type is invalid.");
}
var typeStr = reader.GetString();
var type = typeStr switch
{
"GuestCommenter" => typeof(VmGuestCommenter),
"MemberCommenter" => typeof(VmMemberCommenter),
_ => typeof(Commenter),
};
using var output = new MemoryStream();
ReadObject(ref reader, output, options);
var result = JsonSerializer.Deserialize(output.ToArray(), type, options);
return result as VmCommenter;
}
private void ReadObject(ref Utf8JsonReader reader, Stream output, JsonSerializerOptions options)
{
using var writer = new Utf8JsonWriter(
output,
new JsonWriterOptions { Encoder = options.Encoder, Indented = options.WriteIndented }
);
writer.WriteStartObject();
var objectIntend = 0;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.None:
case JsonTokenType.Null:
writer.WriteNullValue();
break;
case JsonTokenType.StartObject:
writer.WriteStartObject();
objectIntend++;
break;
case JsonTokenType.EndObject:
writer.WriteEndObject();
if (objectIntend == 0)
{
writer.Flush();
return;
}
objectIntend--;
break;
case JsonTokenType.StartArray:
writer.WriteStartArray();
break;
case JsonTokenType.EndArray:
writer.WriteEndArray();
break;
case JsonTokenType.PropertyName:
writer.WritePropertyName(reader.GetString() ?? "");
break;
case JsonTokenType.Comment:
writer.WriteCommentValue(reader.GetComment());
break;
case JsonTokenType.String:
writer.WriteStringValue(reader.GetString());
break;
case JsonTokenType.Number:
writer.WriteNumberValue(reader.GetInt32());
break;
case JsonTokenType.True:
case JsonTokenType.False:
writer.WriteBooleanValue(reader.GetBoolean());
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
/// <summary>
/// Executes Write.
/// </summary>
public override void Write(
Utf8JsonWriter writer,
VmCommenter value,
JsonSerializerOptions options
)
{
writer.WriteStartObject();
var valueType = value.GetType();
//var valueAssemblyName = valueType.Assembly.GetName();
writer.WriteString("$type", $"{valueType.Name.Replace("Vm", "")}");
var json = JsonSerializer.Serialize(value, value.GetType(), options);
using (
var document = JsonDocument.Parse(
json,
new JsonDocumentOptions
{
AllowTrailingCommas = options.AllowTrailingCommas,
MaxDepth = options.MaxDepth,
}
)
)
{
foreach (var jsonProperty in document.RootElement.EnumerateObject())
{
jsonProperty.WriteTo(writer);
}
}
writer.WriteEndObject();
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个用于评论系统的 ViewModel 层代码,主要用于数据传输和 JSON 序列化。以下是详细分析:
整体架构
这个代码文件定义了评论系统的视图模型(ViewModel),用于在 API 返回数据时的数据传输对象(DTO)。
主要组件分析
1. CommentViewModel 类
主评论视图模型
public class CommentViewModel : IHaveCustomMapping
- 表示主评论的完整信息
- 包含评论基本信息:ID、类型、关联对象、发布时间等
- 包含评论者信息(多态处理)
- 包含子评论列表(Children)
- 实现了自定义映射接口,用于从实体类
Comment转换为视图模型
2. CommentChildren 类
子评论视图模型
public class CommentChildren : IHaveCustomMapping
- 表示子评论(回复)的信息
- 结构与主评论类似,但增加了
ReplyTo属性来标识被回复的对象 - 同样实现了自定义映射
3. 评论者模型层次结构
多态设计模式
public abstract class VmCommenter // 抽象基类
├── VmMemberCommenter // 注册会员评论者
└── VmGuestCommenter // 匿名访客评论者
VmCommenter:抽象基类,定义昵称属性VmMemberCommenter:注册会员,包含头像和身份标识VmGuestCommenter:匿名用户,包含网站信息和邮箱MD5(用于头像生成)
4. CommentMappingHelper 工具类
类型转换辅助
internal static class CommentMappingHelper
使用 switch 表达式进行模式匹配,将实体层的 Commenter 转换为对应的视图模型类型。
5. CommenterConverter 自定义 JSON 转换器
多态 JSON 序列化 这是一个复杂的自定义 JSON 转换器,用于处理抽象类 VmCommenter 的序列化和反序列化:
写入过程(Write):
- 添加
$type属性标识具体类型 - 序列化对象的其他属性
读取过程(Read):
- 读取
$type属性确定具体类型 - 根据类型反序列化为对应的具体类
关键特性
1. 删除处理机制
if (entity.IsDelete)
{
commenter = new GuestCommenter
{
Email = "",
NickName = "该用户发言已被删除",
Site = "",
};
commentText = "该用户发言因某些原因已被删除";
}
当评论被删除时,会替换评论者信息和内容,而不是直接隐藏评论。
2. 时间本地化
PublishTime = entity.PublishTime.ToLocalTime()
将 UTC 时间转换为本地时间。
3. 多态序列化
通过自定义 JSON 转换器和 $type 字段,实现了抽象类的正确序列化和反序列化。
设计模式应用
- 多态模式:评论者的不同类型处理
- 适配器模式:通过 Mapster 进行实体到视图模型的转换
- 策略模式:通过自定义转换器处理不同的序列化策略
这个设计很好地分离了数据层和表现层,同时处理了评论系统中的复杂业务逻辑(如删除处理、多类型用户等)。
评论加载中...