44 | * ArrayList<String> params0; 45 | * ArrayList<实体类> params1; 46 | * List<String> params2; 47 | * List<实体类> params3; 48 | * Set<String> params4; 49 | * Set<实体类> params5; 50 | * 实体类[] params6; 51 | *52 | * 但是如果你将这些类型的参数作为字段封装到某个实体类中,那么Spring MVC将会正确处理他们 53 | *
54 | * public class ComplexEntityClass{ 55 | * private ArrayList<String> params0; 56 | * private ArrayList<实体类> params1; 57 | * private List<String> params2; 58 | * private List<实体类> params3; 59 | * private Set<String> params4; 60 | * private Set<实体类> params5; 61 | * private 实体类[] params6; 62 | * } 63 | *64 | *
65 | * \@RequestMapping("action") 66 | * public Result test(ComplexEntityClass complexEntity) { 67 | * //logic 68 | * } 69 | *70 | * 特殊的,如果参数是基本类型或String类型的‘集合类型’,可以使用
org.springframework.web.bind.annotation.RequestParam
使之生效73 | * \@RequestParam ArrayList<String> params0; 74 | * \@RequestParam List<String> params2; 75 | * \@RequestParam Set<String> params4; 76 | *77 | * 78 | * @param parameterDoc 79 | * @param paramTag 80 | * @param paramsJSONArray 81 | */ 82 | public static void formatParamDoc(DefaultSmallDocletImpl doclet, Parameter parameterDoc, ParamTag paramTag, JSONArray paramsJSONArray) { 83 | Type ptype = parameterDoc.type(); 84 | addParamBean(doclet, ptype); 85 | //注意:如果数组类型ArrayTypeImpl中的元素类型是实体类型,那么该数组类型也是实体类型。 86 | if (TypeUtils.isEntity(ptype, doclet)) { 87 | formatEntityParamDoc(doclet, paramTag, paramsJSONArray, ptype); 88 | } else { 89 | formatNoEntityParamDoc(doclet, paramTag, paramsJSONArray, ptype, parameterDoc.annotations()); 90 | } 91 | } 92 | 93 | /** 94 | * 不管参数是不是实体类型,一定要解析它的泛型参数 95 | * 96 | * @param doclet 97 | * @param type 98 | */ 99 | private static void addParamBean(DefaultSmallDocletImpl doclet, Type type) { 100 | //先处理类型参数,后面再处理字段(保证TypeVariable的字段被处理) 101 | TypeUtils.getTypeArguments(type, doclet); 102 | if (TypeUtils.isEntity(type, doclet)) 103 | TypeUtils.addBean(type, doclet); 104 | } 105 | 106 | private static void formatNoEntityParamDoc(DefaultSmallDocletImpl doclet, ParamTag paramTag, JSONArray paramsJSONArray, Type ptype, AnnotationDesc[] annotations) { 107 | boolean file = TypeUtils.isFile(ptype); 108 | boolean dimension = TypeUtils.isArray(ptype); 109 | if (TypeUtils.isCollection(ptype)) { 110 | Type typeArgument = ptype.asParameterizedType().typeArguments()[0]; 111 | String currentMethodSignature = doclet.getCurrentMethodSignature(); 112 | String parameterName = paramTag.parameterName(); 113 | Assert.checkNot(TypeUtils.isEntity(typeArgument, doclet), "Method: %s, Param: %s, Spring MVC does not support collections parameter of entity type.", currentMethodSignature, parameterName); 114 | long count = Stream.of(annotations) 115 | .filter(annotationDesc -> annotationDesc.annotationType().simpleTypeName().equals(REQUEST_PARAM)) 116 | .count(); 117 | Assert.check(count == 1, "Method: %s, Param: %s, Spring MVC need @RequestParam annotation to support collections parameter of base or String type.", currentMethodSignature, parameterName); 118 | file = TypeUtils.isFile(typeArgument); 119 | dimension = true; 120 | } 121 | 122 | extractGeneralParamTag(doclet, paramTag, ptype, file, dimension) 123 | .map(ParamFormatUtils.deriveExample(LocalDateTime.now())) 124 | .ifPresent(paramsJSONArray::add); 125 | } 126 | 127 | private static void formatEntityParamDoc(DefaultSmallDocletImpl doclet, ParamTag paramTag, JSONArray paramsJSONArray, Type ptype) { 128 | boolean array = TypeUtils.isArray(ptype); 129 | Assert.checkNot(array, "Method: %s, Param: %s, Spring MVC does not support entity arrays parameter.", doclet.getCurrentMethodSignature(), paramTag.parameterName()); 130 | //注意:如果是数组类型ArrayTypeImpl,解析字段时默认跳过数组维度,默认得到的即是元素类型字段的映射。 131 | extractEntityParamTag(doclet, paramTag, ptype) 132 | .ifPresent(pStorer -> 133 | paramsJSONArray.addAll(pStorer.getFieldParamStorers()) 134 | ); 135 | } 136 | 137 | /** 138 | * 抽取普通类型参数ParamTag信息并存储 139 | * 140 | * @param doclet 141 | * @param paramTag 142 | * @param type 143 | * @param file 是否是文件 144 | * @param dimension 是否是数组或集合 145 | * @return 146 | */ 147 | private static Optional
@{(*|**|f1[*])[,[-]f2[*]...]}
,将打印警告并返回nullChecks if an array of Objects is not empty and not {@code null}.
31 | *
32 | * @param Shallow clones an array returning a typecast result and handling
46 | * {@code null}.
47 | *
48 | * The objects in the array are not cloned, thus there is no special
49 | * handling for multi-dimensional arrays.
50 | *
51 | * This method returns {@code null} for a {@code null} input array.
52 | *
53 | * @param Adds all the elements of the given arrays into a new array.
66 | * The new array contains all of the element of {@code array1} followed
67 | * by all of the elements {@code array2}. When an array is returned, it is always
68 | * a new array.
69 | *
70 | *
119 | * Null returns true.
120 | *
121 | * @param coll the collection to check, may be null
122 | * @return true if empty or null
123 | */
124 | public static boolean isEmpty(final Collection> coll) {
125 | return coll == null || coll.isEmpty();
126 | }
127 |
128 | /**
129 | * Checks if an array of Objects is empty or {@code null}.
130 | *
131 | * @param array the array to test
132 | * @return {@code true} if the array is empty or {@code null}
133 | */
134 | public static boolean isEmpty(final Object[] array) {
135 | return getLength(array) == 0;
136 | }
137 |
138 | /**
139 | * Returns the length of the specified array.
140 | * This method can deal with {@code Object} arrays and with primitive arrays.
141 | *
142 | * If the input array is {@code null}, {@code 0} is returned.
143 | *
144 | * Checks if a CharSequence is empty (""), null or whitespace only.
189 | * Whitespace is defined by {@link Character#isWhitespace(char)}.
191 | * Joins the elements of the provided {@code Iterable} into
221 | * a single String containing the provided elements.
223 | * No delimiter is added before or after the list.
224 | * A {@code null} separator is the same as an empty String ("").
226 | * Joins the elements of the provided {@code Iterator} into
249 | * a single String containing the provided elements.
251 | * No delimiter is added before or after the list.
252 | * A {@code null} separator is the same as an empty String ("").
254 | *
71 | * ArrayUtils.addAll(null, null) = null
72 | * ArrayUtils.addAll(array1, null) = cloned copy of array1
73 | * ArrayUtils.addAll(null, array2) = cloned copy of array2
74 | * ArrayUtils.addAll([], []) = []
75 | * ArrayUtils.addAll([null], [null]) = [null, null]
76 | * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
77 | *
78 | *
79 | * @param
145 | * ArrayUtils.getLength(null) = 0
146 | * ArrayUtils.getLength([]) = 0
147 | * ArrayUtils.getLength([null]) = 1
148 | * ArrayUtils.getLength([true, false]) = 2
149 | * ArrayUtils.getLength([1, 2, 3]) = 3
150 | * ArrayUtils.getLength(["a", "b", "c"]) = 3
151 | *
152 | *
153 | * @param array the array to retrieve the length from, may be null
154 | * @return The length of the array, or {@code 0} if the array is {@code null}
155 | * @throws IllegalArgumentException if the object argument is not an array.
156 | */
157 | public static int getLength(final Object array) {
158 | if (array == null) {
159 | return 0;
160 | }
161 | return Array.getLength(array);
162 | }
163 |
164 | /**
165 | * 拼接路径信息
166 | *
167 | * @param p0
168 | * @param p1
169 | * @return
170 | */
171 | public static String unitePath(String p0, String p1) {
172 | //去除首斜线
173 | if (p0.startsWith(DEFAULT_PATH_SEPARATOR))
174 | p0 = p0.substring(1);
175 | if (p1.startsWith(DEFAULT_PATH_SEPARATOR))
176 | p1 = p1.substring(1);
177 |
178 | //拼接路径
179 | if (p0.endsWith(DEFAULT_PATH_SEPARATOR) || isBlank(p0)) {
180 | return p0 + p1;
181 | } else {
182 | return p0 + DEFAULT_PATH_SEPARATOR + p1;
183 | }
184 | }
185 |
186 | /**
187 | *
192 | * StringUtils.isBlank(null) = true
193 | * StringUtils.isBlank("") = true
194 | * StringUtils.isBlank(" ") = true
195 | * StringUtils.isBlank("bob") = false
196 | * StringUtils.isBlank(" bob ") = false
197 | *
198 | *
199 | * @param cs the CharSequence to check, may be null
200 | * @return {@code true} if the CharSequence is null, empty or whitespace only
201 | */
202 | public static boolean isBlank(final CharSequence cs) {
203 | int strLen;
204 | if (cs == null || (strLen = cs.length()) == 0) {
205 | return true;
206 | }
207 | for (int i = 0; i < strLen; i++) {
208 | if (!Character.isWhitespace(cs.charAt(i))) {
209 | return false;
210 | }
211 | }
212 | return true;
213 | }
214 |
215 | public static boolean isNotBlank(final CharSequence cs) {
216 | return !isBlank(cs);
217 | }
218 |
219 | /**
220 | *
227 | * StringUtils.join(null, *) = null
228 | * StringUtils.join([], *) = ""
229 | * StringUtils.join([null], *) = ""
230 | * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
231 | * StringUtils.join(["a", "b", "c"], null) = "abc"
232 | * StringUtils.join(["a", "b", "c"], "") = "abc"
233 | * StringUtils.join([null, "", "a"], ',') = ",,a"
234 | *
235 | *
236 | * @param iterable the {@code Iterable} providing the values to join together, may be null
237 | * @param separator the separator character to use, null treated as ""
238 | * @return the joined String, {@code null} if null iterator input
239 | */
240 | public static String join(final Iterable> iterable, final String separator) {
241 | if (iterable == null) {
242 | return null;
243 | }
244 | return join(iterable.iterator(), separator);
245 | }
246 |
247 | /**
248 | *
255 | * StringUtils.join(null, *) = null
256 | * StringUtils.join([], *) = ""
257 | * StringUtils.join([null], *) = ""
258 | * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
259 | * StringUtils.join(["a", "b", "c"], null) = "abc"
260 | * StringUtils.join(["a", "b", "c"], "") = "abc"
261 | * StringUtils.join([null, "", "a"], ',') = ",,a"
262 | *
263 | *
264 | * @param iterator the {@code Iterator} of values to join together, may be null
265 | * @param separator the separator character to use, null treated as ""
266 | * @return the joined String, {@code null} if null iterator input
267 | */
268 | public static String join(final Iterator> iterator, final String separator) {
269 |
270 | // handle null, zero and one elements before building a buffer
271 | if (iterator == null) {
272 | return null;
273 | }
274 | if (!iterator.hasNext()) {
275 | return EMPTY;
276 | }
277 | final Object first = iterator.next();
278 | if (!iterator.hasNext()) {
279 | return Objects.toString(first, "");
280 | }
281 |
282 | // two or more elements
283 | final StringBuilder buf = new StringBuilder(STRING_BUILDER_SIZE); // Java default is 16, probably too small
284 | if (first != null) {
285 | buf.append(first);
286 | }
287 |
288 | while (iterator.hasNext()) {
289 | if (separator != null) {
290 | buf.append(separator);
291 | }
292 | final Object obj = iterator.next();
293 | if (obj != null) {
294 | buf.append(obj);
295 | }
296 | }
297 | return buf.toString();
298 | }
299 |
300 | /**
301 | * 如果首部存在斜线移除它
302 | *
303 | * @param p0
304 | * @return
305 | */
306 | public static String removeHeadSlashIfPresent(String p0) {
307 | //去除首斜线
308 | if (p0.startsWith(DEFAULT_PATH_SEPARATOR))
309 | return p0.substring(1);
310 | return p0;
311 | }
312 |
313 | /**
314 | * 返回数组维度
315 | *
316 | * @param demision 数组维度
317 | * @return
318 | */
319 | public static String dimension(int demision) {
320 | StringBuilder sb = new StringBuilder();
321 | for (int i = 0; i < demision; i++) {
322 | sb.append("[]");
323 | }
324 | return sb.toString();
325 | }
326 |
327 |
328 | public static String read(InputStream in) {
329 | InputStreamReader reader;
330 | try {
331 | reader = new InputStreamReader(in, "UTF-8");
332 | } catch (UnsupportedEncodingException e) {
333 | throw new IllegalStateException(e.getMessage(), e);
334 | }
335 | return read(reader);
336 | }
337 |
338 | public static String readFromResource(String resource) throws IOException {
339 | InputStream in = null;
340 | try {
341 | in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
342 | if (in == null) {
343 | in = Utils.class.getResourceAsStream(resource);
344 | }
345 |
346 | if (in == null) {
347 | return null;
348 | }
349 |
350 | String text = read(in);
351 | return text;
352 | } finally {
353 | close(in);
354 | }
355 | }
356 |
357 | public static byte[] readByteArrayFromResource(String resource) throws IOException {
358 | InputStream in = null;
359 | try {
360 | in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
361 | if (in == null) {
362 | return null;
363 | }
364 |
365 | return readByteArray(in);
366 | } finally {
367 | close(in);
368 | }
369 | }
370 |
371 | public static byte[] readByteArray(InputStream input) throws IOException {
372 | ByteArrayOutputStream output = new ByteArrayOutputStream();
373 | copy(input, output);
374 | return output.toByteArray();
375 | }
376 |
377 | public static long copy(InputStream input, OutputStream output) throws IOException {
378 | final int EOF = -1;
379 |
380 | byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
381 |
382 | long count = 0;
383 | int n;
384 | while (EOF != (n = input.read(buffer))) {
385 | output.write(buffer, 0, n);
386 | count += n;
387 | }
388 | return count;
389 | }
390 |
391 | public static String read(Reader reader) {
392 | try {
393 |
394 | StringWriter writer = new StringWriter();
395 |
396 | char[] buffer = new char[DEFAULT_BUFFER_SIZE];
397 | int n;
398 | while (-1 != (n = reader.read(buffer))) {
399 | writer.write(buffer, 0, n);
400 | }
401 |
402 | return writer.toString();
403 | } catch (IOException ex) {
404 | throw new IllegalStateException("read error", ex);
405 | }
406 | }
407 |
408 | public static void close(Closeable x) {
409 | if (x == null) {
410 | return;
411 | }
412 |
413 | try {
414 | x.close();
415 | } catch (Exception e) {
416 | log.error("close error", e);
417 | }
418 | }
419 | }
420 |
--------------------------------------------------------------------------------
/smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/web/SmallDocServlet.java:
--------------------------------------------------------------------------------
1 | package com.github.liuhuagui.smalldoc.web;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import com.alibaba.fastjson.serializer.SerializerFeature;
6 | import com.github.liuhuagui.smalldoc.core.DefaultSmallDocletImpl;
7 | import com.github.liuhuagui.smalldoc.core.SmallDocContext;
8 | import com.github.liuhuagui.smalldoc.properties.SmallDocProperties;
9 | import com.github.liuhuagui.smalldoc.util.Utils;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import javax.servlet.ServletException;
14 | import javax.servlet.http.HttpServlet;
15 | import javax.servlet.http.HttpServletRequest;
16 | import javax.servlet.http.HttpServletResponse;
17 | import javax.sound.midi.Soundbank;
18 | import java.io.IOException;
19 | import java.util.concurrent.CompletableFuture;
20 | import java.util.concurrent.ExecutionException;
21 |
22 | public class SmallDocServlet extends HttpServlet {
23 | public static final String TITLE = "\\$\\{title}";
24 | public static final String DOCJSON = "\\$\\{docJSON}";
25 | public static final String DEFAULT_SERVLET_PATH = "smalldoc";
26 |
27 | private static Logger log = LoggerFactory.getLogger(SmallDocServlet.class);
28 | private String resourcePath = "smalldoc/support/http/resources";
29 |
30 | private SmallDocProperties smallDocProperties;
31 | private SmallDocContext smallDocContext;
32 | private CompletableFuture