Просмотр исходного кода

😉增加GoView大屏后台服务插件

zuohuaijun 3 лет назад
Родитель
Сommit
5c6509b3bb
18 измененных файлов с 891 добавлено и 1 удалено
  1. 1 0
      Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj
  2. 6 0
      Admin.NET/Admin.NET.Application/Configuration/Swagger.json
  3. 1 1
      Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj
  4. 6 0
      Admin.NET/Admin.NET.sln
  5. 22 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Admin.NET.Plugin.GoView.csproj
  6. 13 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Const/GoViewConst.cs
  7. 35 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProject.cs
  8. 20 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProjectData.cs
  9. 20 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Enum/GoViewProjectState.cs
  10. 24 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/GlobalUsings.cs
  11. 75 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/Dto/ProjectInput.cs
  12. 121 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/Dto/ProjectOutput.cs
  13. 252 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/ProjectService.cs
  14. 19 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/LoginInput.cs
  15. 55 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/LoginOutput.cs
  16. 13 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/OssUrlOutput.cs
  17. 74 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/SystemService.cs
  18. 134 0
      Admin.NET/Plugins/Admin.NET.Plugin.GoView/Util/GoViewResultProvider.cs

+ 1 - 0
Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj

@@ -27,6 +27,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
+    <ProjectReference Include="..\Plugins\Admin.NET.Plugin.GoView\Admin.NET.Plugin.GoView.csproj" />
   </ItemGroup>
 
   <ItemGroup>

+ 6 - 0
Admin.NET/Admin.NET.Application/Configuration/Swagger.json

@@ -27,6 +27,12 @@
                     "Email": "515096995@qq.com",
                     "Url": "https://gitee.com/zuohuaijun/Admin.NET"
                 }
+            },
+            {
+                "Group": "Go-View",
+                "Title": "Go-View",
+                "Description": "GoView 是一个高效的拖拽式低代码数据可视化开发平台,将图表或页面元素封装为基础组件,无需编写代码即可制作数据大屏,减少心智负担。<br/><a href='https://www.mtruning.club/'>https://www.mtruning.club</a>",
+                "Version": "1.2.6"
             }
         ],
         "EnableAllGroups": true,

+ 1 - 1
Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>

+ 6 - 0
Admin.NET/Admin.NET.sln

@@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		.editorconfig = .editorconfig
 	EndProjectSection
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.NET.Plugin.GoView", "Plugins\Admin.NET.Plugin.GoView\Admin.NET.Plugin.GoView.csproj", "{66105CB6-4AFA-4219-991C-504EC17CE210}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -38,6 +40,10 @@ Global
 		{11EA630B-4600-4236-A117-CE6C6CD67586}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{11EA630B-4600-4236-A117-CE6C6CD67586}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{11EA630B-4600-4236-A117-CE6C6CD67586}.Release|Any CPU.Build.0 = Release|Any CPU
