【礼单源码】【快八源码程序】【js火柴特效源码】jfinal 源码下载

2024-11-08 04:51:47 来源:代码算法源码 分类:综合

1.jfinal什么时候出web版的源码后台管理
2.JFinal和Spring Boot对比
3.jfinal不兼容的浏览器有哪些
4.jfinal 如何导出zip压缩文件

jfinal 源码下载

jfinal什么时候出web版的后台管理

       可以加入Jfinal俱乐部,

       引用官网的下载:

       俱乐部当前发放的福利是本社区 jfinal.com 的源代码,并取名为

       jfinal-club。源码jfinal-club 在核心功能上相当于一个迷你的下载 OSChina 社区,newsfeed

       信息流模块整合了整站动态数据,源码交互性极好。下载礼单源码重要功能:动态消息、源码@提到我、下载remind提醒、源码关注、下载好友、源码粉丝、下载私信、源码发贴、下载回贴、源码点赞、收藏、快八源码程序定时任务等功能。常见的功能也很全面:文件下载、上传、用户头像裁剪、登录、注册、邮件激活、找回密码、XSS过滤、缓存、后台管理、以及一些常用工具类等等。

       jfinal-club 是官方出品的唯一 JFinal 最佳实践,绝无仅有的极简设计,获得 jfinal-club 也就获得了作者本人对

       JFinal 的使用精髓。基于 jfinal 3.3 开发,js火柴特效源码获得 jfinal-club 将以令人难以想象的速度掌握新版本功能。

       jfinal-club 是一个长期进化的,不断添加实用功能的项目,加入俱乐部以后,将随之长期享受该福利。

       ---------------

       web版的后台管理社区做的比较好的有:

       JfinalUIB , EOVA  ,JPress, ...  等很多都非常的好

       /project 相关项目

       /club 俱乐部

JFinal和Spring Boot对比

       轻量级MVC框架:JFinal与Spring Boot的较量

       JFinal,作为一款轻量级的MVC+ORM集成框架,它的设计理念就像简化版的SSH,旨在提供快速开发和简单易学的体验。它强调代码量少、学习简单,同时拥有Java语言的高效性和动态语言的开发效率。与之相比,免费兼职平台源码Spring Boot则更像一个自动化配置神器,以Spring全家桶的集成和配置简化为核心,几行代码就能启动应用,无需繁冗的XML配置。

       Spring Boot:简化Spring开发的革命

       Spring Boot以其一键式创建Spring应用的能力脱颖而出,它内嵌Servlet容器,提供了Starter简化Maven配置,通过自动配置尽可能减少开发者的工作量。它的特性包括快速开发、健康检查和外部化配置,无需代码生成,直接上手。

       对比:高效与易用的较量

       尽管两者都旨在简化开发,但JFinal在上手速度和学习成本上更具优势,其文档简洁明了,一天内就能创建项目。网站源码安全检查而Spring Boot虽然功能强大,但文档量大且英语依赖性高,对新手而言可能稍显复杂。

       JFinal的MVC架构设计精巧,使用简单,使开发者能深入理解框架,降低调试成本。而Spring Boot虽然也支持约定优于配置,但在源码阅读和问题定位上,JFinal的精简设计可能更胜一筹。

       JFinal的Db + Record模式让数据库操作更灵活,无需繁琐的JavaBean映射。ActiveRecord的全面支持使得数据库开发高效快捷,相比之下,Spring Boot需额外集成SpringDataJPA或MyBatis来实现这些功能。

       在插件扩展性上,JFinal的Plugin体系结构简洁易用,自定义插件过程简单,而Spring Boot虽有丰富的插件生态,但使用和维护可能较为复杂。

       最后,JFinal的体积小巧且无第三方依赖,这反映了其对简洁和可扩展性的追求。而Spring Boot虽功能全面,但其自身重量级的特性可能成为初学者和小型项目的负担。

       国内支持:社区力量的差异

       JFinal在国内拥有稳定的用户群体,作者直接支持,遇到问题能得到及时解答,而Spring Boot虽然全球范围内应用广泛,但在国内的特定支持可能不如JFinal直接。

       总的来说,JFinal凭借其精简的设计和易用性,以及对国内用户的贴心支持,在与Spring Boot的竞争中展现出了独特的优势。然而,Spring Boot的全面集成和强大的生态系统,使其在大型项目和复杂需求场景下仍然占据一席之地。开发者应根据项目需求和个人偏好,选择最适合自己的框架。

