1、引入pom
<dependency>
<groupId>com.googlecode.plist</groupId>
<artifactId>dd-plist</artifactId>
<version>1.23</version>
</dependency>
2、IosParseUtil工具类
/**
* ios安装包解析工具类
*/
@Log4j2
public class IosParseUtil {
private static final String INFO_PLIST = "info.plist";
private static final String CONFIG_PLIST = "config.plist";
private static final String EMBEDDED_MOBILEPROVISION = "embedded.mobileprovision";
public static JSONObject parseIPA(MultipartFile multipartFile, String appName) throws Exception {
log.info("开始进行解析ios安装包操作");
String version = null; // 内部版本
String displayVer = null; // 显示版本号
Map<String, String> iplist = getInfoPlistFromIPA(multipartFile);
String cplist = getConfigPlistFromIPA(multipartFile);
String teamId = getAppIdFromIPA(multipartFile);
String appId = iplist.get("appId"); // ios的appid
String appTitle = iplist.get("appTitle"); // ios的应用名称
appTitle = StringUtil.isEmpty(appTitle) ? appName : appTitle;
log.info("开始获取版本号");
if(StringUtil.isNotEmpty(cplist)) {
InputStream cplistIn = new ByteArrayInputStream(cplist.getBytes("utf-8"));
HashMap<String, Object> cplistMap = PlistHandler.getMapFromPlist(cplistIn);
version = String.valueOf(cplistMap.get("IPAVersionNum"));
displayVer = String.valueOf(cplistMap.get("IPAVersionDisplay"));
}
JsonObject json = new JSONObject();
json.put("teamId", teamId);
json.put("appId", appId);
json.put("appTitle", appTitle);
json.put("deviceType", "Iphone");
json.put("displayVer", displayVer);
json.put("platform", "IOS");
json.put("version", version);
log.info("解析完成");
return json;
}
/**
* 解析info.plist文件返回AppId和AppName
*/
private static Map<String, String> getInfoPlistFormIPA(MultipartFile multipartFile) throws Exception {
String plistPath = getPlistFromIPA(multipartFile, INFO_PLIST);
String plist1 = null;
String appId = null;
String appTitle = null;
Map<String, String> map = new HashMap<>();
if(StringUtil.isNotEmpty(plistPath)) {
File file = new File(plistPath);
if(file != null && file.exists()) {
FileInputStream fis = new FileInputStream(file);
plist1= convertToString(fis); // 转为string
String plist2 = plist1.replace("<!DOCTYPE", "<!-- !DOCTYPE"); // 注释掉需要联外网才可以查询到www.apple.com,保证断网也可以正常使用
String plist = plist2.replace("dtd\">", "dtd\"> -->"); // 注释掉需要联外网才可以查询到www.apple.com,保证断网也可以正常使用
log.info("string转为xml doc,开始...");
StringReader reader = new StringReader(plist);
InputSource is = new InputSource(reader);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 获取dom解析工厂
DocumentBuilderImpl builder = (DocumentBuilderImpl)factory.newDocumentBuilder(); // 获取具体的dom解析器
DeferredDoumentImpl doc = (DeferredDoumentImpl)builder.parse(is);
log.info("获取doc节点中的信息,开始...");
NodeList list = doc.getElementByTagName("key");
for(int i = 0; i < list.getLength(); i++) {
String keyValue = list.item(i).getFirstChild().getNodeValue();
if("CFBundleIdentifier").equals(keyValue) {
Node idNode = list.item(i).getNextSibling(); // 获取id
while(idNode.getNodeType() != 1){
// 若节点类型非元素
idNode = idNode.getNextSibling();
}
appId = idNode.getFirstChild().getNodeValue();
log.info("成功获取到CFBundleIdentifier,值为:" + appId);
}
if("CFBundleDisplayName").equals(keyValue) {
Node nameNode = list.item(i).getNextSibling(); // 获取名字
while(nameNode.getNodeType() != 1){
// 若节点类型非元素
nameNode = nameNode.getNextSibling();
}
appTitle = nameNode.getFirstChild().getNodeValue();
log.info("成功获取到CFBundleDisplayName,值为:" + appTitle);
}
}
fis.close();
file.delete();
}
}
map.put("appId", appId);
map.put("appTitle", appTitle);
return map;
}
/**
* 获取ipa安装包中的plist文件并保存到本地
*/
private static String getPlistFromIPA(MultipartFile multipartFile, String plistName) throws IOException {
String base = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator");
ZipInputStream zis = new ZipInputStream(multipartFile.getInputStram());
ZipEntry zipEntry;
String xmlPath = null;
BufferedOutputStream dest;
while((entry = zis.getNextEntry()) != null) {
String fileName = entry.getName();
String[] dirName = fileName.split("/");
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
// 目标固定在第二级目录下的文件
if(dirName.length == 3 && plistName.equals(dirName[2])) {
int buffer = 2048;
int count;
byte data[] = new byte[buffer];
xmlPath = base + System.currentTimeMillis() + fileName;
FileOutputStream fos = new FileOutputStream(xmlPath);
dest = new BufferedOutputStream(fos, buffer);
while((count = zis.read(data, 0, buffer)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
break;
}
}
zis.close();
return xmlPath;
}
/**
* 从ipa文件中读取config.plist文件
*/
private static String getConfigPlistFromIPA(MultipartFile multipartFile) throws Exception {
String plistPath = getPlistFromIPA(multipartFile, CONFIG_PLIST);
String plist = null;
if(StringUtil.isNotEmpty(plistPath)) {
File file = new File(plistPath);
if(file != null && file.exists()) {
FileInputStream fis = new FileInputStream(file);
plist = convertToString(fis);
fis.close();
file.delete();
}
}
return plist;
}
public static String getAppIdFromIPA(MultipartFile multipartFile) throws Exception {
String plistPath = getPlistFromIPA(multipartFile, EMBEDDED_MOBILEPROVISION);
String plist = null;
if(StringUtil.isNotEmpty(plistPath)) {
File file = new File(plistPath);
if(file != null && file.exists()) {
InputStreamReader fis = new InputStreamReader(new FileInputStream(file));
BufferedReader br = new BufferedReader(fis);
String data = null;
while((data = br.readLine()) != null) {
if(data.contains("<key>application-identifier</key>")) {
plist = br.readLine();
plist = plist.substring(0, plist.indexOf("</string>"));
plist = plist.substring(plist.indexOf("<string>") + "<string>".length());
break;
}
}
fis.close();
file.delete();
}
}
return plist;
}
private static String convertToString(InputStream in) throws Exception {
NSObject root = parse(in);
return root.toXMLPropertyList();
}
private static NSObject parse(InputStream is) throws Exception {
if(is.markSupported()) {
is.mark(10);
String magicString = new String(readAll(is, 8), 0, 8);
is.reset();
if(magicString.startsWith("bplist00")) return BinaryPropertyListParser.parse(is);
if(magicString.startsWith("<?xml")) return XMLpropertyListParse.parse(is);
if((magicString.startsWith("(")) || (magicString.startsWith("{"))) return ASCIIPropertyListParser.parse(is);
thorw new BizException("the given data is neither a binary not a XML property list, ASCII property lists are not supported");
}
return parse(readAll(is, 2147483647));
}
private static NSObject parse(byte[] bytes) throws Exception {
String magicString = new String(bytes, 0, 8);
if(magicString.startsWith("bplist00")) return BinaryPropertyListParser.parse(is);
if(magicString.startsWith("<?xml")) return XMLpropertyListParse.parse(is);
if((magicString.startsWith("(")) || (magicString.startsWith("{"))) return ASCIIPropertyListParser.parse(is);
thorw new BizException("the given data is neither a binary not a XML property list, ASCII property lists are not supported");
}
private static byte[] readAll(InputStream in, int max) throws IOException {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
while (max > 0) {
int n = in.read();
if(n == -1) break;
buf.write(n);
max--;
}
return buf.toByteArray();
}
}
3、辅助类
public class PlistHandler extends DefaultHandler {
private boolean isRootElement = false;
private boolean keyElementBegin = false;
private String key;
Stack<Object> stack = new Stack<>();
private boolean valueElementBegin = false;
private Object root;
private HashMap<String, Object> getMapResult() { return (HashMap<String, Object>)root;}
@Override
public void startDocument() throws SAXException {}
@Override
public void endDocument() throws SAXException {}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if("plist".equals(qName)) {
isRootElement = true;
}
if("dict".equals(qName)) {
if(isRootElement) {
stack.push(new HashMap<String, Object>()); // 压栈
isRootElement = !isRootElement ;
} else {
Object object = stack.peek();
HashMap<String, Object> dict = new HashMap<>();
if(object instanceof ArrayList) ((ArrayList<Object>)object).add(dict);
else if(Object instanceof HashMap) ((HashMap<String, Object>)object).put(key, dict);
stack.push(dict);
}
}
if("key".equals(qName)) {
keyElementBegin = true;
}
if("true".equals(qName)) {
HashMap<String, Object> parent = (HashMap<String, Object>)stack.peek();
parent.put(key, true);
}
if("false".equals(qName)) {
HashMap<String, Object> parent = (HashMap<String, Object>)stack.peek();
parent.put(key, false);
}
if("array".equals(qName)) {
if(isRootElement) {
ArrayList<Object> obj = new ArrayList<>();
stack.push(obj);
isRootElement = !isRootElement;
} else {
HashMap<String, Object> parent = (HashMap<String, Object>) stack.peek();
ArrayList<Object> obj = new ArrayList<>();
stack.push(obj);
parent.put(key, obj);
}
}
if("string".equals(qName)) {
valueElementBegin = true;
}
}
/**
* 字符串解析(non-javadoc)
*/
@Override
public void characters(char[] ch, int start, int length) {
if(length > 0) {
if(keyElementBegin) key = new String(ch, start, length);
if(valueElementBegin) {
if(HashMap.class.equals(stack.peek().getClass())) {
HashMap<String, Object> parent = (HashMap<String, Object>)stack.peek();
String value = new String(ch, start, length);
parent.put(key, value);
} else if(ArrayList.class.equals(stack.peek().getClass())) {
HashMap<String, Object> parent = (HashMap<String, Object>)stack.peek();
String value = new String(ch, start, length);
parent.add(value);
}
}
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if("plist".equals(qName)) {}
if("key".equals(qName)) keyElementBegin = false;
if("string".equals(qName)) valueElementBegin = false;
if("array".equals(qName)) root = stack.pop();
if("dict".equals(qName)) root = stack.pop();
}
public static HashMap<String, Object> getMapFromPlist(InputStream in) {
HashMap<String, Object> hash = new HashMap<>();
try {
SAXParserFactory factorys = SAXParserFactory.newInstance();
factorys.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factorys.setFeature("http://apache.org/sax/features/validation", false);
SAXParser saxParser = factorys.newSAXParse();
PlistHndler plistHandler = new PlistHndler();
saxParser.parse(in, plistHandler);
hash = plistHandler.getMapFromPlist();
} catch(Exception ex) {
e.printStackTrace();
}
return hash;
}
}