+		{66105CB6-4AFA-4219-991C-504EC17CE210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{66105CB6-4AFA-4219-991C-504EC17CE210}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{66105CB6-4AFA-4219-991C-504EC17CE210}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{66105CB6-4AFA-4219-991C-504EC17CE210}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 22 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Admin.NET.Plugin.GoView.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>disable</Nullable>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn>1701;1702;1591;8632</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn>1701;1702;1591;8632</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Admin.NET.Core\Admin.NET.Core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 13 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Const/GoViewConst.cs

@@ -0,0 +1,13 @@
+namespace Admin.NET.Plugin.GoView.Const;
+
+/// <summary>
+/// GoView 相关常量
+/// </summary>
+[Const("GoView 相关常量")]
+public class GoViewConst
+{
+    /// <summary>
+    /// API分组名称
+    /// </summary>
+    public const string GroupName = "Go-View";
+}

+ 35 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProject.cs

@@ -0,0 +1,35 @@
+namespace Admin.NET.Plugin.GoView.Entity;
+
+/// <summary>
+/// GoView 项目表
+/// </summary>
+[SugarTable(null, "GoView 项目表")]
+public class GoViewProject : EntityTenant
+{
+    /// <summary>
+    /// 项目名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "项目名称", Length = 64)]
+    [Required, MaxLength(64)]
+    public string ProjectName { get; set; }
+
+    /// <summary>
+    /// 项目状态
+    /// </summary>
+    [SugarColumn(ColumnDescription = "项目状态")]
+    public GoViewProjectState State { get; set; }
+
+    /// <summary>
+    /// 预览图片Url
+    /// </summary>
+    [SugarColumn(ColumnDescription = "预览图片Url", Length = 1024)]
+    [MaxLength(1024)]
+    public string? IndexImage { get; set; }
+
+    /// <summary>
+    /// 项目备注
+    /// </summary>
+    [SugarColumn(ColumnDescription = "项目备注", Length = 512)]
+    [MaxLength(512)]
+    public string? Remarks { get; set; }
+}

+ 20 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProjectData.cs

@@ -0,0 +1,20 @@
+namespace Admin.NET.Plugin.GoView.Entity;
+
+/// <summary>
+/// GoView 项目数据表
+/// </summary>
+[SugarTable(null, "GoView 项目数据表")]
+public class GoViewProjectData : EntityTenant
+{
+    /// <summary>
+    /// 项目参数
+    /// </summary>
+    [SugarColumn(ColumnDescription = "项目参数", ColumnDataType = StaticConfig.CodeFirst_BigString)]
+    public string? Content { get; set; }
+
+    /// <summary>
+    /// 预览图片
+    /// </summary>
+    [SugarColumn(ColumnDescription = "预览图片", ColumnDataType = StaticConfig.CodeFirst_BigString)]
+    public string? IndexImageData { get; set; }
+}

+ 20 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Enum/GoViewProjectState.cs

@@ -0,0 +1,20 @@
+namespace Admin.NET.Plugin.GoView.Enum;
+
+/// <summary>
+/// GoView 项目状态
+/// </summary>
+[Description("GoView 项目状态")]
+public enum GoViewProjectState
+{
+    /// <summary>
+    /// 未发布
+    /// </summary>
+    [Description("未发布")]
+    UnPublish = -1,
+
+    /// <summary>
+    /// 已发布
+    /// </summary>
+    [Description("已发布")]
+    Published = 1,
+}

+ 24 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/GlobalUsings.cs

@@ -0,0 +1,24 @@
+global using Admin.NET.Core;
+global using Admin.NET.Core.Service;
+global using Admin.NET.Plugin.GoView.Const;
+global using Admin.NET.Plugin.GoView.Entity;
+global using Admin.NET.Plugin.GoView.Enum;
+global using Admin.NET.Plugin.GoView.Service.Dto;
+global using Furion;
+global using Furion.DatabaseAccessor;
+global using Furion.DataValidation;
+global using Furion.DynamicApiController;
+global using Furion.FriendlyException;
+global using Furion.JsonSerialization;
+global using Furion.UnifyResult;
+global using Mapster;
+global using Microsoft.AspNetCore.Authorization;
+global using Microsoft.AspNetCore.Http;
+global using Microsoft.AspNetCore.Mvc;
+global using Microsoft.AspNetCore.Mvc.Filters;
+global using Newtonsoft.Json;
+global using SqlSugar;
+global using System.ComponentModel;
+global using System.ComponentModel.DataAnnotations;
+global using System.Data;
+global using System.Linq.Dynamic.Core;

+ 75 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/Dto/ProjectInput.cs

@@ -0,0 +1,75 @@
+namespace Admin.NET.Plugin.GoView.Service.Dto;
+
+/// <summary>
+/// GoView 新增项目
+/// </summary>
+public class ProjectCreateInput
+{
+    /// <summary>
+    /// 项目名称
+    /// </summary>
+    public string ProjectName { get; set; }
+
+    /// <summary>
+    /// 项目备注
+    /// </summary>
+    public string Remarks { get; set; }
+
+    /// <summary>
+    /// 预览图片url
+    /// </summary>
+    public string IndexImage { get; set; }
+}
+
+/// <summary>
+/// GoView 保存项目
+/// </summary>
+public class ProjectSaveDataInput
+{
+    /// <summary>
+    /// 项目Id
+    /// </summary>
+    public long ProjectId { get; set; }
+
+    /// <summary>
+    /// 项目参数
+    /// </summary>
+    public string Content { get; set; }
+}
+
+/// <summary>
+/// GoView 编辑项目
+/// </summary>
+public class ProjectEditInput
+{
+    /// <summary>
+    /// 项目Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 项目名称
+    /// </summary>
+    public string ProjectName { get; set; }
+
+    /// <summary>
+    /// 预览图片url
+    /// </summary>
+    public string IndexImage { get; set; }
+}
+
+/// <summary>
+/// GoView 修改项目发布状态
+/// </summary>
+public class ProjectPublishInput
+{
+    /// <summary>
+    /// 项目Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 项目状态
+    /// </summary>
+    public GoViewProjectState State { get; set; }
+}

+ 121 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/Dto/ProjectOutput.cs

@@ -0,0 +1,121 @@
+namespace Admin.NET.Plugin.GoView.Service.Dto;
+
+/// <summary>
+/// GoView 项目 Item
+/// </summary>
+public class ProjectItemOutput
+{
+    /// <summary>
+    /// 项目Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 项目名称
+    /// </summary>
+    public string ProjectName { get; set; }
+
+    /// <summary>
+    /// 项目状态
+    /// </summary>
+    public GoViewProjectState State { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 预览图片url
+    /// </summary>
+    public string IndexImage { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 项目备注
+    /// </summary>
+    public string Remarks { get; set; }
+}
+
+/// <summary>
+/// GoView 项目 Detail
+/// </summary>
+public class ProjectDetailOutput : ProjectItemOutput
+{
+    /// <summary>
+    /// 项目参数
+    /// </summary>
+    public string Content { get; set; }
+}
+
+/// <summary>
+/// GoView 新增项目输出
+/// </summary>
+public class ProjectCreateOutput
+{
+    /// <summary>
+    /// 项目Id
+    /// </summary>
+    public long Id { get; set; }
+}
+
+/// <summary>
+/// GoView 上传项目输出
+/// </summary>
+public class ProjectUploadOutput
+{
+    /// <summary>
+    /// Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 仓储名称
+    /// </summary>
+    public string BucketName { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 文件名称
+    /// </summary>
+    public string FileName { get; set; }
+
+    /// <summary>
+    /// 文件大小KB
+    /// </summary>
+    public int FileSize { get; set; }
+
+    /// <summary>
+    /// 文件后缀
+    /// </summary>
+    public string FileSuffix { get; set; }
+
+    /// <summary>
+    /// 文件 Url
+    /// </summary>
+    [JsonProperty("fileurl")]
+    public string FileUrl { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    public long? UpdateUserId { get; set; }
+}

+ 252 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/Project/ProjectService.cs

@@ -0,0 +1,252 @@
+namespace Admin.NET.Plugin.GoView.Service;
+
+/// <summary>
+/// 项目管理服务
+/// </summary>
+[UnifyProvider("GoView")]
+[ApiDescriptionSettings(GoViewConst.GroupName, Order = 100)]
+public class ProjectService : IDynamicApiController
+{
+    private readonly SqlSugarRepository<GoViewProject> _goViewProjectRep;
+    private readonly SqlSugarRepository<GoViewProjectData> _goViewProjectDataRep;
+    private readonly SqlSugarRepository<SysFile> _sysFileRep;
+    private readonly SysFileService _fileService;
+
+    public ProjectService(SqlSugarRepository<GoViewProject> goViewProjectRep,
+        SqlSugarRepository<GoViewProjectData> goViewProjectDataRep,
+        SqlSugarRepository<SysFile> fileRep,
+        SysFileService fileService)
+    {
+        _goViewProjectRep = goViewProjectRep;
+        _goViewProjectDataRep = goViewProjectDataRep;
+        _sysFileRep = fileRep;
+        _fileService = fileService;
+    }
+
+    /// <summary>
+    /// 获取项目列表
+    /// </summary>
+    [DisplayName("项目列表")]
+    public async Task<List<ProjectItemOutput>> GetList([FromQuery] int page = 1, [FromQuery] int limit = 12)
+    {
+        var pagedList = await _goViewProjectRep.AsQueryable()
+            .Select(u => new ProjectItemOutput(), true)
+            .ToPagedListAsync(page, limit);
+
+        UnifyContext.Fill(pagedList.Total);
+        return pagedList.Items.ToList();
+    }
+
+    /// <summary>
+    /// 新增项目
+    /// </summary>
+    [ApiDescriptionSettings(Name = "Create")]
+    [DisplayName("新增项目")]
+    public async Task<ProjectCreateOutput> Create(ProjectCreateInput input)
+    {
+        var project = input.Adapt<GoViewProject>();
+        project.State = GoViewProjectState.UnPublish;
+
+        project = await _goViewProjectRep.AsInsertable(project).ExecuteReturnEntityAsync();
+
+        return new ProjectCreateOutput
+        {
+            Id = project.Id
+        };
+    }
+
+    /// <summary>
+    /// 获取项目
+    /// </summary>
+    [AllowAnonymous]
+    [ApiDescriptionSettings(Name = "GetData")]
+    [DisplayName("获取项目")]
+    public async Task<ProjectDetailOutput> GetData([FromQuery] long projectId)
+    {
+        var projectData = await _goViewProjectDataRep.GetFirstAsync(u => u.Id == projectId);
+        if (projectData == null) return null;
+
+        var project = await _goViewProjectRep.GetFirstAsync(u => u.Id == projectId);
+
+        var projectDetail = project.Adapt<ProjectDetailOutput>();
+        projectDetail.Content = projectData.Content;
+
+        return projectDetail;
+    }
+
+    /// <summary>
+    /// 保存项目
+    /// </summary>
+    [ApiDescriptionSettings(Name = "save/data")]
+    [DisplayName("保存项目")]
+    public async Task SaveData([FromForm] ProjectSaveDataInput input)
+    {
+        if (await _goViewProjectDataRep.IsAnyAsync(u => u.Id == input.ProjectId))
+        {
+            await _goViewProjectDataRep.AsUpdateable()
+                .SetColumns(u => new GoViewProjectData
+                {
+                    Content = input.Content
+                })
+                .Where(u => u.Id == input.ProjectId)
+                .ExecuteCommandAsync();
+        }
+        else
+        {
+            await _goViewProjectDataRep.InsertAsync(new GoViewProjectData
+            {
+                Id = input.ProjectId,
+                Content = input.Content,
+            });
+        }
+    }
+
+    /// <summary>
+    /// 修改项目
+    /// </summary>
+    [DisplayName("修改项目")]
+    public async Task Edit(ProjectEditInput input)
+    {
+        // 前端只传修改的字段,更新时需要忽略空列
+        var project = await _goViewProjectRep.GetFirstAsync(u => u.Id == input.Id);
+        input.Adapt(project);
+        await _goViewProjectRep.AsUpdateable(project).IgnoreColumns(true).ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 删除项目
+    /// </summary>
+    [ApiDescriptionSettings(Name = "Delete")]
+    [DisplayName("删除项目")]
+    [UnitOfWork]
+    public async Task Delete([FromQuery] string ids)
+    {
+        var idList = ids.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(u => Convert.ToInt64(u)).ToList();
+        await _goViewProjectRep.AsDeleteable().Where(u => idList.Contains(u.Id)).ExecuteCommandAsync();
+        await _goViewProjectDataRep.AsDeleteable().Where(u => idList.Contains(u.Id)).ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 修改发布状态
+    /// </summary>
+    [HttpPut]
+    [DisplayName("修改发布状态")]
+    public async Task Publish(ProjectPublishInput input)
+    {
+        await _goViewProjectRep.AsUpdateable()
+            .SetColumns(u => new GoViewProject
+            {
+                State = input.State
+            })
+            .Where(u => u.Id == input.Id)
+            .ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 上传预览图
+    /// </summary>
+    [DisplayName("上传预览图")]
+    public async Task<ProjectUploadOutput> Upload(IFormFile @object)
+    {
+        /*
+         * 前端逻辑(useSync.hook.ts 的 dataSyncUpdate 方法):
+         * 如果 FileUrl 不为空,使用 FileUrl
+         * 否则使用 GetOssInfo 接口获取到的 BucketUrl 和 FileName 进行拼接
+         */
+
+        //文件名格式示例 13414795568325_index_preview.png
+        var fileNameSplit = @object.FileName.Split('_');
+        var idStr = fileNameSplit[0];
+        if (!long.TryParse(idStr, out var id)) return new ProjectUploadOutput();
+
+        //将预览图转换成 Base64
+        var ms = new MemoryStream();
+        await @object.CopyToAsync(ms);
+        var base64Image = Convert.ToBase64String(ms.ToArray());
+
+        //保存
+        if (await _goViewProjectDataRep.IsAnyAsync(u => u.Id == id))
+        {
+            await _goViewProjectDataRep.AsUpdateable()
+                .SetColumns(u => new GoViewProjectData
+                {
+                    IndexImageData = base64Image
+                })
+                .Where(u => u.Id == id)
+                .ExecuteCommandAsync();
+        }
+        else
+        {
+            await _goViewProjectDataRep.InsertAsync(new GoViewProjectData
+            {
+                Id = id,
+                IndexImageData = base64Image,
+            });
+        }
+
+        var output = new ProjectUploadOutput
+        {
+            Id = id,
+            BucketName = null,
+            CreateTime = null,
+            CreateUserId = null,
+            FileName = null,
+            FileSize = 0,
+            FileSuffix = "png",
+            FileUrl = $"api/goview/project/getIndexImage/{id}",
+            UpdateTime = null,
+            UpdateUserId = null
+        };
+
+        #region 使用 SysFileService 方式(已注释)
+
+        ////删除已存在的预览图
+        //var uploadFileName = Path.GetFileNameWithoutExtension(@object.FileName);
+        //var existFiles = await _fileRep.GetListAsync(u => u.FileName == uploadFileName);
+        //foreach (var f in existFiles)
+        //    await _fileService.DeleteFile(new DeleteFileInput { Id = f.Id });
+
+        ////保存预览图
+        //var result = await _fileService.UploadFile(@object, "");
+        //var file = await _fileRep.GetFirstAsync(u => u.Id == result.Id);
+        //int.TryParse(file.SizeKb, out var size);
+
+        ////本地存储,使用拼接的地址
+        //var fileUrl = file.BucketName == "Local" ? $"{file.FilePath}/{file.Id}{file.Suffix}" : file.Url;
+
+        //var output = new ProjectUploadOutput
+        //{
+        //    Id = file.Id,
+        //    BucketName = file.BucketName,
+        //    CreateTime = file.CreateTime,
+        //    CreateUserId = file.CreateUserId,
+        //    FileName = $"{file.FileName}{file.Suffix}",
+        //    FileSize = size,
+        //    FileSuffix = file.Suffix?[1..],
+        //    FileUrl = fileUrl,
+        //    UpdateTime = null,
+        //    UpdateUserId = null
+        //};
+
+        #endregion 使用 SysFileService 方式(已注释)
+
+        return output;
+    }
+
+    /// <summary>
+    /// 获取预览图
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [ApiDescriptionSettings(Name = "GetIndexImage")]
+    [DisplayName("获取预览图")]
+    public async Task<IActionResult> GetIndexImage(long id)
+    {
+        var projectData = await _goViewProjectDataRep.AsQueryable().IgnoreColumns(u => u.Content).FirstAsync(u => u.Id == id);
+        if (projectData?.IndexImageData == null)
+            return new NoContentResult();
+
+        var bytes = Convert.FromBase64String(projectData.IndexImageData);
+        return new FileStreamResult(new MemoryStream(bytes), "image/png");
+    }
+}

+ 19 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/LoginInput.cs

@@ -0,0 +1,19 @@
+namespace Admin.NET.Plugin.GoView.Service.Dto;
+
+/// <summary>
+/// 登录输入
+/// </summary>
+public class LoginInput
+{
+    /// <summary>
+    /// 用户名
+    /// </summary>
+    [Required(ErrorMessage = "用户名不能为空")]
+    public string Username { get; set; }
+
+    /// <summary>
+    /// 密码
+    /// </summary>
+    [Required(ErrorMessage = "密码不能为空")]
+    public string Password { get; set; }
+}

+ 55 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/LoginOutput.cs

@@ -0,0 +1,55 @@
+namespace Admin.NET.Plugin.GoView.Service.Dto;
+
+/// <summary>
+/// 登录输出
+/// </summary>
+public class LoginOutput
+{
+    /// <summary>
+    /// 登录 token
+    /// </summary>
+    public LoginToken Token { get; set; }
+
+    /// <summary>
+    /// 用户信息
+    /// </summary>
+    [JsonProperty("userinfo")]
+    public LoginUserInfo UserInfo { get; set; }
+}
+
+/// <summary>
+/// 登录 Token
+/// </summary>
+public class LoginToken
+{
+    /// <summary>
+    /// token 值
+    /// </summary>
+    public string TokenValue { get; set; }
+
+    /// <summary>
+    /// token key
+    /// </summary>
+    public string TokenName { get; set; } = "Authorization";
+}
+
+/// <summary>
+/// 用户信息
+/// </summary>
+public class LoginUserInfo
+{
+    /// <summary>
+    /// 昵称
+    /// </summary>
+    public string Nickname { get; set; }
+
+    /// <summary>
+    /// 用户名
+    /// </summary>
+    public string Username { get; set; }
+
+    /// <summary>
+    /// 用户 id
+    /// </summary>
+    public string Id { get; set; }
+}

+ 13 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/Dto/OssUrlOutput.cs

@@ -0,0 +1,13 @@
+namespace Admin.NET.Plugin.GoView.Service.Dto;
+
+/// <summary>
+/// 获取 oss 上传接口输出
+/// </summary>
+public class OssUrlOutput
+{
+    /// <summary>
+    /// bucket 地址
+    /// </summary>
+    [JsonProperty("bucketURL")]
+    public string BucketUrl { get; set; }
+}

+ 74 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/System/SystemService.cs

@@ -0,0 +1,74 @@
+using LoginInput = Admin.NET.Plugin.GoView.Service.Dto.LoginInput;
+using LoginOutput = Admin.NET.Plugin.GoView.Service.Dto.LoginOutput;
+
+namespace Admin.NET.Plugin.GoView.Service;
+
+/// <summary>
+/// 系统登录服务
+/// </summary>
+[UnifyProvider("GoView")]
+[ApiDescriptionSettings(GoViewConst.GroupName, Order = 500)]
+public class SystemService : IDynamicApiController
+{
+    private readonly SysAuthService _sysAuthService;
+    private readonly SqlSugarRepository<SysUser> _userRep;
+
+    public SystemService(SysAuthService sysAuthService, SqlSugarRepository<SysUser> userRep)
+    {
+        _sysAuthService = sysAuthService;
+        _userRep = userRep;
+    }
+
+    /// <summary>
+    /// GoView 登录
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [DisplayName("GoView 登录")]
+    public async Task<LoginOutput> Login(LoginInput input)
+    {
+        var loginResult = await _sysAuthService.Login(new Core.Service.LoginInput()
+        {
+            Account = input.Username,
+            Password = input.Password,
+        });
+
+        var user = await _userRep.AsQueryable().Includes(t => t.SysOrg).Filter(null, true).FirstAsync(u => u.Account.Equals(input.Username));
+
+        return new LoginOutput()
+        {
+            UserInfo = new LoginUserInfo
+            {
+                Id = user.Id + "",
+                Username = user.Account,
+                Nickname = user.NickName,
+            },
+            Token = new LoginToken
+            {
+                TokenValue = $"Bearer {loginResult.AccessToken}",
+            }
+        };
+    }
+
+    /// <summary>
+    /// GoView 退出
+    /// </summary>
+    [HttpGet]
+    [DisplayName("GoView 退出")]
+    public void Logout()
+    {
+        _sysAuthService.Logout();
+    }
+
+    /// <summary>
+    /// 获取 OSS 上传接口
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [ApiDescriptionSettings(Name = "GetOssInfo")]
+    [DisplayName("获取 OSS 上传接口")]
+    public Task<OssUrlOutput> GetOssInfo()
+    {
+        return Task.FromResult(new OssUrlOutput { BucketUrl = "" });
+    }
+}

+ 134 - 0
Admin.NET/Plugins/Admin.NET.Plugin.GoView/Util/GoViewResultProvider.cs

@@ -0,0 +1,134 @@
+namespace Admin.NET.Plugin.GoView.Util;
+
+/// <summary>
+/// GoView 规范化结果
+/// </summary>
+[UnifyModel(typeof(GoViewResult<>))]
+public class GoViewResultProvider : IUnifyResultProvider
+{
+    /// <summary>
+    /// 异常返回值
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="metadata"></param>
+    /// <returns></returns>
+    public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
+    {
+        return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
+    }
+
+    /// <summary>
+    /// 成功返回值
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    public IActionResult OnSucceeded(ActionExecutedContext context, object data)
+    {
+        return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
+    }
+
+    /// <summary>
+    /// 验证失败返回值
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="metadata"></param>
+    /// <returns></returns>
+    public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
+    {
+        return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult));
+    }
+
+    /// <summary>
+    /// 特定状态码返回值
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="statusCode"></param>
+    /// <param name="unifyResultSettings"></param>
+    /// <returns></returns>
+    public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
+    {
+        // 设置响应状态码
+        UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);
+
+        switch (statusCode)
+        {
+            // 处理 401 状态码
+            case StatusCodes.Status401Unauthorized:
+                await context.Response.WriteAsJsonAsync(RESTfulResult(886, errors: "401 登录已过期,请重新登录"),
+                    App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
+                break;
+            // 处理 403 状态码
+            case StatusCodes.Status403Forbidden:
+                await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 禁止访问,没有权限"),
+                    App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
+                break;
+
+            default: break;
+        }
+    }
+
+    /// <summary>
+    /// 返回 RESTful 风格结果集
+    /// </summary>
+    /// <param name="statusCode"></param>
+    /// <param name="succeeded"></param>
+    /// <param name="data"></param>
+    /// <param name="errors"></param>
+    /// <returns></returns>
+    private static GoViewResult RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
+    {
+        if (data == null)
+        {
+            return new GoViewResult
+            {
+                Code = statusCode,
+                Msg = errors is null or string ? (errors + "") : JSON.Serialize(errors),
+            };
+        }
+        else
+        {
+            var parseCount = int.TryParse(UnifyContext.Take() + "", out var count);
+            return new GoViewResult<object>
+            {
+                Code = statusCode,
+                Msg = errors is null or string ? (errors + "") : JSON.Serialize(errors),
+                Data = data,
+                Count = parseCount ? count : null,
+            };
+        }
+    }
+}
+
+/// <summary>
+/// GoView 返回结果
+/// </summary>
+public class GoViewResult
+{
+    /// <summary>
+    /// 状态码
+    /// </summary>
+    public int Code { get; set; }
+
+    /// <summary>
+    /// 信息
+    /// </summary>
+    public string Msg { get; set; }
+}
+
+/// <summary>
+/// GoView 返回结果
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public class GoViewResult<T> : GoViewResult
+{
+    /// <summary>
+    /// 数据
+    /// </summary>
+    public T Data { get; set; }
+
+    /// <summary>
+    /// 总数
+    /// </summary>
+    public int? Count { get; set; }
+}