当前位置:   article > 正文

svn结合checkStyle和svnchecker校验java8代码_svn提交怎么跟阿里巴巴插件对齐

svn提交怎么跟阿里巴巴插件对齐

目录

1、使用的软件版本

2、校验流程

3、搭建校验服务

3.1构建checkStyle服务

3.1.1.下载checkStyle和编译checkStyle源码

3.1.2、部署checkStyle

3.2、构建svnchecker-0.3服务

3.2.1、下载svnchecker-0.3源码

3.2.2、部署svnchecker

3.3、构建svn代码校验

3.3.1、配置svncheckerconfig.ini

3.3.2、修改pre_commit钩子

4、附录



1、使用的软件版本

  • jdk:1.8或以上
  • checkStyle:9.3
  • svnchecker:0.3
  • python:2.7.5
  • maven:3.3.6
  • 操作系统:笔者选的是centos7

2、校验流程

svn客户端提交代码后,svn服务端通过pre-commit钩子在提交前做校验。pre-commit调用svnchecker,svncheker调用checkStyle。所有的代码校验规则都是在checkStyle模块完成。

3、搭建校验服务

3.1构建checkStyle服务

3.1.1.下载checkStyle和编译checkStyle源码

url: Releases · checkstyle/checkstyle · GitHub

版本选择:选择10以下的。10以上的版本需要jdk11.10以下的版本可以使用jdk8运行。

下载选择:可以选择checkstyle-版本号-all.jar,比较简单。建议选择下载源码Source code(zip),原因需要解决中文乱码;也可以自己修改代码。

由源码构建checkstyle-版本号-all.jar:下载源码zip文件后,解压。cmd进入加压后的目录(pom.xml的同级目录)。执行命令 mvn -P assembly package。

执行完的效果:

问题和解决方法:

问题1、checkStyle中文乱码问题:

解决方法:修改DefaultLogger.java的addError(AuditEvent aEvt)。支持Unicode编码。修改后的代码下

  1. @Override
  2. public void addError(AuditEvent aEvt) {
  3. SeverityLevel severityLevel = aEvt.getSeverityLevel();
  4. if (!SeverityLevel.IGNORE.equals(severityLevel)) {
  5. String fileName = aEvt.getFileName();
  6. String message = aEvt.getMessage();
  7. int bufLen = fileName.length() + message.length() + 12;
  8. StringBuffer sb = new StringBuffer(bufLen);
  9. sb.append(fileName);
  10. sb.append(':').append(aEvt.getLine());
  11. if (aEvt.getColumn() > 0) {
  12. sb.append(':').append(aEvt.getColumn());
  13. }
  14. if (SeverityLevel.WARNING.equals(severityLevel)) {
  15. sb.append(": warning");
  16. }
  17. sb.append(": ").append(message);
  18. this.errorWriter.println(this.utf8ToUnicode(sb.toString()));
  19. }
  20. }
  21. private String utf8ToUnicode(String str) {
  22. char[] utfBytes = str.toCharArray();
  23. String unicodeBytes = "";
  24. for(int i = 0; i < utfBytes.length; ++i) {
  25. String hexB = Integer.toHexString(utfBytes[i]);
  26. if (hexB.length() <= 2) {
  27. hexB = "00" + hexB;
  28. }
  29. unicodeBytes = unicodeBytes + "\\u" + hexB;
  30. }
  31. return unicodeBytes;
  32. }

问题2、执行执行命令 mvn -P assembly package命令报错。pom.xml中的reporting模块下的插件引入不了。

解决方法:将插件对应的依赖代码移到dependences下,注意不是复制是迁移。依赖引入后,重新把依赖引入的代码写到reporting下。

3.1.2、部署checkStyle

1、设置alibaba_checks.xml。笔者配置内容请参考第4章

alibaba_checks.xml是checkStyle代码风格校验文件。根据《阿里巴巴Java开发手册(公开版).pdf》编写规则。配置内容教程:checkstyle – Checks

2、将打包好的checkstyle-版本号-all.jar连同配置文件alibaba_checks.xml一起放在linux服务器的/opt/checkStyle目录下。给checkstyle-版本号-all.jar赋予可执行权限。

