first commit
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 安装指南
|
||||
|
||||
#### 介绍
|
||||
本项目使用 Spring Boot 开发的,用于企业内网 APP 分发,解决下载限制,实名认证等繁琐过程。
|
||||
|
||||
#### 效果
|
||||
|
||||
样式与 fir 一致,直接扒的,未进行无用样式清理。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### 安装教程
|
||||
|
||||
项目使用 JAVA 开发,需要 JDK 1.8 运行环境,数据库使用的是 Mysql,需要安装 Mysql。JDK 安装直接找网上教程。
|
||||
|
||||
##### 数据库
|
||||
|
||||
> Mac 下安装 Mysql
|
||||
|
||||
```shell
|
||||
brew install mysql
|
||||
# 后台运行 mysql
|
||||
mysqld &
|
||||
# 登录 mysql
|
||||
mysql -u root -p
|
||||
```
|
||||
|
||||
> 建库
|
||||
|
||||
```shell
|
||||
# 创建库
|
||||
create database app_manager;
|
||||
```
|
||||
|
||||
##### HTTPS 证书
|
||||
|
||||
参考 [Spring Boot Https 证书](Spring_Boot_Https_证书.md) 创建证书,本项目使用的是 `pckcs12`,密码使用的是 `123456`,部署项目时证书需要自己创建。
|
||||
|
||||
##### 配置
|
||||
|
||||
[下载](bin/bin.zip),解压包。
|
||||
|
||||
> 配置 HTTPS
|
||||
|
||||
将上一步生成的 ca.crt 放入 `/static/crt/` 目录中,替换掉里面的 ca.crt,将上一步生成的 `server.pckcs12` 文件替换掉包中的原有文件。
|
||||
|
||||
如果生成的证书密码不是 `123456`,需要修改`/config/application.properties` 中的 `server.ssl.key-store-password`字段的值为自已设定的密码
|
||||
|
||||
> 修改域名
|
||||
|
||||
使用文本编辑器打开 `/config/application.properties`,将 `server.domain`字段修改为部署服务器的 IP 或域名。
|
||||
|
||||
#### 部署
|
||||
|
||||
本项目使用的是 80 和 443 端口,确保端口未被占用。
|
||||
|
||||
> 启动服务
|
||||
|
||||
```shell
|
||||
java -jar intranet_app_manager-1.0.0.jar
|
||||
```
|
||||
|
||||
服务启动后即可输入你的 IP 或域名来访问。
|
||||
|
||||
> 上传与安装
|
||||
|
||||
可以将 ipa 或 apk 拖入上传块中进行上传,上传完成后会在列表中展示。iOS 安装需要使用 https 协议,由于内网部署是用的自建证书,需要将 ca 添加到设备的信用列表中才可正常进行安装。
|
||||
|
||||
#### Jenkins 集成
|
||||
|
||||
集成会用上 Jenkins 展示 HTML,需要在 Jenkins 配置中打开 HTML 展示
|
||||
|
||||

|
||||
|
||||
> 上传脚本
|
||||
|
||||
```shell
|
||||
# 上传到APP管理平台
|
||||
result=$(curl -F "file=@$WORKSPACE/build/Ewt360_debug/Ewt360.ipa" http://172.16.241.203/app/upload)
|
||||
code_url=$(echo $result | sed 's/.*\(http.*\)",.*/\1/g')
|
||||
echo "code_url="$code_url > $WORKSPACE/code.txt
|
||||
```
|
||||
|
||||
> 注入变量
|
||||
|
||||
Properties File Path:`$WORKSPACE/code.txt`
|
||||
|
||||
> 展示二维码
|
||||
|
||||
Description: `<a href="${code_url}" target="_blank"><img src='${code_url}' height="160" width="160" /></a>`
|
||||
|
||||

|
||||
|
||||

