基于.NetCore3.1系列 —— 使用Swagger导出文档 (番外篇)


基于.NetCore3.1系列 —— 使用Swagger导出文档 (番外篇)

前言

回顾之前的两篇Swagger做Api接口文档,我们大体上学会了如何在net core3.1的项目基础上,搭建一套自动生产API接口说明文档的框架。

本来在Swagger的基础上,前后端开发人员在开发生产期间,可以借此进行更加便捷的沟通交流。可是总有些时候,遇到一些难缠的,又不讲道理,偏偏觉得将Swagger文档地址丢给客户会不够正式!死活要一份word文档。

img

可是这个时候,如果接口数量上百个,甚至更多,一个一个手动输入word,那将是一笔耗时的工作。但却有什么办法可以解决呢?

对了,利用Swagge生成的Json文件转换为word文档不就可以了吗?

思路

  1. 获取Swagger接口文档的Json文件

  2. 解析Json文件数据填充到Html的表格中

  3. 根据生成的html转work文档

模板

文档模板

URL /api/Movie/AddMovie
请求方式 Post
参数名 参数类型 是否必填 说明
id Query False 影视ID
Name Query False 电影名称
Type Query False 电影类型
状态码 说明
200 Success
示例
请求参数
返回值

开始

一、根据Swagger版本获取Json数据

1.通过Swagger源码文件可以看到

img

可以拿到swagger生成的文档数据,所以我们可以新建一个控制器SwaggerController.cs,

        private readonly SwaggerGenerator _swaggerGenerator;
        public SwaggerController(SwaggerGenerator swaggerGenerator)
        {
            _swaggerGenerator = swaggerGenerator;
        }
        /// <summary>
        /// 导出文件
        /// </summary>
        /// <param name="type">文件类型</param>
        /// <param name="version">版本号V1</param>
        /// <returns></returns>
        [HttpGet]
        public FileResult ExportWord(string type,string version)
        {
            string contenttype = string.Empty;

            var model = _swaggerGenerator.GetSwagger(version); //1. 根据指定版本获取指定版本的json对象。
        }
  1. 在Startup.cs文件中,利用net core的ioc容器,注入SwaggerGenerator实例化,这样在后面的调用中可以直接使用这个方法
            services.AddScoped<SwaggerGenerator>(); //注入SwaggerGenerator,后面可以直接使用这个方法

二、文件数据填充到Html的表格中

根据上面获取的model文件数据,这个时候,我们利用Razor文件,结合html的table模板,将数据遍历填充到页面中,生成完整的页面

Html模板

@using Swashbuckle.AspNetCore.Swagger;
<!DOCTYPE html>
<html>
<head>
    <title>Swagger API文档代码文件</title>
    <style type='text/css'>

        table, table td, table th {
            border: 1px solid #000000;
            border-collapse: collapse;
        }

        table {
            table-layout: fixed;
            word-break: break-all;
        }

        tr {
            height: 20px;
            font-size: 12px;
        }
    </style>