jfinal不兼容的浏览器有哪些

       官网介绍:

       JFinal 是基于 Java 语言的极速 WEB + ORM

       框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友

        :)

       Jfinal是JAVA框架, 不在浏览器上执行的, 是两个方向。

       如果你说的是Jfinal做为后台,进行下载文件服务时,是否有浏览器兼容问题,在Jfinal3.0之后的版已经全面兼容了

       3.3版的源码中可看到已经有处理:

/

**

        * Copyright (c) -, James Zhan 詹波 (jfinal@.com).

        

*

        * Licensed under the Apache License, Version 2.0 (the "License");

        * you may not use this file except in compliance with the License.

        * You may obtain a copy of the License at

        

*

        *      .jfinal.render;

       import java.io.BufferedInputStream;

       import java.io.File;

       import java.io.FileInputStream;

       import java.io.IOException;

       import java.io.InputStream;

       import java.io.OutputStream;

       import java.io.UnsupportedEncodingException;

       import java.net.URLEncoder;

       import javax.servlet.ServletContext;

       import javax.servlet..jfinal.kit.LogKit;

       import com.jfinal.kit.StrKit;

       /

**

        * FileRender.

        */

       public class FileRender extends Render {

           

           protected static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

           protected static String baseDownloadPath;

           protected static ServletContext servletContext;

           

           protected File file;

           protected String downloadFileName = null;

           

           public FileRender(File file) {

               if (file == null) {

                   throw new IllegalArgumentException("file can not be null.");

               }

               this.file = file;

           }

           

           public FileRender(File file, String downloadFileName) {

               this(file);

               

               if (StrKit.isBlank(downloadFileName)) {

                   throw new IllegalArgumentException("downloadFileName can not be blank.");

               }

               this.downloadFileName = downloadFileName;

           }

           

           public FileRender(String fileName) {

               if (StrKit.isBlank(fileName)) {

                   throw new IllegalArgumentException("fileName can not be blank.");

               }

               

               String fullFileName;

               fileName = fileName.trim();

               if (fileName.startsWith("/") || fileName.startsWith("\\")) {

                   if (baseDownloadPath.equals("/")) {

                       fullFileName = fileName;

                   } else {

                       fullFileName = baseDownloadPath + fileName;    

                   }

               } else {

                   fullFileName = baseDownloadPath + File.separator + fileName;

               }

               

               this.file = new File(fullFileName);

           }

           

           public FileRender(String fileName, String downloadFileName) {

               this(fileName);

               

               if (StrKit.isBlank(downloadFileName)) {

                   throw new IllegalArgumentException("downloadFileName can not be blank.");

               }

               this.downloadFileName = downloadFileName;

           }

           

           static void init(String baseDownloadPath, ServletContext servletContext) {

               FileRender.baseDownloadPath = baseDownloadPath;

               FileRender.servletContext = servletContext;

           }

           

           public void render() {

               if (file == null || !file.isFile()) {

                   RenderManager.me().getRenderFactory().getErrorRender().setContext(request, response).render();

                   return ;

               }

               

               // ---------

               response.setHeader("Accept-Ranges", "bytes");

               String fn = downloadFileName == null ? file.getName() : downloadFileName;

               response.setHeader("Content-disposition", "attachment; " + encodeFileName(request, fn));

               String contentType = servletContext.getMimeType(file.getName());

               response.setContentType(contentType != null ? contentType : DEFAULT_CONTENT_TYPE);

               

               // ---------

               if (StrKit.isBlank(request.getHeader("Range"))) {

                   normalRender();

               } else {

                   rangeRender();

               }

           }

           

           protected String encodeFileName(String fileName) {

               try {

                   // return new String(fileName.getBytes("GBK"), "ISO-1");

                   return new String(fileName.getBytes(getEncoding()), "ISO-1");

               } catch (UnsupportedEncodingException e) {

                   return fileName;

               }

           }

           

           /

**

            * 依据浏览器判断编码规则

            */

           public String encodeFileName(HttpServletRequest request, String fileName) {

               String userAgent = request.getHeader("User-Agent");

               try {

                   String encodedFileName = URLEncoder.encode(fileName, "UTF8");

                   // 如果没有UA,则默认使用IE的方式进行编码

                   if (userAgent == null) {

                       return "filename=\"" + encodedFileName + "\"";

                   }

                   

                   userAgent = userAgent.toLowerCase();

                   // IE浏览器,只能采用URLEncoder编码

                   if (userAgent.indexOf("msie") != -1) {

                       return "filename=\"" + encodedFileName + "\"";

                   }

                   

                   // Opera浏览器只能采用filename

*

                   if (userAgent.indexOf("opera") != -1) {

                       return "filename*=UTF-8''" + encodedFileName;

                   }

                   

                   // Safari浏览器,只能采用ISO编码的中文输出,Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出

                   if (userAgent.indexOf("safari") != -1 || userAgent.indexOf("applewebkit") != -1 || userAgent.indexOf("chrome") != -1) {

                       return "filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO-1") + "\"";

                   }

                   

                   // FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出

                   if (userAgent.indexOf("mozilla") != -1) {

                       return "filename*=UTF-8''" + encodedFileName;

                   }

                   

                   return "filename=\"" + encodedFileName + "\"";

               } catch (UnsupportedEncodingException e) {

                   throw new RuntimeException(e);

               }

           }

           

           protected void normalRender() {

               response.setHeader("Content-Length", String.valueOf(file.length()));

               InputStream inputStream = null;

               OutputStream outputStream = null;

               try {

                   inputStream = new BufferedInputStream(new FileInputStream(file));

                   outputStream = response.getOutputStream();

                   byte[] buffer = new byte[];

                   for (int len = -1; (len = inputStream.read(buffer)) != -1;) {

                       outputStream.write(buffer, 0, len);

                   }

                   outputStream.flush();

                   outputStream.close();

               } catch (IOException e) {

                   String n = e.getClass().getSimpleName();

                   if (n.equals("ClientAbortException") || n.equals("EofException")) {

                   } else {

                       throw new RenderException(e);

                   }

               } catch (Exception e) {

                   throw new RenderException(e);

               } finally {

                   if (inputStream != null)

                       try { inputStream.close();} catch (IOException e) { LogKit.error(e.getMessage(), e);}

               }

           }

           

           protected void rangeRender() {

               Long[] range = { null, null};

               processRange(range);

               

               String contentLength = String.valueOf(range[1].longValue() - range[0].longValue() + 1);

               response.setHeader("Content-Length", contentLength);

               response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);    // status = 

               

               // Content-Range: bytes 0-/

               StringBuilder contentRange = new StringBuilder("bytes ").append(String.valueOf(range[0])).append("-").append(String.valueOf(range[1])).append("/").append(String.valueOf(file.length()));

               response.setHeader("Content-Range", contentRange.toString());

               

               InputStream inputStream = null;

               OutputStream outputStream = null;

               try {

                   long start = range[0];

                   long end = range[1];

                   inputStream = new BufferedInputStream(new FileInputStream(file));

                   if (inputStream.skip(start) != start)

                           throw new RuntimeException("File skip error");

                   outputStream = response.getOutputStream();

                   byte[] buffer = new byte[];

                   long position = start;

                   for (int len; position <= end && (len = inputStream.read(buffer)) != -1;) {

                       if (position + len <= end) {

                           outputStream.write(buffer, 0, len);

                           position += len;

                       }

                       else {

                           for (int i=0; i<len && position <= end; i++) {

                               outputStream.write(buffer[i]);

                               position++;

                           }

                       }

                   }

                   outputStream.flush();

                   outputStream.close();

               }

               catch (IOException e) {

                   String n = e.getClass().getSimpleName();

                   if (n.equals("ClientAbortException") || n.equals("EofException")) {

                   } else {

                       throw new RenderException(e);

                   }

               }

               catch (Exception e) {

                   throw new RenderException(e);

               }

               finally {

                   if (inputStream != null)

                       try { inputStream.close();} catch (IOException e) { LogKit.error(e.getMessage(), e);}

               }

           }

           

           /

