上一篇我们对freemarker及其使用方式做了简单的介绍,最后展示了自己是如何将模板的生成从第一代doclet中抽出来。在最后展示的doclet2中我们可以看到有以下缺陷:
这一篇将针对上面所列的问题,介绍自己是如何将注解名称从代码中抽出,如何设计model层的数据结构,如何将数据及文件的生成从doclet中抽出来,及针对freemarker模板针对这些数据结构所做的修改。于是有了第三代doclet——doclet3。
这个doclet3是可以用的,之后再这个基础上加上异常提示之后会继续更新。
这些注解名称需要用户自己去定义,并按照一定的格式要求写在一个外部文件中,在doclet的入口处即start中读进来并解析。那么,这些注解名称该在一个什么样的形式展示出来呢?自己最开始设想有三种:
最后选择了以xml的形式来展示,理由如下:
确定以xml方式展示后,紧接着面临着下一个问题:xml内的结构和内容怎么设计?
要解决这个问题,我们得先确定doclet需要从这个xml中知道注解的什么信息:
确定这些后,再来思考如何展示这些信息:
1. 我们需要告诉doclet无非是三类信息:包的相关信息,类的相关信息,方法的相关信息。除此之外,我们需要注意的是:doclet是以包为单位读入注解的(即使设置了多个包名,也是一个包一个包的读入注解),而在一个包中能写注解的地方只有在类和方法上。于是,设计doclat标签作为根标签,在doclet下,设计三个标签:package,class,method分别展示包、类、方法的相关信息,在class和method标签下,设计tag标签来展示一个注解的相关信息,tag标签可以有多个,这样我们可以确定一个大致的xml框架如下:
<doclet>
<package>
</package>
<class>
<tag></tag>
<tag></tag>
</class>
<method>
<tag></tag>
<tag></tag>
</method>
</doclet>
2.在每个tag标签下,设计name标签来告诉doclet有哪些注解,设计type标签来标识注解的格式:string表示是字符串,split表示需要拆分。设计symbo标签来展示拆分的符号,设计item标签来展示拆分后每一项对应的名称(item项要按照顺序写出来,如下面xml展示的param,表示有一个方法注解,名称为param,它是以name~select~type~explain顺序来展示的,如在某个方法上写@param userid~true~string~用户id),值得注意的是:name标签,item标签中的内容将作为map的key存放在map中,再讲其传给freemarker模板,所以name标签和item标签的值要与模板中相应值对应。这样设计的xml如下:
<doclet>
<package>
</package>
<class>
<tag>
<name>uri</name>
<type>string</type>
</tag>
</class>
<method>
<tag>
<name>param</name>
<type>split</type>
<symbol>~</symbol>
<item>name</item>
<item>select</item>
<item>type</item>
<item>explain</item>
</tag>
<tag>
<name>uri</name>
<type>string</type>
</tag>
</method>
</doclet>
3.参考html表单提交时,所有的标签都是以list形式提交,相对应的,我把所有的注解都当成多个注解来处理,在代码中对应的为一个list,单个注解就是一个长度为1的list。这个在设计freemarker时一定要注意
4.参考web.xml中servlet的展示,相对应的,在package,class,method标签下,设计viewpath标签来表示模板的信息,在每个viewpath下,设计name标签标识模板的名称(在model设计中其作为map的key存放),value标签标识模板的路径。viewpath标签可以有多个,也可以没有,若有多个,则多个viewpath的name不能相同。
模板的路径有了,那么模板的输出又怎么来获取呢?在package标签中,设计basepath标签来标识这个包下所有文件路径(如果有方法或者类的输出路径,也包括这些路径)的基本路径,所有输出路径都是相对于这个路径而已的(这个标签是必须的),设计outpath标签来标识模板的输出信息,设计name标签与viewpath下的name标签相同,设计value标签来标识相对于basepaht路径的文件路径,在method和class下,由于class和method对应的模板输出路径要在注解中指定,所有输出路径是写在tag标签中的,name标签要和对应的viewpath标签的那么相同,type标签是固定值:string。设计的注解xml如下:
这里写代码片<doclet>
<package>
<outpath>
<name>packagehtml</name>
<value>2.0.html</value>
</outpath>
<viewpath>
<name>packagehtml</name>
<value>D:/model/index.ftl</value>
</viewpath>
<basepath>d:/api2</basepath>
</package>
<class>
<tag>
<name>uri</name>
<type>string</type>
</tag>
</class>
<method>
<tag>
<name>param</name>
<type>split</type>
<symbol>~</symbol>
<item>name</item>
<item>select</item>
<item>type</item>
<item>explain</item>
</tag>
<tag>
<name>uri</name>
<type>string</type>
</tag>
<tag>
<name>html</name>
<type>string</type>
</tag>
<tag>
<name>java</name>
<type>string</type>
</tag>
<viewpath>
<name>html</name>
<value>D:/model/Method.ftl</value>
</viewpath>
<viewpath>
<name>java</name>
<value>D:/model/javaTemplate.ftl</value>
</viewpath>
</method>
</doclet>
如上所示,每个方法有两个模板,其位置分别为D:/model/Method.ftl和D:/model/javaTemplate.ftl,对应的输出文件是在方法上@java和@html这两个注解标识的,包对应的有一个模板,其位置为D:/model/index.ftl,模板的输出文件位置为d:/api2/2.0.html
在标识整个注解信息的xml设计完成之后,doclet该如何知道这个xml的位置呢?
考虑到这个xml需要在程序最开始的时候读入,并且其位置信息还需要用户输入。于是很自然的想到了doclet的自定义标签(关于如何自定义doclet标签,可以参考博客《java doclet概述》)。这里我自定义了-xmlpath标签来标识这个xml的位置。这个xml的名称为xmltest.xml,位置为d:\,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<doclet>
<package>
<outpath>
<name>packagehtml</name>
<value>2.0.html</value>
</outpath>
<viewpath>
<name>packagehtml</name>
<value>D:/model/index.ftl</value>
</viewpath>
<basepath>d:/api2</basepath>
</package>
<class>
<tag>
<name>uri</name>
<type>string</type>
</tag>
<tag>
<name>description</name>
<type>string</type>
</tag>
</class>
<method>
<tag>
<name>param</name>
<type>split</type>
<symbol>~</symbol>
<item>name</item>
<item>select</item>
<item>type</item>
<item>explain</item>
</tag>
<tag>
<name>returnparam</name>
<type>split</type>
<symbol>~</symbol>
<item>name</item>
<item>type</item>
<item>explain</item>
</tag>
<tag>
<name>uri</name>
<type>string</type>
</tag>
<tag>
<name>description</name>
<type>string</type>
</tag>
<tag>
<name>type</name>
<type>string</type>
</tag>
<tag>
<name>returnjson</name>
<type>string</type>
</tag>
<tag>
<name>path</name>
<type>string</type>
</tag>
<tag>
<name>methodname</name>
<type>string</type>
</tag>
<tag>
<name>html</name>
<type>string</type>
</tag>
<tag>
<name>java</name>
<type>string</type>
</tag>
<viewpath>
<name>html</name>
<value>D:/model/Method.ftl</value>
</viewpath>
<viewpath>
<name>java</name>
<value>D:/model/javaTemplate.ftl</value>
</viewpath>
</method>
</doclet>
模板的内容我会在第四节来阐述,需要生成文档的目标java文件为text.java,位置为d:\,内容如下:
package com.jchvip.jch2.appInterface;
import com.bluemobi.common.util.AppResult;
import com.bluemobi.common.util.ValidateUtil;
import com.jchvip.jch2.service.MessageManagerServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* @description 消息类
* @uri 2.0/message
*/
@Controller
@RequestMapping(value = "2.0/message", method= {RequestMethod.POST,RequestMethod.GET} ,produces="text/html;charset=UTF-8")
public class MessageController {
/**
* @uri count/userandmax
* @type read
* @path 根据用户id和消息最大id获取各种类型消息数目
* @html MessageController/userandmax.html
* @java MessageController/userandmax.java
* @description 根据用户id和消息最大id获取各种类型消息数目
* @methodname userandmax
* @param userid~true~string~用户id
* @param maxid~true~int~最大id
* @returnparam data中的key~int~消息类型,other 其他;like 点赞;comment 评论
* @returnparam data中的value~int~多少条未读消息
* @returnjson
*{
{
"status": 0,
"serverTime": "160819101001",
"data": {
"other": 21,
"comment": 1,
"like": 1
}
}
*/
@RequestMapping("/count/userandmax")
@ResponseBody
public String messagecountuserandmax(HttpServletRequest request,HttpServletResponse response,String userid,Integer maxid){
return null;
}
/**
* @uri userandtypeandmax
* @type read
* @path 根据用户id和消息最大id和消息类型获取用户消息
* @html MessageController/userandtypeandmax.html
* @java MessageController/userandtypeandmax.java
* @description 根据用户id和消息最大id和消息类型获取用户消息
* @methodname userandtypeandmax
* @param userid~trur~string~用户id
* @param maxid~true~int~消息最大id
* @param classes~true~int~前端分类,1 点赞,2 评论,3 其他所有分类
* @returnparam content~string~消息内容
* @returnparam createtime~string~消息产生时间
* @returnparam id~int~消息id
* @returnparam title~string~消息标题
* @returnparam status~int~消息状态,2 已读,其他值 未读
* @returnparam type~int~消息类型,1 实名认证通过,2 加好友,3 同意好友,4 点赞, 5 评论,6 实名不通过,7 签约,8 报名,10 活源
* @returnparam classes~int~前端分类,1 点赞,2 评论,3 其他所有分类
* @returnjson
* {
"status": 0,
"serverTime": "160819100844",
"data": [
{
"content": "恭喜您,实名认证通过!",
"createtime": 1471489002000,
"id": 1,
"title": "实名认证通过",
"status": 0,
"type": 1,
"classes":3
}
]
}
*/
@RequestMapping("/userandtypeandmax")
@ResponseBody
public String messageuserandtypeandmax(HttpServletRequest request,HttpServletResponse response,String userid,Integer maxid,Integer classes){
return null;
}
/**
* @uri updatemessagestatus
* @type write
* @path 标识消息为已读
* @html MessageController/updatemessagestatus.html
* @java MessageController/updatemessagestatus.java
* @methodname updatemessagestatus
* @description 标识消息为已读
* @param messageid~true~int~要修改的消息id
* @returnjson
{
"status": 0,
"serverTime": "160819100844",
}
*/
@RequestMapping("/updatemessagestatus")
@ResponseBody
public String updatemessagestatus(HttpServletRequest request,HttpServletResponse response,Integer messageid){
return null;
}
/**
* @uri deletemessagebyid
* @type write
* @html MessageController/deletemessagebyid.html
* @java MessageController/deletemessagebyid.java
* @methodname deletemessagebyid
* @description 删除消息
* @path 删除消息
* @param messageid~true~int~要删除的消息id
* @returnjson
{
"status": 0,
"serverTime": "160819100844",
}
*/
@RequestMapping("/deletemessagebyid")
@ResponseBody
public String deletemessagebyid(HttpServletRequest request,HttpServletResponse response,Integer messageid){
return null;
}
}
于是,整个doclet命令为:
javadoc -doclet doclet3 -docletpath D:\doclet\out\doclet.jar -encoding utf-8 -charset utf-8 -xmlpath d:\xmltest.xml d:\text.java
在dos命令行直接输入上述命令就会在指定位置生成文件。
不难发现,整个model层对应结构大致为一个树型结构:一个包包含多个类,一个类包含多个方法和多个注解,一个方法包含多个注解。我先展示整个model的uml图,让大家有个整体的认识,然后再依次来分析。整个model层uml图如下:
设计tag来标识注解的种类(注意这里抽象的是注解的种类,而不是个数),其结构如下:
public List transformTagvalue(){
if (type == 1){
//string
return itemvalues;
}else if (type == 2){
//split
List<Map> tagvalues = new ArrayList<Map>();
for (String itemvalue : itemvalues){
Map<String,String> itemmap = new HashMap<String,String>();
String[] values = itemvalue.split(symbol);
for (int i =0; i<values.length ; i++){
itemmap.put(items.get(i),values[i]);
}
tagvalues.add(itemmap);
}
return tagvalues;
}else {
return null;
}
}
MethodType类需要实现Serializable接口,这是为了后面的深拷贝。
public Map<String,List> transformTagvalue(){
Map<String,List> map = new HashMap<>();
for (Tag tag : tagList){
map.put(tag.getName(),tag.transformTagvalue());
}
return map;
}
ClassType类需要实现Serializable接口,这是为了后面的深拷贝。
public Map<String,List> transformTagvalue(){
Map<String,List> map = new HashMap<>();
//转换class注解上的值
for (Tag tag : tagList){
map.put(tag.getName(),tag.transformTagvalue());
}
//转换class中method中的值
List methodvalues = new ArrayList();
for (MethodType methodType : methodTypeList){
methodvalues.add(methodType.transformTagvalue());
}
map.put("methods",methodvalues);
return map;
}
public Map<String,List> transformTagvalue(){
Map<String,List> map = new HashMap<>();
List classvalues = new ArrayList();
for (ClassType classType : classTypes){
classvalues.add(classType.transformTagvalue());
}
map.put("classes",classvalues);
return map;
}
在整个model层上,我又添加了三个类:HandleXml,TypeFactory,HandleView。主要是用来处理xml信息,model层下各个类的工厂,输出模板对应的文件。他们与model层的类的关系如下uml图所示:
这个类的作用是用来处理读入的xml信息,然后将对应的信息配置在TypeFactory中,使TypeFactory生成需要的类。
这个类中最重要的方法就是handlexml,在start方法(整个程序的入口)一开始便调用这个handlexml方法,入参是注解xml的地址。其代码如下:
public static void handlexml(String xmlAddress) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(xmlAddress));
Element root = document.getRootElement();
//处理包的标签
Element packages = root.element("package");
//设置包模板路径
List<Element> packageviewpathes = packages.elements("viewpath");
TypeFactory.getPACKAGETYPE().setTemplate(handlePackagePath(packageviewpathes));
List<Element> packageOutpathes = packages.elements("outpath");
TypeFactory.getPACKAGETYPE().setOutpath(handlePackagePath(packageOutpathes));
Element packageBasepath = packages.element("basepath");
if (packageBasepath.getText().endsWith("/")){
TypeFactory.getPACKAGETYPE().setBasepath(packageBasepath.getText());
}else {
TypeFactory.getPACKAGETYPE().setBasepath(packageBasepath.getText()+"/");
}
//处理类的标签
Element classtags = root.element("class");
//设置类模板路径
List<Element> classPathes = classtags.elements("viewpath");
List<Map<String,String>> classpathList = handleMethodOrClassPath(classPathes);
TypeFactory.getCLASSTYPE().setTemplate(classpathList.get(0));
TypeFactory.getCLASSTYPE().setOutpath(classpathList.get(1));
//处理类模板标签
List<Element> classtaglist = classtags.elements("tag");
TypeFactory.getCLASSTYPE().setTagList(handleTags(classtaglist));
//处理方法的标签
Element methodtags = root.element("method");
//设置方法模板路径
List<Element> methodPathes = methodtags.elements("viewpath");
List<Map<String,String>> methodpathList = handleMethodOrClassPath(methodPathes);
TypeFactory.getMETHODTYPE().setTemplate(methodpathList.get(0));
TypeFactory.getMETHODTYPE().setOutpath(methodpathList.get(1));
//处理方法模板标签
List<Element> methodtaglist = methodtags.elements("tag");
TypeFactory.getMETHODTYPE().setTagList(handleTags(methodtaglist));
}
上述代码很简单,就是在读入xml后,分别读取包,类,方法的对应路径,然后将这个xml的信息配置在TypeFactory的三个静态属性上(TypeFactory下面会详细介绍)。
至于handlePackagePath,handleMethodOrClassPath,handleTags这三个私有方法,主要是将xml中的信息转换成TypeFactory中静态属性需要的格式。大家可以在最下面下载源码看看。
这个类是ClassType,MethodType,PackageType的工厂,doclet3中ClassType,MethodType,PackageType的获取都是通过这个工厂对应的三个get方法。
deepCopy这个方法,是用于对象的深度复制(此处我用的是序列化的方式:将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝)。
那么,为什么要用深度复制这样耗性能的方式给doclet3对象呢?要弄清这个问题,我们需要来看一下经过HandleXml的init方法之后,TypeFactory的三个静态属性会分别携带哪些信息:
PackageType类型的PACKAGETYPE变量:静态变量basepath会确定;静态变量template会确定;静态变量outpath会确定;变量classTypes待定。这样,PackageType类型的静态变量都确定,动态静态变量都不定,所以考虑到性能TypeFactory的getPackageType方法就直接new一个PackageType对象作为出参
MethodType类型的METHODTYPE变量:静态变量template会确定;变量outpath的大小和key值会确定,value需要从注解中读入;变量tagList的大小和内容会确定,只是它的每一项Tag类中的itemvalues需要从注解中读入。这样静态变量确定,动态变量的结构和内容大多都确定,只剩下在doclet3中读入注解内容将其填入即可,所以需要以METHODTYPE为原型,用深度复制的方式创建对象。(如果不用复制对象的方式,而是每次在doclet3中创建一个对象,然后读一遍xml将方法注解名称信息填入,这样一来更耗性能,二来耦合性太强,极易出错)
ClassType类型的CLASSTYPE变量:它的情况和METHODTYPE类似,不过methodTypeList值为null,需要在doclet3中填入。也是采用深度复制的方式创建对象。
这个类的init方法主要是依照包,类,方法中的模板路径读入模板,生成Template对象,保存在属性template中,这个map属性的key就是注解xml中viewpath标签下的name值,这也就是为什么所有的viewpath标签不能相同。程序中是在调用HandleXml的handlexml方法之后紧接着调用这个方法
这个类的createfile方法主要是在指定位置生成对应文件。入参data指定模板中填充的数据,入参outpath的key是viewpath标签的name,value是输出路径(是相对于basepath的路径),这样通过这个key就将模板和输出路径对应起来,之后填入数据输出即可。
doclet3的start方法代码如下:
public static boolean start(RootDoc root) {
try {
HandleXml.handlexml(xmlpath);
HandleView.init();
} catch (Exception e) {
e.printStackTrace();
}
PackageType packageType = TypeFactory.getPackageType();
try {
doc(root.classes(), packageType);
//为包生成对应的文档
try {
HandleView.createfile(packageType.transformTagvalue(),PackageType.getOutpath());
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
经过上面的介绍,这个代码应该不难理解。我们重点来介绍doc(root.classes(), packageType);这个方法,看它干了什么,其代码如下:
private static void doc(ClassDoc[] classDocs,PackageType packageType) throws Exception {
List<ClassType> classTypeList = new ArrayList<ClassType>();
packageType.setClassTypes(classTypeList);
//处理各个类
for (int i = 0; i < classDocs.length; i++) {
ClassType classType = TypeFactory.getClassType();
classTypeList.add(classType);
//处理类注解
ClassDoc classDoc = classDocs[i];
List<tag.Tag> classtags = classType.getTagList();
for (tag.Tag classtag : classtags){
classtag.setItemvalues(docClassList(classDoc,classtag.getName()));
}
for (Map.Entry<String,String> outpath : classType.getOutpath().entrySet()){
outpath.setValue(docClassList(classDoc,outpath.getKey()).get(0));
}
//处理类中各个方法的注解
List<MethodType> methodTypeList = new ArrayList<MethodType>();
classType.setMethodTypeList(methodTypeList);
MethodDoc[] methodDocs = classDoc.methods();
for (int j = 0 ; j < methodDocs.length ; j ++){
MethodType methodType = TypeFactory.getMethodType();
methodTypeList.add(methodType);
//处理方法注解
MethodDoc methodDoc = methodDocs[j];
List<tag.Tag> methodtags = methodType.getTagList();
for (tag.Tag methodtag : methodtags){
methodtag.setItemvalues(docMethodList(methodDoc,methodtag.getName()));
}
//设置每个方法的输出路径
for (Map.Entry<String,String> outpath : methodType.getOutpath().entrySet()){
outpath.setValue(docMethodList(methodDoc,outpath.getKey()).get(0));
}
//为每个方法生成对应的文件
HandleView.createfile(methodType.transformTagvalue(),methodType.getOutpath());
}
//设置每个类的输出路径
for (Map.Entry<String,String> outpath : classType.getOutpath().entrySet()){
outpath.setValue(docClassList(classDoc, outpath.getKey()).get(0));
}
//为每个类生成对应的文件
HandleView.createfile(classType.transformTagvalue(),classType.getOutpath());
}
}
看了上面的注解,相信大家也有对其有个大概的了解,这个方法就做两件事:
由于将所有的注解都当成多个注解来处理,这样传入freemarker模板中的数据便都变为list,所以需要修改原来模板中${uri}这样的取单个注解的取值方式。(像之前param这样本来就是多个注解的不变)。
下面是每个方法对应的html模板。(注意uri,returnjson,description的取值方式变)
<!DOCTYPE html>
<html>
<meta charset=utf8>
<head>
<title></title>
</head>
<style type="text/css">
body{
font: 12px/1.125 Arial, Helvetica, sans-serif;
}
.wiki_title{
line-height: 37px;
border-bottom: 1px solid #e5e5e5;
margin: 16px 0 8px 0;
font-size: 20px;
color: #333;
font-family: "Microsoft Yahei";
font-weight: 300;
}
h1.wiki_title{
font-size: 24px;
}
a{
color: #3c7cb3;
text-decoration: none;
}
table{
border-collapse: collapse;
border-spacing: 0;
}
table.parameters{
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
-webkit-border-horizontal-spacing: 0px;
-webkit-border-vertical-spacing: 0px;
width: 100%;
}
th,td{
text-align: center;
font-weight: bolder;
border: 1px solid #cccccc;
height: 20px;
}
.code_type{
text-transform: uppercase;
margin-bottom: 5px;
display: inline-block;*
display: inline;*
zoom: 1;
background: #b4e3b4;
border-radius: 2px;
color: #008200;
padding: 2px 8px;
}
a:hover{
text-decoration: underline;
}
</style>
<body>
<h1 class="wiki_title">
<span class="mw-headline"><#list uri as a>${a}</#list></span>
</h1>
<p><#list description as a>${a}</#list></p>
<h2 class="wiki_title">
<span class="mw-headline">URL</span>
</h2>
<p>
<span style="font-weight:600">
<a rel="nofollow" class="external free" href="">http://test.jchvip.net/<#list uri as a>${a}</#list></a>
</span>
</p>
<h2 class="wiki_title">
<span class="mw-headline" >支持格式</span>
</h2>
<p>
<span style="text-transform:uppercase;font-weight:600">JSON</span>
</p>
<h2 class="wiki_title">
<span class="mw-headline" >HTTP请求方式</span>
</h2>
<p>
<span style="text-transform:uppercase;font-weight:600">POST</span>
</p>
<h2 class="wiki_title">
<span class="mw-headline" >请求参数</span>
</h2>
<table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">
<tbody>
<tr>
<th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">名称</th>
<th width="5%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">必选</th>
<th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">类型及范围</th>
<th width="75%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">说明</th>
</tr>
<#list param as item>
<tr>
<td style="text-align:center;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
<td style="text-align:center;border:1px solid #cccccc">${item.select}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
</tr>
</#list>
</tbody>
</table>
<h2 class="wiki_title">
<span class="mw-headline">返回结果</span>
</h2>
<div class="code_type" style="text-transform:uppercase;margin-bottom:5px;">JSON示例</div>
<pre>
<#list returnjson as a>${a}</#list>
</pre>
<h2 class="wiki_title">
<span class="mw-headline">返回字段说明</span>
</h2>
<table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">
<tbody>
<tr>
<th width="25%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">返回值字段</th>
<th width="15%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段类型</th>
<th width="60%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段说明</th>
</tr>
<#list returnparam as item>
<tr>
<td style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
</tr>
</#list>
</tbody>
</table>
<h2 class="wiki_title">
<span class="mw-headline" >注意事项</span>
</h2>
<p>无</p>
</body>
</html>
下面是每个方法对应的java模板(注意methodname,uri取值方式的变化):
package com.bluemobi.bluecollar.network.request;
import java.util.HashMap;
import java.util.Map;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.bluemobi.bluecollar.network.LlptHttpJsonRequest;
import com.bluemobi.bluecollar.network.response.getTagListResponse;
public class <#list methodname as a>${a}</#list>Request extends LlptHttpJsonRequest<<#list methodname as a>${a}</#list>Response> {
private static final String APIPATH = "<#list uri as a>${a}</#list>";
<#list param as a>
private String ${a.name};
public String get${a.name?cap_first}() {
return ${a.name};}
public void set${a.name?cap_first}(String ${a.name}) {this.${a.name} = ${a.name};}
</#list>
public <#list methodname as a>${a}</#list>Request(Listener<<#list methodname as a>${a}</#list>Response> listener, ErrorListener errorListener) {
super(Method.POST, APIPATH, listener, errorListener);
}
public <#list methodname as a>${a}</#list>Request(int method, String partUrl, Listener<<#list methodname as a>${a}</#list>Response> listener, ErrorListener errorListener) {
super(method, partUrl, listener, errorListener);
}
public Class<<#list methodname as a>${a}</#list>Response> getResponseClass() {
return <#list methodname as a>${a}</#list>Response.class;}
public String GetApiPath() {
return APIPATH;}
public Map<String, String> GetParameters() {
Map<String, String> map = new HashMap<String, String>();
<#list param as a>
map.put("${a.name}",${a.name});
</#list>
return map;
}
}
下面是包对应的html模板(是可以在里面写js代码的):
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<html>
<title></title>
<style type="text/css">
body{
font: 12px/1.125 Arial, Helvetica, sans-serif;}
.custom{
border-collapse:collapse;
border:gray;
width: 100%;
display: table;
text-align: left;
margin-bottom: 20px;
}
.custom .tbF1{
width: 116px;}
.custom .tbF2{
width: 218px;}
.custom th{
padding: 12px 0;
background: #e9e9e9;
border: 1px solid #e8eaec;
padding-left: 10px;
font-weight: 700;
font-size: 14px;
text-align: left;
color: #000;
}
.custom td{
background: #fff;
padding-left: 10px;
padding: 7px 10px;
line-height: 20px;
border: 1px solid #e8eaec;
}
.custom pre{
overflow: hidden;}
a{
text-decoration: none;
color: #3c7cb3;
}
a:hover{
text-decoration: underline;}
</style>
<#list classes as classitem>
<div>
<table class = "custom">
<colgroup><col class = "tbF1"><col class = "tbF2"><col></colgroup>
<tr>
<th colspan="3"><#list classitem.description as a>${a}</#list></th>
</tr>
<#assign classuri><#list classitem.uri as a>${a}</#list></#assign>
<#assign readnum = 0/>
<#assign writenum = 0/>
<#list classitem.methods as method>
<#assign type><#list method.type as a>${a}</#list></#assign>
<#if type == "read">
<#assign readnum = readnum+1>
<#if readnum == 1>
<tr>
<td id="read${classitem_index}">读取接口</td>
<td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
<td><#list method.path as a>${a}</#list></td>
</tr>
<#else>
<tr>
<td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
<td><#list method.path as a>${a}</#list></td>
</tr>
</#if>
</#if>
</#list>
<#list classitem.methods as method>
<#assign type><#list method.type as a>${a}</#list></#assign>
<#if type == "write">
<#assign writenum = writenum+1>
<#if writenum == 1>
<tr>
<td id="write${classitem_index}">写入接口</td>
<td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
<td><#list method.path as a>${a}</#list></td>
</tr>
<#else>
<tr>
<td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
<td><#list method.path as a>${a}</#list></td>
</tr>
</#if>
</#if>
</#list>
<input id="readnum${classitem_index}" hidden="hidden" value="${readnum}">
<input id="writenum${classitem_index}" hidden="hidden" value="${writenum}">
</table>
</div>
</#list>
<script>
for(var i=0 ; i < ${classes?size} ; i++ )
{
var read = document.getElementById("read"+i);
if(read){
var readnum = document.getElementById("readnum"+i);
read.setAttribute("rowspan",readnum.value);
}
var write = document.getElementById("write"+i);
if(write){
var writenum = document.getElementById("writenum"+i);
write.setAttribute("rowspan",writenum.value);
}
}
var alist = document.getElementsByTagName("a");
var date = new Date();
for(var j=0 ; j < alist.length ; j++ ){
alist[j].setAttribute("href",alist[j].getAttribute("href")+ "?" + date.getTime());
}
</script>
</html>
链接:https://www.nowcoder.com/acm/contest/112/B来源:牛客网时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 32768K,其他语言65536K 64bit IO Format: %lld题目描述有n个队伍,每个队伍的人数小于等于5,每辆车最多坐5个人,要求一个队伍的人都在一辆车上,求最少的车数 输入描述:第一行n第二行n个数,表示每个队伍...
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_程序员资料本文网址:第1章 硬盘的分类1.1 根据材料分(1)SSD是固态硬盘:固态硬盘(SSD)采用闪存颗粒来存储。(2)HDD是机械硬盘:采用磁性碟片来存储。(3)HHD是混合硬盘:磁性碟片和闪存集成到一起的一种硬盘。1.2 根据接口分(1)SATA在此我们需要主要介绍一下SATA接口,是一种基于行业标准的串行硬件驱动器接口,是由Intel、IBM、Dell、APT、Maxtor和Se
3.2版本 5.0版本 C config E exception G debug L lang I input D model M db A controller R action U url W widget S cache
连接数据库四个步骤 1.加载驱动2.获取数据库连接3.使用语句操作数据库4 关闭数据库 一 .加载驱动 (要去sun那下一个jdbc的jar包) 先输入驱动名称 (mysql驱动名称 com.mysql.jdbc.Driver) 加载方式:Class.forName(驱动名称) private static...
环境:pyecharts库,echarts-countries-pypkg,echarts-china-provinces-pypkg,echarts-china-cities-pypkg数据:2018年4月16号的全国各地最高最低和天气类型的数据――2018-4-16.json(爬虫爬的)一、公共属性1、标题栏的属性:一般在实例化(初始化)类型时给与,如bar = Bar("大标题”,...
系统集成组数据库调研对比文档关系型数据库和非关系型数据库组内使用数据库为关系型数据库oracle,现在市场上出现了更为流行的关系型数据库诸如Mysql/MariaDB、PostgreSql、PPAS(增强型PostgreSql),以及当前在大数据框架中比较流行的非关系型数据库诸如Redis、MongoDB等。首先从大的框架进行对比,关系型数据库和非关系型数据库之间的区别,以及两者使...
原博客地址:blog.csdn.net/tobetheender在做接口测试时,经常会碰到请求参数为token的类型,但是可能大部分测试人员对token,cookie,session的区别还是一知半解。为此我查阅大量的资料做了如下总结。 此篇文章也许是最全最通俗的关于Token ,Cookie和Session的区别的文章,好好揣摩文章的每一个字,也许你会有更深的理解!Coo
数据库连接的客户端异常断开后,其占有的相应并没有被释放,如从v$session视图中依旧可以看到对应的session处于inactive,且对应的服务器进程也没有释放,导致资源长时间地被占用,对于这种情形开该如何处理呢?SQLNET.EXPIRE_TIME对于这个问题我们提供了解决方案,专门用于清理那些异常断开的情形,如网络异常中断,客户端异常掉电,异常重启等。本文描述了设置SQLNET.EXPI
依靠Linux自带cron定时任务进行备份与删除当然你要是可以直接用logback实现日志的备份也是极好的。loback备份日志教程:java 中使用logback日志,并实现日志按天分类压缩保存。转自wangznscron使用安装教程:Linux之crontab定时任务。转自鹤啸九天-西木话不多说,直接上脚本:#Delete XX.log before 2daysfind /XXX/XXX/XXX -mtime +2 -name "*.log" -exec rm -rf {}\;#删除
最近开始拜读java编程思想这本书,这本书的经典之处我就不过多宣扬了,在这里我只谈谈我读这本书的感受。这本书是由Bruce Eckel,这个人也许不熟悉,但是他的作品可是如雷贯耳啊,Bruce Eckel是MindView公司的总裁,这个公司提供一些软件资讯和培训。他是C++标准委员会拥有表决权的成员之一,拥有应用物理学学士和计算机工程硕士学位。Bruce Eckel从1984年至今,已经
符号化地图图层ArcGIS Pro 2.7|其他版本符号系统使用符号表示地图图层的要素和属性。例如,在城市图层中,黑色圆圈可对城市进行符号化。用于符号化每个城市的人口的圆圈大小可能会有所不同。根据可见属性(例如形状、大小、颜色、间距和(3D 模式的)透视高度)定义符号。总览ArcGIS Pro入门官方教程集【中文字幕】视频长度:3:42 此视频是使用ArcGIS Pro2.3 创建的。在本教程中,您将符号化点、线和面要素,自定义符号并设置可见性范围;并对公交路线应用“...
DVBlast supports several input methods:linux-dvb-supported cards (DVB-S, DVB-S2, DVB-C, DVB-T...) with or without CI interfaceDVB-ASI cards (from Computer Modules or Deltacast)UDP or RTP