</head>
<body>
    <div style='width:1000px; margin: 0 auto'>
        <span><i>Word接口文档</i></span>
        <h1 align="center">@Model.Info.Title</h1>
        <h1 align="center">接口文档 @Model.Info.Version</h1>
        <h4>联系方式</h4>
        <span>作者:@Model.Info.Contact.Name</span>
        <br>
        <a href="mailto:@Model.Info.Contact.Email" rel="noopener noreferrer" class="link">Send email to Xunit.Core</a>
        <br>
        <a href="@Model.Info.Contact.Url" target="_blank" rel="noopener noreferrer" class="link">@Model.Info.Contact.Name - Website</a>
        <br>
        <h3>接口描述</h3>
        <span>@Model.Info.Description</span>
        <br>
        <table border='1' cellspacing='0' cellpadding='0' style="table-layout: fixed; word-break: break-all;border: 1px solid #000000;border-collapse: collapse;" width='100%'>
            <tr style="border: 1px solid #000000;border-collapse: collapse;">
                <td align="center" style="background-color: rgb(84, 127, 177);">说明</td>
                <td></td>
            </tr>
            <tr style="border: 1px solid #000000;border-collapse: collapse;">
                <td align="center" style="background-color: rgb(84, 127, 177);">类型</td>
                <td></td>
            </tr>

        </table>
        @foreach (var item in Model.Paths)
        {
            if (item.Value.Operations != null)
            {
                foreach (var operation in item.Value.Operations)
                {
                    <h3>@operation.Value.Summary</h3>
                    <table border='1' cellspacing='0' cellpadding='0' width='100%' style="table-layout: fixed; word-break: break-all;border: 1px solid #000000;border-collapse: collapse;">
                        <tr style="background-color: rgb(84, 127, 177);" align="center">
                            <td colspan='5'></td>
                        </tr>

                        <tr style="border: 1px solid #000000;border-collapse: collapse;">
                            <td style="border: 1px solid #000000;border-collapse: collapse;">URL</td>
                            <td colspan='4'>@item.Key</td>
                        </tr>
                        <tr style="border: 1px solid #000000;border-collapse: collapse;">
                            <td style="border: 1px solid #000000;border-collapse: collapse;">请求方式</td>
                            <td colspan='4'>
                                @operation.Key
                            </td>
                        </tr>

                        @if (operation.Value.Parameters != null && operation.Value.Parameters.Count > 0)
                        {
                            <tr style="background-color: rgb(84, 127, 177);" align='center'>
                                <td style="border: 1px solid #000000;border-collapse: collapse;">参数名</td>
                                <td style="border: 1px solid #000000;border-collapse: collapse;">参数类型</td>
                                <td style="border: 1px solid #000000;border-collapse: collapse;">是否必填</td>
                                <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='2'>说明</td>
                            </tr>
                            @foreach (var param in operation.Value.Parameters)
                            {
                                <tr align='center' style="border: 1px solid #000000;border-collapse: collapse;">
                                    <td style="border: 1px solid #000000;border-collapse: collapse;">@param.Name</td>
                                    <td style="border: 1px solid #000000;border-collapse: collapse;">@param.In</td>
                                    <td style="border: 1px solid #000000;border-collapse: collapse;">@param.Required</td>
                                    <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='2'>@param.Description</td>
                                </tr>
                            }
                        }

                        <tr style="background-color: rgb(84, 127, 177);" align='center'>
                            <td style="border: 1px solid #000000;border-collapse: collapse;">状态码</td>
                            <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='4'>说明</td>
                        </tr>
                        @if (operation.Value.Responses != null && operation.Value.Responses.Count > 0)
                        {
                            foreach (var response in operation.Value.Responses)
                            {
                                <tr align='center' style="border: 1px solid #000000;border-collapse: collapse;">
                                    <td style="border: 1px solid #000000;border-collapse: collapse;">@response.Key</td>
                                    <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='4'>@response.Value.Description</td>
                                </tr>

                            }
                        }
                        <tr style="background-color: rgb(84, 127, 177);">
                            <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='5'>示例</td>
                        </tr>
                        <tr style="height: 40px;" style="border: 1px solid #000000;border-collapse: collapse;">
                            <td style="background-color: rgb(84, 127, 177);">请求参数</td>
                            <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='4'></td>
                        </tr>
                        <tr style="height: 40px;" style="border: 1px solid #000000;border-collapse: collapse;">
                            <td style="background-color: rgb(84, 127, 177);">返回值</td>
                            <td style="border: 1px solid #000000;border-collapse: collapse;" colspan='4'></td>
                        </tr>
                    </table>
                    <br>
                }

            }

        }
    </div>
</body>
</html>

将数据遍历到静态页面中,

        /// <summary>
        /// 将数据遍历静态页面中
        /// </summary>
        /// <param name="templatePath">静态页面地址</param>
        /// <param name="model">获取到的文件数据</param>
        /// <returns></returns>
        public static string GeneritorSwaggerHtml(string templatePath, OpenApiDocument model)
        {
            var template = System.IO.File.ReadAllText(templatePath);
            var result = Engine.Razor.RunCompile(template, "i3yuan", typeof(OpenApiDocument), model);
            return result;
        }