**

            * Examples of byte-ranges-specifier values (assuming an entity-body of length ):

            * The first  bytes (byte offsets 0-, inclusive): bytes=0-

            * The second  bytes (byte offsets -, inclusive): bytes=-

            * The final  bytes (byte offsets -, inclusive): bytes=-

            *                                                             Or bytes=-

            */

           protected void processRange(Long[] range) {

               String rangeStr = request.getHeader("Range");

               int index = rangeStr.indexOf(',');

               if (index != -1)

                   rangeStr = rangeStr.substring(0, index);

               rangeStr = rangeStr.replace("bytes=", "");

               

               String[] arr = rangeStr.split("-", 2);

               if (arr.length < 2)

                   throw new RuntimeException("Range error");

               

               long fileLength = file.length();

               for (int i=0; i<range.length; i++) {

                   if (StrKit.notBlank(arr[i])) {

                       range[i] = Long.parseLong(arr[i].trim());

                       if (range[i] >= fileLength)

                           range[i] = fileLength - 1;

                   }

               }

               

               // Range format like: -

               if (range[0] != null && range[1] == null) {

                   range[1] = fileLength - 1;

               }

               // Range format like: -

               else if (range[0] == null && range[1] != null) {

                   range[0] = fileLength - range[1];

                   range[1] = fileLength - 1;

               }

               

               // check final range

               if (range[0] == null || range[1] == null || range[0].longValue() > range[1].longValue())

                   throw new RuntimeException("Range error");

           }

       }