3.2、构建svnchecker-0.3服务

3.2.1、下载svnchecker-0.3源码

从百度网盘下载已经修改好的版本。修改的内容:支持中文提示和多文件提交

链接:百度网盘 请输入提取码

提取码:pasf

3.2.2、部署svnchecker

将下载解压后的源码部署到linux服务器的/opt/svnChecker目录下。给目录下的所有文件赋予可执行权限。

3.3、构建svn代码校验

3.3.1、配置svncheckerconfig.ini

在svn仓库目录下,进入hooks目录。创建svncheckerconfig.ini文件。配置内容主要是被svnchecker-0.3/checks/Checkstyle.py调用。使用时,记得把注释去掉,不然会报错。配置内容如下:

注意/opt/checkstyle-8.3改成checkStyle的部署目录

3.3.2、修改pre_commit钩子

在svn仓库目录下,进入hooks目录。将pre-commit.tmpl重命名为pre-commit。赋予可执行权限。

在pre-commit脚本的结尾加入如下内容:

  1. /opt/svnchecker-0.3/Main.py PreCommit $1 $2 || exit 1
  2. LOGMSG=`$SVNLOOK log -t "$TXN" "$REPOS" | grep "[a-zA-Z0-9]" | wc -c`
  3. if [ "$LOGMSG" -lt 5 ];
  4. then
  5. echo -e "注释不能少于5个字" 1>&2
  6. exit 1
  7. fi
  8. # All checks passed, so allow the commit.
  9. exit 0

4、附录

