新增SwaggerDemo

This commit is contained in:
2025-09-23 16:29:17 +08:00
parent dc42b56bf6
commit dad5b8fb89
27 changed files with 505 additions and 39 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

View File

@@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

View File

@@ -0,0 +1,41 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script>
window.onload = function () {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "@url",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold >
};
</script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<!doctype html>
<html lang="en-US">
<body>
</body>
</html>
<script src="oauth2-redirect.js"></script>

View File

@@ -0,0 +1 @@
"use strict";function run(){var e,r,t,a=window.opener.swaggerUIRedirectOauth2,o=a.state,n=a.redirectUrl;if((t=(r=/code|token|error/.test(window.location.hash)?window.location.hash.substring(1).replace("?","&"):location.search.substring(1)).split("&")).forEach((function(e,r,t){t[r]='"'+e.replace("=",'":"')+'"'})),e=(r=r?JSON.parse("{"+t.join()+"}",(function(e,r){return""===e?r:decodeURIComponent(r)})):{}).state===o,"accessCode"!==a.auth.schema.get("flow")&&"authorizationCode"!==a.auth.schema.get("flow")&&"authorization_code"!==a.auth.schema.get("flow")||a.auth.code)a.callback({auth:a.auth,token:r,isValid:e,redirectUrl:n});else if(e||a.errCb({authId:a.auth.name,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),r.code)delete a.state,a.auth.code=r.code,a.callback({auth:a.auth,redirectUrl:n});else{let e;r.error&&(e="["+r.error+"]: "+(r.error_description?r.error_description+". ":"no accessCode received from the server. ")+(r.error_uri?"More info: "+r.error_uri:"")),a.errCb({authId:a.auth.name,source:"auth",level:"error",message:e||"[Authorization failed]: no accessCode received from the server"})}window.close()}"loading"!==document.readyState?run():document.addEventListener("DOMContentLoaded",(function(){run()}));

View File

@@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using .Models;
using .Utility.Core;
using .Utility.Extension;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using .Models;
namespace .Base
{

View File

@@ -1,67 +1,80 @@
using System;
using System.Linq;
using System.Text;
using .Base;
using .Utility.Swagger;
namespace .Base
namespace .Models
{
/// <summary>
/// 配置类
/// </summary>
[SwaggerClassComment("配置项")]
public class MyConfig : ModelBase
{
/// <summary>
/// 时间
/// </summary>
[Comment("时间")]
[SwaggerComment("时间", true)]
public string Time { get; set; }
/// <summary>
/// 时间字体大小
/// </summary>
[Comment("时间字体大小")]
[SwaggerComment("时间字体大小", true, 15, 60)]
public int TimeSize { get; set; }
/// <summary>
/// 负责人
/// </summary>
[Comment("负责人")]
[SwaggerComment("负责人", true)]
public string Charger { get; set; }
/// <summary>
/// 负责人字体大小
/// </summary>
[Comment("负责人字体大小")]
[SwaggerComment("负责人字体大小", true, 15, 60)]
public int ChargerSize { get; set; }
/// <summary>
/// 风险等级
/// </summary>
[Comment("风险等级")]
[SwaggerComment("风险等级", true)]
public string RiskLevel { get; set; }
/// <summary>
/// 风险等级字体大小
/// </summary>
[Comment("风险等级字体大小")]
[SwaggerComment("风险等级字体大小", true, 15, 60)]
public int RiskLevelSize { get; set; }
/// <summary>
/// 工作内容
/// </summary>
[Comment("工作内容")]
[SwaggerComment("工作内容", true)]
public string Content { get; set; }
/// <summary>
/// 工作内容字体大小
/// </summary>
[Comment("工作内容字体大小")]
[SwaggerComment("工作内容字体大小", true, 15, 60)]
public int ContentSize { get; set; }
/// <summary>
/// 危险点
/// </summary>
[Comment("危险点")]
[SwaggerComment("危险点", true)]
public string DangerPoint { get; set; }
/// <summary>
/// 危险点
/// </summary>
[Comment("危险点字体大小")]
[SwaggerComment("危险点字体大小", true, 15, 60)]
public int DangerPointSize { get; set; }
@@ -69,29 +82,40 @@ namespace 电子展板.Base
/// 重点措施
/// </summary>
[Comment("重点措施")]
[SwaggerComment("重点措施", true)]
public string Measures { get; set; }
[Comment("重点措施字体大小")]
public int MeasuresSize { get; set; }
[Comment("党员1图片路径")]
[SwaggerComment("党员1图片路径", true)]
public string CPCMember1Path { get; set; }
[Comment("党员1姓名")]
[SwaggerComment("党员1姓名", true)]
public string CPCMember1Name { get; set; }
[Comment("党员2图片路径")]
[SwaggerComment("党员2图片路径", true)]
public string CPCMember2Path { get; set; }
[Comment("党员2姓名")]
[SwaggerComment("党员2姓名", true)]
public string CPCMember2Name { get; set; }
[Comment("党员3图片路径")]
[SwaggerComment("党员3图片路径", true)]
public string CPCMember3Path { get; set; }
[Comment("党员3姓名")]
[SwaggerComment("党员3姓名", true)]
public string CPCMember3Name { get; set; }
[Comment("党员4图片路径")]
[SwaggerComment("党员4图片路径", true)]
public string CPCMember4Path { get; set; }
[Comment("党员4姓名")]
[SwaggerComment("党员4姓名", true)]
public string CPCMember4Name { get; set; }
[Comment("党员字体大小")]
[SwaggerComment("党员字体大小", true, 15, 60)]
public int CPCMemberSize { get; set; }
/// <summary>
/// 转换成JSON字符串

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using .Utility.Swagger;
namespace .Models
{
[SwaggerClassComment("返回数据")]
public class RetValue
{
[SwaggerComment("返回状态")]
public int state { get; set; }
[SwaggerComment("返回消息")]
public string message { get; set; }
[SwaggerComment("返回数据")]
public object data { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .Utility.Swagger
{
[AttributeUsage(AttributeTargets.Class)]
public class SwaggerClassCommentAttribute : Attribute
{
public string Comment { get; set; }
public SwaggerClassCommentAttribute(string comment)
{
Comment = comment;
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .Utility.Swagger
{
[AttributeUsage(AttributeTargets.Property)]
public class SwaggerCommentAttribute : Attribute
{
public string Comment { get; set; }
public bool? IsRequired { get; set; }
public long? Min { get; set; }
public long? Max { get; set; }
public SwaggerCommentAttribute()
{
Comment = "";
}
public SwaggerCommentAttribute(string comment)
{
Comment = comment;
}
public SwaggerCommentAttribute(string comment, bool isRequired)
{
Comment = comment;
IsRequired = isRequired;
}
public SwaggerCommentAttribute(string comment, bool isRequired, long min, long max)
{
Comment = comment;
IsRequired = isRequired;
Min = min;
Max = max;
}
}
}

View File

@@ -0,0 +1,41 @@
using Nancy.Swagger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace .Utility.Swagger
{
public static class SwaggerModelDataUtils
{
public static SwaggerModelData GetModelData<T>()
{
return SwaggerModelData.ForType<T>(with =>
{
Type type = typeof(T);
SwaggerClassCommentAttribute classComment = type.GetCustomAttribute<SwaggerClassCommentAttribute>();
with.Description(classComment != null ? classComment.Comment : type.Name);
PropertyInfo[] propertyInfos = typeof(T).GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
SwaggerModelPropertyData ss = with.Data.Properties.Where(x => x.Name == propertyInfo.Name).FirstOrDefault();
SwaggerCommentAttribute attribute = propertyInfo.GetCustomAttribute<SwaggerCommentAttribute>();
if (attribute == null)
{
continue;
}
if (!string.IsNullOrEmpty(attribute.Comment))
ss.Description = attribute.Comment;
if (attribute.IsRequired != null)
ss.Required = attribute.IsRequired.Value;
if (attribute.Min != null)
ss.Minimum = attribute.Min.Value;
if (attribute.Max != null)
ss.Maximum = attribute.Max.Value;
}
});
}
}
}

View File

@@ -0,0 +1,56 @@
using Nancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .Utility.Swagger
{
public class SwaggerModule : NancyModule
{
public SwaggerModule()
{
//全局静态资源
Get("/swagger", RedirectToIndex);
Get("/swagger/index.html", Index);
Get("/swagger/{name*}", StaticResource);
}
private object RedirectToIndex(dynamic dynamic)
{
return Response.AsRedirect("/swagger/index.html");
}
private object Index(dynamic dynamic)
{
var url = $"{Request.Url.BasePath}/api-docs";
string packUri = $"pack://application:,,,/Assets/SwaggerUI/index.html";
string contentType = System.Web.MimeMapping.GetMimeMapping(packUri);
byte[] bytes = null;
using (var stream = System.Windows.Application.GetResourceStream(new Uri(packUri, UriKind.RelativeOrAbsolute)).Stream)
{
bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
}
string html = Encoding.UTF8.GetString(bytes);
html = html.Replace("@url", url);
return Response.AsText(html, contentType);
}
private object StaticResource(dynamic dynamic)
{
string name = dynamic.name;
return Resouce(name);
}
private object Resouce(string resourcePath)
{
if (resourcePath == "")
resourcePath = "index.html";
string packUri = $"pack://application:,,,/Assets/SwaggerUI/{resourcePath}";
string contentType = System.Web.MimeMapping.GetMimeMapping(packUri);
return Response.FromStream(System.Windows.Application.GetResourceStream(new Uri(packUri, UriKind.RelativeOrAbsolute)).Stream, contentType);
}
}
}

View File

@@ -0,0 +1,43 @@
using Nancy;
using Nancy.Bootstrapper;
using Nancy.Conventions;
using Nancy.Swagger.Services;
using Nancy.TinyIoc;
using Swagger.ObjectModel;
namespace .WebModule
{
public class ApplicationBootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
SwaggerMetadataProvider.SetInfo("API", "v1.0", "电子展板", new Contact
{
EmailAddress = "18862253202@qq.com",
Name = "大师兄法号随缘",
Url = "https://www.cnblogs.com/zjwno1"
}, "http://www.cnblogs.com/zjwno1");
base.ApplicationStartup(container, pipelines);
}
protected override void ConfigureConventions(NancyConventions nancyConventions)
{
base.ConfigureConventions(nancyConventions);
}
/// <summary>
/// 允许跨域
/// </summary>
/// <param name="container"></param>
/// <param name="pipelines"></param>
/// <param name="context"></param>
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
pipelines.AfterRequest.AddItemToEndOfPipeline(x => x.Response.Headers.Add("Access-Control-Allow-Origin", "*"));
pipelines.AfterRequest.AddItemToEndOfPipeline(x => x.Response.Headers.Add("Access-Control-Allow-Headers", "*"));
pipelines.AfterRequest.AddItemToEndOfPipeline(x => x.Response.Headers.Add("Access-Control-Allow-Methods", "*"));
}
}
}

View File

@@ -0,0 +1,104 @@
using Nancy.Metadata.Modules;
using Nancy.Swagger;
using Swagger.ObjectModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using .Models;
using .Utility.Core;
namespace .WebModule
{
public class HomeMetadataModule : MetadataModule<PathItem>
{
public HomeMetadataModule(ISwaggerModelCatalog modelCatalog)
{
//添加模型
modelCatalog.AddModels(typeof(MyConfig));
Describe["getConfig"] = description => description.AsSwagger(
with => with.Operation(
op =>
{
//设置操作id
op.OperationId("getConfig");
//设置tag
op.Tag("getConfig");
//备注
op.Summary("获得当前配置");
//参数配置
//op.BodyParameter(p =>
// p.Description("配置项")
// .Name("Config")
// .Schema<MyConfig>()
//);
//返回值配置
op.Response(r =>
r.Description("返回数据")
//.Example("application/json", JsonHelper.ToJson(GlobalVariable.Config))
.Schema<MyConfig>()
);
}
)
);
Describe["uploadImage"] = description => description.AsSwagger(
with => with.Operation(
op =>
{
//设置操作id
op.OperationId("uploadImage");
//设置tag
op.Tag("uploadImage");
//备注
op.Summary("上传照片");
//文件上传
op.ConsumeMimeType("multipart/form-data");
//参数配置
op.Parameter(p =>
p.In(ParameterIn.Form)
.Name("file")//参数名
.Type("file")//文件类型
.Description("上传文件")
.IsRequired()
);
//返回值配置
op.Response(r =>
r.Description("返回数据")
.Example("application/json", new RetValue { state = 1, message = "上传成功", data = "/upload/123.png" }.ToJson())
.Schema<RetValue>()
);
}
)
);
Describe["save"] = description => description.AsSwagger(
with => with.Operation(
op =>
{
//设置操作id
op.OperationId("save");
//设置tag
op.Tag("save");
//备注
op.Summary("保存配置");
//参数配置
op.BodyParameter(p =>
p.Description("配置项")
.Name("Config")
.Schema<MyConfig>()
);
//返回值配置
op.Response(r =>
r.Description("返回数据")
.Example("application/json", new RetValue { state = 1, message = "保存成功" }.ToJson())
.Schema<RetValue>()
);
}
)
);
}
}
}

View File

@@ -3,20 +3,23 @@ using Nancy.ModelBinding;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using .Base;
using .Models;
using .Utility;
using .Utility.Core;
using .Utility.Extension;
using .Utility.Logs;
using .Utility.Other;
namespace
namespace .WebModule
{
public class WebModule : NancyModule
public class HomeModule : NancyModule
{
public WebModule()
public HomeModule()
{
//默认页面
Get("", Index);
@@ -26,13 +29,13 @@ namespace 电子展板
Get("/static/{name*}", StaticResource);
//获得配置
Get("/getConfig", GetConfig);
Get("/getConfig", GetConfig, null, "getConfig");
//获得上传的文件
Get("/upload/{id}", GetUploadFiles);
//上传图片
Post("/uploadImage", UploadImage);
Post("/uploadImage", UploadImage, null, "uploadImage");
//保存配置
Post("/save", SaveConfig);
Post("/save", SaveConfig, null, "save");
}
//默认页面
private object Index(dynamic dynamic)
@@ -95,16 +98,10 @@ namespace 电子展板
}
filePathList.Add($"/Upload/{fileName}");
}
var result = new { state = 1, message = "上传成功", data = filePathList.GetStrArray() };
RetValue result = new RetValue { state = 1, message = "上传成功", data = filePathList.GetStrArray() };
return Response.AsJson(result);
}
/// <summary>
/// 保存配置
/// </summary>
/// <param name="dynamic"></param>
/// <returns></returns>
private object SaveConfig(dynamic dynamic)
{
MyConfig config = this.Bind<MyConfig>();
@@ -122,33 +119,14 @@ namespace 电子展板
GlobalVariable.SaveConfig();
//保存并修改界面
EventBus.Instance.Publish("save", "");
var result = new { state = 1, message = "保存成功" };
RetValue result = new RetValue { state = 1, message = "保存成功" };
return Response.AsJson(result);
}
private object Resouce(string resourcePath)
{
string packUri = $"pack://application:,,,/Assets/{resourcePath}";
string contentType = System.Web.MimeMapping.GetMimeMapping(packUri);
return Response.FromStream(System.Windows.Application.GetResourceStream(new Uri(packUri, UriKind.RelativeOrAbsolute)).Stream, contentType);
}
}
//public class ApplicationBootstrapper : DefaultNancyBootstrapper
//{
// protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
// {
// SwaggerMetadataProvider.SetInfo("API", "v1.0", "电子展板", new Swagger.ObjectModel.Contact
// {
// EmailAddress = "18862253202@qq.com",
// Name = "大师兄法号随缘",
// Url = "https://www.cnblogs.com/zjwno1"
// });
// base.ApplicationStartup(container, pipelines);
// }
//}
}

View File

@@ -0,0 +1,24 @@
using Nancy.Swagger;
using Nancy.Swagger.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using .Models;
using .Utility.Swagger;
namespace .WebModule.ModelMetadata
{
public class MyConfigModelDataProvider : ISwaggerModelDataProvider
{
/// <summary>
/// 使用自定义特性注解完成配置
/// </summary>
/// <returns></returns>
public SwaggerModelData GetModelData()
{
return SwaggerModelDataUtils.GetModelData<MyConfig>();
}
}
}

View File

@@ -0,0 +1,19 @@
using Nancy.Swagger;
using Nancy.Swagger.Services;
using .Models;
using .Utility.Swagger;
namespace .WebModule.ModelMetadata
{
public class RetValueDataProvider : ISwaggerModelDataProvider
{
/// <summary>
/// 使用自定义特性注解完成配置
/// </summary>
/// <returns></returns>
public SwaggerModelData GetModelData()
{
return SwaggerModelDataUtils.GetModelData<RetValue>();
}
}
}

View File

@@ -21,7 +21,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>-->
<PackageReference Include="HandyControl" Version="3.5.1" />
<!--<PackageReference Include="Nancy.Swagger.Alyce" Version="2.2.76" />-->
<PackageReference Include="Nancy.Swagger.Alyce" Version="2.2.76" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
</ItemGroup>