当前自己使用的服务器内存比较小,需要扩容,只是服务器比较老了,内存扩展困难,但是服务器的负载并不高。因此可以尝试使用内存压缩的方式来适当扩展内存。
安装
软件包封装好了对应的东西(甚至是systemctl服务),并不需要自己写脚本
1 |
$ sudo apt install zram-config |
重启之后,一个内存大小一半的 ZRAM 就出现了。
当前自己使用的服务器内存比较小,需要扩容,只是服务器比较老了,内存扩展困难,但是服务器的负载并不高。因此可以尝试使用内存压缩的方式来适当扩展内存。
软件包封装好了对应的东西(甚至是systemctl服务),并不需要自己写脚本
1 |
$ sudo apt install zram-config |
重启之后,一个内存大小一半的 ZRAM 就出现了。
鸿蒙手机配置网络代理,只能断开wifi连接,然后再重新连接时才能配置代理?是这样的吗?华为设备网络代理配置需要长按对应的wifi配置弹出,但鸿蒙手机中貌似不管用。
是的。当前确实需要断开WIFI再重新连接时配置代理。
鸿蒙的证书导入有什么推荐的方案吗?使用访问 chls.pro/ssl 的方式不会自动下载?下载证书后,使用「华为管家」,但 Mac 好像没有一个比较稳定的版本?
首先,Mac PC 端 Charles 导出证书,点击 Help -> SSL Proxying -> Save Charles Root Certificate
其次,导入系统根证书至手机,有两个方法。
方法一:启动证书安装器进行指定 pem 证书安装。
1.将 Charles 导出的 pem 文件 hdc file send 到手机存储器内。
2.启动动证书安装
1 |
$ hdc shell aa start -a MainAbility -b com.ohos.certmanager |
3.选择从存储设备安装,选择指定 pem 证书。
方法二:替换 CA 证书,证书路径为沙箱映射路径,系统预设 CA 证书位置:/etc/ssl/certs/cacert.pem,将 Charles 导出的 pem 文件 hdc file send 到此路径下(目前仅支持后缀名为 .pem 的文本格式证书)。
示例命令:
1 2 |
//重新挂载根目录为可写 $ hdc shell mount -o remount,rw / |
1 2 |
//导入根证书 $ hdc file send ./cacert.pem /etc/ssl/certs/ |
再次,安装 Charles 证书到 PC 系统可信目录。
点击 Help -> SSL Proxying -> Install Charles Root Certificate -> 安装证书 -> 选择证书存储路径为:受信任的根证书颁发机构。
最后,设置代理。
点击 Proxy -> SSL Proxy Settings -> 在 Include 添加 *:* 和 *:443
点击 Proxy -> Proxy Settings -> 勾选 Enable transparent HTTP proxying
注意:截止 2024/03/25 ,华为P60 升级到的鸿蒙 HarmonyOS NEXT,按照上述方式配置之后,依旧是无法通过 Charles / Reqable 进行中间人代理的。目前看到系统并没有使用我们刚刚导入的根证书。导致无法完成中间人抓包。
目前可以部分解决的问题方式是自己启动一个 Tomcat 服务器,然后配置客户端通过 HTTP 的方式进行报文的发送,从而可以看到上行报文内容。
鸿蒙的证书信任如何设置?设置中搜索一些配置关键字然后信任,但问题是,我的鸿蒙测试设备的设置中根本没有搜索……
将 Charles 导出的 pem 文件 hdc file send 到手机存储器内。
可以参考如下命令:
1 |
$ hdc file send charles.pem(pc上证书路径) /storage/media/100/local/files/Download/(工程机指定路径) |
注意:截止 2024/03/25 ,华为P60 升级到的鸿蒙 HarmonyOS NEXT,只能通过 hdc file send 发送到手机的临时目录 data/local/tmp/ 路径下,而证书凭证应用并不能找到这个目录,导致依旧无法导入证书。
目前可以解决的问题方式是通过U盘作为中介的方式完成文件的传输。
1. “设置” 页面选择 “隐私和安全”,如下图:
2. “隐私和安全” 页面滑动到最底部,选择 “高级”,如下图:
3. “高级” 页面,选择 “证书与凭据”,如下图:
4. “证书与凭据” 页面,选择 “从存储设备安装”,如下图:
5. “从存储设备安装” 页面,选择 “CA证书”,并在弹出的根证书安装警告对话框中选择 “继续”,如下图:
6. “从存储设备安装” 打开的文件选择器页面,找到下载的根证书,并选中,如下图:
7. 证书导入结果,如下图:
8. 再次点击 “CA证书”,可以查看已经导入的根证书,如下图:
安装必要的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# 安装 podman $ sudo apt install podman # 安装 git $ sudo apt install git # 创建一个低权限用户,尽量不要使用root用户,身份操作podman,降低安全风险 $ sudo adduser podman # 允许用户下的容器在系统启动的时候启动服务 $ sudo loginctl enable-linger podman # 切换到刚刚创建的低权限用户 ,注意,必须使用 su - user 的方式切换 # 如果使用 su user 切换会导致环境变量被带到新用户,导致执行报错 # ERRO[0000] XDG_RUNTIME_DIR directory "/run/user/1000" is not owned by the current user $ su - podman # 准备本地目录映射 $ mkdir ~/.dockers $ mkdir ~/.dockers/opengrok # 准备已经处理过的源代码,源代码需要 src、etc、data 三个目录,src存储源代码,其他目录保持空即可 # 参考 https://www.mobibrw.com/2019/19162 # 更详细的处理参考官方文档即可,后续我们以 Android 4.2.2 的源代码为例子 # Android 4.2.2 源代码存放在 ~/.dockers/opengrok/Android_4.2.2/src $ mkdir ~/.dockers/opengrok/Android_4.2.2/src $ mkdir ~/.dockers/opengrok/Android_4.2.2/etc $ mkdir ~/.dockers/opengrok/Android_4.2.2/data # 手工拉去部分镜像,或者可以在 /etc/containers/registries.conf 配置镜像服务器,可省略前面的服务器 docker.io $ podman pull docker.io/library/tomcat:10-jdk17 $ podman pull docker.io/library/tomcat:10.1-jdk17 |
官方镜像会在报错的时候暴露 Tomcat 10 版本号,错误堆栈,构成安全隐患,我们需要通过构建自定义镜像解决此问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 目前最新代码 commit ad9658443340a6dae425d94ab7cb0cc9d74fb37f $ git clone https://github.com/oracle/opengrok.git $ cd opengrok # 设置 maven 国内代理,否则下载速度太慢了 $ find . -type f -exec grep -l "repo.maven" {} \; | xargs sed -i "s/repo.maven.apache.org\/maven2\//mirrors.cloud.tencent.com\/nexus\/repository\/maven-public\//" # 设置 Maven Wrapper 国内源 $ sed -i "s/repo.maven.apache.org\/maven2\//mirrors.cloud.tencent.com\/nexus\/repository\/maven-public\//" .mvn/wrapper/maven-wrapper.properties # 代码中使用的 linux 系统镜像版本,我们需要明确告知 podman 从 docker.io 获取 $ sed -i "s/ubuntu:jammy/docker.io\/ubuntu:jammy/g" Dockerfile # 代码中使用的 Tomcat 镜像版本,不能通过 docker.io 获取到,我们直接使用通用 Tomcat 10 版本 $ sed -i "s/tomcat:10.1.19-jdk17/docker.io\/library\/tomcat:10.1-jdk17/g" Dockerfile $ sed -i "s/tomcat:10.1.30-jdk17/docker.io\/library\/tomcat:10.1-jdk17/g" Dockerfile |
修改后的完整内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# Copyright (c) 2018, 2021 Oracle and/or its affiliates. All rights reserved. # Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>. FROM docker.io/ubuntu:jammy AS build # hadolint ignore=DL3008 RUN apt-get update && apt-get install --no-install-recommends -y openjdk-17-jdk python3 python3-venv && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Create a first layer to cache the "Maven World" in the local repository. # Incremental docker builds will always resume after that, unless you update the pom WORKDIR /mvn COPY pom.xml /mvn/ COPY mvnw /mvn/ COPY .mvn /mvn/.mvn COPY opengrok-indexer/pom.xml /mvn/opengrok-indexer/ COPY opengrok-web/pom.xml /mvn/opengrok-web/ COPY plugins/pom.xml /mvn/plugins/ COPY suggester/pom.xml /mvn/suggester/ # distribution and tools do not have dependencies to cache RUN sed -i 's:<module>distribution</module>::g' /mvn/pom.xml && \ sed -i 's:<module>tools</module>::g' /mvn/pom.xml && \ mkdir -p /mvn/opengrok-indexer/target/jflex-sources && \ mkdir -p /mvn/opengrok-web/src/main/webapp/js && \ mkdir -p /mvn/opengrok-web/src/main/webapp/WEB-INF/ && \ touch /mvn/opengrok-web/src/main/webapp/WEB-INF/web.xml # dummy build to cache the dependencies RUN ./mvnw -DskipTests -Dcheckstyle.skip -Dmaven.antrun.skip package # build the project COPY ./ /opengrok-source WORKDIR /opengrok-source RUN /mvn/mvnw -DskipTests=true -Dmaven.javadoc.skip=true -B -V package # hadolint ignore=SC2012,DL4006 RUN cp `ls -t distribution/target/*.tar.gz | head -1` /opengrok.tar.gz # Store the version in a file so that the tools can report it. RUN /mvn/mvnw help:evaluate -Dexpression=project.version -q -DforceStdout > /mvn/VERSION FROM docker.io/library/tomcat:10.1-jdk17 LABEL maintainer="https://github.com/oracle/opengrok" LABEL org.opencontainers.image.source="https://github.com/oracle/opengrok" LABEL org.opencontainers.image.description="OpenGrok code search" # Add Perforce apt source. # hadolint ignore=DL3008,DL3009 RUN apt-get update && \ apt-get install --no-install-recommends -y gnupg2 SHELL ["/bin/bash", "-o", "pipefail", "-c"] # hadolint ignore=DL3059 RUN curl -sS https://package.perforce.com/perforce.pubkey | gpg --dearmor > /etc/apt/trusted.gpg.d/perforce.gpg # hadolint ignore=DL3059 RUN echo 'deb https://package.perforce.com/apt/ubuntu jammy release' > /etc/apt/sources.list.d/perforce.list # install dependencies and Python tools # hadolint ignore=DL3008,DL3009 RUN apt-get update && \ apt-get install --no-install-recommends -y git subversion mercurial cvs cssc bzr rcs rcs-blame \ unzip python3 python3-pip \ python3-venv python3-setuptools openssh-client libyaml-dev # hadolint ignore=DL3008,DL3059 RUN architecture=$(uname -m) && if [[ "$architecture" == "aarch64" ]]; then \ echo "aarch64: do not install helix-p4d."; else \ apt-get install --no-install-recommends -y helix-p4d || echo "Failed to install Perforce"; fi # compile and install universal-ctags # hadolint ignore=DL3003,DL3008 RUN apt-get install --no-install-recommends -y pkg-config automake build-essential && \ git clone https://github.com/universal-ctags/ctags /root/ctags && \ cd /root/ctags && ./autogen.sh && ./configure && make && make install && \ apt-get remove -y automake build-essential && \ apt-get -y autoremove && apt-get -y autoclean && \ cd /root && rm -rf /root/ctags && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # prepare OpenGrok binaries and directories # hadolint ignore=DL3010 COPY --from=build opengrok.tar.gz /opengrok.tar.gz # hadolint ignore=DL3013 RUN mkdir -p /opengrok /opengrok/etc /opengrok/data /opengrok/src && \ tar -zxvf /opengrok.tar.gz -C /opengrok --strip-components 1 && \ rm -f /opengrok.tar.gz && \ python3 -m venv /venv ENV PATH=/venv/bin:$PATH # 设置国内源,否则大概率不成功 RUN /venv/bin/python3 -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # Update the Python tooling in order to successfully install the opengrok-tools package. # hadolint ignore=DL3013 RUN /venv/bin/python3 -m pip install --no-cache-dir --upgrade pip setuptools RUN /venv/bin/python3 -m pip install --no-cache-dir /opengrok/tools/opengrok-tools.tar.gz && \ /venv/bin/python3 -m pip install --no-cache-dir Flask Flask-HTTPAuth waitress # for /reindex REST endpoint handled by start.py COPY --from=build /mvn/VERSION /opengrok/VERSION # environment variables ENV SRC_ROOT /opengrok/src ENV DATA_ROOT /opengrok/data ENV URL_ROOT / ENV CATALINA_HOME /usr/local/tomcat ENV CATALINA_BASE /usr/local/tomcat ENV CATALINA_TMPDIR /usr/local/tomcat/temp ENV PATH $CATALINA_HOME/bin:$PATH ENV CLASSPATH /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar ENV JAVA_OPTS="--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED \ --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ --add-opens=jdk.compiler/com.sun.tools.javac=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED \ --add-opens=java.base/java.util=ALL-UNNAMED" # disable all file logging COPY docker/logging.properties /usr/local/tomcat/conf/logging.properties RUN sed -i -e 's/Valve/Disabled/' /usr/local/tomcat/conf/server.xml # add our scripts and configuration COPY docker /scripts RUN chmod -R +x /scripts # 关闭报错详情,这个会导致安全风险 RUN sed -i "s/<\/Host>/ <Valve className='org.apache.catalina.valves.ErrorReportValve' showReport='false' showServerInfo='false' \/>\n\t<\/Host>/" /usr/local/tomcat/conf/server.xml # 配置AJP协议 # 如果只希望通过 AJP访问,可以参考如下命令 移除原有的 AJP 协议配置 # RUN xmlstarlet ed -L -P -S -d '/Server/Service/Connector' /usr/local/tomcat/conf/server.xml # 增加新的协议配置 RUN sed -i "s/<\/Service>/ <Connector port='8009' protocol='AJP\/1.3' address='0.0.0.0' redirectPort='8443' secretRequired=''\/>\n <\/Service>/" /usr/local/tomcat/conf/server.xml # run WORKDIR $CATALINA_HOME EXPOSE 8080 CMD ["/scripts/start.py"] |
构建镜像:
1 2 3 4 5 6 7 8 |
$ podman --cgroup-manager=cgroupfs build -t opengrok-dev . # 注意,如果 树莓派5 并且安装 Raspberry Pi OS Bookworm Arm64 系统, # 则执行上述命令的时候,可能会报错: # Failed to load native library:jansi-2.4.0-a842779cd7f2baa1-libjansi.so. osinfo: Linux/arm # java.lang.UnsatisfiedLinkError: /tmp/jansi-2.4.0-a842779cd7f2baa1-libjansi.so: /tmp/jansi-2.4.0-a842779cd7f2baa1-libjansi.so: Kann die Shared-Object-Datei nicht öffnen: Datei oder Verzeichnis nicht gefunden # 这个报错是由于 Raspberry Pi OS Bookworm Arm64 系统使用了 64位的内核,但是部分环境变量没有设置正确,导致 OSInfo.java # 判断错误,因此要么手工修改代码,要么干脆安装 32位的系统 |
设置容器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$ podman stop android-4.2.2 $ podman rm android-4.2.2 # 启动一个 tomcat 容器 8081 HTTP 访问端口 8010 AJP访问端口 # 注意,Docker 镜像会自动 每隔10分钟进行一次自动索引重建,不需要手动进行索引操作, # 如果需要进行反向代理,则 OPENGROK_WEBAPP_CONTEXT 环境变量是无效的, # Docker 需要使用 URL_ROOT 进行配置 # 详细配置信息参考源代码目录下的 docker/README.md # 配置 SYNC_PERIOD_MINUTES=0 禁止周期性重建索引,但是容器重启的时候仍然会自动重建 # 多个容器的情况下,需要限制容器使用的CPU、内存资源,否则会导致宿主机内存不足,这个需要特别注意 # 这里我们分配两个核心给容器,一般限制核心也可以限制并发的Java进程,从而间接可以限制内存占用 # 同时应该限制容器内的同步线程数量 "WORKERS=1",否则极易引起宿主机的OOM # podman run -d --cpus=2 --name opengrok-android-4.2.2 -p 8081:8080/tcp -p 8010:8009 -v ~/opengrok-src/:/opengrok/src/ -v ~/opengrok-etc/:/opengrok/etc/ -v ~/opengrok-data/:/opengrok/data/ -e "URL_ROOT=Android_4.2.2" -e "SYNC_PERIOD_MINUTES=0" -e "WORKERS=1" localhost/opengrok-dev $ podman run -d --cpus=2 --name opengrok-android-4.2.2 -p 8081:8080/tcp -p 8010:8009 -v /home/podman/.dockers/opengrok/Android_4.2.2/src:/opengrok/src/ -v /home/podman/.dockers/opengrok/Android_4.2.2/etc/:/opengrok/etc/ -v /home/podman/.dockers/opengrok/Android_4.2.2/data/:/opengrok/data/ -e "URL_ROOT=Android_4.2.2" -e "SYNC_PERIOD_MINUTES=0" -e "WORKERS=1" localhost/opengrok-dev # 查看该容器 $ podman ps # 如果需要进入容器查看执行情况,参考如下命令 # podman exec -it opengrok-android-4.2.2 bash # 查看日志 # podman logs -f -t opengrok-android-4.2.2 # 每次都启动新容器方式创建servcie //--new参数,每次启动都删除旧容器,启动一个新容器 $ podman generate systemd --restart-policy=always -n --new -f opengrok-android-4.2.2 |
查看启动文件:
1 |
$ cat container-opengrok-android-4.2.2.service |
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# container-opengrok-android-4.2.2.service # autogenerated by Podman 3.4.4 # Fri Mar 22 10:41:54 CST 2024 [Unit] Description=Podman container-opengrok-android-4.2.2.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=%t/containers [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace -d --cpus=2 --name opengrok-android-4.2.2 -p 8081:8080/tcp -p 8010:8009 -v /home/podman/.dockers/opengrok/Android_4.2.2/src:/opengrok/src/ -v /home/podman/.dockers/opengrok/Android_4.2.2/etc/:/opengrok/etc/ -v /home/podman/.dockers/opengrok/Android_4.2.2/data/:/opengrok/data/ -e "URL_ROOT=Android_4.2.2" -e "SYNC_PERIOD_MINUTES=0" -e "WORKERS=1" localhost/opengrok-dev ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all [Install] WantedBy=default.target |
需要额外注意的一个地方是,给出的路径必须是完整路径 “/home/podman/.dockers/opengrok/Android_4.2.2/src”,不能是 “~/.dockers/opengrok/Android_4.2.2/src”,Systemd不能正确展开 “~” ,导致路径找不到,从而在启动的时候失败,报告错误代码 125 。
另外,Android源代码目录下不能存在 .svn .git 隐藏子目,我们需要手工删除,否则会报错。参考 删除目录下所有的 .svn .git 隐藏子目
Systemd 配置,开机/重启自动启动服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# 保存到 ~/.config/systemd/user/ $ mkdir .config $ mkdir .config/systemd $ mkdir .config/systemd/user $ mv container-opengrok-android-4.2.2.service ~/.config/systemd/user/ # 以当前用户身份刷新配置文件,让其生效 $ systemctl --user daemon-reload # 设置容器开机自启,并且现在启动 $ systemctl --user enable --now ~/.config/systemd/user/container-opengrok-android-4.2.2.service # 如果需要进入容器查看执行情况,参考如下命令 # podman exec -it opengrok-android-4.2.2 bash # 查看日志 # podman logs -f -t opengrok-android-4.2.2 # 测试,重启系统,观察是否能开机自动启动 $ sudo reboot # 启动或重启服务 # systemctl --user start container-opengrok-android-4.2.2.service # systemctl --user restart container-opengrok-android-4.2.2.service # 如果启动失败,观察服务日志 # journalctl --user -xeu container-opengrok-android-4.2.2.service # 或者 # sudo journalctl -f |
On some systemd-based systems, non-root users do not have resource limit delegation permissions. This causes setting resource limits to fail.
Running a container with a resource limit options will fail with an error similar to the following:
--cpus
, --cpu-period
, --cpu-quota
, --cpu-shares
:
1 |
Error: OCI runtime error: crun: the requested cgroup controller `cpu` is not available |
--cpuset-cpus
, --cpuset-mems
:
1 |
Error: OCI runtime error: crun: the requested cgroup controller `cpuset` is not available |
This means that resource limit delegation is not enabled for the current user.
You can verify whether resource limit delegation is enabled by running the following command:
1 |
$ cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers" |
Example output might be:
1 |
memory pids |
In the above example, cpu
and cpuset
are not listed, which means the current user does not have permission to set CPU or CPUSET limits.
If you want to enable CPU or CPUSET limit delegation for all users, you can create the file /etc/systemd/system/user@.service.d/delegate.conf
with the contents:
1 2 |
[Service] Delegate=memory pids cpu cpuset |
After logging out and logging back in, you should have permission to set CPU and CPUSET limits.
安装必要的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 安装 podman $ sudo apt install podman # 创建一个低权限用户,尽量不要使用root用户,身份操作podman,降低安全风险 $ sudo adduser podman # 允许用户下的容器在系统启动的时候启动服务 $ sudo loginctl enable-linger podman # 退出当前用户,切换到刚刚创建的低权限用户, 注意,不要使用 su - user 的方式切换,后续会执行各种报错 # 如果使用 su user 切换会导致环境变量被带到新用户,导致执行报错 # ERRO[0000] XDG_RUNTIME_DIR directory "/run/user/1000" is not owned by the current user # Error running systemd as user - Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined $ exit # 通过 ssh 登录用户名 podman 密码是创建用户的时候设置的密码 # 准备本地目录映射 $ mkdir ~/.dockers/tomcat $ mkdir ~/.dockers/tomcat/webapps $ mkdir ~/.dockers/tomcat/logs |
官方镜像会在报错的时候暴露 Tomcat 10 版本号,错误堆栈,构成安全隐患,我们需要通过构建自定义镜像解决此问题:
1 |
$ touch Dockerfile |
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 详细启动日志在 /usr/local/tomcat/logs 目录下 From docker.io/library/tomcat:10 # xml 编辑工具,方便我们后续修改 tomcat 的xml配置文件 # RUN apt update && apt install -y xmlstarlet # 增加 <Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" /> 阻止错误日志输出 RUN sed -i "s/<\/Host>/ <Valve className='org.apache.catalina.valves.ErrorReportValve' showReport='false' showServerInfo='false' \/>\n\t<\/Host>/" /usr/local/tomcat/conf/server.xml # 配置AJP协议 # 如果只希望通过 AJP访问,可以参考如下命令 移除原有的 AJP 协议配置 # RUN xmlstarlet ed -L -P -S -d '/Server/Service/Connector' /usr/local/tomcat/conf/server.xml # 增加新的协议配置 RUN sed -i "s/<\/Service>/ <Connector port='8009' protocol='AJP\/1.3' address='0.0.0.0' redirectPort='8443' secretRequired=''\/>\n <\/Service>/" /usr/local/tomcat/conf/server.xml # 移除 xml 编辑工具 # RUN apt -y autoremove --purge xmlstarlet |
构建镜像:
1 |
$ podman --cgroup-manager=cgroupfs build -t tomcat-10 . |
设置容器开机自启:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ podman stop tomcat-10 $ podman rm tomcat-10 # 启动一个 tomcat 容器 8080 HTTP 访问端口 8009 AJP访问端口 # podman run -d --name tomcat-10 -p 8080:8080 -p 8009:8009 -v /my/local/path:/usr/local/tomcat/webapps localhost/tomcat-10 $ podman run -d --name tomcat-10 -p 8080:8080 -p 8009:8009 -v ~/.dockers/tomcat/webapps:/usr/local/tomcat/webapps -v ~/.dockers/tomcat/logs:/usr/local/tomcat/logs localhost/tomcat-10 # 查看该容器 $ podman ps # 如果需要进入容器查看执行情况,参考如下命令 # podman exec -it tomcat-10 bash # 每次都启动新容器方式创建servcie //--new参数,每次启动都删除旧容器,启动一个新容器 $ podman generate systemd --restart-policy=always -n --new -f tomcat-10 |
查看启动文件:
1 |
$ cat container-tomcat-10.service |
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# container-tomcat-10.service # autogenerated by Podman 3.4.4 # Sun Mar 10 12:31:31 CST 2024 [Unit] Description=Podman container-tomcat-10.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=%t/containers [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace -d --name tomcat-10 -p 8080:8080 -p 8009:8009 -v /home/podman/.dockers/tomcat/webapps:/usr/local/tomcat/webapps -v /home/podman/.dockers/tomcat/logs:/usr/local/tomcat/logs localhost/tomcat-10 ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all [Install] WantedBy=default.target |
需要额外注意的一个地方是,给出的路径必须是完整路径 “/home/podman/.dockers/tomcat/webapps”,不能是 “~/.dockers/tomcat/webapps”,Systemd不能正确展开 “~” ,导致路径找不到,从而在启动的时候失败,报告错误代码 125 。
Systemd 配置,开机/系统重启自动启动服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 保存到 ~/.config/systemd/user/ $ mkdir .config $ mkdir .config/systemd $ mkdir .config/systemd/user # 保存到/etc/systemd/system/ $ mv container-tomcat-10.service ~/.config/systemd/user/ # 以当前用户身份刷新配置文件,让其生效 $ systemctl --user daemon-reload # 设置容器开机自启,并且现在启动 $ systemctl --user enable --now ~/.config/systemd/user/container-tomcat-10.service # 如果需要进入容器查看执行情况,参考如下命令 # podman exec -it tomcat-10 bash # 测试,重启系统,观察是否能开机自动启动 $ sudo reboot |
后续 WAR 包存储到 ~/.dockers/tomcat/webapps 目录下即可进行正常访问。
基于struts的系统迁移到SpringMVC架构上来,共分六部曲,让系统一部一部迁移过来,本文讲的知识点以Struts2 to Spring4,但是针对其他应用场景也是可以参考的。
Firstly while migrating from struts to spring we have to replace our struts related libraries with spring libraries in lib folder.
I have mentioned basic libraries of both struts and spring for your clarification.
Have you ever use : Javadoc comment in Java
In this step we have to remove Action filter dispatcher for the web.xml and add Spring dipatcher servlet as Front controller
Work on new technology : Create and manage cloud applications using Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Struts2MyFirstApp</display-name> <filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.FilterDispatcher </filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>Login.jsp</welcome-file> </welcome-file-list> </web-app> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>dispatcher</display-name> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> |
Now replace all struts configuration files to spring configuration file as follows
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="false" /> <constant name="struts.devMode" value="false" /> <constant name="struts.custom.i18n.resources" value="myapp" /> <package name="default" extends="struts-default" namespace="/"> <action name="login" class="com.geekonjavaonjava.struts2.login.LoginAction"> <result name="success">Welcome.jsp</result> <result name="error">Login.jsp</result> </action> </package> </struts> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.geekonjavaonjava.spring.login.controller" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/views/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> </beans> |
Here, <context:component-scan> tag is used, so that spring will load all the components from given package i.e. " com.geekonjavaonjava.spring.login.controller".
Use this in Struts2 : Get value of struts property tag into jsp variable
We can use different view resolver, here I have used InternalResourceViewResolver. In which prefix and suffix are used to resolve the view by prefixing and suffixing values to the object returned by ModelAndView in action class.
While migration an application from struts to spring we need to change in jsp file as following
1 2 3 4 |
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <%@ taglib uri="http://struts.apache.org/tags-tiles" prefix="tiles" %> |
1 2 |
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> |
1 |
<html:form action="/addLogin" method="post"> |
1 |
<form:form method="POST" commandName="loginForm" name="loginForm" action="login.do"> |
Here commandName is going to map with corresponding formbean for that jsp. Next we will see, how action is getting called with spring 4 annotations.
Now following changes need to be done in action classes for struts to spring migration using annotations-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package com.geekonjavaonjava.struts2.login; import com.opensymphony.xwork2.ActionSupport; /** * @author geekonjava * */ @SuppressWarnings("serial") public class LoginAction extends ActionSupport{ private String username; private String password; public String execute() { if (this.username.equals("geekonjava") && this.password.equals("sweety")) { return "success"; } else { addActionError(getText("error.login")); return "error"; } } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.geekonjavaonjava.spring.login.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * @author GeekOnJava * */ @Controller public class LoginController { @RequestMapping(value="/login.do", method = RequestMethod.GET) public String doLogin(ModelMap model, LoginForm loginForn) { if (this.username.equals("geekonjava") && this.password.equals("sweety")) { model.addAttribute("message", "Login Success"); } else { model.addAttribute("message", "Login Failure"); } return "home"; } } |
1 |
<% ActionErrors actionErrors = (ActionErrors)request.getAttribute("org.apache.struts.action.ERROR"); %> |
1 |
<form:errors path="*" cssClass="error" /> |
一般使用setType()方法来实现文件过滤,如:只显示PDF文件:
1 2 3 4 5 |
int requestCode = 100; Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("application/pdf"); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, requestCode); |
但如果要指定多种类型呢,同时要指定pdf,excel,word,ppt这些类型的文件,那要怎样设置呢?
指定多种类型文件
在网上查了,有些答案是
错误方式1——setType中进行拼接:
1 |
intent.setType(“video/*;image/*”);//同时选择视频和图片 |
错误方式2——调用多次setType:
1 2 |
intent.setType("video/*"); intent.setType("image/*"); |
这两种方式都是错误的
我们看下源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/** * Set an explicit MIME data type. * * <p>This is used to create intents that only specify a type and not data, * for example to indicate the type of data to return. * * <p>This method automatically clears any data that was * previously set (for example by {@link #setData}). * * <p><em>Note: MIME type matching in the Android framework is * case-sensitive, unlike formal RFC MIME types. As a result, * you should always write your MIME types with lower case letters, * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize} * to ensure that it is converted to lower case.</em> * * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getType * @see #setTypeAndNormalize * @see #setDataAndType * @see #normalizeMimeType */ public Intent setType(String type) { mData = null; mType = type; return this; } |
我们可以看到,setType每次都是重新赋值,没有添加到list和数组中,因此这两种方式是实现不了指定多种类型文件的。
既然这种方式实现不了,那么Intent会不会提供字段以便我们传递过滤数据,我们通过官方文档及源码,发现Intent提供了EXTRA_MIME_TYPES这个字段来传递,而且是数组类型:
1 2 3 4 5 6 7 8 9 10 |
/** * Extra used to communicate a set of acceptable MIME types. The type of the * extra is {@code String[]}. Values may be a combination of concrete MIME * types (such as "image/png") and/or partial MIME types (such as * "audio/*"). * * @see #ACTION_GET_CONTENT * @see #ACTION_OPEN_DOCUMENT */ public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; |
因此结果就简单了,我们要指定ppt,word,excel,pdf类型的文件,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static void openDirChooseFile(Activity activity, int requestCode, String[] mimeTypes) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); if (mimeTypes != null) { intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); } intent.setType("*/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); // intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);//多选 activity.startActivityForResult(intent, requestCode); } public static void chooseFile(Activity activity, int requestCode) { String[] mimeTypes = {MimeType.DOC, MimeType.DOCX, MimeType.PDF, MimeType.PPT, MimeType.PPTX, MimeType.XLS, MimeType.XLSX}; FileUtil.openDirChooseFile(activity, requestCode, mimeTypes); } |
MimeType文件
1 2 3 4 5 6 7 8 9 |
public class MimeType { public static final String DOC = "application/msword"; public static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; public static final String XLS = "application/vnd.ms-excel application/x-excel"; public static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; public static final String PPT = "application/vnd.ms-powerpoint"; public static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; public static final String PDF = "application/pdf"; } |
Intent指定多种类型的文件,正确的做法,是通过Intent.EXTRA_MIME_TYPES传递Mime类型数组实现
1 2 |
intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); |
MimeType 文件列表参考
1. Mozilla MDN Web Docs: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
扩展名 | 文档类型 | MIME 类型 |
---|---|---|
.aac | AAC audio | audio/aac |
.abw | AbiWord document | application/x-abiword |
.arc | Archive document (multiple files embedded) | application/x-freearc |
.avi | AVI: Audio Video Interleave | video/x-msvideo |
.azw | Amazon Kindle eBook format | application/vnd.amazon.ebook |
.bin | Any kind of binary data | application/octet-stream |
.bmp | Windows OS/2 Bitmap Graphics | image/bmp |
.bz | BZip archive | application/x-bzip |
.bz2 | BZip2 archive | application/x-bzip2 |
.csh | C-Shell script | application/x-csh |
.css | Cascading Style Sheets (CSS) | text/css |
.csv | Comma-separated values (CSV) | text/csv |
.doc | Microsoft Word | application/msword |
.docx | Microsoft Word (OpenXML) | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
.eot | MS Embedded OpenType fonts | application/vnd.ms-fontobject |
.epub | Electronic publication (EPUB) | application/epub+zip |
.gif | Graphics Interchange Format (GIF) | image/gif |
.htm .html |
HyperText Markup Language (HTML) | text/html |
.ico | Icon format | image/vnd.microsoft.icon |
.ics | iCalendar format | text/calendar |
.jar | Java Archive (JAR) | application/java-archive |
.jpeg .jpg |
JPEG images | image/jpeg |
.js | JavaScript | text/javascript |
.json | JSON format | application/json |
.jsonld | JSON-LD format | application/ld+json |
.mid .midi |
Musical Instrument Digital Interface (MIDI) | audio/midi audio/x-midi |
.mjs | JavaScript module | text/javascript |
.mp3 | MP3 audio | audio/mpeg |
.mpeg | MPEG Video | video/mpeg |
.mpkg | Apple Installer Package | application/vnd.apple.installer+xml |
.odp | OpenDocument presentation document | application/vnd.oasis.opendocument.presentation |
.ods | OpenDocument spreadsheet document | application/vnd.oasis.opendocument.spreadsheet |
.odt | OpenDocument text document | application/vnd.oasis.opendocument.text |
.oga | OGG audio | audio/ogg |
.ogv | OGG video | video/ogg |
.ogx | OGG | application/ogg |
.otf | OpenType font | font/otf |
.png | Portable Network Graphics | image/png |
Adobe Portable Document Format (PDF) | application/pdf | |
.ppt | Microsoft PowerPoint | application/vnd.ms-powerpoint |
.pptx | Microsoft PowerPoint (OpenXML) | application/vnd.openxmlformats-officedocument.presentationml.presentation |
.rar | RAR archive | application/x-rar-compressed |
.rtf | Rich Text Format (RTF) | application/rtf |
.sh | Bourne shell script | application/x-sh |
.svg | Scalable Vector Graphics (SVG) | image/svg+xml |
.swf | Small web format (SWF) or Adobe Flash document | application/x-shockwave-flash |
.tar | Tape Archive (TAR) | application/x-tar |
.tif .tiff |
Tagged Image File Format (TIFF) | image/tiff |
.ttf | TrueType Font | font/ttf |
.txt | Text, (generally ASCII or ISO 8859-n) | text/plain |
.vsd | Microsoft Visio | application/vnd.visio |
.wav | Waveform Audio Format | audio/wav |
.weba | WEBM audio | audio/webm |
.webm | WEBM video | video/webm |
.webp | WEBP image | image/webp |
.woff | Web Open Font Format (WOFF) | font/woff |
.woff2 | Web Open Font Format (WOFF) | font/woff2 |
.xhtml | XHTML | application/xhtml+xml |
.xls | Microsoft Excel | application/vnd.ms-excel |
.xlsx | Microsoft Excel (OpenXML) | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
.xml | XML | application/xml 代码对普通用户来说不可读 (RFC 3023, section 3) text/xml 代码对普通用户来说可读 (RFC 3023, section 3) |
.xul | XUL | application/vnd.mozilla.xul+xml |
.zip | ZIP archive | application/zip |
.3gp | 3GPP audio/video container | video/3gpp audio/3gpp(若不含视频) |
.3g2 | 3GPP2 audio/video container | video/3gpp2 audio/3gpp2(若不含视频) |
.7z | 7-zip archive | application/x-7z-compressed |
2. AOSP MediaFle:
1 2 3 4 |
public static String getMimeTypeForFile(String path) { MediaFileType mediaFileType = getFileType(path); return (mediaFileType == null ? null : mediaFileType.mimeType); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
static { /// M: Add more audio file types to maps. {@ addFileType("3GP", FILE_TYPE_3GPP3, "audio/3gpp"); addFileType("3GA", FILE_TYPE_3GA, "audio/3gpp"); addFileType("MOV", FILE_TYPE_QUICKTIME_AUDIO, "audio/quicktime"); addFileType("QT", FILE_TYPE_QUICKTIME_AUDIO, "audio/quicktime"); /// Add to support Apple Lossless Codec(audio/alac) addFileType("CAF", FILE_TYPE_CAF, "audio/alac"); /// Add to support PCM(audio/wav) addFileType("WAV", FILE_TYPE_WAV, "audio/wav", MtpConstants.FORMAT_WAV); addFileType("OGG", FILE_TYPE_OGG, "audio/vorbis", MtpConstants.FORMAT_OGG); addFileType("OGG", FILE_TYPE_OGG, "audio/webm", MtpConstants.FORMAT_OGG); /// Add to support MP2, first add video/mp2p, so that use MP2 can return as audio type addFileType("MP2", FILE_TYPE_MP2PS, "video/mp2p"); addFileType("MP2", FILE_TYPE_MP2, "audio/mpeg"); /// Add to support Monkey's Audio APE(audio/ape) if (SystemProperties.getBoolean("ro.mtk_audio_ape_support", false)) { addFileType("APE", FILE_TYPE_APE, "audio/ape"); } /// Add to support OMA DRM audio type DCF if (SystemProperties.getBoolean("ro.mtk_oma_drm_support", false)) { addFileType("DCF", FILE_TYPE_MP3, "audio/mpeg"); } /// @} addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", MtpConstants.FORMAT_MP3); addFileType("MPGA", FILE_TYPE_MP3, "audio/mpeg", MtpConstants.FORMAT_MP3); addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", MtpConstants.FORMAT_MPEG); addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", MtpConstants.FORMAT_WAV); addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); if (isWMAEnabled()) { addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", MtpConstants.FORMAT_WMA); } addFileType("OGG", FILE_TYPE_OGG, "audio/ogg", MtpConstants.FORMAT_OGG); addFileType("OGG", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG); addFileType("OGA", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG); addFileType("AAC", FILE_TYPE_AAC, "audio/aac", MtpConstants.FORMAT_AAC); addFileType("AAC", FILE_TYPE_AAC, "audio/aac-adts", MtpConstants.FORMAT_AAC); addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); addFileType("MID", FILE_TYPE_MID, "audio/midi"); addFileType("MIDI", FILE_TYPE_MID, "audio/midi"); addFileType("XMF", FILE_TYPE_MID, "audio/midi"); addFileType("RTTTL", FILE_TYPE_MID, "audio/midi"); addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi"); addFileType("IMY", FILE_TYPE_IMY, "audio/imelody"); addFileType("RTX", FILE_TYPE_MID, "audio/midi"); addFileType("OTA", FILE_TYPE_MID, "audio/midi"); addFileType("MXMF", FILE_TYPE_MID, "audio/midi"); /// M: Add more video file types to maps. {@ addFileType("MTS", FILE_TYPE_MP2TS, "video/mp2ts"); addFileType("M2TS", FILE_TYPE_MP2TS, "video/mp2ts"); addFileType("MOV", FILE_TYPE_QUICKTIME_VIDEO, "video/quicktime"); addFileType("QT", FILE_TYPE_QUICKTIME_VIDEO, "video/quicktime"); addFileType("OGV", FILE_TYPE_OGM, "video/ogm"); addFileType("OGM", FILE_TYPE_OGM, "video/ogm"); if (SystemProperties.getBoolean("ro.mtk_flv_playback_support", false)) { addFileType("FLV", FILE_TYPE_FLV, "video/x-flv"); addFileType("F4V", FILE_TYPE_FLV, "video/x-flv"); addFileType("PFV", FILE_TYPE_FLV, "video/x-flv"); addFileType("FLA", FILE_TYPE_FLA, "audio/x-flv"); } if (SystemProperties.getBoolean("ro.mtk_mtkps_playback_support", false)) { addFileType("PS", FILE_TYPE_MP2PS, "video/mp2p"); /// Only support VOB when mtkps feature option is enabled addFileType("VOB", FILE_TYPE_MP2PS, "video/mp2p"); /// DAT files should not be scanned as mpeg2 PS if PS is not supported. addFileType("DAT", FILE_TYPE_MP2PS, "video/mp2p"); } /// @} addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg", MtpConstants.FORMAT_MPEG); addFileType("MPG", FILE_TYPE_MP4, "video/mpeg", MtpConstants.FORMAT_MPEG); addFileType("MP4", FILE_TYPE_MP4, "video/mp4", MtpConstants.FORMAT_MPEG); addFileType("M4V", FILE_TYPE_M4V, "video/mp4", MtpConstants.FORMAT_MPEG); addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp", MtpConstants.FORMAT_3GP_CONTAINER); addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", MtpConstants.FORMAT_3GP_CONTAINER); addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.FORMAT_3GP_CONTAINER); addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.FORMAT_3GP_CONTAINER); addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); addFileType("WEBM", FILE_TYPE_WEBM, "video/webm"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); addFileType("AVI", FILE_TYPE_AVI, "video/avi"); if (isWMVEnabled()) { addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV); addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); } /// M: Add more image file types to maps. {@ if (!SystemProperties.getBoolean("ro.mtk_bsp_package", false)) { /// Mpo files should not be scanned as images in BSP addFileType("MPO", FILE_TYPE_MPO, "image/mpo"); } /// @} addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG); addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG); addFileType("GIF", FILE_TYPE_GIF, "image/gif", MtpConstants.FORMAT_GIF); addFileType("PNG", FILE_TYPE_PNG, "image/png", MtpConstants.FORMAT_PNG); addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", MtpConstants.FORMAT_BMP); addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp"); addFileType("WEBP", FILE_TYPE_WEBP, "image/webp"); addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST); addFileType("M3U", FILE_TYPE_M3U, "application/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST); addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", MtpConstants.FORMAT_PLS_PLAYLIST); addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", MtpConstants.FORMAT_WPL_PLAYLIST); addFileType("M3U8", FILE_TYPE_HTTPLIVE, "application/vnd.apple.mpegurl"); addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/mpegurl"); addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/x-mpegurl"); addFileType("FL", FILE_TYPE_FL, "application/x-android-drm-fl"); addFileType("TXT", FILE_TYPE_TEXT, "text/plain", MtpConstants.FORMAT_TEXT); addFileType("HTM", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML); addFileType("HTML", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML); addFileType("PDF", FILE_TYPE_PDF, "application/pdf"); addFileType("DOC", FILE_TYPE_MS_WORD, "application/msword", MtpConstants.FORMAT_MS_WORD_DOCUMENT); addFileType("XLS", FILE_TYPE_MS_EXCEL, "application/vnd.ms-excel", MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET); addFileType("PPT", FILE_TYPE_MS_POWERPOINT, "application/vnd.ms-powerpoint", MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION); addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac", MtpConstants.FORMAT_FLAC); addFileType("ZIP", FILE_TYPE_ZIP, "application/zip"); addFileType("MPG", FILE_TYPE_MP2PS, "video/mp2p"); addFileType("MPEG", FILE_TYPE_MP2PS, "video/mp2p"); /// M: Add more Other popular file types to maps. {@ addFileType("ICS", FILE_TYPE_ICS, "text/calendar"); addFileType("ICZ", FILE_TYPE_ICZ, "text/calendar"); addFileType("VCF", FILE_TYPE_VCF, "text/x-vcard"); addFileType("VCS", FILE_TYPE_VCS, "text/x-vcalendar"); addFileType("APK", FILE_TYPE_APK, "application/vnd.android.package-archive"); addFileType("DOCX", FILE_TYPE_MS_WORD, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); addFileType("DOTX", FILE_TYPE_MS_WORD, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"); addFileType("XLSX", FILE_TYPE_MS_EXCEL, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); addFileType("XLTX", FILE_TYPE_MS_EXCEL, "application/vnd.openxmlformats-officedocument.spreadsheetml.template"); addFileType("PPTX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.presentation"); addFileType("POTX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.template"); addFileType("PPSX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"); /// @} } |
根据不同的MIME类型,显示不同的图标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * This method gets the drawable id based on the mimetype * * @param mimeType the mimeType of a file/folder * @return the drawable icon id based on the mimetype */ public static int getDrawableId(Context context, String mimeType) { if (TextUtils.isEmpty(mimeType)) { return R.drawable.fm_unknown; } else if (mimeType.startsWith("application/vnd.android.package-archive")) { return R.drawable.fm_apk; } else if (mimeType.startsWith("application/zip")) { return R.drawable.fm_zip; } else if (mimeType.startsWith("application/ogg")) { return R.drawable.fm_audio; } else if (mimeType.startsWith("audio/")) { return R.drawable.fm_audio; } else if (mimeType.startsWith("image/")) { return R.drawable.fm_picture; } else if (mimeType.startsWith("text/")) { return R.drawable.fm_txt; } else if (mimeType.startsWith("video/")) { return R.drawable.fm_video; } return getCustomDrawableId(context, mimeType); } |
安装必要的依赖:
1 2 3 4 5 6 7 8 9 |
# 安装 podman $ sudo apt install podman # 准备本地目录映射 $ mkdir /home/data/.tomcat $ mkdir /home/data/.tomcat/webapps $ mkdir /home/data/.tomcat/logs |
官方镜像会在报错的时候暴露 Tomcat 9 版本号,错误堆栈,构成安全隐患,我们需要通过构建自定义镜像解决此问题:
1 |
$ touch Dockerfile |
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 详细启动日志在 /usr/local/tomcat/logs 目录下 From docker.io/library/tomcat:9 # xml 编辑工具,方便我们后续修改 tomcat 的xml配置文件 # RUN apt update && apt install -y xmlstarlet # 增加 <Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" /> 阻止错误日志输出 RUN sed -i "s/<\/Host>/ <Valve className='org.apache.catalina.valves.ErrorReportValve' showReport='false' showServerInfo='false' \/>\n\t<\/Host>/" /usr/local/tomcat/conf/server.xml # 配置AJP协议 # 如果只希望通过 AJP访问,可以参考如下命令 移除原有的 AJP 协议配置 # RUN xmlstarlet ed -L -P -S -d '/Server/Service/Connector' /usr/local/tomcat/conf/server.xml # 增加新的协议配置 RUN sed -i "s/<\/Service>/ <Connector port='8009' protocol='AJP\/1.3' address='0.0.0.0' redirectPort='8443' secretRequired=''\/>\n <\/Service>/" /usr/local/tomcat/conf/server.xml # 移除 xml 编辑工具 # RUN apt -y autoremove --purge xmlstarlet |
构建镜像:
1 |
$ sudo podman build -t tomcat-9 . |
设置容器开机自启:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ sudo podman stop tomcat-9 $ sudo podman rm tomcat-9 # 启动一个 tomcat 容器 8080 HTTP 访问端口 8009 AJP访问端口 # sudo podman run -d --name tomcat-9 -p 8080:8080 -p 8009:8009 -v /my/local/path:/usr/local/tomcat/webapps localhost/tomcat-9 $ sudo podman run -d --name tomcat-9 -p 8080:8080 -p 8009:8009 -v /home/data/.tomcat/webapps:/usr/local/tomcat/webapps -v /home/data/.tomcat/logs:/usr/local/tomcat/logs localhost/tomcat-9 # 查看该容器 $ sudo podman ps # 如果需要进入容器查看执行情况,参考如下命令 # sudo podman exec -it tomcat-9 bash # 每次都启动新容器方式创建servcie //--new参数,每次启动都删除旧容器,启动一个新容器 $ sudo podman generate systemd --restart-policy=always -n --new -f tomcat-9 |
查看启动文件:
1 |
$ cat container-tomcat-9.service |
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# container-tomcat-9.service # autogenerated by Podman 3.4.4 # Sun Mar 10 12:31:31 CST 2024 [Unit] Description=Podman container-tomcat-9.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=%t/containers [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace -d --name tomcat-9 -p 8080:8080 -p 8009:8009 -v /home/data/.tomcat/webapps:/usr/local/tomcat/webapps -v /home/data/.tomcat/logs:/usr/local/tomcat/logs localhost/tomcat-9 ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all [Install] WantedBy=default.target |
Systemd 配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 保存到/etc/systemd/system/ $ sudo mv container-tomcat-9.service /etc/systemd/system/ # 刷新配置文件,让其生效 $ sudo systemctl daemon-reload # 设置容器开机自启,并且现在启动 $ sudo systemctl enable --now /etc/systemd/system/container-tomcat-9.service # 如果需要进入容器查看执行情况,参考如下命令 # sudo podman exec -it tomcat-9 bash # 测试,重启虚拟机 $ sudo reboot # 启动或重启服务 # systemctl --user start container-tomcat-9.service # systemctl --user restart container-tomcat-9.service # 如果启动失败,观察服务日志 # sudo journalctl -f |
后续 WAR 包存储到 /home/data/.tomcat/webapps 目录下即可进行正常访问。
macOS Sonoma Final | Version | Build | App | Avail | Date |
InstallAssistant.pkg | 14.3.1 | 23D60 | YES | 2/08 | |
InstallAssistant.pkg | 14.3 | 23D56 | YES | 1/22 | |
InstallAssistant.pkg | 14.2.1 | 23C71 | YES | 12/19 | |
InstallAssistant.pkg | 14.2 | 23C64 | YES | 12/11 | |
InstallAssistant.pkg | 14.1.2 | 23B92 | YES | 11/30 | |
InstallAssistant.pkg | 14.1.2 | 23B2091 | M3 only | YES | 11/30 |
^ For M3 Macs ONLY | |||||
InstallAssistant.pkg | 14.1.1 | 23B81 | 19.1.02 | YES | 11/07 |
InstallAssistant.pkg | 14.1.1 | 23B2082 | M3 only | YES | 11/07 |
^ For M3 Macs ONLY | |||||
InstallAssistant.pkg | 14.1 | 23B74 | 19.1.01 | YES | 10/25 |
InstallAssistant.pkg | 14.1 | 23B2077 | M3 only | YES | 11/01 |
^ For M3 Macs ONLY | |||||
InstallAssistant.pkg | 14.0 | 22A344 | 19.0.02 | YES | 9/26 |
几经搜索,发现了群晖的Diagnosis Tool工具,里面有78个工具,其中就包括iperf3,方法很简单。
1 |
$ sudo -i |
1 |
$ sudo synogear install |
1 |
$ /var/packages/DiagnosisTool/target/tool/iperf3 -c 192.168.188.1 |
如果不想使用synogear了, 一个命令就可移除:
1 |
$ sudo synogear remove |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
/var/packages/DiagnosisTool/target/tool/addr2name /var/packages/DiagnosisTool/target/tool/arping /var/packages/DiagnosisTool/target/tool/ash /var/packages/DiagnosisTool/target/tool/bash /var/packages/DiagnosisTool/target/tool/cifsiostat /var/packages/DiagnosisTool/target/tool/clockdiff /var/packages/DiagnosisTool/target/tool/dig /var/packages/DiagnosisTool/target/tool/domain_test.sh /var/packages/DiagnosisTool/target/tool/file /var/packages/DiagnosisTool/target/tool/fix_idmap.sh /var/packages/DiagnosisTool/target/tool/free /var/packages/DiagnosisTool/target/tool/gcore /var/packages/DiagnosisTool/target/tool/gdb /var/packages/DiagnosisTool/target/tool/gdbserver /var/packages/DiagnosisTool/target/tool/iftop /var/packages/DiagnosisTool/target/tool/iostat /var/packages/DiagnosisTool/target/tool/iotop /var/packages/DiagnosisTool/target/tool/iperf /var/packages/DiagnosisTool/target/tool/iperf3 /var/packages/DiagnosisTool/target/tool/kill /var/packages/DiagnosisTool/target/tool/killall /var/packages/DiagnosisTool/target/tool/ldd /var/packages/DiagnosisTool/target/tool/lsof /var/packages/DiagnosisTool/target/tool/ltrace /var/packages/DiagnosisTool/target/tool/mpstat /var/packages/DiagnosisTool/target/tool/name2addr /var/packages/DiagnosisTool/target/tool/ncat /var/packages/DiagnosisTool/target/tool/ndisc6 /var/packages/DiagnosisTool/target/tool/nethogs /var/packages/DiagnosisTool/target/tool/nfsiostat-sysstat /var/packages/DiagnosisTool/target/tool/nmap /var/packages/DiagnosisTool/target/tool/nping /var/packages/DiagnosisTool/target/tool/nslookup /var/packages/DiagnosisTool/target/tool/perf-check.py /var/packages/DiagnosisTool/target/tool/pgrep /var/packages/DiagnosisTool/target/tool/pidof /var/packages/DiagnosisTool/target/tool/pidstat /var/packages/DiagnosisTool/target/tool/ping6 /var/packages/DiagnosisTool/target/tool/ping /var/packages/DiagnosisTool/target/tool/pkill /var/packages/DiagnosisTool/target/tool/pmap /var/packages/DiagnosisTool/target/tool/ps /var/packages/DiagnosisTool/target/tool/pstree /var/packages/DiagnosisTool/target/tool/pwdx /var/packages/DiagnosisTool/target/tool/rarpd /var/packages/DiagnosisTool/target/tool/rdisc6 /var/packages/DiagnosisTool/target/tool/rdisc /var/packages/DiagnosisTool/target/tool/rltraceroute6 /var/packages/DiagnosisTool/target/tool/sa1 /var/packages/DiagnosisTool/target/tool/sa2 /var/packages/DiagnosisTool/target/tool/sadc /var/packages/DiagnosisTool/target/tool/sadf /var/packages/DiagnosisTool/target/tool/sar /var/packages/DiagnosisTool/target/tool/sh /var/packages/DiagnosisTool/target/tool/sid2ugid.sh /var/packages/DiagnosisTool/target/tool/slabtop /var/packages/DiagnosisTool/target/tool/sockstat /var/packages/DiagnosisTool/target/tool/speedtest-cli.py /var/packages/DiagnosisTool/target/tool/strace /var/packages/DiagnosisTool/target/tool/sysctl /var/packages/DiagnosisTool/target/tool/sysstat /var/packages/DiagnosisTool/target/tool/tcpdump /var/packages/DiagnosisTool/target/tool/tcpdump_wrapper /var/packages/DiagnosisTool/target/tool/tcpspray6 /var/packages/DiagnosisTool/target/tool/tcptraceroute6 /var/packages/DiagnosisTool/target/tool/telnet /var/packages/DiagnosisTool/target/tool/tload /var/packages/DiagnosisTool/target/tool/top /var/packages/DiagnosisTool/target/tool/tracepath /var/packages/DiagnosisTool/target/tool/traceroute6 /var/packages/DiagnosisTool/target/tool/tracert6 /var/packages/DiagnosisTool/target/tool/uptime /var/packages/DiagnosisTool/target/tool/vmstat /var/packages/DiagnosisTool/target/tool/w /var/packages/DiagnosisTool/target/tool/watch /var/packages/DiagnosisTool/target/tool/zblacklist /var/packages/DiagnosisTool/target/tool/zmap /var/packages/DiagnosisTool/target/tool/ztee |
java代码是依赖 BouncyCastle 类库,经修改此类库中的 SM2Engine 类的原码而来,用于支持 SM2 公钥加密算法,符合《GB/T 35276-2017: 信息安全技术 SM2密码算法使用规范》。
SM4 国标《GB/T 32907-2016 信息安全技术 SM4分组密码算法》。
可以使用 gmssl 工具进行交互测试(http://gmssl.org)
引入jar:
1 2 3 4 5 |
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.68</version> </dependency> |
加解密工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
import java.io.IOException; import java.math.BigInteger; import java.security.SecureRandom; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.math.ec.ECFieldElement; import org.bouncycastle.math.ec.ECMultiplier; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.Memoable; import org.bouncycastle.util.Pack; /** * 自义的 SM2 公钥加密、私钥解密引擎, 用于替换 BouncyCastle 中的 SM2Engine 的实现, * 可用于非 java 开发的系统之间交换数据时的公钥加密、私钥解密,完全符合 GM/T 以下两个标准: * <br/> * <li>《GM/T 0003.4-2012: SM2椭圆曲线公钥密钥算法:第4部分:公钥加密算法》</li> * <li>《GM/T 0009-2012: SM2密码算法使用规范》</li> * <br/> * <p/> * <li>1.加密密文默认为 C1||C3||C2, 输出内容为 ASN.1 编码。符合 《GM/T 0009-2012: SM2密码算法使用规范》 标准 * </li> * <li>2.加密密文设置为 C1||C2||C3,则输出内容不是 ASN.1 编码。与 《GM/T 0009-2012 ...》 标准不兼容。</li> * <br/> * 建议可以使用 GmSSL 工具进行交互测试,请参考 {@code http://gmssl.org} * <p> * SM2 public key encryption engine - based on https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02. * * * @author YangHongFeng * @since 2020/5/28 creation */ public class MySm2Engine { public enum Mode { C1C2C3, C1C3C2; } private final Digest digest; private final Mode mode; private boolean forEncryption; private ECKeyParameters ecKey; private ECDomainParameters ecParams; private int curveLength; private SecureRandom random; /*** * 默认采用国标:C1||C3||C2 */ public MySm2Engine() { this(new SM3Digest(), Mode.C1C3C2); } public MySm2Engine(Mode mode) { this(new SM3Digest(), mode); } public MySm2Engine(Digest digest) { this(digest, Mode.C1C2C3); } public MySm2Engine(Digest digest, Mode mode) { if (mode == null) { throw new IllegalArgumentException("mode cannot be NULL"); } this.digest = digest; this.mode = mode; } /** * 初始化 * @param forEncryption true-公钥加密, false-私钥解密 * @param param 密码参数,从中获取公或私钥、及椭圆曲线相关参数 */ public void init(boolean forEncryption, CipherParameters param) { this.forEncryption = forEncryption; if (forEncryption) { ParametersWithRandom rParam = (ParametersWithRandom) param; ecKey = (ECKeyParameters) rParam.getParameters(); ecParams = ecKey.getParameters(); ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH()); if (s.isInfinity()) { throw new IllegalArgumentException("invalid key: [h]Q at infinity"); } random = rParam.getRandom(); } else { ecKey = (ECKeyParameters) param; ecParams = ecKey.getParameters(); } curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; } /** * 进行加密、或解密 * @param in * @param inOff * @param inLen * @return * @throws InvalidCipherTextException */ public byte[] processBlock( byte[] in, int inOff, int inLen) throws IOException, InvalidCipherTextException { if (forEncryption) { return encrypt(in, inOff, inLen); } else { return decrypt(in, inOff, inLen); } } public int getOutputSize(int inputLen) { return (1 + 2 * curveLength) + inputLen + digest.getDigestSize(); } protected ECMultiplier createBasePointMultiplier() { return new FixedPointCombMultiplier(); } private byte[] encrypt(byte[] in, int inOff, int inLen) throws IOException { byte[] c2 = new byte[inLen]; System.arraycopy(in, inOff, c2, 0, c2.length); ECMultiplier multiplier = createBasePointMultiplier(); ECPoint c1P; ECPoint kPB; do { BigInteger k = nextK(); c1P = multiplier.multiply(ecParams.getG(), k).normalize(); // c1 = c1P.getEncoded(false); kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize(); kdf(digest, kPB, c2); } while (notEncrypted(c2, in, inOff)); byte[] c3 = new byte[digest.getDigestSize()]; addFieldElement(digest, kPB.getAffineXCoord()); digest.update(in, inOff, inLen); addFieldElement(digest, kPB.getAffineYCoord()); digest.doFinal(c3, 0); switch (mode) { case C1C3C2: // 2020/06/01 按国标组装为 ANS.1 编码 final ASN1EncodableVector vector = new ASN1EncodableVector(); vector.add(new ASN1Integer(c1P.getXCoord().toBigInteger())); vector.add(new ASN1Integer(c1P.getYCoord().toBigInteger())); vector.add(new DEROctetString(c3)); vector.add(new DEROctetString(c2)); return new DERSequence(vector).getEncoded(); default: byte[] c1 = c1P.getEncoded(false); return Arrays.concatenate(c1, c2, c3); } } private byte[] decrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException, IOException { ECPoint c1P; // = ecParams.getCurve().decodePoint(c1); byte[] inHash ; byte[] inCipherData; if (mode == Mode.C1C3C2) { // 2020/06/01 按国标 ANS.1 编码 进行解码 ASN1InputStream inputStream = new ASN1InputStream(in); ASN1Sequence seq = (ASN1Sequence) inputStream.readObject(); if (seq.size() != 4){ throw new InvalidCipherTextException("invalid cipher text"); } int index = 0; // C1 == XCoordinate 、YCoordinate BigInteger x = ((ASN1Integer) seq.getObjectAt(index ++)).getPositiveValue(); // YCoordinate BigInteger y = ((ASN1Integer) seq.getObjectAt(index ++)).getPositiveValue(); // XCoord 、YCoord ==> CEPoint (C1) c1P = ecParams.getCurve().createPoint(x, y); // HASH (C3) inHash = ((ASN1OctetString)seq.getObjectAt(index ++)).getOctets(); // CipherText (C2) inCipherData = ((ASN1OctetString)seq.getObjectAt(index)).getOctets(); } else { // C1 byte[] c1 = new byte[curveLength * 2 + 1]; System.arraycopy(in, inOff, c1, 0, c1.length); c1P = ecParams.getCurve().decodePoint(c1); // C2 == inCipherData int digestSize = this.digest.getDigestSize(); inCipherData = new byte[inLen - c1.length - digestSize]; System.arraycopy(in, inOff + c1.length, inCipherData, 0, inCipherData.length); // C3 == Hash inHash = new byte[digestSize]; System.arraycopy(in, inOff + c1.length + inCipherData.length, inHash, 0, inHash.length); } // 解密 ==> inCipherData; ECPoint s = c1P.multiply(ecParams.getH()); if (s.isInfinity()) { throw new InvalidCipherTextException("[h]C1 at infinity"); } c1P = c1P.multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize(); kdf(digest, c1P, inCipherData); // 动态计算已解密的明文的摘要并比较 byte[] cipherDigest = new byte[digest.getDigestSize()]; addFieldElement(digest, c1P.getAffineXCoord()); digest.update(inCipherData, 0, inCipherData.length); addFieldElement(digest, c1P.getAffineYCoord()); digest.doFinal(cipherDigest, 0); int check = 0; if (mode == Mode.C1C3C2) { for (int i = 0; i != cipherDigest.length; i++) { check |= cipherDigest[i] ^ inHash[i]; } } else { for (int i = 0; i != cipherDigest.length; i++) { check |= cipherDigest[i] ^ inHash[i]; } } // Arrays.fill(c1, (byte)0); Arrays.fill(cipherDigest, (byte)0); if (check != 0) { Arrays.fill(inCipherData, (byte)0); throw new InvalidCipherTextException("invalid cipher text"); } // return c2; return inCipherData; } private boolean notEncrypted(byte[] encData, byte[] in, int inOff) { for (int i = 0; i != encData.length; i++) { if (encData[i] != in[inOff + i]) { return false; } } return true; } private void kdf(Digest digest, ECPoint c1, byte[] encData) { int digestSize = digest.getDigestSize(); byte[] buf = new byte[Math.max(4, digestSize)]; int off = 0; Memoable memo = null; Memoable copy = null; if (digest instanceof Memoable) { addFieldElement(digest, c1.getAffineXCoord()); addFieldElement(digest, c1.getAffineYCoord()); memo = (Memoable) digest; copy = memo.copy(); } int ct = 0; while (off < encData.length) { if (memo != null) { memo.reset(copy); } else { addFieldElement(digest, c1.getAffineXCoord()); addFieldElement(digest, c1.getAffineYCoord()); } Pack.intToBigEndian(++ct, buf, 0); digest.update(buf, 0, 4); digest.doFinal(buf, 0); int xorLen = Math.min(digestSize, encData.length - off); xor(encData, buf, off, xorLen); off += xorLen; } } private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { for (int i = 0; i != dRemaining; i++) { data[dOff + i] ^= kdfOut[i]; } } private BigInteger nextK() { int qBitLength = ecParams.getN().bitLength(); BigInteger k; do { k = BigIntegers.createRandomBigInteger(qBitLength, random); } while (k.equals(BigIntegers.ZERO) || k.compareTo(ecParams.getN()) >= 0); return k; } private void addFieldElement(Digest digest, ECFieldElement v) { byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); digest.update(p, 0, p.length); } } |
工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.util.encoders.Hex; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; public class SM2Util { //椭圆曲线ECParameters ASN.1 结构 private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); //椭圆曲线公钥或私钥的基本域参数。 private static final ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); /** * 公钥字符串转换为 BCECPublicKey 公钥对象 * * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除) * @return BCECPublicKey SM2公钥对象 */ public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) { //截取64字节有效的SM2公钥(如果公钥首个字节为0x04) if (pubKeyHex.length() > 128) { pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128); } //将公钥拆分为x,y分量(各32字节) String stringX = pubKeyHex.substring(0, 64); String stringY = pubKeyHex.substring(stringX.length()); //将公钥x、y分量转换为BigInteger类型 BigInteger x = new BigInteger(stringX, 16); BigInteger y = new BigInteger(stringY, 16); //通过公钥x、y分量创建椭圆曲线公钥规范 ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters); //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签) return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION); } /** * 私钥字符串转换为 BCECPrivateKey 私钥对象 * * @param privateKeyHex: 32字节十六进制私钥字符串 * @return BCECPrivateKey:SM2私钥对象 */ public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) { //将十六进制私钥字符串转换为BigInteger对象 BigInteger d = new BigInteger(privateKeyHex, 16); //通过私钥和私钥域参数集创建椭圆曲线私钥规范 ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters); //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名) return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION); } /** * SM2加密算法 * * @param parameters 公钥参数 * @param data 明文数据 */ public static String encrypt(ECPublicKeyParameters parameters, String data) throws InvalidCipherTextException, IOException { MySm2Engine sm2Engine = new MySm2Engine(); sm2Engine.init(true, new ParametersWithRandom(parameters, new SecureRandom())); byte[] in = data.getBytes(StandardCharsets.UTF_8); return Hex.toHexString(sm2Engine.processBlock(in, 0, in.length)); } /** * SM2加密算法 * * @param publicKey 公钥 * @param data 明文数据 */ public static String encrypt(PublicKey publicKey, String data) throws InvalidCipherTextException, IOException { ECPublicKeyParameters ecPublicKeyParameters = null; if (publicKey instanceof BCECPublicKey) { BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey; ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN()); ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters); } return encrypt(ecPublicKeyParameters, data); } /** * SM2解密算法 * * @param privateKey 私钥 * @param cipherData 密文数据 */ public static String decrypt(PrivateKey privateKey, String cipherData) throws InvalidCipherTextException, IOException { byte[] cipherDataByte = Hex.decode(cipherData); BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey; ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN()); ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters); MySm2Engine sm2Engine = new MySm2Engine(); sm2Engine.init(false, ecPrivateKeyParameters); byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length); return new String(arrayOfBytes, StandardCharsets.UTF_8); } } |
测试例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.ECGenParameterSpec; public class sm2_demo { public static void main(String[] args) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidCipherTextException, IOException { final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1"); // 获取一个椭圆曲线类型的密钥对生成器 final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); // 使用SM2参数初始化生成器 kpg.initialize(sm2Spec); // 获取密钥对 KeyPair keyPair = kpg.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String m = "lzc_SM2_demo"; String data = SM2Util.encrypt(publicKey, m); System.out.println(data); String text = SM2Util.decrypt(privateKey, data); System.out.println(text); } } |