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 字段,实现了抽象类的正确序列化和反序列化。

设计模式应用

  1. 多态模式:评论者的不同类型处理
  2. 适配器模式:通过 Mapster 进行实体到视图模型的转换
  3. 策略模式:通过自定义转换器处理不同的序列化策略

这个设计很好地分离了数据层和表现层,同时处理了评论系统中的复杂业务逻辑(如删除处理、多类型用户等)。

评论加载中...