|
||||
168
Spring_Boot_Https_证书.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Spring Boot Https 证书
|
||||
|
||||
## 创建目录和文件
|
||||
|
||||
```shell
|
||||
mkdir -p CA/{certs,crl,newcerts,private}
|
||||
touch CA/index.txt
|
||||
touch CA/certs.db
|
||||
touch openssl.cnf
|
||||
echo 00 > CA/serial
|
||||
```
|
||||
|
||||
## 设置配置
|
||||
|
||||
> openssl.cnf
|
||||
|
||||
```shell
|
||||
[ req ]
|
||||
distinguished_name=req_distinguished_name
|
||||
req_extensions=v3_req
|
||||
|
||||
[ req_distinguished_name ]
|
||||
countryName=Country Name (2 letter code)
|
||||
countryName_default=CN
|
||||
stateOrProvinceName=State or Province Name (full name)
|
||||
stateOrProvinceName_default=ZheJiang
|
||||
localityName=Locality Name (eg, city)
|
||||
localityName_default=HangZhou
|
||||
organizationalUnitName=Organizational Unit Name (eg, section)
|
||||
organizationalUnitName_default=Domain Control Validated
|
||||
commonName=Internet Widgits Ltd
|
||||
commonName_default=192.168.0.*
|
||||
commonName_max=64
|
||||
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[ alt_names ]
|
||||
DNS.1 = 192.168.0.110
|
||||
DNS.2 = 192.168.0.111
|
||||
# section for the "default_ca" option
|
||||
[ca]
|
||||
default_ca=my_ca_default
|
||||
|
||||
# default section for "ca" command options
|
||||
[my_ca_default]
|
||||
new_certs_dir=./CA/certs
|
||||
database=./CA/certs.db
|
||||
default_md = sha256
|
||||
policy=my_ca_policy
|
||||
serial = ./CA/serial
|
||||
default_days = 365
|
||||
|
||||
# section for DN field validation and order
|
||||
[my_ca_policy]
|
||||
commonName = supplied
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
localityName = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
emailAddress = optional
|
||||
```
|
||||
|
||||
**注意**
|
||||
|
||||
```shell
|
||||
[ alt_names ]
|
||||
DNS.1 = 192.168.0.110
|
||||
DNS.2 = 192.168.0.111
|
||||
```
|
||||
|
||||
这里配置需要部署的域名或 IP 地址列表。
|
||||
|
||||
|
||||
|
||||
## 创建 CA
|
||||
|
||||
### 生成ca.key并自签署
|
||||
|
||||
```shell
|
||||
openssl req -new -x509 -days 3650 -keyout ca.key -out ca.crt -config openssl.cnf
|
||||
```
|
||||
|
||||
## 创建服务器证书
|
||||
|
||||
### 生成server.key(名字不重要)
|
||||
|
||||
```shell
|
||||
openssl genrsa -out server.key 2048
|
||||
```
|
||||
|
||||
### 生成证书签名请求
|
||||
|
||||
```shell
|
||||
openssl req -new -key server.key -out server.csr -config openssl.cnf
|
||||
```
|
||||
|
||||
Common Name 这个写主要域名就好了(注意:这个域名也要在openssl.cnf的DNS.x里)
|
||||
|
||||
### 使用自签署的CA,签署server.scr
|
||||
|
||||
```shell
|
||||
openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -extensions v3_req -config openssl.cnf
|
||||
```
|
||||
|
||||
## 创建 Spring Boot 所需证书
|
||||
|
||||
### 导出 pckcs12格式
|
||||
|
||||
```shell
|
||||
openssl pkcs12 -export -in server.crt -inkey server.key -out server.pkcs12
|
||||
```
|
||||
|
||||
### 导出 jks 格式
|
||||
|
||||
```shell
|
||||
keytool -importkeystore -srckeystore server.pkcs12 -destkeystore server.jks -srcstoretype pkcs12
|
||||
```
|
||||
|
||||
## Spring Boot 配置
|
||||
|
||||
```properties
|
||||
# 证书
|
||||
server.port=443
|
||||
server.ssl.key-store=classpath:server.pkcs12
|
||||
server.ssl.key-store-password=123456
|
||||
server.ssl.key-store-type=PKCS12
|
||||
server.ssl.key-alias=1
|
||||
```
|
||||
|
||||
|
||||
|
||||
### SpringBootApplication
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public TomcatServletWebServerFactory servletContainer() {
|
||||
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
|
||||
@Override
|
||||
protected void postProcessContext(Context context) {
|
||||
SecurityConstraint constraint = new SecurityConstraint();
|
||||
constraint.setUserConstraint("CONFIDENTIAL");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPattern("/*");
|
||||
constraint.addCollection(collection);
|
||||
context.addConstraint(constraint);
|
||||
}
|
||||
};
|
||||
tomcat.addAdditionalTomcatConnectors(httpConnector());
|
||||
return tomcat;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Connector httpConnector() {
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
connector.setScheme("http");
|
||||
connector.setPort(9090);
|
||||
connector.setSecure(false);
|
||||
connector.setRedirectPort(8443);
|
||||
return connector;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
BIN
bin/bin.zip
Normal file
41
build.gradle
Normal file
@@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.1.6.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'org.yzr'
|
||||
version = '1.0.0'
|
||||
sourceCompatibility = '1.8'
|
||||
|
||||
configurations {
|
||||
developmentOnly
|
||||
runtimeClasspath {
|
||||
extendsFrom developmentOnly
|
||||
}
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
compile group: 'com.googlecode.plist', name: 'dd-plist', version: '1.21'
|
||||
compile group: 'net.dongliu', name: 'apk-parser', version: '2.6.9'
|
||||
compile group: 'net.glxn.qrgen', name: 'javase', version: '2.0'
|
||||
compile group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
compile group: 'com.jcraft', name: 'jzlib', version: '1.1.3'
|
||||
compile group: 'org.freemarker', name: 'freemarker', version: '2.3.28'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 28 17:18:31 CST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
172
gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
BIN
images/code.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
images/index.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
images/install.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
images/jenkins.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
images/list.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
images/shell.jpg
Normal file
|
After Width: | Height: | Size: 159 KiB |
6
settings.gradle
Normal file
@@ -0,0 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
rootProject.name = 'intranet_app_manager'
|
||||
BIN
src/main/.DS_Store
vendored
Normal file
BIN
src/main/java/.DS_Store
vendored
Normal file
BIN
src/main/java/org/.DS_Store
vendored
Normal file
BIN
src/main/java/org/yzr/.DS_Store
vendored
Normal file
57
src/main/java/org/yzr/Application.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package org.yzr;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@SpringBootApplication
|
||||
//@EnableJpaRepositories("org.yzr.dao") // JPA扫描该包路径下的Repositorie
|
||||
//@EntityScan("org.yzr.model") // 扫描Entity实体类
|
||||
public class Application {
|
||||
@Resource
|
||||
private Environment environment;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TomcatServletWebServerFactory servletContainer() {
|
||||
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
|
||||
@Override
|
||||
protected void postProcessContext(Context context) {
|
||||
SecurityConstraint constraint = new SecurityConstraint();
|
||||
constraint.setUserConstraint("CONFIDENTIAL");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPattern("/*");
|
||||
constraint.addCollection(collection);
|
||||
context.addConstraint(constraint);
|
||||
}
|
||||
};
|
||||
tomcat.addAdditionalTomcatConnectors(httpConnector());
|
||||
return tomcat;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Connector httpConnector() {
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
int httpPort = Integer.parseInt(environment.getProperty("server.http.port"));
|
||||
int httpsPort = Integer.parseInt(environment.getProperty("server.port"));
|
||||
connector.setScheme("http");
|
||||
connector.setPort(httpPort);
|
||||
connector.setSecure(true);
|
||||
connector.setRedirectPort(httpsPort);
|
||||
return connector;
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/org/yzr/config/WebAppConfigurer.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.yzr.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Configuration
|
||||
public class WebAppConfigurer extends WebMvcConfigurationSupport {
|
||||
|
||||
@Resource
|
||||
private Environment environment;
|
||||
@Override
|
||||
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
try {
|
||||
String path = ResourceUtils.getURL("").getPath();
|
||||
String prefix = ResourceUtils.FILE_URL_PREFIX + path;
|
||||
String debug = environment.getProperty("config.debug");
|
||||
if (debug !=null && debug.equals("debug")) {
|
||||
prefix = "classpath:/";
|
||||
}
|
||||
|
||||
registry.addResourceHandler("/android/**").addResourceLocations(prefix+ "static/upload/android/");
|
||||
registry.addResourceHandler("/ios/**").addResourceLocations(prefix + "static/upload/ios/");
|
||||
registry.addResourceHandler("/crt/**").addResourceLocations(prefix + "static/crt/");
|
||||
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
|
||||
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
|
||||
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
|
||||
super.addResourceHandlers(registry);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController( "/" ).setViewName( "forward:/apps" );
|
||||
super.addViewControllers(registry);
|
||||
}
|
||||
}
|
||||
61
src/main/java/org/yzr/controller/AppController.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.yzr.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.yzr.service.AppService;
|
||||
import org.yzr.utils.PathManager;
|
||||
import org.yzr.vo.AppViewModel;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
public class AppController {
|
||||
|
||||
@Resource
|
||||
private AppService appService;
|
||||
@Resource
|
||||
private PathManager pathManager;
|
||||
|
||||
@GetMapping("/apps")
|
||||
public String apps(HttpServletRequest request) {
|
||||
try{
|
||||
List<AppViewModel> apps = this.appService.findAll();
|
||||
request.setAttribute("apps", apps);
|
||||
request.setAttribute("baseURL", this.pathManager.getBaseURL(false));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/apps/{appID}")
|
||||
public String getAppById(@PathVariable("appID") String appID, HttpServletRequest request) {
|
||||
AppViewModel appViewModel = this.appService.getById(appID);
|
||||
request.setAttribute("package", appViewModel);
|
||||
request.setAttribute("apps", appViewModel.getPackageList());
|
||||
return "list";
|
||||
}
|
||||
|
||||
@RequestMapping("/app/delete/{id}")
|
||||
@ResponseBody
|
||||
public Map<String, Object> deleteById(@PathVariable("id") String id) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
try {
|
||||
this.appService.deleteById(id);
|
||||
map.put("success", true);
|
||||
} catch (Exception e) {
|
||||
map.put("success", false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/org/yzr/controller/ErrorController.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.yzr.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Controller
|
||||
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
|
||||
|
||||
/**
|
||||
* 所有错误都转到首页
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping("/error")
|
||||
public void handleError(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
response.sendRedirect("/apps");
|
||||
}
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return "/error";
|
||||
}
|
||||
}
|
||||
194
src/main/java/org/yzr/controller/PackageController.java
Normal file
@@ -0,0 +1,194 @@
|
||||
package org.yzr.controller;
|
||||
|
||||
|
||||
import net.glxn.qrgen.javase.QRCode;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.yzr.model.App;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.service.AppService;
|
||||
import org.yzr.service.PackageService;
|
||||
import org.yzr.utils.PathManager;
|
||||
import org.yzr.utils.ipa.PlistGenerator;
|
||||
import org.yzr.vo.AppViewModel;
|
||||
import org.yzr.vo.PackageViewModel;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Controller
|
||||
public class PackageController {
|
||||
@Resource
|
||||
private AppService appService;
|
||||
@Resource
|
||||
private PackageService packageService;
|
||||
@Resource
|
||||
private PathManager pathManager;
|
||||
|
||||
/**
|
||||
* 预览页
|
||||
* @param code
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/s/{code}")
|
||||
public String get(@PathVariable("code") String code, HttpServletRequest request) {
|
||||
String id = request.getParameter("id");
|
||||
AppViewModel viewModel = this.appService.findByCode(code, id);
|
||||
request.setAttribute("app", viewModel);
|
||||
request.setAttribute("ca_path", this.pathManager.getCAPath());
|
||||
return "install";
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传包
|
||||
* @param file
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/app/upload")
|
||||
@ResponseBody
|
||||
public Map<String, Object> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
try {
|
||||
String filePath = transfer(file);
|
||||
Package aPackage = this.packageService.buildPackage(filePath);
|
||||
App app = this.appService.getByPackage(aPackage);
|
||||
app.getPackageList().add(aPackage);
|
||||
app.setCurrentPackage(aPackage);
|
||||
aPackage.setApp(app);
|
||||
app = this.appService.save(app);
|
||||
// URL
|
||||
String codeURL = this.pathManager.getBaseURL(false) + "p/code/" + app.getCurrentPackage().getId();
|
||||
map.put("code", codeURL);
|
||||
map.put("success", true);
|
||||
} catch (Exception e) {
|
||||
map.put("success", false);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件源文件(ipa 或 apk)
|
||||
* @param id
|
||||
* @param response
|
||||
*/
|
||||
@RequestMapping("/p/{id}")
|
||||
public void download(@PathVariable("id") String id, HttpServletResponse response) {
|
||||
try {
|
||||
Package aPackage = this.packageService.get(id);
|
||||
String path = PathManager.getFullPath(aPackage) + aPackage.getFileName();
|
||||
File file = new File(path);
|
||||
if(file.exists()){ //判断文件父目录是否存在
|
||||
response.setContentType("application/force-download");
|
||||
// 文件名称转换
|
||||
String fileName = aPackage.getName() + "_" + aPackage.getVersion();
|
||||
String ext = "." + FilenameUtils.getExtension(aPackage.getFileName());
|
||||
String appName = new String(fileName.getBytes("UTF-8"), "iso-8859-1");
|
||||
response.setHeader("Content-Disposition", "attachment;fileName=" + appName + ext);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
OutputStream os = response.getOutputStream();
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
int i = bis.read(buffer);
|
||||
while(i != -1){
|
||||
os.write(buffer);
|
||||
i = bis.read(buffer);
|
||||
}
|
||||
bis.close();
|
||||
fis.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 manifest
|
||||
* @param id
|
||||
* @param response
|
||||
*/
|
||||
@RequestMapping("/m/{id}")
|
||||
public void getManifest(@PathVariable("id") String id, HttpServletResponse response) {
|
||||
try {
|
||||
PackageViewModel viewModel = this.packageService.findById(id);
|
||||
if (viewModel != null && viewModel.isIOS()) {
|
||||
response.setContentType("application/force-download");
|
||||
response.setHeader("Content-Disposition", "attachment;fileName=manifest.plist");
|
||||
Writer writer = new OutputStreamWriter(response.getOutputStream());
|
||||
PlistGenerator.generate(viewModel, writer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包二维码
|
||||
* @param id
|
||||
* @param response
|
||||
*/
|
||||
@RequestMapping("/p/code/{id}")
|
||||
public void getQrCode(@PathVariable("id") String id, HttpServletResponse response) {
|
||||
try {
|
||||
PackageViewModel viewModel = this.packageService.findById(id);
|
||||
if (viewModel != null) {
|
||||
response.setContentType("image/png");
|
||||
QRCode.from(viewModel.getPreviewURL()).withSize(250, 250).writeTo(response.getOutputStream());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除包
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/p/delete/{id}")
|
||||
@ResponseBody
|
||||
public Map<String, Object> deleteById(@PathVariable("id") String id) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
try {
|
||||
this.packageService.deleteById(id);
|
||||
map.put("success", true);
|
||||
} catch (Exception e) {
|
||||
map.put("success", false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转存文件
|
||||
* @param srcFile
|
||||
* @return
|
||||
*/
|
||||
private String transfer(MultipartFile srcFile) {
|
||||
try {
|
||||
// 获取文件后缀
|
||||
String fileName = srcFile.getOriginalFilename();
|
||||
String ext = FilenameUtils.getExtension(fileName);
|
||||
// 生成文件名
|
||||
String newFileName = UUID.randomUUID().toString() + "." + ext;
|
||||
// 转存到 tmp
|
||||
String destPath = FileUtils.getTempDirectory() + newFileName;
|
||||
srcFile.transferTo(new File(destPath));
|
||||
return destPath;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/java/org/yzr/dao/AppDao.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.yzr.dao;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.yzr.model.App;
|
||||
|
||||
public interface AppDao extends CrudRepository<App, String> {
|
||||
|
||||
@Query("select a from App a where a.bundleID=:bundleID and a.platform=:platform")
|
||||
public App get(@Param("bundleID") String bundleID, @Param("platform") String platform);
|
||||
|
||||
@Query("select a from App a where a.shortCode=:shortCode")
|
||||
public App findByShortCode(@Param("shortCode") String shortCode);
|
||||
|
||||
@Override
|
||||
@Query("select a from App a order by a.currentPackage.createTime desc ")
|
||||
Iterable<App> findAll();
|
||||
}
|
||||
8
src/main/java/org/yzr/dao/PackageDao.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.yzr.dao;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.yzr.model.Package;
|
||||
|
||||
public interface PackageDao extends CrudRepository <Package, String > {
|
||||
|
||||
}
|
||||
44
src/main/java/org/yzr/model/App.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package org.yzr.model;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "tb_app", uniqueConstraints = {@UniqueConstraint(columnNames={"platform", "bundleID"})})
|
||||
@Setter
|
||||
@Getter
|
||||
public class App {
|
||||
// 主键
|
||||
@Id
|
||||
@GeneratedValue(generator = "system-uuid")
|
||||
@GenericGenerator(name = "system-uuid", strategy = "uuid")
|
||||
@Column(length = 32)
|
||||
private String id;
|
||||
// APP ID
|
||||
private String bundleID;
|
||||
// 应用名称
|
||||
private String name;
|
||||
// 短链接码
|
||||
@Column(unique = true)
|
||||
private String shortCode;
|
||||
// 平台
|
||||
private String platform;
|
||||
// 简介
|
||||
private String description;
|
||||
// 创建时间
|
||||
private long createTime;
|
||||
// 包列表
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "app")
|
||||
private List<Package> packageList;
|
||||
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
// 当前包
|
||||
@JoinColumn(name = "currentID",referencedColumnName = "id")
|
||||
private Package currentPackage;
|
||||
|
||||
}
|
||||
45
src/main/java/org/yzr/model/Package.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.yzr.model;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.ManyToAny;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name="tb_package")
|
||||
@Setter
|
||||
@Getter
|
||||
public class Package {
|
||||
// 主键
|
||||
@Id
|
||||
@GeneratedValue(generator = "system-uuid")
|
||||
@GenericGenerator(name = "system-uuid", strategy = "uuid")
|
||||
@Column(length = 32)
|
||||
private String id;
|
||||
// 应用ID
|
||||
private String bundleID;
|
||||
// 名称
|
||||
private String name;
|
||||
// 版本
|
||||
private String version;
|
||||
// 构建版本
|
||||
private String buildVersion;
|
||||
// 创建时间
|
||||
private long createTime;
|
||||
// 包大小
|
||||
private long size;
|
||||
// 最低支持版本
|
||||
private String minVersion;
|
||||
// 平台(Android 或 iOS)
|
||||
private String platform;
|
||||
// 文件名
|
||||
private String fileName;
|
||||
@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name="appId")
|
||||
private App app;
|
||||
|
||||
}
|
||||
105
src/main/java/org/yzr/service/AppService.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.yzr.service;
|
||||
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.yzr.dao.AppDao;
|
||||
import org.yzr.model.App;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.CodeGenerator;
|
||||
import org.yzr.utils.PathManager;
|
||||
import org.yzr.vo.AppViewModel;
|
||||
import org.yzr.vo.PackageViewModel;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.transaction.Transactional;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class AppService {
|
||||
@Resource
|
||||
private AppDao appDao;
|
||||
@Resource
|
||||
private PathManager pathManager;
|
||||
|
||||
@Transactional
|
||||
public App save(App app) {
|
||||
App app1 = this.appDao.save(app);
|
||||
app1.getCurrentPackage();
|
||||
return app1;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<AppViewModel> findAll() {
|
||||
Iterable<App> apps = this.appDao.findAll();
|
||||
List<AppViewModel> list = new ArrayList<>();
|
||||
for (App app : apps) {
|
||||
AppViewModel appViewModel = new AppViewModel(app, this.pathManager, false);
|
||||
list.add(appViewModel);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AppViewModel getById(String appID) {
|
||||
Optional<App> optionalApp = this.appDao.findById(appID);
|
||||
App app = optionalApp.get();
|
||||
if (app != null) {
|
||||
app.getPackageList().forEach(aPackage -> {});
|
||||
AppViewModel appViewModel = new AppViewModel(app, this.pathManager, true);
|
||||
return appViewModel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public App getByPackage(Package aPackage) {
|
||||
App app = this.appDao.get(aPackage.getBundleID(), aPackage.getPlatform());
|
||||
if (app == null) {
|
||||
app = new App();
|
||||
String shortCode = CodeGenerator.generate(4);
|
||||
while (this.appDao.findByShortCode(shortCode) != null) {
|
||||
shortCode = CodeGenerator.generate(4);
|
||||
}
|
||||
BeanUtils.copyProperties(aPackage, app);
|
||||
app.setShortCode(shortCode);
|
||||
} else {
|
||||
app.setName(aPackage.getName());
|
||||
// 触发级联查询
|
||||
app.getPackageList().forEach(p->{});
|
||||
}
|
||||
if (app.getPackageList() == null) {
|
||||
app.setPackageList(new ArrayList<>());
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteById(String id) {
|
||||
App app = this.appDao.findById(id).get();
|
||||
if (app != null) {
|
||||
this.appDao.deleteById(id);
|
||||
// 消除整个 APP 目录
|
||||
String path = PathManager.getAppPath(app);
|
||||
PathManager.deleteDirectory(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 code 和 packageId 查询
|
||||
* @param code
|
||||
* @param packageId
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public AppViewModel findByCode(String code, String packageId) {
|
||||
App app = this.appDao.findByShortCode(code);
|
||||
AppViewModel viewModel = new AppViewModel(app, pathManager, packageId);
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
84
src/main/java/org/yzr/service/PackageService.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package org.yzr.service;
|
||||
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.yzr.dao.AppDao;
|
||||
import org.yzr.dao.PackageDao;
|
||||
import org.yzr.model.App;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.PathManager;
|
||||
import org.yzr.utils.ipa.PlistGenerator;
|
||||
import org.yzr.utils.parser.ParserClient;
|
||||
import org.yzr.vo.PackageViewModel;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.transaction.Transactional;
|
||||
import java.io.File;
|
||||
|
||||
@Service
|
||||
public class PackageService {
|
||||
|
||||
@Resource
|
||||
private PackageDao packageDao;
|
||||
@Resource
|
||||
private PathManager pathManager;
|
||||
|
||||
public Package buildPackage(String filePath) {
|
||||
Package aPackage = ParserClient.parse(filePath);
|
||||
try {
|
||||
String fileName = aPackage.getPlatform() + "." + FilenameUtils.getExtension(filePath);
|
||||
// 更新文件名
|
||||
aPackage.setFileName(fileName);
|
||||
|
||||
String packagePath = PathManager.getFullPath(aPackage);
|
||||
String tempIconPath = PathManager.getTempIconPath(aPackage);
|
||||
String iconPath = packagePath + File.separator + "icon.png";
|
||||
String sourcePath = packagePath + File.separator + fileName;
|
||||
// 拷贝图标
|
||||
FileUtils.copyFile(new File(tempIconPath), new File(iconPath));
|
||||
// 源文件
|
||||
FileUtils.copyFile(new File(filePath), new File(sourcePath));
|
||||
|
||||
// 删除临时图标
|
||||
FileUtils.forceDelete(new File(tempIconPath));
|
||||
// 源文件
|
||||
FileUtils.forceDelete(new File(filePath));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return aPackage;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Package save(Package aPackage) {
|
||||
return this.packageDao.save(aPackage);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Package get(String id) {
|
||||
Package aPackage = this.packageDao.findById(id).get();
|
||||
return aPackage;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PackageViewModel findById(String id) {
|
||||
Package aPackage = this.packageDao.findById(id).get();
|
||||
PackageViewModel viewModel = new PackageViewModel(aPackage, this.pathManager);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteById(String id) {
|
||||
Package aPackage = this.packageDao.findById(id).get();
|
||||
if (aPackage != null) {
|
||||
this.packageDao.deleteById(id);
|
||||
String path = PathManager.getFullPath(aPackage);
|
||||
PathManager.deleteDirectory(path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
BIN
src/main/java/org/yzr/utils/.DS_Store
vendored
Normal file
28
src/main/java/org/yzr/utils/CodeGenerator.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.yzr.utils;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class CodeGenerator {
|
||||
// 所有编码
|
||||
private static final String ALL_CODE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
// 随机种子
|
||||
private static Random random = new Random();
|
||||
|
||||
public static String generate(int length) {
|
||||
if (length < 1) {
|
||||
return null;
|
||||
}
|
||||
StringBuffer code = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
code.append(randomCode());
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
private static char randomCode() {
|
||||
int count = ALL_CODE.length();
|
||||
int index = random.nextInt(count) % count;
|
||||
return ALL_CODE.charAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
450
src/main/java/org/yzr/utils/PNGConverter.java
Normal file
@@ -0,0 +1,450 @@
|
||||
package org.yzr.utils;
|
||||
|
||||
import com.jcraft.jzlib.Deflater;
|
||||
import com.jcraft.jzlib.GZIPException;
|
||||
import com.jcraft.jzlib.Inflater;
|
||||
import com.jcraft.jzlib.JZlib;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.SimpleFormatter;
|
||||
import java.util.logging.StreamHandler;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
public class PNGConverter {
|
||||
|
||||
private final File source;
|
||||
private final File target;
|
||||
private ArrayList<PNGTrunk> trunks = null;
|
||||
|
||||
/**
|
||||
* iOS PNG 图标转换
|
||||
* @param srcPath
|
||||
* @param destPath
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void convert(String srcPath, String destPath) {
|
||||
try {
|
||||
PNGConverter converter = new PNGConverter(new File(srcPath), new File(destPath));
|
||||
converter.convert();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private PNGConverter(File source, File target) {
|
||||
if (source == null) throw new NullPointerException("'source' cannot be null");
|
||||
if (target == null) throw new NullPointerException("'target' cannot be null");
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
private File getTargetFile(File convertedFile) throws IOException {
|
||||
if (source.isFile()) {
|
||||
if (target.isDirectory()) {
|
||||
return new File(target, source.getName());
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
|
||||
} else { // source is a directory
|
||||
if (target.isFile()) { // single existing target
|
||||
return target;
|
||||
|
||||
} else { // otherwise reconstruct a similar directory structure
|
||||
if (!target.isDirectory() && !target.mkdirs()) {
|
||||
throw new IOException("failed to create folder " + target.getAbsolutePath());
|
||||
}
|
||||
|
||||
Path relativeConvertedPath = source.toPath().relativize(convertedFile.toPath());
|
||||
File targetFile = new File(target, relativeConvertedPath.toString());
|
||||
File targetFileDir = targetFile.getParentFile();
|
||||
if (targetFileDir != null && !targetFileDir.exists() && !targetFileDir.mkdirs()) {
|
||||
throw new IOException("unable to create folder " + targetFileDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
return targetFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void convert() throws IOException {
|
||||
convert(source);
|
||||
}
|
||||
|
||||
private boolean isPngFileName(File file) {
|
||||
return file.getName().toLowerCase().endsWith(".png");
|
||||
}
|
||||
|
||||
private PNGTrunk getTrunk(String szName) {
|
||||
if (trunks == null) {
|
||||
return null;
|
||||
}
|
||||
PNGTrunk trunk;
|
||||
for (int n = 0; n < trunks.size(); n++) {
|
||||
trunk = trunks.get(n);
|
||||
if (trunk.getName().equalsIgnoreCase(szName)) {
|
||||
return trunk;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void convertPngFile(File pngFile, File targetFile) throws IOException {
|
||||
readTrunks(pngFile);
|
||||
|
||||
if (getTrunk("CgBI") != null) {
|
||||
// Convert data
|
||||
|
||||
PNGIHDRTrunk ihdrTrunk = (PNGIHDRTrunk) getTrunk("IHDR");
|
||||
|
||||
int nMaxInflateBuffer = 4 * (ihdrTrunk.m_nWidth + 1) * ihdrTrunk.m_nHeight;
|
||||
byte[] outputBuffer = new byte[nMaxInflateBuffer];
|
||||
|
||||
convertDataTrunk(ihdrTrunk, outputBuffer, nMaxInflateBuffer);
|
||||
|
||||
writePng(targetFile);
|
||||
|
||||
} else {
|
||||
// Likely a standard PNG: just copy
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
InputStream inputStream = new FileInputStream(pngFile);
|
||||
try {
|
||||
OutputStream outputStream = new FileOutputStream(targetFile);
|
||||
try {
|
||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long inflate(byte[] conversionBuffer, int nMaxInflateBuffer) throws GZIPException {
|
||||
Inflater inflater = new Inflater(-15);
|
||||
|
||||
for (PNGTrunk dataTrunk : trunks) {
|
||||
if (!"IDAT".equalsIgnoreCase(dataTrunk.getName())) continue;
|
||||
inflater.setInput(dataTrunk.getData(), true);
|
||||
}
|
||||
|
||||
inflater.setOutput(conversionBuffer);
|
||||
|
||||
int nResult;
|
||||
try {
|
||||
nResult = inflater.inflate(JZlib.Z_NO_FLUSH);
|
||||
checkResultStatus(nResult);
|
||||
} finally {
|
||||
inflater.inflateEnd();
|
||||
}
|
||||
|
||||
if (inflater.getTotalOut() > nMaxInflateBuffer) {
|
||||
|
||||
}
|
||||
|
||||
return inflater.getTotalOut();
|
||||
}
|
||||
|
||||
private Deflater deflate(byte[] buffer, int length, int nMaxInflateBuffer) throws GZIPException {
|
||||
Deflater deflater = new Deflater();
|
||||
deflater.setInput(buffer, 0, length, false);
|
||||
|
||||
int nMaxDeflateBuffer = nMaxInflateBuffer + 1024;
|
||||
byte[] deBuffer = new byte[nMaxDeflateBuffer];
|
||||
deflater.setOutput(deBuffer);
|
||||
|
||||
deflater.deflateInit(JZlib.Z_BEST_COMPRESSION);
|
||||
int nResult = deflater.deflate(JZlib.Z_FINISH);
|
||||
checkResultStatus(nResult);
|
||||
|
||||
if (deflater.getTotalOut() > nMaxDeflateBuffer) {
|
||||
throw new GZIPException("deflater output buffer was too small");
|
||||
}
|
||||
|
||||
return deflater;
|
||||
}
|
||||
|
||||
private void checkResultStatus(int nResult) throws GZIPException {
|
||||
switch (nResult) {
|
||||
case JZlib.Z_OK:
|
||||
case JZlib.Z_STREAM_END:
|
||||
break;
|
||||
|
||||
case JZlib.Z_NEED_DICT:
|
||||
throw new GZIPException("Z_NEED_DICT - " + nResult);
|
||||
case JZlib.Z_DATA_ERROR:
|
||||
throw new GZIPException("Z_DATA_ERROR - " + nResult);
|
||||
case JZlib.Z_MEM_ERROR:
|
||||
throw new GZIPException("Z_MEM_ERROR - " + nResult);
|
||||
case JZlib.Z_STREAM_ERROR:
|
||||
throw new GZIPException("Z_STREAM_ERROR - " + nResult);
|
||||
case JZlib.Z_BUF_ERROR:
|
||||
throw new GZIPException("Z_BUF_ERROR - " + nResult);
|
||||
default:
|
||||
throw new GZIPException("inflater error: " + nResult);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean convertDataTrunk(PNGIHDRTrunk ihdrTrunk, byte[] conversionBuffer, int nMaxInflateBuffer)
|
||||
throws IOException {
|
||||
|
||||
long inflatedSize = inflate(conversionBuffer, nMaxInflateBuffer);
|
||||
|
||||
// Switch the color
|
||||
int nIndex = 0;
|
||||
byte nTemp;
|
||||
for (int y = 0; y < ihdrTrunk.m_nHeight; y++) {
|
||||
nIndex++;
|
||||
for (int x = 0; x < ihdrTrunk.m_nWidth; x++) {
|
||||
nTemp = conversionBuffer[nIndex];
|
||||
conversionBuffer[nIndex] = conversionBuffer[nIndex + 2];
|
||||
conversionBuffer[nIndex + 2] = nTemp;
|
||||
nIndex += 4;
|
||||
}
|
||||
}
|
||||
|
||||
Deflater deflater = deflate(conversionBuffer, (int) inflatedSize, nMaxInflateBuffer);
|
||||
|
||||
// Put the result in the first IDAT chunk (the only one to be written out)
|
||||
PNGTrunk firstDataTrunk = getTrunk("IDAT");
|
||||
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(firstDataTrunk.getName().getBytes());
|
||||
crc32.update(deflater.getNextOut(), 0, (int) deflater.getTotalOut());
|
||||
long lCRCValue = crc32.getValue();
|
||||
|
||||
firstDataTrunk.m_nData = deflater.getNextOut();
|
||||
firstDataTrunk.m_nCRC[0] = (byte) ((lCRCValue & 0xFF000000) >> 24);
|
||||
firstDataTrunk.m_nCRC[1] = (byte) ((lCRCValue & 0xFF0000) >> 16);
|
||||
firstDataTrunk.m_nCRC[2] = (byte) ((lCRCValue & 0xFF00) >> 8);
|
||||
firstDataTrunk.m_nCRC[3] = (byte) (lCRCValue & 0xFF);
|
||||
firstDataTrunk.m_nSize = (int) deflater.getTotalOut();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writePng(File newFileName) throws IOException {
|
||||
FileOutputStream outStream = new FileOutputStream(newFileName);
|
||||
try {
|
||||
byte[] pngHeader = {-119, 80, 78, 71, 13, 10, 26, 10};
|
||||
outStream.write(pngHeader);
|
||||
boolean dataWritten = false;
|
||||
for (PNGTrunk trunk : trunks) {
|
||||
// Skip Apple specific and misplaced CgBI chunk
|
||||
if (trunk.getName().equalsIgnoreCase("CgBI")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only write the first IDAT chunk as they have all been put together now
|
||||
if ("IDAT".equalsIgnoreCase(trunk.getName())) {
|
||||
if (dataWritten) {
|
||||
continue;
|
||||
} else {
|
||||
dataWritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
trunk.writeToStream(outStream);
|
||||
}
|
||||
outStream.flush();
|
||||
|
||||
} finally {
|
||||
outStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void readTrunks(File pngFile) throws IOException {
|
||||
DataInputStream input = new DataInputStream(new FileInputStream(pngFile));
|
||||
try {
|
||||
byte[] nPNGHeader = new byte[8];
|
||||
input.readFully(nPNGHeader);
|
||||
|
||||
boolean bWithCgBI = false;
|
||||
|
||||
trunks = new ArrayList<PNGTrunk>();
|
||||
if ((nPNGHeader[0] == -119) && (nPNGHeader[1] == 0x50) && (nPNGHeader[2] == 0x4e) && (nPNGHeader[3] == 0x47)
|
||||
&& (nPNGHeader[4] == 0x0d) && (nPNGHeader[5] == 0x0a) && (nPNGHeader[6] == 0x1a) && (nPNGHeader[7] == 0x0a)) {
|
||||
|
||||
PNGTrunk trunk;
|
||||
do {
|
||||
trunk = PNGTrunk.generateTrunk(input);
|
||||
trunks.add(trunk);
|
||||
|
||||
if (trunk.getName().equalsIgnoreCase("CgBI")) {
|
||||
bWithCgBI = true;
|
||||
}
|
||||
}
|
||||
while (!trunk.getName().equalsIgnoreCase("IEND"));
|
||||
}
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void convertDirectory(File dir) throws IOException {
|
||||
for (File file : dir.listFiles()) {
|
||||
convert(file);
|
||||
}
|
||||
}
|
||||
|
||||
private void convert(File sourceFile) throws IOException {
|
||||
if (sourceFile.isDirectory()) {
|
||||
convertDirectory(sourceFile);
|
||||
} else if (isPngFileName(sourceFile)) {
|
||||
File targetFile = getTargetFile(sourceFile);
|
||||
convertPngFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SimpleFormatter fmt = new SimpleFormatter();
|
||||
StreamHandler sh = new StreamHandler(System.out, fmt);
|
||||
sh.setLevel(Level.FINE);
|
||||
|
||||
new PNGConverter(new File(args[0]), new File(args[1])).convert();
|
||||
}
|
||||
}
|
||||
|
||||
class PNGTrunk {
|
||||
protected int m_nSize;
|
||||
protected String m_szName;
|
||||
protected byte[] m_nData;
|
||||
protected byte[] m_nCRC;
|
||||
|
||||
|
||||
public static PNGTrunk generateTrunk(DataInputStream input) throws IOException {
|
||||
int nSize = readPngInt(input);
|
||||
|
||||
byte[] nData = new byte[4];
|
||||
input.readFully(nData);
|
||||
String szName = new String(nData, "ASCII");
|
||||
|
||||
byte[] nDataBuffer = new byte[nSize];
|
||||
input.readFully(nDataBuffer);
|
||||
|
||||
byte[] nCRC = new byte[4];
|
||||
input.readFully(nCRC);
|
||||
|
||||
if (szName.equalsIgnoreCase("IHDR")) {
|
||||
return new PNGIHDRTrunk(nSize, szName, nDataBuffer, nCRC);
|
||||
}
|
||||
|
||||
return new PNGTrunk(nSize, szName, nDataBuffer, nCRC);
|
||||
}
|
||||
|
||||
protected PNGTrunk(int nSize, String szName, byte[] nCRC) {
|
||||
m_nSize = nSize;
|
||||
m_szName = szName;
|
||||
m_nCRC = nCRC;
|
||||
}
|
||||
|
||||
protected PNGTrunk(int nSize, String szName, byte[] nData, byte[] nCRC) {
|
||||
this(nSize, szName, nCRC);
|
||||
m_nData = nData;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return m_nSize;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return m_szName;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return m_nData;
|
||||
}
|
||||
|
||||
public byte[] getCRC() {
|
||||
return m_nCRC;
|
||||
}
|
||||
|
||||
public void writeToStream(FileOutputStream outStream) throws IOException {
|
||||
byte nSize[] = new byte[4];
|
||||
nSize[0] = (byte) ((m_nSize & 0xFF000000) >> 24);
|
||||
nSize[1] = (byte) ((m_nSize & 0xFF0000) >> 16);
|
||||
nSize[2] = (byte) ((m_nSize & 0xFF00) >> 8);
|
||||
nSize[3] = (byte) (m_nSize & 0xFF);
|
||||
|
||||
outStream.write(nSize);
|
||||
outStream.write(m_szName.getBytes("ASCII"));
|
||||
outStream.write(m_nData, 0, m_nSize);
|
||||
outStream.write(m_nCRC);
|
||||
}
|
||||
|
||||
public static void writeInt(byte[] nDes, int nPos, int nVal) {
|
||||
nDes[nPos] = (byte) ((nVal & 0xff000000) >> 24);
|
||||
nDes[nPos + 1] = (byte) ((nVal & 0xff0000) >> 16);
|
||||
nDes[nPos + 2] = (byte) ((nVal & 0xff00) >> 8);
|
||||
nDes[nPos + 3] = (byte) (nVal & 0xff);
|
||||
}
|
||||
|
||||
public static int readPngInt(DataInputStream input) throws IOException {
|
||||
final byte[] buffer = new byte[4];
|
||||
input.readFully(buffer);
|
||||
return readInt(buffer, 0);
|
||||
}
|
||||
|
||||
public static int readInt(byte[] nDest, int nPos) { //读一个int
|
||||
return ((nDest[nPos++] & 0xFF) << 24)
|
||||
| ((nDest[nPos++] & 0xFF) << 16)
|
||||
| ((nDest[nPos++] & 0xFF) << 8)
|
||||
| (nDest[nPos] & 0xFF);
|
||||
}
|
||||
|
||||
public static void writeCRC(byte[] nData, int nPos) {
|
||||
int chunklen = readInt(nData, nPos);
|
||||
int sum = CRCChecksum(nData, nPos + 4, 4 + chunklen) ^ 0xffffffff;
|
||||
writeInt(nData, nPos + 8 + chunklen, sum);
|
||||
}
|
||||
|
||||
public static int[] crc_table = null;
|
||||
|
||||
public static int CRCChecksum(byte[] nBuffer, int nOffset, int nLength) {
|
||||
int c = 0xffffffff;
|
||||
int n;
|
||||
if (crc_table == null) {
|
||||
int mkc;
|
||||
int mkn, mkk;
|
||||
crc_table = new int[256];
|
||||
for (mkn = 0; mkn < 256; mkn++) {
|
||||
mkc = mkn;
|
||||
for (mkk = 0; mkk < 8; mkk++) {
|
||||
if ((mkc & 1) == 1) {
|
||||
mkc = 0xedb88320 ^ (mkc >>> 1);
|
||||
} else {
|
||||
mkc = mkc >>> 1;
|
||||
}
|
||||
}
|
||||
crc_table[mkn] = mkc;
|
||||
}
|
||||
}
|
||||
for (n = nOffset; n < nLength + nOffset; n++) {
|
||||
c = crc_table[(c ^ nBuffer[n]) & 0xff] ^ (c >>> 8);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
class PNGIHDRTrunk extends PNGTrunk {
|
||||
public int m_nWidth;
|
||||
public int m_nHeight;
|
||||
|
||||
public PNGIHDRTrunk(int nSize, String szName, byte[] nData, byte[] nCRC) {
|
||||
super(nSize, szName, nData, nCRC);
|
||||
|
||||
m_nWidth = readInt(nData, 0);
|
||||
m_nHeight = readInt(nData, 4);
|
||||
}
|
||||
}
|
||||
166
src/main/java/org/yzr/utils/PathManager.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package org.yzr.utils;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.yzr.model.App;
|
||||
import org.yzr.model.Package;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Component
|
||||
public class PathManager {
|
||||
|
||||
@Resource
|
||||
private Environment environment;
|
||||
private String httpsBaseURL;
|
||||
private String httpBaseURL;
|
||||
|
||||
/**
|
||||
* 获取基础路径
|
||||
* @param isHttps
|
||||
* @return
|
||||
*/
|
||||
public String getBaseURL(boolean isHttps) {
|
||||
if (isHttps) {
|
||||
if (httpsBaseURL != null) {
|
||||
return httpsBaseURL;
|
||||
}
|
||||
} else {
|
||||
if (httpBaseURL != null) {
|
||||
return httpBaseURL;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// URL
|
||||
InetAddress address = InetAddress.getLocalHost();
|
||||
String domain=environment.getProperty("server.domain");
|
||||
if (domain == null) {
|
||||
domain = address.getHostAddress();
|
||||
}
|
||||
int httpPort = Integer.parseInt(environment.getProperty("server.http.port"));
|
||||
int httpsPort = Integer.parseInt(environment.getProperty("server.port"));
|
||||
int port = isHttps ? httpPort : httpsPort;
|
||||
String protocol = isHttps ? "https" : "http";
|
||||
String portString = ":" + port;
|
||||
if (port == 80 || port == 443) {
|
||||
portString = "";
|
||||
}
|
||||
|
||||
String baseURL = protocol + "://" + domain + portString + "/";
|
||||
return baseURL;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包所在路径
|
||||
* @param aPackage
|
||||
* @param isHttps
|
||||
* @return
|
||||
*/
|
||||
public String getPackageResourceURL(Package aPackage, boolean isHttps) {
|
||||
String baseURL = getBaseURL(isHttps);
|
||||
String resourceURL = baseURL + aPackage.getPlatform() + "/" + aPackage.getBundleID() + "/" + aPackage.getCreateTime() + "/";
|
||||
return resourceURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书路径
|
||||
* @return
|
||||
*/
|
||||
public String getCAPath() {
|
||||
return getBaseURL(false) + "crt/ca.crt";
|
||||
}
|
||||
/**
|
||||
* 获取图标的临时路径
|
||||
* @param aPackage
|
||||
* @return
|
||||
*/
|
||||
public static String getTempIconPath(Package aPackage) {
|
||||
if (aPackage == null) return null;
|
||||
StringBuilder path = new StringBuilder();
|
||||
path.append(FileUtils.getTempDirectoryPath()).append(aPackage.getPlatform());
|
||||
path.append(File.separator).append(aPackage.getBundleID());
|
||||
// 如果目录不存在,创建目录
|
||||
File dir = new File(path.toString());
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
path.append(File.separator).append(aPackage.getCreateTime()).append(".png");
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传路径
|
||||
* @return
|
||||
*/
|
||||
public static String getUploadPath() {
|
||||
try {
|
||||
//获取跟目录
|
||||
File path = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
if(!path.exists()) path = new File("");
|
||||
|
||||
//如果上传目录为/static/upload/,则可以如下获取:
|
||||
File upload = new File(path.getAbsolutePath(),"static/upload/");
|
||||
if(!upload.exists()) upload.mkdirs();
|
||||
return upload.getPath();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 APP 路径
|
||||
* @param app
|
||||
* @return
|
||||
*/
|
||||
public static String getAppPath(App app) {
|
||||
return getUploadPath() + File.separator + app.getPlatform() + File.separator + app.getBundleID() + File.separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包的完整路径
|
||||
* @param aPackage
|
||||
* @return
|
||||
*/
|
||||
public static String getFullPath(Package aPackage) {
|
||||
return getUploadPath() + File.separator + getRelativePath(aPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包的相对路径
|
||||
* @param aPackage
|
||||
* @return
|
||||
*/
|
||||
public static String getRelativePath(Package aPackage) {
|
||||
if (aPackage == null) return null;
|
||||
StringBuilder path = new StringBuilder();
|
||||
path.append(aPackage.getPlatform()).append(File.separator);
|
||||
path.append(aPackage.getBundleID()).append(File.separator);
|
||||
path.append(aPackage.getCreateTime()).append(File.separator);
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除目录
|
||||
* @param path
|
||||
*/
|
||||
public static void deleteDirectory(String path) {
|
||||
File dir = new File(path);
|
||||
if (dir.exists()) {
|
||||
try {
|
||||
FileUtils.deleteDirectory(dir);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/main/java/org/yzr/utils/ZipUtils.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package org.yzr.utils;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ZipUtils {
|
||||
|
||||
public static String unzip(String path) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
String destDirPath = System.getProperty("java.io.tmpdir") + start;
|
||||
ZipFile zipFile = new ZipFile(path);
|
||||
Enumeration<?> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = (ZipEntry)entries.nextElement();
|
||||
if (entry.isDirectory()) {
|
||||
String dirPath = destDirPath + File.separator + entry.getName();
|
||||
File dir = new File(dirPath);
|
||||
dir.mkdirs();
|
||||
} else {
|
||||
File targetFile = new File(destDirPath + File.separator + entry.getName());
|
||||
if (!targetFile.getParentFile().exists()) {
|
||||
targetFile.getParentFile().mkdirs();
|
||||
}
|
||||
targetFile.createNewFile();
|
||||
InputStream is = zipFile.getInputStream(entry);
|
||||
FileOutputStream fos = new FileOutputStream(targetFile);
|
||||
int len;
|
||||
byte[] buf = new byte[1024];
|
||||
while ((len = is.read(buf)) != -1) {
|
||||
fos.write(buf, 0, len);
|
||||
}
|
||||
fos.close();
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
zipFile.close();
|
||||
return destDirPath;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
98
src/main/java/org/yzr/utils/ipa/Plist.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package org.yzr.utils.ipa;
|
||||
|
||||
import com.dd.plist.NSArray;
|
||||
import com.dd.plist.NSDictionary;
|
||||
import com.dd.plist.NSObject;
|
||||
import com.dd.plist.PropertyListParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Plist {
|
||||
private NSDictionary dictionary;
|
||||
|
||||
public Plist(NSDictionary dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
public static Plist parseWithFile(File file) {
|
||||
try {
|
||||
NSDictionary dictionary = (NSDictionary)PropertyListParser.parse(file);
|
||||
return new Plist(dictionary);
|
||||
}catch (Exception e){}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Plist parseWithString(String plist) {
|
||||
try {
|
||||
NSDictionary dictionary = (NSDictionary)PropertyListParser.parse(plist.getBytes());
|
||||
return new Plist(dictionary);
|
||||
}catch (Exception e){}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 通过 keyPath 获取值
|
||||
public NSObject valueForKeyPath(String keyPath) {
|
||||
String[] values = keyPath.split("\\.");
|
||||
try {
|
||||
if (values.length > 0) {
|
||||
int i = 0;
|
||||
NSObject value = null;
|
||||
NSDictionary dictionary = this.dictionary;
|
||||
while (i < values.length) {
|
||||
value = dictionary.objectForKey(values[i]);
|
||||
if (value instanceof NSDictionary) {
|
||||
dictionary = (NSDictionary)value;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String stringValueForKeyPath(String keyPath) {
|
||||
Object object = valueForKeyPath(keyPath);
|
||||
if (object != null) {
|
||||
return object.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public NSObject valueForPath(String path) {
|
||||
try {
|
||||
return this.dictionary.objectForKey(path);
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String stringValueForPath(String keyPath) {
|
||||
Object object = valueForKeyPath(keyPath);
|
||||
if (object != null) {
|
||||
return object.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<String> arrayValueForPath(String path) {
|
||||
Object object = valueForKeyPath(path);
|
||||
if (object != null) {
|
||||
NSArray deviceArray = (NSArray)object;
|
||||
List<String> devices = new ArrayList<>();
|
||||
if (deviceArray != null && deviceArray.count() > 0) {
|
||||
for (int i = 0; i < deviceArray.count(); i++) {
|
||||
devices.add(deviceArray.objectAtIndex(i).toString());
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
54
src/main/java/org/yzr/utils/ipa/PlistGenerator.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package org.yzr.utils.ipa;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.Template;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.yzr.vo.PackageViewModel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlistGenerator {
|
||||
public static void generate(PackageViewModel aPackage, String destPath) {
|
||||
try {
|
||||
Writer out = new FileWriter(new File(destPath));
|
||||
generate(aPackage, out);
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 manifest
|
||||
* @param aPackage
|
||||
* @param out
|
||||
*/
|
||||
public static void generate(PackageViewModel aPackage, Writer out) {
|
||||
try {
|
||||
//1.0 创建配置对象
|
||||
//创建Configuration实例,指定FreeMarker的版本
|
||||
Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
|
||||
//指定模板所在的目录
|
||||
cfg.setClassLoaderForTemplateLoading(PlistGenerator.class.getClassLoader(), "/freemarker");
|
||||
//设置默认字符集
|
||||
cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
|
||||
//2.0 创建数据模型
|
||||
Map<String, Object> root = new HashMap<>();
|
||||
root.put("aPackage", aPackage);
|
||||
|
||||
//3.0 获取模板
|
||||
Template template = cfg.getTemplate("manifest.plist");
|
||||
//4.0 给模板绑定数据模型
|
||||
template.process(root, out);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/main/java/org/yzr/utils/ipa/Provision.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.yzr.utils.ipa;
|
||||
|
||||
import com.dd.plist.NSDate;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class Provision {
|
||||
private String teamName;
|
||||
private String teamID;
|
||||
private Date createDate;
|
||||
private Date expirationDate;
|
||||
private String UUID;
|
||||
private List<String> devices;
|
||||
private int deviceCount;
|
||||
private String type;
|
||||
|
||||
public Provision(String appPath) {
|
||||
String profile = appPath + File.separator + "embedded.mobileprovision";
|
||||
try {
|
||||
boolean started = false;
|
||||
boolean ended = false;
|
||||
BufferedReader reader = new BufferedReader(new FileReader(profile));
|
||||
StringBuffer plist = new StringBuffer();
|
||||
String str = null;
|
||||
while ((str = reader.readLine()) != null) {
|
||||
if (str.contains("</plist>")) {
|
||||
ended = true;
|
||||
plist.append("</plist>").append("\n");
|
||||
} else if (started && !ended) {
|
||||
plist.append(str).append("\n");
|
||||
} else if (str.contains("<?xml")) {
|
||||
started = true;
|
||||
plist.append(str.substring(str.indexOf("<?xml"))).append("\n");
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
Plist provisionFile = Plist.parseWithString(plist.toString());
|
||||
this.devices = provisionFile.arrayValueForPath("ProvisionedDevices");
|
||||
this.deviceCount = this.devices.size();
|
||||
this.teamName = provisionFile.stringValueForPath("TeamName");
|
||||
this.teamID = provisionFile.arrayValueForPath("TeamIdentifier").get(0);
|
||||
this.createDate = ((NSDate)provisionFile.valueForKeyPath("CreationDate")).getDate();
|
||||
this.expirationDate = ((NSDate)provisionFile.valueForKeyPath("ExpirationDate")).getDate();
|
||||
this.UUID = provisionFile.stringValueForPath("UUID");
|
||||
this.type = this.deviceCount > 0 ? "Ad-hoc" : "Release";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/main/java/org/yzr/utils/parser/APKParser.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.yzr.utils.parser;
|
||||
|
||||
import net.dongliu.apk.parser.ApkFile;
|
||||
import net.dongliu.apk.parser.bean.ApkMeta;
|
||||
import net.dongliu.apk.parser.bean.IconFace;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.PathManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
public class APKParser implements PackageParser {
|
||||
@Override
|
||||
public Package parse(String filePath) {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) return null;
|
||||
ApkFile apkFile = new ApkFile(file);
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
Package aPackage = new Package();
|
||||
aPackage.setSize(file.length());
|
||||
ApkMeta meta = apkFile.getApkMeta();
|
||||
aPackage.setName(meta.getName());
|
||||
aPackage.setVersion(meta.getPlatformBuildVersionName());
|
||||
aPackage.setBuildVersion(meta.getPlatformBuildVersionCode());
|
||||
aPackage.setBundleID(meta.getPackageName());
|
||||
aPackage.setMinVersion(meta.getMinSdkVersion());
|
||||
aPackage.setPlatform("android");
|
||||
aPackage.setCreateTime(currentTimeMillis);
|
||||
int iconCount = apkFile.getAllIcons().size();
|
||||
if (iconCount > 0) {
|
||||
IconFace icon = apkFile.getAllIcons().get(iconCount - 1);
|
||||
String iconPath = PathManager.getTempIconPath(aPackage);
|
||||
File iconFile = new File(iconPath);
|
||||
FileUtils.writeByteArrayToFile(iconFile, icon.getData());
|
||||
}
|
||||
apkFile.close();
|
||||
return aPackage;
|
||||
}catch (Exception e){
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
103
src/main/java/org/yzr/utils/parser/IPAParser.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package org.yzr.utils.parser;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.PNGConverter;
|
||||
import org.yzr.utils.PathManager;
|
||||
import org.yzr.utils.ZipUtils;
|
||||
import org.yzr.utils.ipa.Plist;
|
||||
import org.yzr.utils.ipa.Provision;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IPAParser implements PackageParser {
|
||||
@Override
|
||||
public Package parse(String filePath) {
|
||||
try {
|
||||
Package aPackage = new Package();
|
||||
// 解压 IPA 包
|
||||
String targetPath = ZipUtils.unzip(filePath);
|
||||
String appPath = appPath(targetPath);
|
||||
String infoPlistPath = appPath + File.separator + "Info.plist";
|
||||
File infoPlistFile = new File(infoPlistPath);
|
||||
// Plist 文件获取失败
|
||||
if (!infoPlistFile.exists()) return null;
|
||||
// 获取 infoPlist
|
||||
Plist infoPlist = Plist.parseWithFile(infoPlistFile);
|
||||
File ipaFile = new File(filePath);
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
aPackage.setSize(ipaFile.length());
|
||||
aPackage.setName(infoPlist.stringValueForPath("CFBundleDisplayName"));
|
||||
if (aPackage.getName() == null) {
|
||||
aPackage.setName(infoPlist.stringValueForPath("CFBundleName"));
|
||||
}
|
||||
aPackage.setVersion(infoPlist.stringValueForPath("CFBundleShortVersionString"));
|
||||
aPackage.setBuildVersion(infoPlist.stringValueForPath("CFBundleVersion"));
|
||||
aPackage.setBundleID(infoPlist.stringValueForPath("CFBundleIdentifier"));
|
||||
aPackage.setMinVersion(infoPlist.stringValueForPath("MinimumOSVersion"));
|
||||
aPackage.setCreateTime(currentTimeMillis);
|
||||
aPackage.setPlatform("ios");
|
||||
|
||||
// 获取应用图标
|
||||
String iconPath = appIcon(appPath, infoPlist.stringValueForKeyPath("CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconName"));
|
||||
String iconTempPath = PathManager.getTempIconPath(aPackage);
|
||||
PNGConverter.convert(iconPath, iconTempPath);
|
||||
|
||||
// 解析 Provision
|
||||
Provision provision = new Provision(appPath);
|
||||
|
||||
// 清除目录
|
||||
FileUtils.deleteDirectory(new File(targetPath));
|
||||
return aPackage;
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*获取 APP 路径*/
|
||||
private static String appPath(String path) {
|
||||
try {
|
||||
String payloadPath = path + File.separator + "Payload";
|
||||
File payloadFile = new File(payloadPath);
|
||||
if (!payloadFile.exists()) return null;
|
||||
if (!payloadFile.isDirectory()) return null;
|
||||
File[] listFiles = payloadFile.listFiles();
|
||||
String appName = null;
|
||||
for (File file : listFiles) {
|
||||
if (file.getName().contains(".app")) {
|
||||
appName = file.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (appName == null) return null;
|
||||
return payloadPath + File.separator + appName;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取 APP 图标
|
||||
private static String appIcon(String appPath, String iconName) {
|
||||
List<String> iconNames = new ArrayList<>();
|
||||
File appFile = new File(appPath);
|
||||
File[] listFiles = appFile.listFiles();
|
||||
for (File file : listFiles) {
|
||||
String pattern = iconName + "[4,6]0x[4,6]0@[2,3]?x.png";
|
||||
boolean isMatch = Pattern.matches(pattern, file.getName());
|
||||
if (isMatch) {
|
||||
iconNames.add(file.getName());
|
||||
}
|
||||
}
|
||||
if (iconNames.size() > 0) {
|
||||
return appPath + File.separator + iconNames.get(iconNames.size() - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
8
src/main/java/org/yzr/utils/parser/PackageParser.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.yzr.utils.parser;
|
||||
|
||||
import org.yzr.model.Package;
|
||||
|
||||
public interface PackageParser {
|
||||
// 解析包
|
||||
public Package parse(String filePath);
|
||||
}
|
||||
38
src/main/java/org/yzr/utils/parser/ParserClient.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.yzr.utils.parser;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.yzr.model.Package;
|
||||
|
||||
public class ParserClient {
|
||||
|
||||
/**
|
||||
* 解析包
|
||||
* @param filePath 文件路径
|
||||
* @return
|
||||
*/
|
||||
public static Package parse(String filePath) {
|
||||
PackageParser parser = getParser(filePath);
|
||||
if (parser != null) {
|
||||
return parser.parse(filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件后缀名获取解析器
|
||||
* @param filePath
|
||||
* @return
|
||||
*/
|
||||
private static PackageParser getParser(String filePath) {
|
||||
String extension = FilenameUtils.getExtension(filePath);
|
||||
try {
|
||||
// 动态获取解析器
|
||||
Class aClass = Class.forName("org.yzr.utils.parser." + extension.toUpperCase()+"Parser");
|
||||
PackageParser packageParser = (PackageParser)aClass.newInstance();
|
||||
return packageParser;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
109
src/main/java/org/yzr/vo/AppViewModel.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package org.yzr.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.yzr.model.App;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.PathManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Getter
|
||||
public class AppViewModel {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String platform;
|
||||
|
||||
private String bundleID;
|
||||
|
||||
private String icon;
|
||||
|
||||
private String version;
|
||||
|
||||
private String buildVersion;
|
||||
|
||||
private String minVersion;
|
||||
|
||||
private String shortCode;
|
||||
|
||||
private String installPath;
|
||||
|
||||
private List<PackageViewModel> packageList;
|
||||
|
||||
private PackageViewModel currentPackage;
|
||||
|
||||
/***
|
||||
* 初始化是否加载列表
|
||||
* @param app
|
||||
* @param pathManager
|
||||
* @param loadList
|
||||
*/
|
||||
public AppViewModel(App app, PathManager pathManager, boolean loadList) {
|
||||
this.id = app.getId();
|
||||
this.platform = app.getPlatform();
|
||||
this.bundleID = app.getBundleID();
|
||||
this.icon = PathManager.getRelativePath(app.getCurrentPackage()) + "icon.png";
|
||||
Package aPackage = findPackageById(app, null);
|
||||
this.version = aPackage.getVersion();
|
||||
this.buildVersion = aPackage.getBuildVersion();
|
||||
this.shortCode = app.getShortCode();
|
||||
this.name = app.getName();
|
||||
this.installPath = pathManager.getBaseURL(false) + "s/" + app.getShortCode();
|
||||
this.minVersion = aPackage.getMinVersion();
|
||||
this.currentPackage = new PackageViewModel(aPackage, pathManager);
|
||||
if (loadList) {
|
||||
// 排序
|
||||
this.packageList = sortPackages(app.getPackageList(), pathManager);
|
||||
}
|
||||
}
|
||||
|
||||
public AppViewModel(App app, PathManager pathManager, String packageId) {
|
||||
this.id = app.getId();
|
||||
this.platform = app.getPlatform();
|
||||
this.bundleID = app.getBundleID();
|
||||
this.icon = PathManager.getRelativePath(app.getCurrentPackage()) + "icon.png";
|
||||
Package aPackage = findPackageById(app, packageId);
|
||||
this.version = aPackage.getVersion();
|
||||
this.buildVersion = aPackage.getBuildVersion();
|
||||
this.shortCode = app.getShortCode();
|
||||
this.name = app.getName();
|
||||
this.installPath = pathManager.getBaseURL(false) + "s/" + app.getShortCode();
|
||||
this.minVersion = aPackage.getMinVersion();
|
||||
this.currentPackage = new PackageViewModel(aPackage, pathManager);
|
||||
}
|
||||
|
||||
private static Package findPackageById(App app, String id) {
|
||||
if (id != null) {
|
||||
for (Package aPackage : app.getPackageList()) {
|
||||
if (aPackage.getId().equals(id)) {
|
||||
return aPackage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return app.getCurrentPackage();
|
||||
}
|
||||
|
||||
private static List<PackageViewModel> sortPackages(List<Package> packages, PathManager pathManager) {
|
||||
// 排序
|
||||
List<PackageViewModel> packageViewModels = new ArrayList<>();
|
||||
for (Package aPackage : packages) {
|
||||
PackageViewModel packageViewModel = new PackageViewModel(aPackage, pathManager);
|
||||
packageViewModels.add(packageViewModel);
|
||||
}
|
||||
packageViewModels.sort((o1, o2) -> {
|
||||
if (o1.getCreateTime() > o2.getCreateTime()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
return packageViewModels;
|
||||
}
|
||||
}
|
||||
56
src/main/java/org/yzr/vo/PackageViewModel.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.yzr.vo;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.utils.PathManager;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@Getter
|
||||
public class PackageViewModel {
|
||||
private String downloadURL;
|
||||
private String safeDownloadURL;
|
||||
private String iconURL;
|
||||
private String installURL;
|
||||
private String previewURL;
|
||||
private String id;
|
||||
private String version;
|
||||
private String bundleID;
|
||||
private String name;
|
||||
private long createTime;
|
||||
private String buildVersion;
|
||||
private String displaySize;
|
||||
private String displayTime;
|
||||
private boolean iOS;
|
||||
|
||||
public PackageViewModel(Package aPackage, PathManager pathManager) {
|
||||
this.downloadURL = pathManager.getBaseURL(false) + "p/" + aPackage.getId();
|
||||
this.safeDownloadURL = pathManager.getBaseURL(true) + "p/" + aPackage.getId();
|
||||
this.iconURL = pathManager.getPackageResourceURL(aPackage, true) + "icon.png";
|
||||
this.id = aPackage.getId();
|
||||
this.version = aPackage.getVersion();
|
||||
this.bundleID = aPackage.getBundleID();
|
||||
this.name = aPackage.getName();
|
||||
this.createTime = aPackage.getCreateTime();
|
||||
this.buildVersion = aPackage.getBuildVersion();
|
||||
this.displaySize = String.format("%.2f MB", aPackage.getSize() / (1.0F * FileUtils.ONE_MB));
|
||||
Date updateTime = new Date(this.createTime);
|
||||
String displayTime = (new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(updateTime);
|
||||
this.displayTime = displayTime;
|
||||
if (aPackage.getPlatform().equals("ios")) {
|
||||
this.iOS = true;
|
||||
String url = pathManager.getBaseURL(true) + "m/" + aPackage.getId();
|
||||
try {
|
||||
this.installURL = "itms-services://?action=download-manifest&url=" + URLEncoder.encode(url, "utf-8");
|
||||
} catch (Exception e){e.printStackTrace();}
|
||||
} else if (aPackage.getPlatform().equals("android")) {
|
||||
this.iOS = false;
|
||||
this.installURL = pathManager.getPackageResourceURL(aPackage, false) + aPackage.getFileName();
|
||||
}
|
||||
this.previewURL = pathManager.getBaseURL(false) + "s/" + aPackage.getApp().getShortCode() + "?id=" + aPackage.getId();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
src/main/resources/.DS_Store
vendored
Normal file
44
src/main/resources/application.properties
Normal file
@@ -0,0 +1,44 @@
|
||||
########################################################
|
||||
### Mysql
|
||||
########################################################
|
||||
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/app_manager
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
########################################################
|
||||
### Java Persistence Api
|
||||
########################################################
|
||||
# Specify the DBMS
|
||||
spring.jpa.database = MYSQL
|
||||
# Show or not log for each sql query
|
||||
spring.jpa.show-sql = true
|
||||
# Hibernate ddl auto (create, create-drop, update)
|
||||
spring.jpa.hibernate.ddl-auto = update
|
||||
|
||||
# Naming strategy
|
||||
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
|
||||
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
############################################################
|
||||
#
|
||||
# 上传文件大小
|
||||
#
|
||||
############################################################
|
||||
spring.servlet.multipart.max-file-size=300MB
|
||||
spring.servlet.multipart.max-request-size=300MB
|
||||
############################################################
|
||||
#
|
||||
# ssl
|
||||
#
|
||||
############################################################
|
||||
server.ssl.key-store=classpath:server.pkcs12
|
||||
server.ssl.key-store-password=123456
|
||||
server.ssl.key-store-type=PKCS12
|
||||
server.ssl.key-alias=1
|
||||
|
||||
# 自定义配置
|
||||
server.port=443
|
||||
server.http.port=80
|
||||
config.debug=debug
|
||||
server.domain=192.168.0.108
|
||||
44
src/main/resources/freemarker/manifest.plist
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plist version="1.0"><dict>
|
||||
<key>items</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>assets</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>kind</key>
|
||||
<string>software-package</string>
|
||||
<key>url</key>
|
||||
<string><![CDATA[${aPackage.safeDownloadURL}]]></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>kind</key>
|
||||
<string>display-image</string>
|
||||
<key>needs-shine</key>
|
||||
<integer>0</integer>
|
||||
<key>url</key>
|
||||
<string><![CDATA[${aPackage.iconURL}]]></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>kind</key>
|
||||
<string>full-size-image</string>
|
||||
<key>needs-shine</key>
|
||||
<true/>
|
||||
<key>url</key>
|
||||
<string><![CDATA[${aPackage.iconURL}]]></string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>metadata</key>
|
||||
<dict>
|
||||
<key>bundle-identifier</key>
|
||||
<string>${aPackage.bundleID}</string>
|
||||
<key>bundle-version</key>
|
||||
<string><![CDATA[${aPackage.version}]]></string>
|
||||
<key>kind</key>
|
||||
<string>software</string>
|
||||
<key>title</key>
|
||||
<string><![CDATA[${aPackage.name}]]></string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict></plist>
|
||||
BIN
src/main/resources/server.pkcs12
Normal file
BIN
src/main/resources/static/.DS_Store
vendored
Normal file
BIN
src/main/resources/static/crt/.DS_Store
vendored
Normal file
20
src/main/resources/static/crt/ca.crt
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDVDCCAjwCCQCZ0rtCbbUBAjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJD
|
||||
TjERMA8GA1UECAwIWmhlSmlhbmcxETAPBgNVBAcMCEhhbmdaaG91MSEwHwYDVQQL
|
||||
DBhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxFDASBgNVBAMMCzE5Mi4xNjguMC4q
|
||||
MB4XDTE5MDYzMDA4MjI0NFoXDTI5MDYyNzA4MjI0NFowbDELMAkGA1UEBhMCQ04x
|
||||
ETAPBgNVBAgMCFpoZUppYW5nMREwDwYDVQQHDAhIYW5nWmhvdTEhMB8GA1UECwwY
|
||||
RG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRQwEgYDVQQDDAsxOTIuMTY4LjAuKjCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIEYFPCU5m4kP3/IqAzX21q
|
||||
8rRzwpwe0FMxef3Q3NI64H6Y+Hi064WZuX5W9PuAHyyBuxUYTObMC+AI6rmtV8na
|
||||
5+oGrpnJH0dBjtLL/04unFs5ArSd2ob5hQDZ+bukJMo8ffiOw4T882P9wEiRBNXw
|
||||
RQOJBKuG4zAhDLyAgcpZ96g4Is+3TtgfQnVfbSWIcT6B+mS7yVKJM+Ov0twWb60q
|
||||
94Fk8NlPyq5AhIiM8O7W74IMr9Uu/woMMJ91wJoBnp5MqDl79ebGQzuzOHY0E1VX
|
||||
qyp2RkAZgzEgGOpwLXWOzYfGu2A+HdaMleCZQ2d/xF81w17x6hue3CCS0AIqvMUC
|
||||
AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAlEeut6xHWX7H58c2uqUtak7AYS1P0KmB
|
||||
hN1zuFQ/lwn87TQ9JtLJK8Ock+2mIDadnRzKPPwGfKlKgNRT6kj+MYixnL+xeiE8
|
||||
0bIPs2RAPu0XGbbPFIX9VeHC7gTbixbjPQY8MmrvgTizwnooAdrFg9XrkN9BNgj/
|
||||
f43kwlEA9lw0L8hjcr1EM86IQCFXTUzLdJAW13WrVMQsO7ZVz/09mtHnH0GEVfTv
|
||||
jn/j7Sykr9sEwBNBtKSmbCahSj2CBft4WZl0PyE1LnEbeu29e6xN0XUh9C6OUyXZ
|
||||
wTAwy+aaBE2XID9nHHH0G9EVHlDgm9SnvjNRN8nXag1/z2iESkqK/A==
|
||||
-----END CERTIFICATE-----
|
||||
BIN
src/main/resources/static/css/.DS_Store
vendored
Normal file
6
src/main/resources/static/css/bootstrap-theme.min.css
vendored
Normal file
6
src/main/resources/static/css/bootstrap.css
vendored
Normal file
3275
src/main/resources/static/css/download.css
Normal file
BIN
src/main/resources/static/css/fonts/.DS_Store
vendored
Normal file
BIN
src/main/resources/static/css/fonts/d_icomoon.ttf
Normal file
BIN
src/main/resources/static/css/fonts/icomoon.ttf
Normal file
459
src/main/resources/static/css/icons.css
Normal file
@@ -0,0 +1,459 @@
|
||||
@font-face {
|
||||
font-family: partner;
|
||||
src: url(./fonts/ipartner.eot?rkodm6);
|
||||
src: url(./fonts/ipartner.eot?rkodm6#iefix) format("embedded-opentype"), url(./fonts/ipartner.ttf?rkodm6) format("truetype"), url(./fonts/ipartner.woff?rkodm6) format("woff"), url(./fonts/ipartner.svg?rkodm6#ipartner) format("svg");
|
||||
font-weight: 400;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
[class*=" ipartner-"], [class^=ipartner-] {
|
||||
font-family: partner!important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
}
|
||||
|
||||
.ipartner-swift:before {
|
||||
content: "\e900"
|
||||
}
|
||||
|
||||
.ipartner-qingcloud:before {
|
||||
content: "\e902"
|
||||
}
|
||||
|
||||
.ipartner-devlink:before {
|
||||
content: "\e920"
|
||||
}
|
||||
|
||||
.ipartner-logo-pingxx:before {
|
||||
content: "\e921"
|
||||
}
|
||||
|
||||
.ipartner-logo-qiniu:before {
|
||||
content: "\e922"
|
||||
}
|
||||
|
||||
.ipartner-logo-leancloud:before {
|
||||
content: "\e923"
|
||||
}
|
||||
|
||||
.ipartner-logo-flowci:before {
|
||||
content: "\e924"
|
||||
}
|
||||
|
||||
.ipartner-deveco:before {
|
||||
content: "\e925"
|
||||
}
|
||||
|
||||
.ipartner-wilddog:before {
|
||||
content: "\e926"
|
||||
}
|
||||
|
||||
.ipartner-xitu:before {
|
||||
content: "\e927"
|
||||
}
|
||||
|
||||
.ipartner-36kr:before {
|
||||
content: "\e928"
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: icomoon;
|
||||
src: url(./fonts/icomoon.eot?wcusdg);
|
||||
src: url(./fonts/icomoon.eot?wcusdg#iefix) format("embedded-opentype"), url(./fonts/icomoon.ttf?wcusdg) format("truetype"), url(./fonts/icomoon.woff?wcusdg) format("woff"), url(./fonts/icomoon.svg?wcusdg#icomoon) format("svg");
|
||||
font-weight: 400;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
[class*=" icon-"], [class^=icon-] {
|
||||
font-family: icomoon!important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
}
|
||||
|
||||
.icon-cake:before {
|
||||
content: "\e900"
|
||||
}
|
||||
|
||||
.icon-weibo:before {
|
||||
content: "\e600"
|
||||
}
|
||||
|
||||
.icon-wechat:before {
|
||||
content: "\e601"
|
||||
}
|
||||
|
||||
.icon-firim:before {
|
||||
content: "\e602"
|
||||
}
|
||||
|
||||
.icon-bug:before {
|
||||
content: "\e603"
|
||||
}
|
||||
|
||||
.icon-ion:before {
|
||||
content: "\e604"
|
||||
}
|
||||
|
||||
.icon-angle-right:before {
|
||||
content: "\e606"
|
||||
}
|
||||
|
||||
.icon-cloud-download:before {
|
||||
content: "\e607"
|
||||
}
|
||||
|
||||
.icon-question:before {
|
||||
content: "\e608"
|
||||
}
|
||||
|
||||
.icon-optimize-upload:before {
|
||||
content: "\e609"
|
||||
}
|
||||
|
||||
.icon-console:before {
|
||||
content: "\e60a"
|
||||
}
|
||||
|
||||
.icon-microscope:before {
|
||||
content: "\e60b"
|
||||
}
|
||||
|
||||
.icon-user-access:before {
|
||||
content: "\e60c"
|
||||
}
|
||||
|
||||
.icon-logo-jiecao:before {
|
||||
content: "\e60d"
|
||||
}
|
||||
|
||||
.icon-m:before {
|
||||
content: "\e60e"
|
||||
}
|
||||
|
||||
.icon-f:before {
|
||||
content: "\e60f"
|
||||
}
|
||||
|
||||
.icon-plugin:before {
|
||||
content: "\e610"
|
||||
}
|
||||
|
||||
.icon-launch:before {
|
||||
content: "\e611"
|
||||
}
|
||||
|
||||
.icon-brace-left:before {
|
||||
content: "\e613"
|
||||
}
|
||||
|
||||
.icon-logo:before {
|
||||
content: "\e620"
|
||||
}
|
||||
|
||||
.icon-thumbsup:before {
|
||||
content: "\e615"
|
||||
}
|
||||
|
||||
.icon-webhooks:before {
|
||||
content: "\e616"
|
||||
}
|
||||
|
||||
.icon-brace-right:before {
|
||||
content: "\e617"
|
||||
}
|
||||
|
||||
.icon-comma-eye:before {
|
||||
content: "\e618"
|
||||
}
|
||||
|
||||
.icon-mouth:before {
|
||||
content: "\e619"
|
||||
}
|
||||
|
||||
.icon-brace-hor:before {
|
||||
content: "\e61a"
|
||||
}
|
||||
|
||||
.icon-brace-right-b:before {
|
||||
content: "\e61b"
|
||||
}
|
||||
|
||||
.icon-brace-left-b:before {
|
||||
content: "\e61c"
|
||||
}
|
||||
|
||||
.icon-brace-box:before {
|
||||
content: "\e61d"
|
||||
}
|
||||
|
||||
.icon-menu:before {
|
||||
content: "\e61e"
|
||||
}
|
||||
|
||||
.icon-app:before {
|
||||
content: "\e61f"
|
||||
}
|
||||
|
||||
.icon-box:before {
|
||||
content: "\e620"
|
||||
}
|
||||
|
||||
.icon-combo:before {
|
||||
content: "\e621"
|
||||
}
|
||||
|
||||
.icon-device:before {
|
||||
content: "\e622"
|
||||
}
|
||||
|
||||
.icon-face:before {
|
||||
content: "\e623"
|
||||
}
|
||||
|
||||
.icon-file:before {
|
||||
content: "\e624"
|
||||
}
|
||||
|
||||
.icon-msg:before {
|
||||
content: "\e625"
|
||||
}
|
||||
|
||||
.icon-pen:before {
|
||||
content: "\e626"
|
||||
}
|
||||
|
||||
.icon-ipa:before {
|
||||
content: "\e627"
|
||||
}
|
||||
|
||||
.icon-lock2:before {
|
||||
content: "\e628"
|
||||
}
|
||||
|
||||
.icon-qrcode:before {
|
||||
content: "\e629"
|
||||
}
|
||||
|
||||
.icon-rollback:before {
|
||||
content: "\e62a"
|
||||
}
|
||||
|
||||
.icon-eye:before {
|
||||
content: "\e62b"
|
||||
}
|
||||
|
||||
.icon-plus:before {
|
||||
content: "\e62c"
|
||||
}
|
||||
|
||||
.icon-chart:before {
|
||||
content: "\e62d"
|
||||
}
|
||||
|
||||
.icon-idea:before {
|
||||
content: "\e62e"
|
||||
}
|
||||
|
||||
.icon-owner:before {
|
||||
content: "\e62f"
|
||||
}
|
||||
|
||||
.icon-search:before {
|
||||
content: "\e630"
|
||||
}
|
||||
|
||||
.icon-studio:before {
|
||||
content: "\e631"
|
||||
}
|
||||
|
||||
.icon-upload-cloud2:before {
|
||||
content: "\e632"
|
||||
}
|
||||
|
||||
.icon-android:before {
|
||||
content: "\e633"
|
||||
}
|
||||
|
||||
.icon-trash:before {
|
||||
content: "\e634"
|
||||
}
|
||||
|
||||
.icon-attachment:before {
|
||||
content: "\e635"
|
||||
}
|
||||
|
||||
.icon-apple:before {
|
||||
content: "\e636"
|
||||
}
|
||||
|
||||
.icon-calendar:before {
|
||||
content: "\e637"
|
||||
}
|
||||
|
||||
.icon-calendar2:before {
|
||||
content: "\e638"
|
||||
}
|
||||
|
||||
.icon-eye-close:before {
|
||||
content: "\e639"
|
||||
}
|
||||
|
||||
.icon-reply:before {
|
||||
content: "\e63a"
|
||||
}
|
||||
|
||||
.icon-email:before {
|
||||
content: "\e63b"
|
||||
}
|
||||
|
||||
.icon-error2:before {
|
||||
content: "\e63c"
|
||||
}
|
||||
|
||||
.icon-cross:before {
|
||||
content: "\e63d"
|
||||
}
|
||||
|
||||
.icon-user-plus:before {
|
||||
content: "\e63e"
|
||||
}
|
||||
|
||||
.icon-ios:before {
|
||||
content: "\e63f"
|
||||
}
|
||||
|
||||
.icon-filter:before {
|
||||
content: "\e640"
|
||||
}
|
||||
|
||||
.icon-test-speed:before {
|
||||
content: "\e641"
|
||||
}
|
||||
|
||||
.icon-udid:before {
|
||||
content: "\e642"
|
||||
}
|
||||
|
||||
.icon-update:before {
|
||||
content: "\e643"
|
||||
}
|
||||
|
||||
.icon-comma:before {
|
||||
content: "\e644"
|
||||
}
|
||||
|
||||
.icon-i:before {
|
||||
content: "\e645"
|
||||
}
|
||||
|
||||
.icon-r:before {
|
||||
content: "\e646"
|
||||
}
|
||||
|
||||
.icon-f-dot:before {
|
||||
content: "\e647"
|
||||
}
|
||||
|
||||
.icon-layers:before {
|
||||
content: "\e648"
|
||||
}
|
||||
|
||||
.icon-news:before {
|
||||
content: "\e649"
|
||||
}
|
||||
|
||||
.icon-percent:before {
|
||||
content: "\e64a"
|
||||
}
|
||||
|
||||
.icon-bughd:before {
|
||||
content: "\e64b"
|
||||
}
|
||||
|
||||
.icon-incode:before {
|
||||
content: "\e64c"
|
||||
}
|
||||
|
||||
.icon-message:before {
|
||||
content: "\e64d"
|
||||
}
|
||||
|
||||
.icon-eclipse:before {
|
||||
content: "\e64e"
|
||||
}
|
||||
|
||||
.icon-turkey:before {
|
||||
content: "\e901"
|
||||
}
|
||||
|
||||
.icon-jenkins:before {
|
||||
content: "\e902"
|
||||
}
|
||||
|
||||
.icon-gradle:before {
|
||||
content: "\e903"
|
||||
}
|
||||
|
||||
.icon-statistics:before {
|
||||
content: "\e904"
|
||||
}
|
||||
|
||||
.icon-done:before {
|
||||
content: "\e905"
|
||||
}
|
||||
|
||||
.icon-qiniu:before {
|
||||
content: "\e906"
|
||||
}
|
||||
|
||||
.icon-logo-leancloud:before {
|
||||
content: "\e907"
|
||||
}
|
||||
|
||||
.icon-logo-jd:before {
|
||||
content: "\e908"
|
||||
}
|
||||
|
||||
.icon-logo-xiachufang:before {
|
||||
content: "\e909"
|
||||
}
|
||||
|
||||
.icon-logo-ebaoyang:before {
|
||||
content: "\e90a"
|
||||
}
|
||||
|
||||
.icon-logo-jumei:before {
|
||||
content: "\e90b"
|
||||
}
|
||||
|
||||
.icon-cart:before {
|
||||
content: "\e90c"
|
||||
}
|
||||
|
||||
.icon-users:before {
|
||||
content: "\e90d"
|
||||
}
|
||||
|
||||
.icon-spinner3:before {
|
||||
content: "\e97c"
|
||||
}
|
||||
|
||||
.icon-infinite:before {
|
||||
content: "\ea2f"
|
||||
}
|
||||
|
||||
.icon-spinner:before {
|
||||
content: "\f110"
|
||||
}
|
||||
4746
src/main/resources/static/css/index.css
Normal file
1
src/main/resources/static/css/manage.css
Normal file
BIN
src/main/resources/static/images/download_pattern_left.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
src/main/resources/static/images/download_pattern_right.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
src/main/resources/static/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/main/resources/static/js/.DS_Store
vendored
Normal file
4
src/main/resources/static/js/jquery-1.11.0.min.js
vendored
Normal file
1
src/main/resources/static/js/qrcode.js
Normal file
183
src/main/resources/templates/index.html
Normal file
@@ -0,0 +1,183 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge">
|
||||
<meta name="renderer" content="webkit">
|
||||
|
||||
<title>应用列表</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/images/favicon.ico}" />
|
||||
<link rel="stylesheet" th:href="@{/css/icons.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/manage.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/index.css}">
|
||||
|
||||
<script type="text/javascript" th:src="@{/js/jquery-1.11.0.min.js}"></script>
|
||||
</head>
|
||||
|
||||
<body class="ng-scope">
|
||||
<nav class="navbar navbar-transparent fade-out navbar-black" role="navigation">
|
||||
<div class="navbar-header"><a class="navbar-brand" href="/apps"><i class="icon-logo"></i> </a></div>
|
||||
<div class="collapse navbar-collapse navbar-ex1-collapse ng-scope" ng-controller="NavbarController">
|
||||
<div class="dropdown">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="menu-toggle fade-out"><i class="icon-menu"></i></div>
|
||||
<div class="navbar-wrapper ng-scope">
|
||||
<div ng-controller="NavbarController" class="ng-scope">
|
||||
<div class="navbar-header-wrap">
|
||||
<div class="middle-wrapper">
|
||||
<nav>
|
||||
<h1 class="navbar-title logo">
|
||||
<i class="icon-logo"></i>
|
||||
</h1>
|
||||
<i class="icon-angle-right"></i>
|
||||
<div class="navbar-title primary-title">
|
||||
<a class="ng-binding" th:href="${baseURL} + 'apps'">我的应用</a>
|
||||
</div>
|
||||
<i class="icon-angle-right ng-hide"></i>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- ngInclude: '/templates_manage/upload_modal.html' -->
|
||||
<section data-ui-view="" class="ng-scope" style="">
|
||||
<div class="page-apps ng-scope">
|
||||
<div class="middle-wrapper">
|
||||
</div><!-- ngIf: !appsReady -->
|
||||
<div class="middle-wrapper container-fluid" ng-show="appsReady">
|
||||
<div class="apps row">
|
||||
<upload-card id="uploadCard" class="components-upload-card col-xs-4 col-sm-4 col-md-4 app-animator" >
|
||||
<div class="card text-center">
|
||||
<div class="dashed-space">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="icon-upload-cloud2"></i>
|
||||
<div class="text drag-state">
|
||||
<span id="upload-progress" translate="DRAG_TO_UPLOAD" class="ng-scope">拖拽到这里上传</span>
|
||||
<span translate="DROP_TO_UPLOAD" class="ng-scope">快松手</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</upload-card>
|
||||
<div th:each="app : ${apps}" class="col-xs-4 col-sm-4 col-md-4 app-animator ng-scope">
|
||||
<div th:class="'card app ' + @{'card-' + ${app.platform}}">
|
||||
<i th:class="'type-icon ' + @{'icon-' + ${app.platform}}"></i>
|
||||
<div class="type-mark"></div>
|
||||
<a class="appicon" th:href="'/apps/' + ${app.id}" target="_blank">
|
||||
<img class="icon ng-isolate-scope" width="100" height="100" th:src="@{'/' + ${app.icon}}" />
|
||||
</a>
|
||||
<!-- ngIf: app.has_combo --><br>
|
||||
<p class="appname" th:data="@{${baseURL} + 'apps/' + ${app.id}}">
|
||||
<i class="icon-owner"></i>
|
||||
<span class="ng-binding">[[${app.name}]]</span></p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="ng-binding">短链接:</td>
|
||||
<td><span class="ng-binding">[[${baseURL}]]s/[[${app.shortCode}]]</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="ng-binding">包名:</td>
|
||||
<td>
|
||||
<span title="com.mistong.ewt360" class="ng-binding">[[${app.bundleID}]]</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="ng-binding">版本:</td>
|
||||
<td>
|
||||
<span class="ng-binding">[[${app.version}]] ( Build [[${app.buildVersion}]] )</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="action">
|
||||
<a class="ng-binding" th:href="'/apps/' + ${app.id}">
|
||||
<i class="icon-pen"></i> 编辑</a>
|
||||
<a th:href="@{${baseURL}+'s/'+${app.shortCode}}" target="_blank" class="ng-binding">
|
||||
<i class="icon-eye"></i> 预览</a>
|
||||
<button class="btn btn-remove ng-scope" th:data="${app.id}">
|
||||
<i class="icon icon-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div alert-bar="" class="alert-bar ng-hide" ng-show="anyErrors">
|
||||
<div class="action" ng-click="close()"></div>
|
||||
<div class="inner">
|
||||
<p ng-bind="errors" class="ng-binding"></p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var dashboard = document.getElementById("uploadCard")
|
||||
dashboard.addEventListener("dragover", function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
dashboard.addEventListener("dragenter", function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
dashboard.addEventListener("drop", function (e) {
|
||||
// 必须要禁用浏览器默认事件
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var files = this.files || e.dataTransfer.files
|
||||
var file = files[0]
|
||||
//上传
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("post", "/app/upload", true);
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
// 获取上传进度
|
||||
xhr.upload.onprogress = function (event) {
|
||||
if (event.lengthComputable) {
|
||||
var percent = Math.floor(event.loaded / event.total * 100);
|
||||
var uploadText = "拖拽到这里上传"
|
||||
var uploadElement = document.getElementById("upload-progress")
|
||||
if (percent < 100) {
|
||||
uploadElement.innerText="正在上传:" + percent + "%"
|
||||
} else {
|
||||
uploadElement.innerText=uploadText
|
||||
}
|
||||
}
|
||||
};
|
||||
// 上传完成
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
window.location.href=window.location.href
|
||||
window.location.reload
|
||||
}
|
||||
}
|
||||
var fd = new FormData();
|
||||
fd.append('file', file);
|
||||
xhr.send(fd);
|
||||
})
|
||||
|
||||
$(".btn-remove").click(function () {
|
||||
var url = "/app/delete/" + $(this).attr("data");
|
||||
$.ajax({url:url,success:function(result){
|
||||
window.location.href=window.location.href
|
||||
window.location.reload
|
||||
}
|
||||
});
|
||||
});
|
||||
$(".appname").click(function () {
|
||||
window.open($(this).attr("data"))
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
102
src/main/resources/templates/install.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport"
|
||||
content="minimal-ui,width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/images/favicon.ico}" />
|
||||
<link rel="stylesheet" th:href="@{/css/download.css}">
|
||||
<script type="text/javascript" th:src="@{/js/qrcode.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/jquery-1.11.0.min.js}"></script>
|
||||
<title>[[${app.name}]]</title>
|
||||
</head>
|
||||
|
||||
<body class="app">
|
||||
<div class="masklayer" id="MaskLayer" style="display:none"></div>
|
||||
<span class="pattern left">
|
||||
<img th:src="@{/images/download_pattern_left.png}" />
|
||||
</span>
|
||||
<span class="pattern right">
|
||||
<img th:src="@{/images/download_pattern_right.png}" />
|
||||
</span>
|
||||
<div class="wechat_tip_content"></div>
|
||||
<div class="out-container">
|
||||
<div class="main">
|
||||
<header itemscope="" itemtype="http://schema.org/SoftwareApplication">
|
||||
<div class="table-container">
|
||||
<div class="cell-container">
|
||||
<div class="app-brief">
|
||||
<div class="icon-container wrapper">
|
||||
<i class="icon-icon_path bg-path"></i>
|
||||
<span class="icon">
|
||||
<img th:src="@{'/' + ${app.icon}}" itemprop="image">
|
||||
</span>
|
||||
<span class="qrcode" id="qrcode" th:data="${app.currentPackage.previewURL}">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="release-type wrapper">内测版</p>
|
||||
|
||||
<h1 class="name wrapper">
|
||||
<span class="icon-warp">
|
||||
<i th:class="'icon-'+ ${app.platform}"></i>
|
||||
[[${app.name}]]
|
||||
</span>
|
||||
|
||||
</h1>
|
||||
<p class="scan-tips">扫描二维码下载<br>或用手机浏览器输入这个网址: <span
|
||||
class="text-black">[[${app.installPath}]]</span></p>
|
||||
<div class="release-info">
|
||||
<p>内测版 -
|
||||
<span itemprop="softwareVersion">[[${app.version}]] (Build [[${app.buildVersion}]]) -
|
||||
[[${app.currentPackage.displaySize}]]</span></p>
|
||||
<p>更新于: <span itemprop="datePublished">[[${app.currentPackage.displayTime}]]</span></p>
|
||||
</div>
|
||||
<div class="action-animate">
|
||||
<input id="installURL" th:value="${app.currentPackage.installURL}" style="display: none" />
|
||||
<div class="action-animate-text" id="install">下载安装</div>
|
||||
<div class="action-animate-active"></div>
|
||||
</div>
|
||||
<div class="action-animate">
|
||||
<input id="crtURL" th:value="${ca_path}" style="display: none"/>
|
||||
<div class="action-animate-text" id="installCRT">安装证书</div>
|
||||
<div class="action-animate-active"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
<!-- Release list -->
|
||||
</div>
|
||||
</div>
|
||||
<script type="application/javascript">
|
||||
$("#install").click(function () {
|
||||
window.location.href = ($("#installURL").val())
|
||||
})
|
||||
|
||||
$("#installCRT").click(function () {
|
||||
window.open($("#crtURL").val())
|
||||
})
|
||||
|
||||
var codeData = $("#qrcode").attr("data");
|
||||
new QRCode("qrcode", {
|
||||
text: codeData,
|
||||
width: 200,
|
||||
height: 200,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.L
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
161
src/main/resources/templates/list.html
Normal file
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Stict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge">
|
||||
<meta name="renderer" content="webkit">
|
||||
|
||||
<title class="ng-binding">[[${package.name}]] - 应用动态</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/images/favicon.ico}" />
|
||||
<link rel="stylesheet" th:href="@{/css/icons.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/manage.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/index.css}">
|
||||
<script type="text/javascript" th:src="@{/js/jquery-1.11.0.min.js}"></script>
|
||||
</head>
|
||||
|
||||
<body class="ng-scope">
|
||||
<nav class="navbar navbar-transparent fade-out navbar-black">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/apps"><i class="icon-logo"></i> </a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-ex1-collapse ng-scope">
|
||||
<div class="dropdown">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="menu-toggle fade-out"><i class="icon-menu"></i></div>
|
||||
<div class="navbar-wrapper ng-scope">
|
||||
<div ng-controller="NavbarController" class="ng-scope">
|
||||
<div class="navbar-header-wrap">
|
||||
<div class="middle-wrapper">
|
||||
<nav>
|
||||
<h1 class="navbar-title logo">
|
||||
<i class="icon-logo"></i>
|
||||
</h1>
|
||||
<i class="icon-angle-right"></i>
|
||||
<div class="navbar-title primary-title">
|
||||
<a class="ng-binding" href="/apps">我的应用</a>
|
||||
</div>
|
||||
<i class="icon-angle-right"></i>
|
||||
<div class="navbar-title secondary-title ng-binding"
|
||||
style="">[[${package.name}]]</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- ngInclude: '/templates_manage/upload_modal.html' -->
|
||||
<section data-ui-view="" class="ng-scope" style="">
|
||||
<div class="page-app app-activities">
|
||||
<div class="banner has-devices">
|
||||
<div class="middle-wrapper clearfix">
|
||||
<div class="pull-left icon-container appicon">
|
||||
<img th:src="'/' + ${package.icon}" width="100" height="100" class="change_icon ng-isolate-scope"/>
|
||||
</div>
|
||||
<div class="badges">
|
||||
<span tooltip-top="" tooltip="复制到剪贴板" id="js-app-short-copy-trigger"
|
||||
class="short tooltip-top ng-binding ng-isolate-scope" th:value="${package.installPath}"
|
||||
copy-trigger="">[[${package.installPath}]]</span>
|
||||
<span class="apptype ng-binding" th:if="${#strings.containsIgnoreCase(package.platform,'ios')}">iOS</span>
|
||||
<span class="apptype ng-binding" th:if="${#strings.containsIgnoreCase(package.platform,'android')}">Android</span>
|
||||
<span class="bundleid ng-binding">BundleID<b class="ng-binding"> [[${package.bundleID}]]</b></span>
|
||||
<span class="version ng-scope" th:if="${#strings.containsIgnoreCase(package.platform,'ios')}" >iOS [[${package.minVersion}]] 或者高版本</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="download ng-binding"th:href="${package.installPath}" target="_blank">
|
||||
<i class="icon-eye"></i>
|
||||
预览
|
||||
</a>
|
||||
</div>
|
||||
<div class="tabs-container">
|
||||
<ul class="list-inline">
|
||||
<li>
|
||||
<a class="ng-binding"><i class="icon-file"></i> 基本信息</a></li>
|
||||
<li>
|
||||
<a class="ng-binding"><i class="icon-device"></i> 设备列表</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- uiView: -->
|
||||
<div data-ui-view="" class="ng-scope">
|
||||
<div class="page-app-activities page-tabcontent ng-scope">
|
||||
<!-- ngIf: !activitiesReady -->
|
||||
<div class="middle-wrapper" ng-show="activitiesReady">
|
||||
<ul class="list-unstyled time-line">
|
||||
<li>
|
||||
<span class="dot"></span>
|
||||
<span class="filter ng-binding">版本更新</span>
|
||||
<span class="filter version-rollback ng-scope"></span>
|
||||
</li>
|
||||
<li>
|
||||
<div class="market-app-info">
|
||||
</div>
|
||||
</li>
|
||||
<li th:each="app,appStat : ${apps}">
|
||||
<div>
|
||||
<div class="directive-view-release">
|
||||
<i class="icon-upload-cloud2"></i>
|
||||
<b class="ng-binding">[[${app.version}]] (Build [[${app.buildVersion}]])</b>
|
||||
<div class="release-metainfo ng-hide">
|
||||
<small>
|
||||
<i class="icon-calendar"></i>
|
||||
<span class="ng-binding">[[${app.displayTime}]]</span>
|
||||
</small>
|
||||
</div>
|
||||
<div class="release-metainfo">
|
||||
<small>
|
||||
<i class="icon-calendar"></i>
|
||||
<span class="ng-binding">[[${app.displayTime}]]</span></small> ·
|
||||
<small class="ng-scope">内测版</small>
|
||||
<i class="ng-hide"> · </i>
|
||||
<small class="ng-binding ng-hide"></small>
|
||||
</div>
|
||||
<div class="release-actions">
|
||||
<button class="tooltip-top download-action" tooltip="下载原文件" th:value="${app.downloadURL}">
|
||||
<i class="icon-cloud-download"></i>
|
||||
<span class="ng-binding">[[${app.displaySize}]]</span>
|
||||
</button>
|
||||
<button class="preview" th:value="${app.previewURL}">
|
||||
<i class="icon-eye"></i>
|
||||
<span class="ng-binding">预览</span>
|
||||
</button>
|
||||
<button class="ng-scope app-delete" th:data="${app.id}" th:if="${appStat.index > 0}">
|
||||
<i class="icon-trash"></i>
|
||||
<span class="ng-binding">删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="more ng-hide" ng-show="currentApp.releases.current_page < currentApp.releases.total_pages">
|
||||
<button ng-click="moreRelease()" class="ng-binding">显示更多版本</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script type="application/javascript">
|
||||
$(".download-action").click(function(){
|
||||
window.open($(this).val())
|
||||
})
|
||||
|
||||
$(".preview").click(function () {
|
||||
window.open($(this).val())
|
||||
})
|
||||
|
||||
|
||||
$(".app-delete").click(function () {
|
||||
var url = "/p/delete/" + $(this).attr("data");
|
||||
$.ajax({url:url,success:function(result){
|
||||
window.location.href=window.location.href
|
||||
window.location.reload
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
43
src/test/java/org/yzr/main/ApplicationTests.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package org.yzr.main;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.yzr.model.Package;
|
||||
import org.yzr.service.PackageService;
|
||||
import org.yzr.utils.ipa.PlistGenerator;
|
||||
import org.yzr.utils.parser.ParserClient;
|
||||
import org.yzr.vo.PackageViewModel;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class ApplicationTests {
|
||||
@Resource
|
||||
private PackageService packageService;
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
Package aPackage = new Package();
|
||||
aPackage.setName("升学e网通");
|
||||
aPackage.setBundleID("org.yzr.test");
|
||||
aPackage.setVersion("6.9.7");
|
||||
this.packageService.save(aPackage);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testFileName() {
|
||||
// Package aPackage = new Package();
|
||||
// aPackage.setName("升学e网通");
|
||||
// aPackage.setBundleID("org.yzr.test");
|
||||
// aPackage.setVersion("6.9.7");
|
||||
// PackageViewModel viewModel = new PackageViewModel(aPackage);
|
||||
// PlistGenerator.generate(viewModel, "/Users/zhaorongyi/Documents/Learn/intranet_app_manager/out/test.plist");
|
||||
// }
|
||||
}
|
||||