swagger2隐藏在API文档显示某些参数的操作

网友投稿 583 2022-06-07


swagger2隐藏在API文档显示某些参数

有时候,利用swagger2建立API文档时,有些参数是需要隐藏在API文档显示,在方法中,参数的名字前加上

@ApiIgnore 就可以了:

@PostMapping("modi/certificate") @ApiResponses({@ApiResponse(code = 0, message = "请求成功"), @ApiResponse(code = 10031, message = "商家的营业执照已经存在,不能重复入驻") }) @ApiOperation(value = "修改商家证照资料", notes = "修改商家证照资料", response = MerchantExtendVdo.class) @ApiImplicitParams({ @ApiImplicitParam(name = "merchantExtendVdo", value = "商铺对象", required = true, dataType = "MerchantExtendVdo"), @ApiImplicitParam(name = "merchantProvepicVdo", value = "商铺证明图片", required = false, dataType = "MerchantProvepicVdo"), @ApiImplicitParam(name = "merchantOtherVdoList", value = "商家的其他资质图片对象,List数组形式", required = false, dataType = "MerchantOtherVdo", allowMultiple = true, paramType = "body")}) public ResultData modiCertificate(@MultiRequestBody @ApiIgnore MerchantExtendVdo merchantExtendVdo, @MultiRequestBody @ApiIgnore MerchantProvepicVdo merchantProvepicVdo, @MultiRequestBody @ApiIgnore List merchantOtherVdoList) { String accessToken = getAccessToken(); ResultData rd = storeService.modiCertificate(accessToken, merchantProvepicVdo, merchantOtherVdoList, merchantExtendVdo); return rd; }

swagger2自定义隐藏实体类属性

问题:

假如接收参数的实体类中关联了其他对象,那么swagger2的页面中参数应该会多出来这些,dept.id,dept.deptName,或者集合属性,roles[0].id,roles[0].roleName等等。

​​​这些属性有可能是不需要用来接收参数的,出现在文档中会给前端开发人员带来困惑

笔者在swagger2提供的配置中没有找到隐藏此类参数的设置

解决

但是通过阅读源码找到了展开参数的类

springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander

笔者通过继承这个类,并添加

@Primary

注解覆盖了源码中的逻辑,修改了

getBeanPropertyNames

方法,其他不变

swagger2版本2.8.0 解决方案