jfinal 如何导出zip压缩文件

       官网介绍:JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 :)

       Jfinal做为后台,进行下载文件服务时,源码中可看到:

       Controller中已经提供了,方法:

/

**

            * Render with file

            */

           public void renderFile(String fileName) {

               render = renderManager.getRenderFactory().getFileRender(fileName);

           }

           

           /

**

            * Render with file, using the new file name to the client

            */

           public void renderFile(String fileName, String downloadFileName) {

               render = renderManager.getRenderFactory().getFileRender(fileName, downloadFileName);

           }

           

           /

**

            * Render with file

            */

           public void renderFile(File file) {

               render = renderManager.getRenderFactory().getFileRender(file);

           }

           

           /

**

            * Render with file, using the new file name to the client

            file=文件 ,downloadFileName=下载时客户端显示的文件名称 ,很贴心

            */

           public void renderFile(File file, String downloadFileName) {

               render = renderManager.getRenderFactory().getFileRender(file, downloadFileName);

           }

       大家可以看到源码中 FileRender  是有处理各个浏览器的兼容问题,所以可以方便的使用

/

**

        * Copyright (c) -, James Zhan 詹波 (jfinal@.com).

        

*

        * Licensed under the Apache License, Version 2.0 (the "License");

        * you may not use this file except in compliance with the License.

        * You may obtain a copy of the License at

        

*

        *      .jfinal.render;

        

       import java.io.BufferedInputStream;

       import java.io.File;

       import java.io.FileInputStream;

       import java.io.IOException;

       import java.io.InputStream;

       import java.io.OutputStream;

       import java.io.UnsupportedEncodingException;

       import java.net.URLEncoder;

       import javax.servlet.ServletContext;

       import javax.servlet..jfinal.kit.LogKit;

       import com.jfinal.kit.StrKit;

        

       /

