开发至简网格的过程中,既要做服务侧开发,也要多端开发,服务侧分为JAVA服务侧、Android服务侧,端侧分为安卓、Windows,技术繁杂,碰到不少基本问题,全部记录在这里,便于以后查找。
顺便做个小广告,至简网格是一套端&云结合的开发框架,极大简化了服务侧与端侧开发,服务侧使用简单的json配置与sql、js脚本,就可以搞定95%以上的业务场景;端侧支持Android与Windows,本质是一个轻应用开发框架,用vue+quasar实现UI,使用极其简单。实现了几个业务代码,比如CRM、会员等,已在码云、CSDN开源。项目还在持续完善中,欢迎使用。
目录
1.1.6. Kotlin-android not found错误
1. 环境问题
1.1. AndroidStudio
1.1.1. 加入jar、aar的方法:
a)在app下创建目录libs
b)在app\build.grale中增加implementation fileTree(dir: 'libs', include : ['*.jar','*.aar'])
c)将jar、aar文件拷贝到下面;
d)如果AndroidStudio不能识别,则点击菜单File->Invalidate caches/Restart,然后等等重启即可;
1.1.2. 模拟器路径权限
如果手动在AndroidStudio的Device File Explore中创建路径、文件,会导致在app中无权限访问,必须在App中自己创建。
1.1.3. gradle安装
- 解压到指定路径;
- 配置GRADLE_HOME指向该路径;
- 在路径下创建user目录,配置GRADLE_USER_HOME为%GRADLE_HOME%\user,用于存放临时文件;
- 将%GRADLE_HOME%\bin加入PATH变量
- AndroidStudio的File->Settings中搜索Gradle,设置Gradle路径及GradleUser路径;
- 如果升级gradle,建议下载后,仍然解压到相同路径,这样所有应用的设置不用变动。
1.1.4. gradle问题
工程目录下build.gradle中指定的是AndroidStudio的gradle的版本,可能是适配器,尽量不要改,或者改成AndroidStudio的版本;
gradle\wrapper\gradle-wrapper.properties指定gradle版本,路径可以写成本地下载的zip文件,比如file\:///本地路径,所以这个目录下gradle的zip文件不可以删除。这样可以避免不同的工程都下载一遍。
1.1.5. kotlin、gradle插件被禁用
这两个插件是不可以禁用的,如果禁用,AndroidStudio启动会异常。
这时可以在disabled_plugins.txt中删除相应记录即可,位置如下:
C:\Users\用户名\AppData\Roaming\Google\AndroidStudio4.1\disabled_plugins.txt
1.1.6. Kotlin-android not found错误
在项目build.gradle中删除导致错误的行,然后在Tools-Kotlin选择运行Config Kotlin in Project即可。
1.1.7. 修改gradle配置
每次修改gradle文件,会导致无法编译运行工程,这时选择File->Sync Project With Gradle Files后即可。
1.1.8. 打开logcat查看日志
菜单View-Tool Windows中,打开logcat查看日志。
还有其他一些功能也在此目录下;
1.1.9. 删除多余import
菜单 Code-Optimize Imports可以自动删除所有多余的import;或者使用ctrl+alt+’o’热键。
1.1.10. 修改checkstyle规则
在Settings-Inspections中搜索提示的关键词,找到规则,然后勾选或勾除
1.1.11. 修改工程名称
比如将样例工程修改成最终的工程名称,按以下步骤即可完成:
1.关闭Android Studio;
2.修改项目文件夹的名字;
3.修改OldProjectName.iml文件(在项目的根目录的.idea目录下)的名称为新项名称,即OldProjectName.iml修改为NewProjectName.iml;
4.修改.idea/workspace.xml中相应的名称;
5.修改app/build.gradle中的applicationId;
6.然后把该文件中的external.linked.project.id的值也设置为新项目的名称,即 external.linked.project.id=”NewProjectName”;
7.再次打开AndroidStudio即可。
1.1.12. adb覆盖安装
adb install xxx.apk 如果已安装了,此时会提示
Failure [INSTALL_FAILED_ALREADY_EXISTS: Attempt to re-install xxx without first uninstalling.]
使用adb install -r xxx.apk,可以覆盖安装它,但是仍然保留前面的数据。
1.1.13. 导入样例工程时需要修改的地方
- 修改项目目录下gradle/wrapper/gradle-wrapper.properties中的distributionUrl,将版本改成graddle中已有的版本,不然又要下载个老的;
- 修改项目目录下build.gradle,将ext.kotlin_version改为当前已有的版本,kotlin版本可以在file-settings-plugins中查看;
- 修改项目目录下app/build.gradle,删除buildSdkVersion,使用当前已有的版本,修改compileSdk、targetSdk为AndroidStudio当前最新版本,修改minSdk为合适的版本,注意这三个配置项的名称在新版本的gradle中,末尾不能带Version;
1.1.14 修改项目applicationId
以下操作是在“Android Studio Flamingo | 2022.2.1 Patch 2”中执行的,其他版本可能不同。
在项目根目录的build.gradle中修改applicationId,如果需要generated的包名也跟着改变,还需要修改build.gradle中的namespace。然后在build菜单中选择“Clean project”,然后在File菜单中选择“Sync Project With Gradle files”,一次不行就执行几次。如果还不行就在File菜单中选择“Invalidate Caches”,重启后再同步几次,直到出现generated目录为止。
1.1.15 proguard保留所有公共构造函数
以下例子是保留所有继承了ScriptElement类的子类中的所有公共的构造函数。 注意“...”表示任何的参数个数、任何参数类型,不能用“***”,它表示的是一个任意类型的参数,而不是多个。 -keepclassmembers class * extends cn.net.zhijian.mesh.frm.config.placeholder.ScriptElement { public <init>(...); }
1.2. 模拟器
1.2.1. 模拟器IP及外部访问
在模拟器内部,宿主机器IP为10.0.0.2,模拟器自身IP为10.0.2.15/127.0.0.1/localhost
如果需要在宿主机中直接访问模拟器内部的TCP端口,需要先做映射。
adb forward tcp:8081 tcp:8080
这样就可以访问 http://localhost:8081/xxxxx,请求会被转到虚拟机的8080端口
1.2.2. 进入模拟器命令行
adb -s emulator-5554 shell
1.3. 网络
1.3.1. 手机与PC之间网络不通
一般是路由器设置有问题,可能在路由器无线设置中开启了AP隔离,使得同一路由器下各个节点之间不可互通。
1.3.2. PC不能ping手机
网络防火墙默认是不会禁用出站请求的,但是如果安装了360,在360的安全防护中心->入口防护体系中,如果选择了局域网防护,则PC无法联通手机。
1.3.3. 同局域网下手机访问PC
首先,PC上需启动web服务;
其次,要在系统防火墙高级设置中,添加入站规则开放相应的端口,比如TCP的8080端口;
最后,如果安装了360,需要在安全防护中心->系统防护体系中,关闭网络安全防火。
1.4. 小米手机
1.4.1. 小米手机,无法打开usb安装
插入一张Sim卡,没用的Sim卡也可以
1.4.2. 真机进入开发者模式
不同型号得手机,包括华为、小米等,都是在设置的安卓版本上多次点击,即可进入开发者模式。进入开发者模式后,才可以打开USB调试。
1.5. 华为或荣耀手机
1.5.1. 打开debug级别日志
华为手机默认日志级别是info,无论AndroidStudio中设置的是什么,如果要打开debug级别,按以下步骤设置。
1.拨号界面拨号*#*#2846579#*#*可以看到工程菜单;
2.选择后台设置进入;
3.打开 LOG设置,选择 AP日志;
4.回到AndroidStudio中,改变一下日志级别,就可以看到debug了;
5.如果还是无法显示,但是adb logcat -d可以查看,则重启以下AndroidStudio即可。
1.6. iOS
1.6.1. IPhone webserver备忘
基于SwiftNio开发webserver,SwiftNio是iOS中的netty。
https://www.5axxw.com/wiki/content/zdz096https://www.5axxw.com/wiki/content/zdz096
其他的如GCDWebServer、CocoaHttpServer都已长期无更新
1.7. Eclipse
1.7.1. 更换包名
在包上点右键,选择Refactor,出现更名窗口,输入新的名称,一定要选择Rename subpackages,否则只会新建一个空的包
1.7.2. js(vue)支持
已有的vue插件已经不维护了,插件wild web develop声称可以支持vue,实际安装后,可以满足工作的基本要求。在eclipse中help->Eclipse Marketplace中搜索javascript,图标是wwd的那个就是,点击安装,重启eclipse后,右键打开js或vue文件,选择“Generic TextEditor”,以后直接双击文件就行了,自动使用Generic TextEditor打开。
1.8. Gradle
1.8.1. asset下以下划线“_”开头的目录被忽略
在项目的gradle文件的android下添加以下配置,将此功能关闭掉
aaptOptions{
ignoreAssetsPattern '!._'
}
1.9. Java
1.9.1. Windows安装GraalVm
下载安装:与java配置完全相同,解压后,在系统的高级配置中添加JAVA_HOME。并将%JAVA_HOME%\bin设置到系统变量path中
安装native-image:gu install native-image
安装llvm:gu install llvm-toolchain
安装js引擎:gu install js
原生编译命令:native-image 或者gradle nativeCompile
Js插件下载(选择正确的版本,注意下载jar格式的):
下载的版本号必须与graalvm一致,graalvm版本通过 java -version查看
1.9.2 Linux下安装GraalVm
- 下载以下两个文件,上传到linux服务上;
graalvm-ce-java11-linux-amd64-22.3.1.tar.gz
如果不用内嵌的js支持,可以不用安装js
js-installable-svm-java11-linux-amd64-22.3.2.jar
如果不作原生编译,可以不安装native-image
native-image-installable-svm-svmee-java11-windows-amd64-22.3.2.jar
插件安装命令 gu install js 或者native-image,安装包是从github下载的,所以很慢,并且经常下到一半时提示下载失败,所以找到github网站,直接用迅雷下载到本地后(一定要注意,下载amd的jar文件),再用命令行从本地安装。
gu install -L 本地安装文件 js
- 解压graalvm;
tar xfz graalvm-ce-java11-linux-amd64-22.3.1.tar.gz
【注意】不要解压在/root下,因为这个目录是root用户的根目录,其他用户无法访问
- 并在/etc/profile中增加以下配置
export JAVA_HOME=解压路径
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
- 安装js支持,通过-L选项选择本地安装。
gu install -L js-installable-svm-java11-linux-amd64-22.3.1.jar
不要使用gu install js,因为国内访问github不顺畅,安装极难成功,所以用下载工具(比如迅雷)下载后再在本地安装。
1.10.Linux环境
1.10.1.创建用户
用root用户安装服务是个坏习惯,特别是对外保留接口的服务,一旦有漏洞,黑客获取的就是root权限,所以另建用户安装服务。
useradd -m mesh
-m参数要求系统在/home下自动创建用户目录,mesh为用户名称
passwd mesh
为用户设置一个密码
1.10.2.端口转发
服务程序运行在8523端口,需要将80与443都转发到这个端口,用iptables添加转发规则就可以实现。
1.首先安装iptables,如果已安装,开启它就可以了;
//systemctl stop firewalld # 关闭防火墙
yum -y install iptables-services # 安装 iptables 服务
systemctl enable iptables # 设置 iptables 服务开机启动
systemctl start iptables # 启动 iptables 服务
service iptables save # 保存 iptables 配置
service iptables restart # 重启 iptables 服务
2.开放端口;
iptables服务启动后,默认禁止了1024以上的端口,所以必须打开
iptables -I INPUT -p tcp --dport 8523 -j ACCEPT
3.然后添加端口转发规则;
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8523
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8523
4.查看某个端口的转发规则
iptables -t nat -L -n | grep 80
5.删除端口转发规则
iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8523
6.最后,保存规则。
service iptables save
这样在80与443端口都可以访问。
1.10.3.图片验证码需要安装字体
Linux下生成图片验证码,在FontManagerFactory出现中异常,是因为没有安装字体。使用以下命令安装:
yum -y install fontconfig
fc-list查看已安装的字体
1.11 Oppo
1.11.1 JUnit单元测试
除了要开启USB调试开关外,禁止权限监控的选项不能关闭,否则所有需要存储、网络等的权限申请都会被禁止
2. 安卓开发问题
2.1. 权限
2.1.1. 应用权限设置
在AndroidMenifest.xml中设置,与application同一级别
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
同时,在application中设置android:requestLegacyExternalStorage="true"
2.2. 底层
2.2.1. 动态加载dex插件
- 使用PathClassLoader类加载器实现动态加载dex插件;
- 加载前需要调用dex命令将jar文件转为dex文件,dex文件可以从外部下载获得;
- 因为ClassLoader加载的规则,不同插件不能互访,但是,插件可以访问apk中的类;
2.2.2. 实现禁止手动删除数据
实现一个删除数据的Activity,并在AndroidMenifest.xml-application-android:manageSpaceActivity引用此Acitivity,实现自定义的删除数据管理界面,在此只删除可以删除的,或者全部不删除,比如禁止删除sqlite数据库等。
此Activity的定义与普通Activity毫无差异。
2.2.3. 定义安全策略
在AndroidMenifest.xml-application-android:networkSecurityConfig中可以自定义安全策略,比如预置自签名的根证书等。
2.2.4. 依赖了kotlin编写的库
比如okhttp4.x,提示Failed resolution of: Lkotlin/jvm/internal/Intrinsics,
Kotlin并无特别的优点,建议别用了。现在OkHttp4依赖Kotlin,也用不成了。
2.3. 安全
2.3.1. 可信根加解密
使用KeyStore进行加解密,KeyStore的底层用的是Tee。
它的问题是,在黑屏情况下,无法使用。
EncryptedSharedPreferences 使用的也是KeyStore。
https://source.android.google.cn/security/keystore?hl=zh-cn
2.3.2. 添加自签名根证书
使用CA机构签发证书,通常成本较高,对于一个测试应用,没必要。所以自己产生一个自签名的根证书;然后用根证书产生二级证书;最后用二级证书生成自己的用户证书。这样就形成了一个证书链。在程序中预置根证书,并信任自己的根证书即可。
自签名证书链可以参照以下连接:
KeyTool生成证书链及使用_flyinmind的博客-CSDN博客
以上连接介绍了使用keytool生成根证书、二级证书、三级证书的全部过程。
2.4. JUnit测试
2.4.1. Android-Unit中无法写文件
使用ApplicationProvider.getApplicationContext获得Context,在这个Context中取得的路径是可以读写,写入的内容会存在正式的应用中,而不是在测试的应用中。
2.4.2. 测试准备与清理
在测试函数前加@Before与@After注解,可以控制放在最前面与最后面执行,利用它们做准备与清理工作。
2.4.3 测试中加载文件资源
Java在eclipse中可以将文件放在与java同级的resources目录中,然后使用classloader加载:
InputStream in = XX.class.getClassLoader().getResourceAsStream("fileName");
Junit测试代码所需资源,就放在Junit源码对应目录下的resources目录。
但是在Android studio中不能这样用,必须用context.getAssets().open("fileName")打开输入流,而在JUnit里,重点是怎样获得context,推荐以下方法:
Context context = ApplicationProvider.getApplicationContext();
InputStream in = context.getAssets().open("fileName")
2.5. Logback日志
2.5.1. 配置中的属性
logback读配置文件时,其中用到的属性,用${propertyName}引用。属性需要在初始化的Context中设置,比如指定根路径。此Context不能reset,否则property会丢失。
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
lc.putProperty("loggerHome", outputDir);
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
//lc.reset(); //reset会清除property
configurator.doConfigure(cfgFile);
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
2.5.2. DATA_DIR等内置属性未定义
在一些例子中,出现诸如DATA_DIR、PACKAGE_NAME等属性,其实它们不能用,通过看代码,猜测可能是因为logback获取应用Context的方法有误。所以需要在程序里加载配置前,设置自定义属性,然后在logback.xml中引用。
2.6. 版本发布
2.6.1. 应用签名
版本发布需证书进行签名,这个证书可以使用EC也可以使用RSA,可以用证书链进行签名。Debug情况下,生成了默认的证书,但是发布时不能使用。
Release时,选择菜单Build->Generate Signed Bundle/APK,选择已有的证书或新建一个证书,此证书要伴随应用终身,所以必须保存好,并且记住key密码及store密码。
也可以使用自己签名的证书,生成方法请参考以下连接:
KeyTool生成证书链及使用_flyinmind的博客-CSDN博客
2.6.2. 图标不更新
在“new->image asset”中创建的图标可以保证在不同分辨率下,提供不同的图标,保障合适的清晰度。但是image asset创建的图标与app的工程是独立的,需要将它们拷贝到main的res目录下,并且,不能忘记拷贝mipmap-anydpi-v26或者mipmap-anydpi-v24与values目录,这两个目录不是打酱油的,如果不拷贝它,图标就不会更新,因为安卓里面使用的是mipmap-anydpi-vxx.xml,由它区分不同的分辨率,选择不同的图标。
2.7 进程间共享SharedPreferences
虽然名称中有shared的,其实进程间共享时有诸多问题,最大的问题是不同步。
SharedPreferences第一次打开时会从配置文件中读取k-v对,存到一个map中,后面的变更使用Editor.apply,Editor.commit写到文件中。如果其他进程也在修改SharedPreferences,当前进程是不能即使感知的,除非重新调用getSharedPreferences:
Context.getSharedPreferences(NAME, Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
注意要设置MODE_MULTI_PROCESS(此标志位已不建议使用了),此标志位只是告诉getSharedPreferences在获得SharedPreferences对象时,重新读取一次文件,并不会保证多进程之间的同步;如果不设置,则直接使用以前曾经打开过的SharedPreferences对象(SharedPreferences也会缓存)。
3. C#开发问题
3.1. 目录权限
应用安装在programs目录下时,程序是无权限写当前路径的,可以通过
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
获得应用可以写入的路径,比如C:\Users\帐号\AppData\Roaming\应用名称。在此路径下可以写入日志、运行时文件等。
3.2. DEBUG宏
代码中可以通过判断DEBUG宏是否定义给出不同的实现。
#if DEBUG
public const string API_DOMAIN = "192.168.0.102";
#else
public const string API_DOMAIN = "api" + CERT_DOMAIN;
#endif
为了使DEBUG生效,还必须右键项目,选择“属性->生成”,在DEBUG配置中,选中“定义DEBUG常数”,否则#if DEBUG判断将失败
3.3. Log4net输出文件路径
与目录权限有关,需要根据运行时情况设置日志输出的根路径,可以设置 GlobalContext.Properties["loggerHome"] = outputDir;
然后在log4net.xml的appender.file中引用loggerHome,形式如下:
<file type="log4net.Util.PatternString" value="%property{loggerHome}\\logs\\mesh\\run.log" />
注意,一定要设type为log4net.Util.PatternString,否则%property{loggerHome}被当成普通字符串解析
3.4. 嵌入资源文件
推荐以文件形式嵌入资源,这样便于直接在文件夹中修改文件,而不必每次修改文件后重新刷新到Resources.resx中
在工程上右键,选择添加->新建文件夹,建立Resources目录,然后在里面添加各种文件,注意,资源的生成操作一定要选择“嵌入的资源”。
然后在程序中,使用如下方式打开资源文件流:
Assembly assm = Assembly.GetExecutingAssembly();
Stream s = assm.GetManifestResourceStream("工程名.Resources." + fileName);
此处的fileName是包括扩展名的。
3.5. 单元测试
首先写单元测试函数,在class上面写 [TestClass],测试函数上写[TestMethod],通过Assert.xxx断言。
然后在视图菜单中打开“测试资源管理器”,一定要选中那个烧瓶形状的图标,然后运行所有测试。单元测试做完之后,建议关闭“测试资源管理器”,它会占用一部分资源。
3.6. setup工程配置
.net自带的打包工具很别扭,开发了这么多年也没有提升。所以,项目使用inno setup制作安装文件,其中要包括release目录下的dll。在使用webview2的情况下,需要包括runtimes\win-x64\native\WebView2Loader.dll。
如果用打包成中文,在添加ChineseSimplified.isl时,需要转为utf8-with-BOM格式(可以用notepad++修改),否则界面会显示乱码;如果还需要指定license等文件,也同样要改成utf8-with-BOM格式。
3.7. 混淆
使用.Net reactor,选中release下的主程序exe,然后选中obfuscation,对程序进行混淆。混淆之后再用inno settup生成安装包。
4. Hybrid
4.1. 框架
使用vue+vue-router+quasar开发,在浏览器中输出界面,调用底层的接口。
注意:vue要使用vue.global.prod.js版本,不能使用vue.runtime.global.prod.js。
可以从https://cdn.jsdelivr.net/npm/vue@next/dist/下载。
quasar从https://quasar.dev/start/umd下载,包括quasar.umd.prod.js与quasar.prod.css,这个连接中css可以与quasar.prod.css合并,其中用到的字体也需要逐个下载,放到本地,链接为:
https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons
4.2. 禁止选中文字
在游戏的场景中,经常要用到拖动,容易造成文字被选中,这时可以通过在css中增加user-select: none; 来禁止选中文字
4.3. 引用component
1) 引入component文件
import AlertDialog from "/assets/v3/components/alert_dialog.js"
2) 注册component
app.component('component-alert-dialog', AlertDialog);
注册要放在app.mount之前,否则调用component中方法会提示xxx is not a function
3) 在template中引入component
<component-alert-dialog :title="tags.failToCall" :close="tags.close" ref="errDlg"></component-alert-dialog>
4) 在js中调用component
this.$refs.errDlg.show(“xxxx”);
4.4. 生成二维码
因为不是在nodejs中开发,不能用import方式引入qrcodejs2,所以只能在index.html直接包含它:
<script src="/js路径/qrcode.js"></script>
然后,在template中增加一个div,用以容纳二维码,这里用的是相对宽度vw,所以在生成时要计算一下。
<div ref="qrCodeUrl" style="width:60vw; height:60vw;"></div>
最后,在需要显示时调用:
new QRCode(this.$refs.qrCodeUrl, {
text: 'https://www.baidu.com',
width: document.documentElement.clientWidth * 0.6,
height: document.documentElement.clientWidth * 0.6,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
如果用在dialog中,必须在dialog的@show中调用显示二维码,如果太早了,dialog的元素还没有创建,此时显示就会失败
5. 数据库
5.1 Sqlite算不算数据库
这个问题,我纠结了很久,以前在大厂工作时,跟人提议用sqlite解决一些问题,也是被不耻的。即在我使用了很长时间后,也不好意思跟别人说,我在服务器上是用sqlite代替mysql或其他大型数据库的:(
Sqlite算不算数据库呢,它只是一个单例的小型数据库,难上台面的感觉。但是,我觉得算,并且经常使用,因为在至简网格选择的奇葩而苛刻的场景中,必然不能用大型数据库。
首先从拷贝数量看,它绝对全球领先,因为每部智能机上必有多个拷贝,每个浏览器中必有一份拷贝,它们面临的场景也会千奇百怪。其次,从功能上说,基本的都有。我在其上添加基本的同步能力,在数据量不大的情况下(百万行级的样子),很合适。最后从性能上看,单机直接批量写入,7个字段,上万没问题,即使逐行插入,上千不成问题,查询上3万问题不大,这样的性能足可解决绝大部分问题了。
5.2 Sqlite并发写问题
首先Sqlite不支持并发写入,所以创建连接池时,只能创建一个可写连接;但是它可以支持并发读,所以可以创建多个读连接。如果不小心出现了两个写连接,并且是并发访问,就会出现吊死。所以在连接池维护上一定要小心。
6. 常识
6.1. 统一信用码编码规则及校验
Java实现:校验社会统一信用代码JAVA_王魂凤气的博客-CSDN博客_校验统一社会信用代码
6.2. 行政区划编号
https://www.mca.gov.cn/article/sj/xzqh/1980/202105/20210500033655.shtml
6.3. Git命令集
请参照这篇文章Git操作备忘_flyinmind的博客-CSDN博客