package com.example.swagger; import com.example.annotation.IgnoreSwaggerParameter; import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.members.ResolvedField; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.schema.Maps; import springfox.documentation.schema.Types; import springfox.documentation.schema.property.field.FieldProvider; import springfox.documentation.service.Parameter; import springfox.documentation.spi.schema.AlternateTypeProvider; import springfox.documentation.spi.schema.EnumTypeDeterminer; import springfox.documentation.spi.service.contexts.DocumentationContext; import springfox.documentation.spi.service.contexts.ParameterExpansionContext; import springfox.documentation.spring.web.readers.parameter.ExpansionContext; import springfox.documentation.spring.web.readers.parameter.ModelAttributeField; import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.util.HashSet; import java.util.List; import java.util.Set; import static com.google.common.base.Objects.equal; import static com.google.common.base.Predicates.*; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static springfox.documentation.schema.Collections.collectionElementType; import static springfox.documentation.schema.Collections.isContainerType; import static springfox.documentation.schema.Types.typeNameFor; /** * 覆盖{@link ModelAttributeParameterExpander} * @see CustomizeModelAttributeParameterExpander#getBeanPropertyNames(Class) * @see ModelAttributeParameterExpander#getBeanPropertyNames(Class) * @see IgnoreSwaggerParameter */ @Component @Primary public class CustomizeModelAttributeParameterExpander extends ModelAttributeParameterExpander { private static final Logger LOG = LoggerFactory.getLogger(CustomizeModelAttributeParameterExpander.class); private final FieldProvider fieldProvider; private final EnumTypeDeterminer enumTypeDeterminer; @Autowired public CustomizeModelAttributeParameterExpander(FieldProvider fields, EnumTypeDeterminer enumTypeDeterminer) { super(fields, enumTypeDeterminer); this.fieldProvider = fields; this.enumTypeDeterminer = enumTypeDeterminer; } @Override public List expand(ExpansionContext context) { List parameters = newArrayList(); Set beanPropNames = getBeanPropertyNames(context.getParamType().getErasedType()); Iterable fields = FluentIterable.from(fieldProvider.in(context.getParamType())) .filter(onlyBeanProperties(beanPropNames)); LOG.debug("Expanding parameter type: {}", context.getParamType()); AlternateTypeProvider alternateTypeProvider = context.getDocumentationContext().getAlternateTypeProvider(); FluentIterable modelAttributes = from(fields) .transform(toModelAttributeField(alternateTypeProvider)); FluentIterable expendables = modelAttributes .filter(not(simpleType())) .filter(not(recursiveType(context))); for (ModelAttributeField each : expendables) { LOG.debug("Attempting to expand expandable field: {}", each.getField()); parameters.addAll( expand( context.childContext( nestedParentName(context.getParentName(), each.getField()), each.getFieldType(), context.getDocumentationContext()))); } FluentIterable collectionTypes = modelAttributes .filter(and(isCollection(), not(recursiveCollectionItemType(context.getParamType())))); for (ModelAttributeField each : collectionTypes) { LOG.debug("Attempting to expand collection/array field: {}", each.getField()); ResolvedType itemType = collectionElementType(each.getFieldType()); if (Types.isBaseType(itemType) || enumTypeDeterminer.isEnum(itemType.getErasedType())) { parameters.add(simpleFields(context.getParentName(), context.getDocumentationContext(), each)); } else { parameters.addAll( expand( context.childContext( nestedParentName(context.getParentName(), each.getField()), itemType, context.getDocumentationContext()))); } } FluentIterable simpleFields = modelAttributes.filter(simpleType()); for (ModelAttributeField each : simpleFields) { parameters.add(simpleFields(context.getParentName(), context.getDocumentationContext(), each)); } return FluentIterable.from(parameters).filter(not(hiddenParameters())).toList(); } private Predicate recursiveCollectionItemType(final ResolvedType paramType) { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return equal(collectionElementType(input.getFieldType()), paramType); } }; } private Predicate hiddenParameters() { return new Predicate() { @Override public boolean apply(Parameter input) { return input.isHidden(); } }; } private Parameter simpleFields( String parentName, DocumentationContext documentationContext, ModelAttributeField each) { LOG.debug("Attempting to expand field: {}", each); String dataTypeName = Optional.fromNullable(typeNameFor(each.getFieldType().getErasedType())) .or(each.getFieldType().getErasedType().getSimpleName()); LOG.debug("Building parameter for field: {}, with type: ", each, each.getFieldType()); ParameterExpansionContext parameterExpansionContext = new ParameterExpansionContext( dataTypeName, parentName, each.getField(), documentationContext.getDocumentationType(), new ParameterBuilder()); return pluginsManager.expandParameter(parameterExpansionContext); } private Predicate recursiveType(final ExpansionContext context) { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return context.hasSeenType(input.getFieldType()); } }; } private Predicate simpleType() { return and(not(isCollection()), not(isMap()), or( belongsToJavaPackage(), isBaseType(), isEnum())); } private Predicate isCollection() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return isContainerType(input.getFieldType()); } }; } private Predicate isMap() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return Maps.isMapType(input.getFieldType()); } }; } private Predicate isEnum() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return enumTypeDeterminer.isEnum(input.getFieldType().getErasedType()); } }; } private Predicate belongsToJavaPackage() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return ClassUtils.getPackageName(input.getFieldType().getErasedType()).startsWith("java.lang"); } }; } private Predicate isBaseType() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return Types.isBaseType(input.getFieldType()) || input.getField().getType().isPrimitive(); } }; } private Function toModelAttributeField( final AlternateTypeProvider alternateTypeProvider) { return new Function() { @Override public ModelAttributeField apply(ResolvedField input) { return new ModelAttributeField(fieldType(alternateTypeProvider, input), input); } }; } private Predicate onlyBeanProperties(final Set beanPropNames) { return new Predicate() { @Override public boolean apply(ResolvedField input) { return beanPropNames.contains(input.getName()); } }; } private String nestedParentName(String parentName, ResolvedField field) { String name = field.getName(); ResolvedType fieldType = field.getType(); if (isContainerType(fieldType) && !Types.isBaseType(collectionElementType(fieldType))) { name += "[0]"; } if (isNullOrEmpty(parentName)) { return name; } return String.format("%s.%s", parentName, name); } private ResolvedType fieldType(AlternateTypeProvider alternateTypeProvider, ResolvedField field) { return alternateTypeProvider.alternateFor(field.getType()); } private Set getBeanPropertyNames(final Class clazz) { try { Set beanProps = new HashSet(); PropertyDescriptor[] propDescriptors = getBeanInfo(clazz).getPropertyDescriptors(); for (PropertyDescriptor propDescriptor : propDescriptors) { // 增加逻辑,忽略@IgnoreSwaggerParameter注解的字段 Field field = clazz.getDeclaredField(propDescriptor.getName()); if (field!=null) { field.setAccessible(true); IgnoreSwaggerParameter ignoreSwaggerParameter = field.getDeclaredAnnotation(IgnoreSwaggerParameter.class); if (ignoreSwaggerParameter != null) { continue; } } // 增加结束 if (propDescriptor.getReadMethod() != null) { beanProps.add(propDescriptor.getName()); } } return beanProps; } catch (Exception e) { LOG.warn(String.format("Failed to get bean properties on (%s)", clazz), e); } return newHashSet(); } @VisibleForTesting BeanInfo getBeanInfo(Class clazz) throws IntrospectionException { return Introspector.getBeanInfo(clazz); } }

swagger2版本2.9.2 解决方案

package com.zihuiinfo.facesdk.framework.common.swagger; import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.members.ResolvedField; import com.fasterxml.classmate.members.ResolvedMethod; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.schema.Maps; import springfox.documentation.schema.Types; import springfox.documentation.schema.property.bean.AccessorsProvider; import springfox.documentation.schema.property.field.FieldProvider; import springfox.documentation.service.Parameter; import springfox.documentation.spi.schema.AlternateTypeProvider; import springfox.documentation.spi.schema.EnumTypeDeterminer; import springfox.documentation.spi.service.contexts.ParameterExpansionContext; import springfox.documentation.spring.web.plugins.DocumentationPluginsManager; import springfox.documentation.spring.web.readers.parameter.ExpansionContext; import springfox.documentation.spring.web.readers.parameter.ModelAttributeField; import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander; import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterMetadataAccessor; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; import static com.google.common.base.Objects.equal; import static com.google.common.base.Predicates.*; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static springfox.documentation.schema.Collections.collectionElementType; import static springfox.documentation.schema.Collections.isContainerType; import static springfox.documentation.schema.Types.isVoid; import static springfox.documentation.schema.Types.typeNameFor; /** * 覆盖{@link ModelAttributeParameterExpander} * * @see CustomizeModelAttributeParameterExpander#getBeanPropertyNames(Class) * @see ModelAttributeParameterExpander#getBeanPropertyNames(Class) * @see IgnoreSwaggerParameter */ @Component @Primary public class CustomizeModelAttributeParameterExpander extends ModelAttributeParameterExpander { private static final Logger LOG = LoggerFactory.getLogger(ModelAttributeParameterExpander.class); private final FieldProvider fields; private final AccessorsProvider accessors; private final EnumTypeDeterminer enumTypeDeterminer; @Autowired protected DocumentationPluginsManager pluginsManager; @Autowired public CustomizeModelAttributeParameterExpander(FieldProvider fields, AccessorsProvider accessors, EnumTypeDeterminer enumTypeDeterminer) { super(fields, accessors, enumTypeDeterminer); this.fields = fields; this.accessors = accessors; this.enumTypeDeterminer = enumTypeDeterminer; } public List expand(ExpansionContext context) { List parameters = newArrayList(); Set propertyDescriptors = propertyDescriptors(context.getParamType().getErasedType()); Map propertyLookupByGetter = propertyDescriptorsByMethod(context.getParamType().getErasedType(), propertyDescriptors); Iterable getters = FluentIterable.from(accessors.in(context.getParamType())) .filter(onlyValidGetters(propertyLookupByGetter.keySet())); Map fieldsByName = FluentIterable.from(this.fields.in(context.getParamType())) .uniqueIndex(new Function() { @Override public String apply(ResolvedField input) { return input.getName(); } }); LOG.debug("Expanding parameter type: {}", context.getParamType()); final AlternateTypeProvider alternateTypeProvider = context.getDocumentationContext().getAlternateTypeProvider(); FluentIterable attributes = allModelAttributes( propertyLookupByGetter, getters, fieldsByName, alternateTypeProvider); FluentIterable expendables = attributes .filter(not(simpleType())) .filter(not(recursiveType(context))); for (ModelAttributeField each : expendables) { LOG.debug("Attempting to expand expandable property: {}", each.getName()); parameters.addAll( expand( context.childContext( nestedParentName(context.getParentName(), each), each.getFieldType(), context.getOperationContext()))); } FluentIterable collectionTypes = attributes .filter(and(isCollection(), not(recursiveCollectionItemType(context.getParamType())))); for (ModelAttributeField each : collectionTypes) { LOG.debug("Attempting to expand collection/array field: {}", each.getName()); ResolvedType itemType = collectionElementType(each.getFieldType()); if (Types.isBaseType(itemType) || enumTypeDeterminer.isEnum(itemType.getErasedType())) { parameters.add(simpleFields(context.getParentName(), context, each)); } else { ExpansionContext childContext = context.childContext( nestedParentName(context.getParentName(), each), itemType, context.getOperationContext()); if (!context.hasSeenType(itemType)) { parameters.addAll(expand(childContext)); } } } FluentIterable simpleFields = attributes.filter(simpleType()); for (ModelAttributeField each : simpleFields) { parameters.add(simpleFields(context.getParentName(), context, each)); } return FluentIterable.from(parameters) .filter(not(hiddenParameters())) .filter(not(voidParameters())) .toList(); } private FluentIterable allModelAttributes( Map propertyLookupByGetter, Iterable getters, Map fieldsByName, AlternateTypeProvider alternateTypeProvider) { FluentIterable modelAttributesFromGetters = from(getters) .transform(toModelAttributeField(fieldsByName, propertyLookupByGetter, alternateTypeProvider)); FluentIterable modelAttributesFromFields = from(fieldsByName.values()) .filter(publicFields()) .transform(toModelAttributeField(alternateTypeProvider)); return FluentIterable.from(Sets.union( modelAttributesFromFields.toSet(), modelAttributesFromGetters.toSet())); } private Function toModelAttributeField( final AlternateTypeProvider alternateTypeProvider) { return new Function() { @Override public ModelAttributeField apply(ResolvedField input) { return new ModelAttributeField( alternateTypeProvider.alternateFor(input.getType()), input.getName(), input, input); } }; } private Predicate publicFields() { return new Predicate() { @Override public boolean apply(ResolvedField input) { return input.isPublic(); } }; } private Predicate voidParameters() { return new Predicate() { @Override public boolean apply(Parameter input) { return isVoid(input.getType().orNull()); } }; } private Predicate recursiveCollectionItemType(final ResolvedType paramType) { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return equal(collectionElementType(input.getFieldType()), paramType); } }; } private Predicate hiddenParameters() { return new Predicate() { @Override public boolean apply(Parameter input) { return input.isHidden(); } }; } private Parameter simpleFields( String parentName, ExpansionContext context, ModelAttributeField each) { LOG.debug("Attempting to expand field: {}", each); String dataTypeName = Optional.fromNullable(typeNameFor(each.getFieldType().getErasedType())) .or(each.getFieldType().getErasedType().getSimpleName()); LOG.debug("Building parameter for field: {}, with type: ", each, each.getFieldType()); ParameterExpansionContext parameterExpansionContext = new ParameterExpansionContext( dataTypeName, parentName, determineScalarParameterType( context.getOperationContext().consumes(), context.getOperationContext().httpMethod()), new ModelAttributeParameterMetadataAccessor( each.annotatedElements(), each.getFieldType(), each.getName()), context.getDocumentationContext().getDocumentationType(), new ParameterBuilder()); return pluginsManager.expandParameter(parameterExpansionContext); } private Predicate recursiveType(final ExpansionContext context) { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return context.hasSeenType(input.getFieldType()); } }; } private Predicate simpleType() { return and(not(isCollection()), not(isMap()), or( belongsToJavaPackage(), isBaseType(), isEnum())); } private Predicate isCollection() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return isContainerType(input.getFieldType()); } }; } private Predicate isMap() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return Maps.isMapType(input.getFieldType()); } }; } private Predicate isEnum() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return enumTypeDeterminer.isEnum(input.getFieldType().getErasedType()); } }; } private Predicate belongsToJavaPackage() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return ClassUtils.getPackageName(input.getFieldType().getErasedType()).startsWith("java.lang"); } }; } private Predicate isBaseType() { return new Predicate() { @Override public boolean apply(ModelAttributeField input) { return Types.isBaseType(input.getFieldType()) || input.getFieldType().isPrimitive(); } }; } private Function toModelAttributeField( final Map fieldsByName, final Map propertyLookupByGetter, final AlternateTypeProvider alternateTypeProvider) { return new Function() { @Override public ModelAttributeField apply(ResolvedMethod input) { String name = propertyLookupByGetter.get(input.getRawMember()).getName(); return new ModelAttributeField( fieldType(alternateTypeProvider, input), name, input, fieldsByName.get(name)); } }; } private Predicate onlyValidGetters(final Set methods) { return new Predicate() { @Override public boolean apply(ResolvedMethod input) { return methods.contains(input.getRawMember()); } }; } private String nestedParentName(String parentName, ModelAttributeField attribute) { String name = attribute.getName(); ResolvedType fieldType = attribute.getFieldType(); if (isContainerType(fieldType) && !Types.isBaseType(collectionElementType(fieldType))) { name += "[0]"; } if (isNullOrEmpty(parentName)) { return name; } return String.format("%s.%s", parentName, name); } private ResolvedType fieldType(AlternateTypeProvider alternateTypeProvider, ResolvedMethod method) { return alternateTypeProvider.alternateFor(method.getType()); } private Set propertyDescriptors(final Class clazz) { try { Set beanProps = new HashSet<>(); PropertyDescriptor[] descriptors = getBeanInfo(clazz).getPropertyDescriptors(); for (PropertyDescriptor descriptor : descriptors) { // 增加逻辑,忽略@IgnoreSwaggerParameter注解的字段 Field field = null; try { field = clazz.getDeclaredField(descriptor.getName()); } catch (Exception e) { LOG.debug(String.format("Failed to get bean properties on (%s)", clazz), e); } if (field != null) { field.setAccessible(true); IgnoreSwaggerParameter ignoreSwaggerParameter = field.getDeclaredAnnotation(IgnoreSwaggerParameter.class); if (ignoreSwaggerParameter != null) { continue; } } // 增加结束 if (descriptor.getReadMethod() != null) { beanProps.add(descriptor); } } return beanProps; } catch (Exception e) { LOG.warn(String.format("Failed to get bean properties on (%s)", clazz), e); } return newHashSet(); } private Map propertyDescriptorsByMethod( final Class clazz, Set propertyDescriptors) { return FluentIterable.from(propertyDescriptors) .filter(new Predicate() { @Override public boolean apply(PropertyDescriptor input) { return input.getReadMethod() != null && !clazz.isAssignableFrom(Collection.class) && !"isEmpty".equals(input.getReadMethod().getName()); } }) .uniqueIndex(new Function() { @Override public Method apply(PropertyDescriptor input) { return input.getReadMethod(); } }); } @VisibleForTesting BeanInfo getBeanInfo(Class clazz) throws IntrospectionException { return Introspector.getBeanInfo(clazz); } public static String determineScalarParameterType(Set consumes, HttpMethod method) { String parameterType = "query"; if (consumes.contains(MediaType.APPLICATION_FORM_URLENCODED) && method == HttpMethod.POST) { parameterType = "form"; } else if (consumes.contains(MediaType.MULTIPART_FORM_DATA) && method == HttpMethod.POST) { parameterType = "formData"; } return parameterType; } }

用到了自定义的IgnoreSwaggerParamer注解

package com.example.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // swagger忽略的参数 @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface IgnoreSwaggerParameter { }

使用方式,在不需要递归展开的属性上加上IgnoreSwaggerParameter注解

package com.example.model.po; import com.example.annotation.IgnoreSwaggerParameter; import com.example.model.BaseModel; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.sql.Date; import java.sql.Timestamp; import java.util.List; @Data @ApiModel(value = "用户") public class User extends BaseModel { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "用户id") private Integer id; @ApiModelProperty(value = "用户名") private String username; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "邮箱") private String email; @ApiModelProperty(value = "昵称") private String nickname; @ApiModelProperty(value = "生日") private Date birth; @ApiModelProperty(value="登录时间") private Timestamp logintime; @ApiModelProperty(value = "部门id") private Integer deptId; @ApiModelProperty(value = "部门信息") @IgnoreSwaggerParameter // 在不需要递归展开的属性上加上IgnoreSwaggerParameter注解 private Dept dept; @ApiModelProperty(value = "角色信息") @IgnoreSwaggerParameter private List roles; }

这样就可以自定义隐藏swagger2页面中的参数了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python利用Django如何写restful api接口详解(python创建django)
下一篇:Python如何通过百度翻译API实现翻译功能(调用百度翻译api)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~