Itext生成pdf文件,itext+Freemarker生成pdf,(中文空白解决)
来源:https://my.oschina.net/lujianing/blog/894365
1.背景
在某些业务场景中,需要提供相关的电子凭证,比如网银/支付宝中转账的电子回单,签约的电子合同等。方便用户查看,下载,打印。目前常用的解决方案是,把相关数据信息,生成对应的pdf文件返回给用户。
本文源码:http://git.oschina.net/lujianing/java_pdf_demo
2.iText
iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。
iText 官网:http://itextpdf.com/
iText 开发文档: http://developers.itextpdf.com/developers-home
iText目前有两套版本iText5和iText7。iText5应该是网上用的比较多的一个版本。iText5因为是很多开发者参与贡献代码,因此在一些规范和设计上存在不合理的地方。iText7是后来官方针对iText5的重构,两个版本差别还是挺大的。不过在实际使用中,一般用到的都比较简单,所以不用特别拘泥于使用哪个版本。比如我们在http://mvnrepository.com/中搜索iText,出来的都是iText5的依赖。
来个最简单的例子:
添加依赖:
1 2 3 4 5 6 | <!-- https: //mvnrepository.com/artifact/com.itextpdf/itextpdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.11</version> </dependency> |
测试代码:JavaToPdf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.lujianing.test; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.PdfWriter; import java.io.FileNotFoundException; import java.io.FileOutputStream; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdf { private static final String DEST = "target/HelloWorld.pdf" ; public static void main(String[] args) throws FileNotFoundException, DocumentException { Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST)); document.open(); document.add( new Paragraph( "hello world" )); document.close(); writer.close(); } } |
运行结果:
3.iText-中文支持
iText默认是不支持中文的,因此需要添加对应的中文字体,比如黑体simhei.ttf
可参考文档:http://developers.itextpdf.com/examples/font-examples/using-fonts#1227-tengwarquenya1.java
测试代码:JavaToPdfCN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package com.lujianing.test; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.FontFactory; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.text.pdf.PdfWriter; import java.io.FileNotFoundException; import java.io.FileOutputStream; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdfCN { private static final String DEST = "target/HelloWorld_CN.pdf" ; private static final String FONT = "simhei.ttf" ; public static void main(String[] args) throws FileNotFoundException, DocumentException { Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST)); document.open(); Font f1 = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); document.add( new Paragraph( "hello world,我是鲁家宁" , f1)); document.close(); writer.close(); } } |
输出结果:
4.iText-Html渲染
在一些比较复杂的pdf布局中,我们可以通过html去生成pdf
可参考文档:http://developers.itextpdf.com/examples/xml-worker-itext5/xml-worker-examples
添加依赖:
1 2 3 4 5 6 | <!-- https: //mvnrepository.com/artifact/com.itextpdf.tool/xmlworker --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.11</version> </dependency> |
添加模板:template.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" /> <title>Title</title> <style> body{ font-family:SimHei; } .red{ color: red; } </style> </head> <body> <div class = "red" > 你好,鲁家宁 </div> </body> </html> |
测试代码:JavaToPdfHtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package com.lujianing.test; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; import com.lujianing.test.util.PathUtil; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdfHtml { private static final String DEST = "target/HelloWorld_CN_HTML.pdf" ; private static final String HTML = PathUtil.getCurrentPath()+ "/template.html" ; private static final String FONT = "simhei.ttf" ; public static void main(String[] args) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST)); // step 3 document.open(); // step 4 XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS); fontImp.register(FONT); XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(HTML), null , Charset.forName( "UTF-8" ), fontImp); // step 5 document.close(); } } |
输出结果:
需要注意:
1.html中必须使用标准的语法,标签一定需要闭合
2.html中如果有中文,需要在样式中添加对应字体的样式
5.iText-Html-Freemarker渲染
在实际使用中,html内容都是动态渲染的,因此我们需要加入模板引擎支持,可以使用FreeMarker/Velocity,这里使用FreeMarker举例
添加FreeMarke依赖:
1 2 3 4 5 6 | <!-- https: //mvnrepository.com/artifact/org.freemarker/freemarker --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.19</version> </dependency> |
添加模板:template_freemarker.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" /> <title>Title</title> <style> body{ font-family:SimHei; } .blue{ color: blue; } </style> </head> <body> <div class = "blue" > 你好,${name} </div> </body> </html> |
测试代码:JavaToPdfHtmlFreeMarker
注意:setDirectoryForTemplateLoading(new File(PathUtil.getCurrentPath())) 指定的是目录,所以如果你的服务器是linux/centos的话,需要指定为,例如:/root/pdfmodel/freemarker.html这样指定。
如果你的项目生成的是jar包,就不能这样指定,应该改为:setClassForTemplateLoading(当前文件.class, "/pdfmodel"); 其中pdfmodel已经被编译成jar里面的东西了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | package com.lujianing.test; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; import com.lujianing.test.util.PathUtil; import freemarker.template.Configuration; import freemarker.template.Template; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdfHtmlFreeMarker { private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER.pdf" ; private static final String HTML = "template_freemarker.html" ; private static final String FONT = "simhei.ttf" ; private static Configuration freemarkerCfg = null ; static { freemarkerCfg = new Configuration(); //freemarker的模板目录 try { freemarkerCfg.setDirectoryForTemplateLoading( new File(PathUtil.getCurrentPath())); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, DocumentException { Map<String,Object> data = new HashMap(); data.put( "name" , "鲁家宁" ); String content = JavaToPdfHtmlFreeMarker.freeMarkerRender(data,HTML); JavaToPdfHtmlFreeMarker.createPdf(content,DEST); } public static void createPdf(String content,String dest) throws IOException, DocumentException { // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest)); // step 3 document.open(); // step 4 XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS); fontImp.register(FONT); XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(content.getBytes()), null , Charset.forName( "UTF-8" ), fontImp); // step 5 document.close(); } /** * freemarker渲染html */ public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) { Writer out = new StringWriter(); try { // 获取模板,并设置编码方式 Template template = freemarkerCfg.getTemplate(htmlTmp); template.setEncoding( "UTF-8" ); // 合并数据模型与模板 template.process(data, out ); //将合并后的数据和模板写入到流中,这里使用的字符流 out .flush(); return out .toString(); } catch (Exception e) { e.printStackTrace(); } finally { try { out .close(); } catch (IOException ex) { ex.printStackTrace(); } } return null ; } } |
输出结果:
目前为止,我们已经实现了iText通过Html模板生成Pdf的功能,但是实际应用中,我们发现iText并不能对高级的CSS样式进行解析,比如CSS中的position属性等,因此我们要引入新的组件
6.Flying Saucer-CSS高级特性支持
Flying Saucer is a pure-Java library for rendering arbitrary well-formed XML (or XHTML) using CSS 2.1 for layout and formatting, output to Swing panels, PDF, and images.
Flying Saucer是基于iText的,支持对CSS高级特性的解析。
添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- https: //mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf --> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.5</version> </dependency> <!-- https: //mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf-itext5 --> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf-itext5</artifactId> <version>9.1.5</version> </dependency> |
添加模板:template_freemarker_fs.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" /> <title>Title</title> <style> body{ font-family:SimHei; } .color{ color: green; } .pos{ position:absolute; left:200px; top:5px; width: 200px; font-size: 10px; } </style> </head> <body> <img src= "logo.png" width= "600px" /> <div class = "color pos" > 你好,${name} </div> </body> </html> |
测试代码:JavaToPdfHtmlFreeMarker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | package com.lujianing.test.flyingsaucer; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.BaseFont; import com.lujianing.test.util.PathUtil; import freemarker.template.Configuration; import freemarker.template.Template; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdfHtmlFreeMarker { private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER_FS.pdf" ; private static final String HTML = "template_freemarker_fs.html" ; private static final String FONT = "simhei.ttf" ; private static final String LOGO_PATH = "file://" +PathUtil.getCurrentPath()+ "/logo.png" ; private static Configuration freemarkerCfg = null ; static { freemarkerCfg = new Configuration(); //freemarker的模板目录 try { freemarkerCfg.setDirectoryForTemplateLoading( new File(PathUtil.getCurrentPath())); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, DocumentException, com.lowagie.text.DocumentException { Map<String,Object> data = new HashMap(); data.put( "name" , "鲁家宁" ); String content = JavaToPdfHtmlFreeMarker.freeMarkerRender(data,HTML); JavaToPdfHtmlFreeMarker.createPdf(content,DEST); } /** * freemarker渲染html */ public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) { Writer out = new StringWriter(); try { // 获取模板,并设置编码方式 Template template = freemarkerCfg.getTemplate(htmlTmp); template.setEncoding( "UTF-8" ); // 合并数据模型与模板 template.process(data, out ); //将合并后的数据和模板写入到流中,这里使用的字符流 out .flush(); return out .toString(); } catch (Exception e) { e.printStackTrace(); } finally { try { out .close(); } catch (IOException ex) { ex.printStackTrace(); } } return null ; } public static void createPdf(String content,String dest) throws IOException, DocumentException, com.lowagie.text.DocumentException { ITextRenderer render = new ITextRenderer(); ITextFontResolver fontResolver = render.getFontResolver(); fontResolver.addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // 解析html生成pdf render.setDocumentFromString(content); //解决图片相对路径的问题 render.getSharedContext().setBaseURL(LOGO_PATH); render.layout(); render.createPDF( new FileOutputStream(dest)); } } |
输出结果:
在某些场景下,html中的静态资源是在本地,我们可以使用render.getSharedContext().setBaseURL()加载文件资源,注意资源URL需要使用文件协议 "file://"。
对于生成的pdf页面大小,可以用css的@page属性设置。
7.PDF转图片
在某些场景中,我们可能只需要返回图片格式的电子凭证,我们可以使用Jpedal组件,把pdf转成图片
添加依赖:
1 2 3 4 5 6 | <!-- https: //mvnrepository.com/artifact/org.jpedal/jpedal-lgpl --> <dependency> <groupId>org.jpedal</groupId> <artifactId>jpedal-lgpl</artifactId> <version>4.74b27</version> </dependency> |
测试代码:JavaToPdfImgHtmlFreeMarker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | package com.lujianing.test.flyingsaucer; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.BaseFont; import com.lujianing.test.util.PathUtil; import freemarker.template.Configuration; import freemarker.template.Template; import org.jpedal.PdfDecoder; import org.jpedal.exception.PdfException; import org.jpedal.fonts.FontMappings; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; /** * Created by lujianing on 2017/5/7. */ public class JavaToPdfImgHtmlFreeMarker { private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER_FS_IMG.png" ; private static final String HTML = "template_freemarker_fs.html" ; private static final String FONT = "simhei.ttf" ; private static final String LOGO_PATH = "file://" +PathUtil.getCurrentPath()+ "/logo.png" ; private static final String IMG_EXT = "png" ; private static Configuration freemarkerCfg = null ; static { freemarkerCfg = new Configuration(); //freemarker的模板目录 try { freemarkerCfg.setDirectoryForTemplateLoading( new File(PathUtil.getCurrentPath())); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, DocumentException, com.lowagie.text.DocumentException { Map<String,Object> data = new HashMap(); data.put( "name" , "鲁家宁" ); String content = JavaToPdfImgHtmlFreeMarker.freeMarkerRender(data,HTML); ByteArrayOutputStream pdfStream = JavaToPdfImgHtmlFreeMarker.createPdf(content); ByteArrayOutputStream imgSteam = JavaToPdfImgHtmlFreeMarker.pdfToImg(pdfStream.toByteArray(),2,1,IMG_EXT); FileOutputStream fileStream = new FileOutputStream( new File(DEST)); fileStream.write(imgSteam.toByteArray()); fileStream.close(); } /** * freemarker渲染html */ public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) { Writer out = new StringWriter(); try { // 获取模板,并设置编码方式 Template template = freemarkerCfg.getTemplate(htmlTmp); template.setEncoding( "UTF-8" ); // 合并数据模型与模板 template.process(data, out ); //将合并后的数据和模板写入到流中,这里使用的字符流 out .flush(); return out .toString(); } catch (Exception e) { e.printStackTrace(); } finally { try { out .close(); } catch (IOException ex) { ex.printStackTrace(); } } return null ; } /** * 根据模板生成pdf文件流 */ public static ByteArrayOutputStream createPdf(String content) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ITextRenderer render = new ITextRenderer(); ITextFontResolver fontResolver = render.getFontResolver(); try { fontResolver.addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); } catch (com.lowagie.text.DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 解析html生成pdf render.setDocumentFromString(content); //解决图片相对路径的问题 render.getSharedContext().setBaseURL(LOGO_PATH); render.layout(); try { render.createPDF(outStream); return outStream; } catch (com.lowagie.text.DocumentException e) { e.printStackTrace(); } finally { try { outStream.close(); } catch (IOException e) { e.printStackTrace(); } } return null ; } /** * 根据pdf二进制文件 生成图片文件 * * @param bytes pdf二进制 * @param scaling 清晰度 * @param pageNum 页数 */ public static ByteArrayOutputStream pdfToImg( byte [] bytes, float scaling, int pageNum,String formatName) { //推荐的方法打开PdfDecoder PdfDecoder pdfDecoder = new PdfDecoder( true ); FontMappings.setFontReplacements(); //修改图片的清晰度 pdfDecoder.scaling = scaling; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { //打开pdf文件,生成PdfDecoder对象 pdfDecoder.openPdfArray(bytes); //bytes is byte[] array with PDF //获取第pageNum页的pdf BufferedImage img = pdfDecoder.getPageAsImage(pageNum); ImageIO.write(img, formatName, out ); } catch (PdfException e) { e.printStackTrace(); } catch (IOException e){ e.printStackTrace(); } return out ; } } |
输出结果:
Jpedal支持将指定页Pdf生成图片,pdfDecoder.scaling设置图片的分辨率(不同分辨率下文件大小不同) ,支持多种图片格式,具体更多可自行研究
8.总结
对于电子凭证的技术方案,总结如下:
1.html模板+model数据,通过freemarker进行渲染,便于维护和修改
2.渲染后的html流,可通过Flying Saucer组件生成pdf文件流,或者生成pdf后再转成jpg文件流
3.在Web项目中,对应的文件流,可以通过ContentType设置,在线查看/下载,不需通过附件服务
9.纯前端解决方案
还有一种解决方案是使用PhantomJS
git地址: https://github.com/ariya/phantomjs
PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API。它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。 PhantomJS 可以用于 页面自动化 , 网络监测 , 网页截屏 ,以及 无界面测试 等。
具体方法可自行查询。
10.中文支持(中文空白解决)
首先需要添加中文字库,也就是你的页面中用到的所有字体:
1 2 3 4 | ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont( "C:/Windows/Fonts/simsun.ttc" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont( "C:/Windows/Fonts/simhei.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont( "C:/Windows/Fonts/simkai.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); |
注意:页面中字体不能使用中文,需要使用英文名称,而且是大小写敏感的!例如宋体的英文名称是 SimSun(注意不是simsun!,首字母都是大写的)
错误写法:font-family:宋体 或者 font-family:simsun
正确写法:font-family:SimSun 或者 font-family:SimHei
如果生成的pdf中文不显示或者乱码,请确认如下信息:
-
确保页面中所有内容都指定了字体,最好能指定 body {font-family:....},以防止漏网之鱼。
-
确保上述所有字体均通过addFont加入,字体名称错误或者字体不存在会抛出异常,很方便,但是没导入的字体不会有任何提示。
-
确保字体名称正确,不使用中文,大小写正确。
-
确保html标签都正确,简单的方法是所有内容都去掉,随便写几个中文看看能否正常生成,如果可以,在认真检查html标签,否则再次检查上述几条。
还有就是中文换行的问题了,带有中文而且文字较多存在换行情况时,需要给table加入样式:
table-layout:fixed,然后表格中的td使用%还指定td的宽度。
https://www.cnblogs.com/reese-blogs/p/5546806.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架