**

        * FileRender.

        */

       public class FileRender extends Render {

            

           protected static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

           protected static String baseDownloadPath;

           protected static ServletContext servletContext;

            

           protected File file;

           protected String downloadFileName = null;

            

           public FileRender(File file) {

               if (file == null) {

                   throw new IllegalArgumentException("file can not be null.");

               }

               this.file = file;

           }

            

           public FileRender(File file, String downloadFileName) {

               this(file);

                

               if (StrKit.isBlank(downloadFileName)) {

                   throw new IllegalArgumentException("downloadFileName can not be blank.");

               }

               this.downloadFileName = downloadFileName;

           }

            

           public FileRender(String fileName) {

               if (StrKit.isBlank(fileName)) {

                   throw new IllegalArgumentException("fileName can not be blank.");

               }

                

               String fullFileName;

               fileName = fileName.trim();

               if (fileName.startsWith("/") || fileName.startsWith("\\")) {

                   if (baseDownloadPath.equals("/")) {

                       fullFileName = fileName;

                   } else {

                       fullFileName = baseDownloadPath + fileName;    

                   }

               } else {

                   fullFileName = baseDownloadPath + File.separator + fileName;

               }

                

               this.file = new File(fullFileName);

           }

            

           public FileRender(String fileName, String downloadFileName) {

               this(fileName);

                

               if (StrKit.isBlank(downloadFileName)) {

                   throw new IllegalArgumentException("downloadFileName can not be blank.");

               }

               this.downloadFileName = downloadFileName;

           }

            

           static void init(String baseDownloadPath, ServletContext servletContext) {

               FileRender.baseDownloadPath = baseDownloadPath;

               FileRender.servletContext = servletContext;

           }

            

           public void render() {

               if (file == null || !file.isFile()) {

                   RenderManager.me().getRenderFactory().getErrorRender().setContext(request, response).render();

                   return ;

               }

                

               // ---------

               response.setHeader("Accept-Ranges", "bytes");

               String fn = downloadFileName == null ? file.getName() : downloadFileName;

               response.setHeader("Content-disposition", "attachment; " + encodeFileName(request, fn));

               String contentType = servletContext.getMimeType(file.getName());

               response.setContentType(contentType != null ? contentType : DEFAULT_CONTENT_TYPE);

                

               // ---------

               if (StrKit.isBlank(request.getHeader("Range"))) {

                   normalRender();

               } else {

                   rangeRender();

               }

           }

            

           protected String encodeFileName(String fileName) {

               try {

                   // return new String(fileName.getBytes("GBK"), "ISO-1");

                   return new String(fileName.getBytes(getEncoding()), "ISO-1");

               } catch (UnsupportedEncodingException e) {

                   return fileName;

               }

           }

            

           /

**

            * 依据浏览器判断编码规则

            */

           public String encodeFileName(HttpServletRequest request, String fileName) {

               String userAgent = request.getHeader("User-Agent");

               try {

                   String encodedFileName = URLEncoder.encode(fileName, "UTF8");

                   // 如果没有UA,则默认使用IE的方式进行编码

                   if (userAgent == null) {

                       return "filename=\"" + encodedFileName + "\"";

                   }

                    

                   userAgent = userAgent.toLowerCase();

                   // IE浏览器,只能采用URLEncoder编码

                   if (userAgent.indexOf("msie") != -1) {

                       return "filename=\"" + encodedFileName + "\"";

                   }

                    

                   // Opera浏览器只能采用filename

*

                   if (userAgent.indexOf("opera") != -1) {

                       return "filename*=UTF-8''" + encodedFileName;

                   }

                    

                   // Safari浏览器,只能采用ISO编码的中文输出,Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出

                   if (userAgent.indexOf("safari") != -1 || userAgent.indexOf("applewebkit") != -1 || userAgent.indexOf("chrome") != -1) {

                       return "filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO-1") + "\"";

                   }

                    

                   // FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出

                   if (userAgent.indexOf("mozilla") != -1) {

                       return "filename*=UTF-8''" + encodedFileName;

                   }

                    

                   return "filename=\"" + encodedFileName + "\"";

               } catch (UnsupportedEncodingException e) {

                   throw new RuntimeException(e);

               }

           }

            

           protected void normalRender() {

               response.setHeader("Content-Length", String.valueOf(file.length()));

               InputStream inputStream = null;

               OutputStream outputStream = null;

               try {

                   inputStream = new BufferedInputStream(new FileInputStream(file));

                   outputStream = response.getOutputStream();

                   byte[] buffer = new byte[];

                   for (int len = -1; (len = inputStream.read(buffer)) != -1;) {

                       outputStream.write(buffer, 0, len);

                   }

                   outputStream.flush();

                   outputStream.close();

               } catch (IOException e) {

                   String n = e.getClass().getSimpleName();

                   if (n.equals("ClientAbortException") || n.equals("EofException")) {

                   } else {

                       throw new RenderException(e);

                   }

               } catch (Exception e) {

                   throw new RenderException(e);

               } finally {

                   if (inputStream != null)

                       try { inputStream.close();} catch (IOException e) { LogKit.error(e.getMessage(), e);}

               }

           }

            

           protected void rangeRender() {

               Long[] range = { null, null};

               processRange(range);

                

               String contentLength = String.valueOf(range[1].longValue() - range[0].longValue() + 1);

               response.setHeader("Content-Length", contentLength);

               response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);    // status = 

                

               // Content-Range: bytes 0-/

               StringBuilder contentRange = new StringBuilder("bytes ").append(String.valueOf(range[0])).append("-").append(String.valueOf(range[1])).append("/").append(String.valueOf(file.length()));

               response.setHeader("Content-Range", contentRange.toString());

                

               InputStream inputStream = null;

               OutputStream outputStream = null;

               try {

                   long start = range[0];

                   long end = range[1];

                   inputStream = new BufferedInputStream(new FileInputStream(file));

                   if (inputStream.skip(start) != start)

                           throw new RuntimeException("File skip error");

                   outputStream = response.getOutputStream();

                   byte[] buffer = new byte[];

                   long position = start;

                   for (int len; position <= end && (len = inputStream.read(buffer)) != -1;) {

                       if (position + len <= end) {

                           outputStream.write(buffer, 0, len);

                           position += len;

                       }

                       else {

                           for (int i=0; i<len && position <= end; i++) {

                               outputStream.write(buffer[i]);

                               position++;

                           }

                       }

                   }

                   outputStream.flush();

                   outputStream.close();

               }

               catch (IOException e) {

                   String n = e.getClass().getSimpleName();

                   if (n.equals("ClientAbortException") || n.equals("EofException")) {

                   } else {

                       throw new RenderException(e);

                   }

               }

               catch (Exception e) {

                   throw new RenderException(e);

               }

               finally {

                   if (inputStream != null)

                       try { inputStream.close();} catch (IOException e) { LogKit.error(e.getMessage(), e);}

               }

           }

            

           /