三、根据生成的html转work文档

        /// <summary>
        /// 静态页面转文件
        /// </summary>
        /// <param name="html">静态页面html</param>
        /// <param name="type">文件类型</param>
        /// <param name="contenttype">上下文类型</param>
        /// <returns></returns>
        public Stream SwaggerConversHtml(string html, string type, out string contenttype)
        {
            string fileName = Guid.NewGuid().ToString() + type;
            //文件存放路径
            string webRootPath = _hostingEnvironment.WebRootPath;
            string path = webRootPath + @"\Files\TempFiles\";
            var addrUrl = path + $"{fileName}";
            FileStream fileStream = null;
            var provider = new FileExtensionContentTypeProvider();
            contenttype = provider.Mappings[type];
            try
            {
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                var data = Encoding.Default.GetBytes(html);
                var stream = ByteHelper.BytesToStream(data);
                //创建Document实例
                Document document = new Document();
                //加载HTML文档
                document.LoadFromStream(stream, FileFormat.Html, XHTMLValidationType.None);
                //保存为Word
                document.SaveToFile(addrUrl, FileFormat.Docx);

                document.Close();
                fileStream = File.Open(addrUrl, FileMode.OpenOrCreate);
                var filedata = ByteHelper.StreamToBytes(fileStream);
                var outdata = ByteHelper.BytesToStream(filedata);

                return outdata;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (fileStream != null)
                    fileStream.Close();
                if (File.Exists(addrUrl))
                    File.Delete(addrUrl);//删掉文件
            }
        }
    public class ByteHelper
    {
        public static byte[] StreamToBytes(Stream stream)
        {
            byte[] bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
            // 设置当前流的位置为流的开始 
            stream.Seek(0, SeekOrigin.Begin);
            return bytes;
        }

        /// 将 byte[] 转成 Stream
        public static Stream BytesToStream(byte[] bytes)
        {
            Stream stream = new MemoryStream(bytes);
            return stream;
        }
    }

四、最终效果

将html转换为word后,我们就可以看到带有 .doc 的效果了!差不多是如下效果

img

总结

  1. 到这基本就结束了,通过简易的几个接口的方式,展示了如何通过将Swagger接口文档生成word文档。可以根据自己的html模板生成各式的word样式文档说明。

  2. 写这篇番外主要是因为之前介绍了关于如何使用Swagger生成在线文档,但实际工作中,可能也会遇到这种要各种正式word文档的客户,所以在此分享一些想法和思路,同时希望大家不吝指教。

  3. 后续还会不断修改和完善,可以更多的生成不同的文件类型和按需生成不同版本的接口文档,持续更新。。。

  4. 注:搜索关注公众号【DotNet技术谷】–回复【文档生成器】,可获取本篇Swagger转换work文件

  5. 参考资料:Spire.Doc文件 、Swagger开源地址

  6. 源码下载


文章作者: 艾三元
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 艾三元 !
 上一篇
基于.NetCore3.1系列 —— 使用Swagger导出文档 (补充篇) 基于.NetCore3.1系列 —— 使用Swagger导出文档 (补充篇)
基于.NetCore3.1系列 —— 使用Swagger导出文档 (补充篇)前言 在上一篇导出文档番外篇中,我们已经熟悉了怎样根据json数据导出word的文档,生成接口文档,而在这一篇,将对上一篇进行完善补充,增加多种导出方式,实现更加完
2020-04-12
下一篇 
基于.NetCore3.1系列 —— 使用Swagger做Api文档 (下篇) 基于.NetCore3.1系列 —— 使用Swagger做Api文档 (下篇)
基于.NetCore3.1系列 —— 使用Swagger做Api文档 (下篇)前言​ 回顾上一篇文章《使用Swagger做Api文档 》,文中介绍了在.net core 3.1中,利用Swagger轻量级框架,如何引入程序包,配
2020-03-23
  目录