java原装代码完成pdf在线预览和pdf打印及下载

来源:互联网 发布:域名怎么注册 编辑:程序博客网 时间:2024/06/09 17:34

这是我在工作中,遇到这样需求,完成需求后,总结的成果,就当做是工作笔记,以免日后忘记,当然,能帮助到别人是最好的啦!下面进入正题:

  若有不正之处请多多谅解,并欢迎批评指正。

  请尊重作者劳动成果,转载请标明原文链接:


前提准备:

 

  1. 项目中至少需要引入的jar包,注意版本:

    a) core-renderer.jar

    b) freemarker-2.3.16.jar

    c) iText-2.0.8.jar

    d) iTextAsian.jar

上代码:

注释: 此类为自定义的Tag类的基类,在action中怎么放的数据,在ftl中就怎么取数据,简洁明了。

 1. 自定义Tag类的基类

复制代码
/** * 通用的生成pdf预览和生成打印的html文件 *  * @author xg君 *  */public abstract class PDFTag extends BodyTagSupport {    private static final long serialVersionUID = 1L;    // 标签属性变量    private String json = "";    private String tempDir = "";    // 非标签属性变量    private Map<String, Object> rootMap = new HashMap<String, Object>();    private String templateStr = null;    private String freemarkereConfigurationBeanName = null;    private String fileName = null;    private String basePath = null;    private String fileEncoding = "utf-8";    @Override    public int doStartTag() throws JspException {        setConfigParams();        WebApplicationContext application = WebApplicationContextUtils.getWebApplicationContext(pageContext                .getServletContext());        doServiceStart();        String ctx = (String) pageContext.getAttribute("ctx");        rootMap.put("ctx", ctx);        Map<String, Object> map = parseJSON2Map(json);        rootMap.putAll(map);        if (freemarkereConfigurationBeanName == null) {            try {                throw new CstuException("FreemarkereConfigurationBeanName不能为空!");            } catch (CstuException e) {                e.printStackTrace();            }        }        Configuration cptFreemarkereConfiguration = (Configuration) application                .getBean(freemarkereConfigurationBeanName);        try {            if (templateStr == null) {                throw new CstuException("模板文件不能为空!");            }            Template template = cptFreemarkereConfiguration.getTemplate(templateStr);            if (basePath == null) {                throw new CstuException("文件的基本路径(父路径)不能为空!");            }            File htmlPath = new File(tempDir + File.separator + basePath);            if (!htmlPath.exists()) {                htmlPath.mkdirs();            }            if (fileName == null) {                throw new CstuException("生成的html文件名不能为空!");            }            File htmlFile = new File(htmlPath, File.separator + fileName);            if (!htmlFile.exists()) {                htmlFile.createNewFile();            }            BufferedWriter out = new BufferedWriter(                    new OutputStreamWriter(new FileOutputStream(htmlFile), fileEncoding));            template.process(rootMap, out);            out.flush();            doServiceDoing();            // 显示在页面            template.process(rootMap, pageContext.getResponse().getWriter());        } catch (Exception e) {            e.printStackTrace();        }        doServiceEnd();        return SKIP_BODY;    }    /**     * 配置基础参数,如     */    public abstract void setConfigParams();    /**     * 业务处理方法-开始 填充数据     *      * @return     */    public abstract void doServiceStart();    /**     * 业务处理方法-执行中 备用,可空实现,若rootMap中存在双份数据则可在此处填充判断条件     *      * @return     */    public abstract void doServiceDoing();    /**     * 业务处理方法-结束 清空rootMap并调用垃圾回收,也可空实现     *      * @return     */    public abstract void doServiceEnd();    /**     * 将元素放入rootMap中     */    public void putKV(String key, Object value) {        rootMap.put(key, value);    }    /**     * 将map放入rootMap中     *      * @param m     */    public void putMap(Map m) {        rootMap.putAll(m);    }    public void clear() {        rootMap.clear();        rootMap = null;    }    /**     * 移除元素     *      * @param key     * @return     */    public Object remove(String key) {        return rootMap.remove(key);    }    public static Map<String, Object> parseJSON2Map(String jsonStr) {        Map<String, Object> map = new HashMap<String, Object>();        JSONObject json = JSONObject.fromObject(jsonStr);        for (Object k : json.keySet()) {            Object v = json.get(k);            if (v instanceof JSONArray) {                List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();                Iterator<JSONObject> it = ((JSONArray) v).iterator();                while (it.hasNext()) {                    JSONObject json2 = it.next();                    list.add(parseJSON2Map(json2.toString()));                }                map.put(k.toString(), list);            } else {                map.put(k.toString(), v);            }        }        return map;    }    public String getJson() {        return json;    }    public void setJson(String json) {        this.json = json;    }    public String getTempDir() {        return tempDir;    }    public void setTempDir(String tempDir) {        this.tempDir = tempDir;    }    public String getTemplateStr() {        return templateStr;    }    public void setTemplateStr(String templateStr) {        this.templateStr = templateStr;    }    public String getFreemarkereConfigurationBeanName() {        return freemarkereConfigurationBeanName;    }    public void setFreemarkereConfigurationBeanName(String freemarkereConfigurationBeanName) {        this.freemarkereConfigurationBeanName = freemarkereConfigurationBeanName;    }    public String getFileName() {        return fileName;    }    public void setFileName(String fileName) {        this.fileName = fileName;    }    public String getBasePath() {        return basePath;    }    public void setBasePath(String basePath) {        this.basePath = basePath;    }    public String getFileEncoding() {        return fileEncoding;    }    public void setFileEncoding(String fileEncoding) {        this.fileEncoding = fileEncoding;    }}
复制代码

 

注释: setConfigParams方法是用于调用接口定义的配置参数的方法,如:templateStr,basePath等,doServiceStart,doServiceDoing和doServiceEnd等方法用于处理业务逻辑,比如我的需求是做出合同在一个页面显示,要分页,要加水印,但生成的pdf样式与预览的是不同的,所以我加了一个doServiceDoing中给rootMap添加判断条件,这样就能一个flt文件作出两种效果(预览和打印),当然如果预览和打印要一样的效果,doServiceDoing方法可以空实现。这四个方法总结一下就是:

1. setConfigParams : 配置参数

2. doServiceStart : 填充数据/条件

3. doServiceDoing : 填充数据/条件,到这里是个分界线,此方法之前,rootMap数据先进入html再进入浏览器(预览),此方法之后,rootMap数据会再次进入html文件,结束,所以此处可写判断

4. doServiceEnd : 可有可无,我还是写上了,万一哪天的数据集太大,此处便可以在数据填充完后,清理掉,节省内存空间

 

2. PDFTag的子类

复制代码
/** * 用户自定义PDFTag类 *  * @author xg君 *  */public class ViewPDFTag extends PDFTag {    private static final long serialVersionUID = 4528567203497016087L;    private String prjNature = "";    private String bookName = "";    private String prjCode = "";    /**     * 用户自定义的配置参数     */    public PDFConfigurationInterface pDFConfigurationInterface = new PDFConfigurationInterface() {        @Override        public void configTemplateStr() {            // 横,纵向            if (prjNature.equalsIgnoreCase("2") || prjNature.equalsIgnoreCase("1")) {                setTemplateStr("wj-project-print.ftl");            }        }        @Override        public void configFreemarkereConfigurationBeanName() {            setFreemarkereConfigurationBeanName("cptFreemarkereConfiguration");        }        @Override        public void configFileName() {            // 填入html文件            setFileName(prjCode + ".html");        }        @Override        public void configFileEncoding() {            // 默认utf-8        }        @Override        public void configBasePath() {            setBasePath("html_pdf");        }    };    @Override    public void doServiceStart() {        putKV("prjNature", prjNature);        putKV("bookName", bookName);        putKV("flag", "0");    }    @Override    public void doServiceDoing() {        putKV("flag", "1");    }    @Override    public void doServiceEnd() {        clear();        System.gc();    }    public String getPrjNature() {        return prjNature;    }    public void setPrjNature(String prjNature) {        this.prjNature = prjNature;    }    public String getBookName() {        return bookName;    }    public void setBookName(String bookName) {        this.bookName = bookName;    }    public String getPrjCode() {        return prjCode;    }    public void setPrjCode(String prjCode) {        this.prjCode = prjCode;    }    @Override    public void setConfigParams() {        pDFConfigurationInterface.configTemplateStr();        pDFConfigurationInterface.configFreemarkereConfigurationBeanName();        pDFConfigurationInterface.configFileName();        pDFConfigurationInterface.configBasePath();        pDFConfigurationInterface.configFileEncoding();    }}
复制代码

注释: PDFConfigurationInterface 是自定义的标签参数配置接口,子类必须定义一个该接口的实现类的成员变量或定义一个成员变量内部类,并在setConfigParams方法中调用使其生效。

子类的成员变量接收在tld文件中配置的属性。

3. 自定义的标签参数配置接口

复制代码
/** * PdfTag类的配置 *  * @author xg君 *  */public interface PDFConfigurationInterface {    /**     * 设置模板名称     */    void configTemplateStr();    /**     * 设置配置的FreemarkereConfigurationBean的名称     */    void configFreemarkereConfigurationBeanName();    /**     * 设置生成的html文件名称     */    void configFileName();    /**     * 设置生成的html文件的基本路径(父目录)     */    void configBasePath();    /**     * 设置文件编码,默认utf-8     */    void configFileEncoding();}
复制代码

 

4. 自定义异常类

复制代码
/** * 自定义异常类 *  * @author Administrator *  */public class CstuException extends Exception {    private static final long serialVersionUID = 4266461814747405870L;    public CstuException(String msg) {        super(msg);    }}
复制代码

 

5. tld文件配置

复制代码
<tag>        <name>print</name>        <tagclass>com.iris.taglib.web.PreviewPDFTag</tagclass>        <bodycontent>JSP</bodycontent>        <attribute>            <name>json</name>            <required>true</required>            <rtexprvalue>true</rtexprvalue>        </attribute>        <attribute>            <name>prjNature</name>            <required>true</required>            <rtexprvalue>true</rtexprvalue>        </attribute>        <attribute>            <name>bookName</name>            <required>true</required>            <rtexprvalue>true</rtexprvalue>        </attribute>        <attribute>            <name>tempDir</name>            <required>true</required>            <rtexprvalue>true</rtexprvalue>        </attribute>        <attribute>            <name>prjCode</name>            <required>true</required>            <rtexprvalue>true</rtexprvalue>        </attribute>    </tag>
复制代码

6. action

复制代码
/** * 处理PDF导出 *  */@Namespace("/export")@Results({ @Result(name = "exceltemplate", location = "/WEB-INF/content/pdf/export-pdf.jsp"),        @Result(name = "exprotPdf2", location = "/WEB-INF/content/project/project/export/export-pdf2.jsp") })public class ExportPdfAction extends ActionSupport {    private static final long serialVersionUID = -5454188364706173477L;    @Value("${tempDir}")    private String tempDir;    @Value("${pdfFont}")    private String pdfFont;    @Value("${staticResRootDir}")    private String staticResRootDir;    @Value("${staticResRootDir2}")    private String staticResRootDir2;    @Value("${WaterMarkImgDir}")    private String waterMarkImgDir;    @Autowired    private ProjectService projectService;    @Autowired    private PersonService personService;    @Autowired    private ConstDictionaryService constDictionaryService;    @Autowired    private FdPlanDetailService fdPlanDetailService;    @Autowired    private ServiceFactory serviceFactory;    @Action("exprotPdf2")    public String exprotPdf2() {        String prjCode = Struts2Utils.getParameter("prjCode");        prjCode = Struts2Utils.decodeDesString(prjCode);        Project project = projectService.getProjectById(Long.parseLong(prjCode));        Map<String, String> baseInfo = new HashMap<String, String>();        baseInfo.put("tempDir", tempDir);        // 项目编号        baseInfo.put("prjCode", prjCode);        // 项目类型        String prjNature = project.getPrjNature();        baseInfo.put("prjNature", prjNature);        // 水印名称格式:watermark+"-"+prjNature        baseInfo.put("waterMarkImg", waterMarkImgDir + File.separator + "watermark-" + prjNature + ".png");        // 负责人        Person person = personService.getPerson(project.getPsnCode());        String zhName = person.getZhName();        baseInfo.put("zhName", addStr(9, "<br/>", zhName));        // 项目编号        String prjNo = project.getPrjNo();        baseInfo.put("prjNo", prjNo);        // 项目来源        ConstDictionary cd = constDictionaryService.findCdByCategoryCode("project_from", project.getGrantNo());        String project_from = cd.getzh_cn_caption();        baseInfo.put("project_from", addStr(9, "<br/>", project_from));        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");        // 起止年限        String startDate = sdf.format(project.getStartDate());        String endDate = sdf.format(project.getEndDate());        String startEndDate = startDate + "~" + endDate;        baseInfo.put("startEndDate", startEndDate);        // 项目类别--资助类别        String grantName = project.getGrantName();        baseInfo.put("grantName", addStr(9, "<br/>", grantName));        // 合同金额        String totalAmt = project.getTotalAmt().toString();        BigDecimal totalAmt_ = checkNumber(totalAmt);        baseInfo.put("totalAmt", totalAmt_.toString());        // 项目名称        String zhTitle = project.getZhTitle();        baseInfo.put("zhTitle", addStr(38, "<br/>", zhTitle));        List<Map<String, String>> ps = null;        try {            ps = getMembers(project.getPrjXml());        } catch (Exception e) {            e.printStackTrace();        }        String bookName = "";        // 判断项目类型        Map<String, Object> itemMap = new HashMap<String, Object>();        if (prjNature.equalsIgnoreCase("1")) {            bookName = "第一份合同";            // 获取fdPlanDetail            List<Map<String, Object>> list = fdPlanDetailService.getFdPlanDetailsByPrjCode(Long.parseLong(prjCode));            // test            /*             * Map<String, Object> test = new HashMap<String, Object>(); test.put("itemValue", "6");             * test.put("itemName", "差旅费"); test.put("remark", "xxxxx"); list.add(test);             */            for (Map<String, Object> m : list) {                String key = (String) m.get("ITEMNAME");                BigDecimal itemValue = (BigDecimal) m.get("ITEMVALUE");                BigDecimal proportion = new BigDecimal(0.00);                if (itemValue != null && totalAmt_.compareTo(new BigDecimal("0.00")) != 0) {                    proportion = itemValue.divide(totalAmt_, 6, BigDecimal.ROUND_HALF_EVEN);                }                if (itemValue == null) {                    itemValue = new BigDecimal("0.00");                }                proportion = checkNumber(proportion.toString());                Map<String, Object> data = new HashMap<String, Object>();                // 添加比例                data.put("proportion", proportion.toString());                // 检测金额规范                BigDecimal amt = checkNumber(itemValue.toString());                data.put("itemValue", amt.toString());                // remark                String remark = (String) m.get("REAMRK");                data.put("remark", remark == null ? "" : remark);                itemMap.put(key, data);            }        } else if (prjNature.equalsIgnoreCase("2")) {            bookName = "第二份合同";        }        Map<String, Object> map = new HashMap<String, Object>();        map.put("baseInfo", baseInfo);        map.put("projectMember", ps);        map.put("itemMap", itemMap);        map.put("psCount", ps.size());        map.put("psSum", 25 - ps.size());        String json = JSONObject.fromObject(map).toString();        Struts2Utils.getRequest().setAttribute("jsonData", json);        Struts2Utils.getRequest().setAttribute("prjNature", prjNature);        Struts2Utils.getRequest().setAttribute("bookName", bookName);        Struts2Utils.getRequest().setAttribute("tempDir", tempDir);        Struts2Utils.getRequest().setAttribute("prjCode", prjCode);        return "exprotPdf2";    }    public List<Map<String, String>> getMembers(String xmlData) throws Exception {        List<Map<String, String>> list = new ArrayList<Map<String, String>>();        Document doc = DocumentHelper.parseText(xmlData);        Node ps = doc.selectSingleNode("/data/project/persons");        List<Node> psList = ps.selectNodes("person");        String totalAmt = doc.selectSingleNode("/data/project/basic_info/total_amt").getText();        for (Node person : psList) {            Map<String, String> map = new HashMap<String, String>();            Node fund_proportion = person.selectSingleNode("fund_proportion");            String fund_proportion_text = "";            if (fund_proportion == null) {                map.put("proportion", "0.00");                map.put("fpAmt", "0.00");            } else {                fund_proportion_text = fund_proportion.getText();                BigDecimal fp = new BigDecimal(fund_proportion_text);                fp = fp.multiply(new BigDecimal("0.01"));                fp = checkNumber(fp.toString());                map.put("proportion", fp.toString());                BigDecimal fdAmt_ = fp.multiply(new BigDecimal(totalAmt));                fdAmt_ = checkNumber(fdAmt_.toString());                map.put("fpAmt", fdAmt_.toString());            }            Node psn_name = person.selectSingleNode("psn_name");            String psn_name_text = psn_name.getText();            map.put("zhName_p", addStr(9, "<br/>", psn_name_text));            Node psn_work = person.selectSingleNode("psn_work");            String psn_work_text = "";            if (psn_work != null) {                psn_work_text = psn_work.getText();            }            map.put("work", addStr(9, "<br/>", psn_work_text));            Node dept_code_name = person.selectSingleNode("dept_code_name");            String dept_code_name_text = "";            if (dept_code_name != null) {                dept_code_name_text = dept_code_name.getText();            }            map.put("deptName", addStr(9, "<br/>", dept_code_name_text));            Node psn_type_name = person.selectSingleNode("psn_type_name");            String psn_type_name_text = "";            if (psn_type_name != null) {                psn_type_name_text = psn_type_name.getText();            }            map.put("psnTypeName", psn_type_name_text);            list.add(map);        }        return list;    }    /**     * 为字符串添加指定字符     *      * @param num     * @param splitStr     * @param str     * @return     */    public String addStr(int num, String splitStr, String str) {        StringBuffer sb = new StringBuffer();        String temp = str;        int len = str.length();        while (len > 0) {            if (len < num) {                num = len;            }            sb.append(temp.substring(0, num)).append(splitStr);            temp = temp.substring(num);            len = temp.length();        }        return sb.toString();    }    /**     * 两个数字/英文     *      * @param str     * @param num     * @return 最终索引     */    public static int getEndIndex(String str, double num) {        int idx = 0;        int count = 0;        double val = 0.00;        // 判断是否是英文/数字        for (int i = 0; i < str.length(); i++) {            if ((str.charAt(i) >= 'A' && str.charAt(i) <= 'Z') || (str.charAt(i) >= 'a' && str.charAt(i) <= 'z')                    || Character.isDigit(str.charAt(i))) {                val += 0.50;            } else {                val += 1.00;            }            count = i + 1;            if (val >= num) {                idx = i;                break;            }        }        if (idx == 0) {            idx = count;        }        return idx;    }    /**     * 下载pdf文件     *      * @return     */    @Action("downLoad")    public String downLoad() {        String prjCode = Struts2Utils.getParameter("prjCode");        String basePath = "html_pdf";        Project project = projectService.getProjectById(Long.parseLong(prjCode));        String zhTitle = project.getZhTitle();        FileOutputStream fos = null;        // html        File htmlFile = new File(tempDir + File.separator + basePath + File.separator + prjCode + ".html");        String pdfPath = tempDir + File.separator + basePath + File.separator + zhTitle + ".pdf";        try {            fos = new FileOutputStream(pdfPath);            String url = htmlFile.toURI().toURL().toString();            ITextRenderer renderer = new ITextRenderer();            renderer.setDocument(url);            ITextFontResolver fontResolver = renderer.getFontResolver();            fontResolver.addFont(pdfFont + File.separator + "SimSun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);            renderer.layout();            renderer.createPDF(fos);        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (fos != null) {                    fos.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        // 添加水印        String wartermark = "";        if (project.getPrjNature().equalsIgnoreCase("1")) {            // 横向            wartermark = "CTGU横向科研项目立项通知书";        } else if (project.getPrjNature().equalsIgnoreCase("2")) {            // 纵向            wartermark = "CTGU纵向科研项目立项通知书";        }        String wm_pdf = tempDir + File.separator + "wm_" + project.getZhTitle() + ".pdf";        try {            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(wm_pdf));            waterMark(bos, pdfPath, wartermark, staticResRootDir2 + File.separator + waterMarkImgDir + File.separator                    + "watermark-" + project.getPrjNature() + ".png");        } catch (Exception e2) {            e2.printStackTrace();        }        // 拿到pdf        File pdfFile = new File(wm_pdf);        BufferedOutputStream out = null;        FileInputStream in = null;        try {            in = new FileInputStream(pdfFile);            HttpServletResponse response = Struts2Utils.getResponse();            response.reset();            String fileName = zhTitle + ".pdf";            String fileName2 = URLEncoder.encode(fileName, "UTF-8");            String agent = Struts2Utils.getRequest().getHeader("USER-AGENT");            // IE            if (null != agent && -1 != agent.indexOf("MSIE")) {                fileName2 = new String(fileName.getBytes("GBK"), "ISO8859-1");            } else if (null != agent && -1 != agent.indexOf("Mozilla")) {                fileName2 = new String(fileName.getBytes("UTF-8"), "ISO8859-1");            }            response.setCharacterEncoding("UTF-8");            response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName2 + "\"");            response.setContentType(FileContentTypes.getContentType(zhTitle + ".pdf"));            out = new BufferedOutputStream(response.getOutputStream());            byte[] buffer = new byte[16 * 1024];            int len = 0;            while ((len = in.read(buffer)) > 0) {                out.write(buffer, 0, len);            }            out.flush();        } catch (Exception e) {            e.printStackTrace();        } finally {            if (out != null) {                try {                    out.close();                } catch (IOException e1) {                    e1.printStackTrace();                }                try {                    in.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        // 清理残留文件        // if (htmlFile.exists()) {        // htmlFile.delete();        // }        File temp_file = new File(pdfPath);        if (temp_file.exists()) {            temp_file.delete();        }        if (pdfFile.exists()) {            pdfFile.delete();        }        return null;    }        public BigDecimal checkNumber(String number) {        // 初始化为6位小数        DecimalFormat df = new DecimalFormat("0.000000");        String num = df.format(Double.parseDouble(number));        BigDecimal bd = new BigDecimal(num);        String val = bd.toString();        val = val.replaceAll("^(0+)", "");        val = val.replaceAll("(0+)$", "");        int idx = val.indexOf(".");        int len = val.substring(idx + 1).length();        if (len < 2) {            if (len == 0 && idx == 0) {                bd = new BigDecimal("0.00");            } else {                bd = new BigDecimal(val).setScale(2);            }        } else {            bd = new BigDecimal(val).setScale(len);        }        return bd;    }    private String replaceStr(String str, String reVal) {        Pattern pattern = Pattern.compile("^" + reVal + "+|" + reVal + "+$");        Matcher matcher = pattern.matcher(str);        return matcher.replaceAll("");    }    /**     * 添加水印     */    private void waterMark(BufferedOutputStream bos, String input, String waterMarkName, String imagePath) {        try {            PdfReader reader = new PdfReader(input);            PdfStamper stamper = new PdfStamper(reader, bos);            int total = reader.getNumberOfPages() + 1;            PdfContentByte content;            BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);            PdfGState gs = new PdfGState();            for (int i = 1; i < total; i++) {                content = stamper.getOverContent(i);// 在内容上方加水印                content.setGState(gs);                content.beginText();                Image image = Image.getInstance(imagePath);                image.setAbsolutePosition(-30, 200);                image.scalePercent(80);                content.addImage(image);                content.endText();            }            stamper.close();        } catch (Exception e) {            e.printStackTrace();        }    }}
复制代码

7. ftl文件(模板文件)

复制代码
<?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml">    <head>      <title>打印预览</title>     <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />......</head><body></body></html>  
复制代码

预览效果:

 

打印效果:

 

  过几天周末,我会上传一个独立的demo到github上,大家有问题可以来问我哦~。

       我的另一篇文章地址:http://www.cnblogs.com/king-xg/p/6369291.html


签名:雪糕君

  

0 0
原创粉丝点击