笔者使用的alibaba_check.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE module PUBLIC
  3. "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
  4. "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
  5. <module name="Checker">
  6. <!-- 检查文件是否以一个空行结束 -->
  7. <module name="NewlineAtEndOfFile"/>
  8. <!-- 文件长度不超过1500行 -->
  9. <module name="FileLength">
  10. <property name="max" value="1500"/>
  11. </module>
  12. <!-- 每个java文件一个语法树 -->
  13. <module name="TreeWalker">
  14. <!-- import检查-->
  15. <!-- 避免使用* -->
  16. <module name="AvoidStarImport">
  17. <property name="excludes" value="java.io,java.net,java.lang.Math"/>
  18. <!-- 实例;import java.util.*;.-->
  19. <property name="allowClassImports" value="false"/>
  20. <!-- 实例 ;import static org.junit.Assert.*;-->
  21. <property name="allowStaticMemberImports" value="true"/>
  22. <message key="import.avoidStar" value="引用包时不能使用*号"/>
  23. </module>
  24. <!-- 检查是否从非法的包中导入了类 -->
  25. <module name="IllegalImport">
  26. <message key="import.illegal" value="非法导入: {0}"/>
  27. </module>
  28. <!-- 检查是否导入了多余的包 -->
  29. <module name="RedundantImport">
  30. <message key="import.control.disallowed" value="不允许的导入: {0} 。"/>
  31. <message key="import.duplicate" value="第{0,number,integer}行重复导入:{1} 。"/>
  32. <message key="import.illegal" value="非法导入: {0} 。"/>
  33. </module>
  34. <!-- 没用的import检查,比如:1.没有被用到2.重复的3.import java.lang的4.import 与该类在同一个package的 -->
  35. <module name="UnusedImports">
  36. <message key="import.unused" value="无用导入 - {0} 。"/>
  37. </module>
  38. <!-- 注释检查 -->
  39. <!-- 检查方法和构造函数的javadoc -->
  40. <module name="JavadocType">
  41. <property name="allowUnknownTags" value="true"/>
  42. <message key="javadoc.missing" value="类注释:缺少Javadoc注释。"/>
  43. </module>
  44. <module name="JavadocMethod">
  45. <property name="tokens" value="METHOD_DEF" />
  46. <message key="javadoc.missing" value="方法注释:缺少Javadoc注释。"/>
  47. </module>
  48. <!-- 命名检查 -->
  49. <!-- 局部的final变量,包括catch中的参数的检查 -->
  50. <module name="LocalFinalVariableName" >
  51. <property name="severity" value="error"/>
  52. <message key="name.invalidPattern" value="变量名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  53. </module>
  54. <!-- 局部的非final型的变量,包括catch中的参数的检查 -->
  55. <module name="LocalVariableName">
  56. <property name="format" value="(^[a-z][a-zA-Z0-9]*$)"/>
  57. <message key="name.invalidPattern" value="变量名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  58. </module>
  59. <!-- 包名的检查(只允许小写字母),默认^[a-z]+(\.[a-zA-Z_][a-zA-Z_0-9_]*)*$ -->
  60. <module name="PackageName">
  61. <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$" />
  62. <message key="name.invalidPattern" value="包名 ''{0}'' 要符合 ''{1}''格式."/>
  63. </module>
  64. <!-- 仅仅是static型的变量(不包括static final型)的检查 -->
  65. <module name="StaticVariableName">
  66. <message key="name.invalidPattern" value="变量名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  67. </module>
  68. <!-- 接口、枚举、注解类的命名,匹配规则默认:(^[A-Z][a-zA-Z0-9]*$),必须以大写字母开始 -->
  69. <module name="TypeName">
  70. <property name="tokens" value="INTERFACE_DEF , ENUM_DEF , ANNOTATION_DEF"/>
  71. <message key="name.invalidPattern" value="接口类,枚举类,注解类 ''{0}''没有通过大驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  72. </module>
  73. <!-- 类命名,支持DO\VO\DAO\DTO.匹配规则默认:(^[A-Z][a-zA-Z0-9]*$),必须以大写字母开始 -->
  74. <module name="TypeName">
  75. <property name="format" value="(^(([A-Z][a-z]+)+)(VO)?(DO)?(DAO)?(DTO)?$)"/>
  76. <property name="tokens" value="CLASS_DEF"/>
  77. <message key="name.invalidPattern" value="普通类名 ''{0}''没有通过大驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  78. </module>
  79. <!-- 非static型变量的检查 -->
  80. <!-- 变量命名 -->
  81. <module name="MemberName">
  82. <message key="name.invalidPattern" value="变量名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  83. </module>
  84. <!-- 方法名的检查 -->
  85. <module name="MethodName">
  86. <property name="severity" value="error"/>
  87. <property name="format" value="(^[a-z][a-zA-Z0-9]*$)"/>
  88. <message key="name.invalidPattern" value="方法名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  89. </module>
  90. <!-- 方法的参数名 -->
  91. <!--参数名-->
  92. <module name="ParameterName">
  93. <property name="severity" value="error"/>
  94. <message key="name.invalidPattern" value="参数名 ''{0}''没有通过小驼峰命名法命名,具体名称应使用意义完整的英文描述"/>
  95. </module>
  96. <!-- 常量名的检查(只允许大写),默认^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$ -->
  97. <!-- 常量命名-->
  98. <module name="ConstantName">
  99. <property name="severity" value="error"/>
  100. <message key="name.invalidPattern" value="常量 ''{0}'' 命名不符合规范,常量名必须全部大写且以下划线相连"/>
  101. </module>
  102. <!-- 定义检查 -->
  103. <!-- 检查数组类型定义的样式 -->
  104. <!-- 数组变量命名 -->
  105. <module name="ArrayTypeStyle">
  106. <property name="severity" value="error"/>
  107. <message key="array.type.style" value="数组定义没有采取int[] index这种方式"/>
  108. </module>
  109. <!-- 检查long型定义是否有大写的“L” -->
  110. <module name="UpperEll">
  111. <message key="upperEll" value="请使用大写''L''"/>
  112. </module>
  113. <!-- 方法不超过50行 -->
  114. <module name="MethodLength">
  115. <property name="tokens" value="METHOD_DEF" />
  116. <property name="max" value="50" />
  117. <message key="maxLen.method" value="方法 {2} {0,number,integer} 行(最多: {1,number,integer} 行)。"/>
  118. </module>
  119. <!-- 方法的参数个数不超过5个。 并且不对构造方法进行检查-->
  120. <module name="ParameterNumber">
  121. <property name="max" value="5" />
  122. <property name="ignoreOverriddenMethods" value="true"/>
  123. <property name="tokens" value="METHOD_DEF" />
  124. <message key="maxParam" value="参数共: {1,number,integer}个,最多:{0,number,integer}个。"/>
  125. </module>
  126. <!-- 空格检查-->
  127. <!-- 方法名后跟左圆括号"(" -->
  128. <module name="MethodParamPad" >
  129. <message key="line.previous" value="''{0}'' 应在前一行。"/>
  130. <message key="ws.notPreceded" value="''{0}'' 前应有空格。"/>
  131. <message key="ws.preceded" value="''{0}'' 前不应有空格。"/>
  132. </module>
  133. <!-- 在类型转换时,不允许左圆括号右边有空格,也不允许与右圆括号左边有空格 -->
  134. <module name="TypecastParenPad">
  135. <property name="severity" value="error"/>
  136. <property name="tokens" value="RPAREN,TYPECAST"/>
  137. <message key="ws.followed" value="''{0}''后面多一个空格"/>
  138. <message key="ws.preceded" value="''{0}''前面多一个空格"/>
  139. </module>
  140. <!-- 检查在某个特定关键字之后应保留空格 -->
  141. <module name="NoWhitespaceAfter">
  142. <message key="ws.followed" value="''{0}'' 后不应有空格。"/>
  143. </module>
  144. <!-- 检查在某个特定关键字之前应保留空格 -->
  145. <module name="NoWhitespaceBefore">
  146. <message key="ws.preceded" value="''{0}''前面多一个空格"/>
  147. </module>
  148. <!--操作符换行策略检查-->
  149. <module name="OperatorWrap">
  150. <property name="severity" value="error"/>
  151. <message key="line.after" value="''{0}'没有放在新行之首"/>
  152. <message key="line.new" value="{0} 没有放在新行之首"/>
  153. </module>
  154. <!-- 圆括号空白 -->
  155. <module name="ParenPad">
  156. <message key="ws.followed" value="''{0}'' 后不应有空格。"/>
  157. <message key="ws.notFollowed" value="''{0}'' 后应有空格。"/>
  158. <message key="ws.notPreceded" value="''{0}'' 前应有空格。"/>
  159. <message key="ws.preceded" value="''{0}''前面多一个空格"/>
  160. </module>
  161. <!-- 检查分隔符是否在空白之后 -->
  162. <module name="WhitespaceAfter">
  163. <property name="severity" value="error"/>
  164. <property name="tokens" value="COMMA,SEMI"/>
  165. <message key="ws.notFollowed" value="''{0}''后面多一个空格"/>
  166. </module>
  167. <!-- 检查分隔符周围是否有空白 -->
  168. <module name="WhitespaceAround">
  169. <property name="severity" value="error"/>
  170. <property name="tokens" value="RCURLY,LITERAL_ASSERT,LITERAL_ASSERT,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE"/>
  171. <property name="allowEmptyConstructors" value="true"/>
  172. <property name="allowEmptyMethods" value="true"/>
  173. <message key="ws.notPreceded" value="''{0}'' 关键字前少一个空格"/>
  174. <message key="ws.notFollowed" value="''{0}'' 关键字后少一个空格"/>
  175. </module>
  176. <!-- 修饰符检查 -->
  177. <!-- 检查修饰符的顺序是否遵照java语言规范,默认public、protected、private、abstract、static、final、transient、volatile、synchronizednative、strictfp -->
  178. <module name="ModifierOrder">
  179. <message key="line.previous" value="''{0}'' 应在前一行。"/>
  180. <message key="ws.notPreceded" value="''{0}'' 关键字前少一个空格"/>
  181. <message key="ws.preceded" value="''{0}''前面多一个空格"/>
  182. </module>
  183. <!-- 检查接口和annotation中是否有多余修饰符,如接口方法不必使用public -->
  184. <!-- 多余的关键字,包含不适用this作为关键字 -->
  185. <module name="RedundantModifier">
  186. <message key="redundantModifier" value="''{0}'' 多余的修饰符"/>
  187. </module>
  188. <!-- 代码块检查 -->
  189. <!-- 检查是否有嵌套代码块 -->
  190. <module name="AvoidNestedBlocks">
  191. <message key="block.nested" value="避免内嵌块。"/>
  192. </module>
  193. <!-- 检查是否有空代码块 -->
  194. <module name="EmptyBlock">
  195. <message key="block.empty" value="空 {0} 块。"/>
  196. <message key="block.noStatement" value="块中应至少有一条代码语句。"/>
  197. </module>
  198. <!-- 检查左大括号位置 -->
  199. <module name="LeftCurly">
  200. <property name="severity" value="error"/>
  201. <message key="line.previous" value="左侧大括号没有放在前一行代码的行尾"/>
  202. </module>
  203. <!-- 检查代码块是否缺失{} -->
  204. <!-- 检查代码块是否缺失大括号 -->
  205. <module name="NeedBraces">
  206. <message key="needBraces" value="''{0}'' 结构没有用大括号 '''{}'''s"/>
  207. </module>
  208. <!-- 检查右大括号位置 -->
  209. <module name="RightCurly">
  210. <property name="option" value="alone"/>
  211. <property name="severity" value="error"/>
  212. <message key="line.alone" value="''{0}''应该独占一行"/>
  213. </module>
  214. <!-- 代码检查 -->
  215. <!-- 检查空的代码段 -->
  216. <module name="EmptyStatement">
  217. <message key="empty.statement" value="避免空行。"/>
  218. </module>
  219. <!-- 检查在重写了equals方法后是否重写了hashCode方法 -->
  220. <module name="EqualsHashCode">
  221. <message key="equals.noEquals" value="重写''hashCode()''方法后,必须重写''equals()''方法。"/>
  222. <message key="equals.noHashCode" value="重写''equals()''方法后,必须重写''hashCode()''方法。"/>
  223. </module>
  224. <!-- 检查局部变量或参数是否隐藏了类中的变量 -->
  225. <module name="HiddenField">
  226. <property name="tokens" value="VARIABLE_DEF"/>
  227. <message key="hidden.field" value="''{0}'' 隐藏属性。"/>
  228. </module>
  229. <!-- 检查是否使用工厂方法实例化 -->
  230. <module name="IllegalInstantiation">
  231. <message key="instantiation.avoid" value="应避免 {0} 的实例化。"/>
  232. </module>
  233. <!-- 检查子表达式中是否有赋值操作 -->
  234. <module name="InnerAssignment">
  235. <message key="assignment.inner.avoid" value="应避免在子表达式中赋值。"/>
  236. </module>
  237. <!-- 检查是否有"魔术"数字 -->
  238. <module name="MagicNumber">
  239. <property name="ignoreNumbers" value="0, 1"/>
  240. <property name="ignoreAnnotation" value="true"/>
  241. <message key="magic.number" value="''{0}'' 是一个魔法数(即常数)"/>
  242. </module>
  243. <!-- 检查switch语句是否有default -->
  244. <module name="MissingSwitchDefault" >
  245. <property name="severity" value="error"/>
  246. <message key="missing.switch.default" value="switch 语句后边没有 default 语句"/>
  247. </module>
  248. <!-- 检查是否有过度复杂的布尔表达式 -->
  249. <module name="SimplifyBooleanExpression">
  250. <property name="severity" value="error"/>
  251. <message key="simplify.expression" value="存在布尔冗余"/>
  252. </module>
  253. <!-- 检查是否有过于复杂的布尔返回代码段 -->
  254. <module name="SimplifyBooleanReturn">
  255. <message key="simplify.boolReturn" value="不必要的条件逻辑。"/>
  256. </module>
  257. <!-- 类设计检查 -->
  258. <!-- 检查类是否为扩展设计l -->
  259. <!-- 检查只有private构造函数的类是否声明为final -->
  260. <module name="FinalClass">
  261. <message key="final.class" value="只有私有构造器的类必须声明为final"/>
  262. </module>
  263. <!-- 检查工具类是否有putblic的构造器 -->
  264. <module name="HideUtilityClassConstructor">
  265. <message key="hide.utility.class" value="工具类应隐藏 public 构造器。"/>
  266. </module>
  267. <!-- 检查接口是否仅定义类型 -->
  268. <module name="InterfaceIsType">
  269. <message key="interface.type" value="接口应描述一种类型,从而必须拥有方法。"/>
  270. </module>
  271. <!-- 检查类成员的可见度 检查类成员的可见性。只有static final 成员是public的
  272. 除非在本检查的protectedAllowed和packagedAllowed属性中进行了设置-->
  273. <module name="VisibilityModifier">
  274. <property name="packageAllowed" value="true"/>
  275. <property name="protectedAllowed" value="true"/>
  276. <message key="variable.notPrivate" value="变量 ''{0}'' 应定义为 private 的,并配置访问方法。"/>
  277. </module>
  278. <!-- 语法 -->
  279. <!-- String的比较不能用!=== -->
  280. <module name="StringLiteralEquality">
  281. <message key="string.literal.equality" value="字符串应使用equals()方法进行比较,而非''{0}''。"/>
  282. </module>
  283. <!-- 限制for循环最多嵌套2层 -->
  284. <module name="NestedForDepth">
  285. <property name="max" value="3"/>
  286. <message key="nested.for.depth" value="至多{1,number,integer}层 for,目前{0,number,integer}层。"/>
  287. </module>
  288. <!-- if最多嵌套3层 -->
  289. <module name="NestedIfDepth">
  290. <property name="max" value="3"/>
  291. <message key="nested.if.depth" value="至多{1,number,integer}层 if,目前{0,number,integer}层。"/>
  292. </module>
  293. <!-- 检查未被注释的main方法,排除以App结尾命名的类 -->
  294. <module name="UncommentedMain">
  295. <property name="excludedClasses" value=".*App$"/>
  296. <message key="uncommented.main" value="未注释的Main方法。"/>
  297. </module>
  298. <!-- 禁止使用System.out.println -->
  299. <module name="Regexp">
  300. <property name="format" value="System\.out\.println"/>
  301. <property name="illegalPattern" value="true"/>
  302. <message key="duplicate.regexp" value="重复表达式: ''{0}''。"/>
  303. <message key="illegal.regexp" value="当前行匹配非法表达式: ''{0}''。"/>
  304. <message key="required.regexp" value="文件缺少表达式: ''{0}'' 。"/>
  305. </module>
  306. <!-- return个数 3个-->
  307. <module name="ReturnCount">
  308. <property name="max" value="3"/>
  309. <message key="return.count" value="Return 次数 {0,number,integer} 次(最大允许非空虚方法/ 拉姆达: {1,number,integer} 次)。"/>
  310. <message key="return.countVoid" value="Return 次数 {0,number,integer} 次(最大允許為void方法/構造函數/ 拉姆达: {1,number,integer} 次)。"/>
  311. </module>
  312. <!--try catch 异常处理数量 3-->
  313. <module name="NestedTryDepth ">
  314. <property name="max" value="3"/>
  315. <message key="nested.try.depth" value="至多{1,number,integer}层 try,目前{0,number,integer}层。"/>
  316. </module>
  317. <!-- clone方法必须调用了super.clone() -->
  318. <module name="SuperClone">
  319. <message key="missing.super.call" value="方法 ''{0}'' 应调用 ''super.{0}''。"/>
  320. </module>
  321. <!-- finalize 必须调用了super.finalize() -->
  322. <module name="SuperFinalize">
  323. <message key="missing.super.call" value="方法 ''{0}'' 应调用 ''super.{0}''。"/>
  324. </module>
  325. </module>
  326. <!-- 单行字符数 -->
  327. <module name="LineLength">
  328. <property name="max" value="120"/>
  329. <message key="maxLineLen" value="行字符数超过120个"/>
  330. </module>
  331. </module>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/847009?site
推荐阅读
相关标签
  

闽ICP备14008679号