**

            * Examples of byte-ranges-specifier values (assuming an entity-body of length ):

            * The first  bytes (byte offsets 0-, inclusive): bytes=0-

            * The second  bytes (byte offsets -, inclusive): bytes=-

            * The final  bytes (byte offsets -, inclusive): bytes=-

            *                                                             Or bytes=-

            */

           protected void processRange(Long[] range) {

               String rangeStr = request.getHeader("Range");

               int index = rangeStr.indexOf(',');

               if (index != -1)

                   rangeStr = rangeStr.substring(0, index);

               rangeStr = rangeStr.replace("bytes=", "");

                

               String[] arr = rangeStr.split("-", 2);

               if (arr.length < 2)

                   throw new RuntimeException("Range error");

                

               long fileLength = file.length();

               for (int i=0; i<range.length; i++) {

                   if (StrKit.notBlank(arr[i])) {

                       range[i] = Long.parseLong(arr[i].trim());

                       if (range[i] >= fileLength)

                           range[i] = fileLength - 1;

                   }

               }

                

               // Range format like: -

               if (range[0] != null && range[1] == null) {

                   range[1] = fileLength - 1;

               }

               // Range format like: -

               else if (range[0] == null && range[1] != null) {

                   range[0] = fileLength - range[1];

                   range[1] = fileLength - 1;

               }

                

               // check final range

               if (range[0] == null || range[1] == null || range[0].longValue() > range[1].longValue())

                   throw new RuntimeException("Range error");

           }

       }

本文地址:http://5o.net.cn/html/95e51499390.html 欢迎转发