Просмотр исходного кода

Merge branch 'master' of http://git.izouma.com/xiongzhu/raex_back into dev-meta

sunkean 3 лет назад
Родитель
Сommit
d5c337cca3
25 измененных файлов с 1194 добавлено и 110 удалено
  1. 230 0
      src/main/java/com/izouma/nineth/push/AndroidNotification.java
  2. 6 0
      src/main/java/com/izouma/nineth/push/App.java
  3. 93 0
      src/main/java/com/izouma/nineth/push/IOSNotification.java
  4. 102 0
      src/main/java/com/izouma/nineth/push/PushClient.java
  5. 273 0
      src/main/java/com/izouma/nineth/push/PushUtils.java
  6. 87 0
      src/main/java/com/izouma/nineth/push/UmengNotification.java
  7. 12 0
      src/main/java/com/izouma/nineth/push/android/AndroidBroadcast.java
  8. 23 0
      src/main/java/com/izouma/nineth/push/android/AndroidCustomizedcast.java
  9. 16 0
      src/main/java/com/izouma/nineth/push/android/AndroidFilecast.java
  10. 17 0
      src/main/java/com/izouma/nineth/push/android/AndroidGroupcast.java
  11. 17 0
      src/main/java/com/izouma/nineth/push/android/AndroidUnicast.java
  12. 13 0
      src/main/java/com/izouma/nineth/push/ios/IOSBroadcast.java
  13. 22 0
      src/main/java/com/izouma/nineth/push/ios/IOSCustomizedcast.java
  14. 16 0
      src/main/java/com/izouma/nineth/push/ios/IOSFilecast.java
  15. 16 0
      src/main/java/com/izouma/nineth/push/ios/IOSGroupcast.java
  16. 16 0
      src/main/java/com/izouma/nineth/push/ios/IOSUnicast.java
  17. 20 0
      src/main/java/com/izouma/nineth/web/PushController.java
  18. 1 1
      src/main/pc-space/src/components/ConsignmentInfo.vue
  19. 1 1
      src/main/pc-space/src/views/Send.vue
  20. 1 1
      src/main/resources/templates/EditViewTemplate.ftl
  21. 143 104
      src/main/vue/src/router.js
  22. 1 1
      src/main/vue/src/views/Admin.vue
  23. 1 1
      src/main/vue/src/views/MenuAuthority.vue
  24. 1 1
      src/main/vue/src/views/PhotoProcessing.vue
  25. 66 0
      src/main/vue/src/views/Push.vue

+ 230 - 0
src/main/java/com/izouma/nineth/push/AndroidNotification.java

@@ -0,0 +1,230 @@
+package com.izouma.nineth.push;
+
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+public abstract class AndroidNotification extends UmengNotification {
+    // Keys can be set in the payload level
+    protected static final HashSet<String> PAYLOAD_KEYS = new HashSet<>(Arrays.asList("display_type"));
+
+    // Keys can be set in the body level
+    protected static final HashSet<String> BODY_KEYS = new HashSet<>(Arrays.asList(
+            "ticker", "title", "text", "builder_id", "icon", "largeIcon", "img", "play_vibrate", "play_lights",
+            "play_sound", "sound", "after_open", "url", "activity", "custom"));
+
+    public enum DisplayType {
+        NOTIFICATION {
+            public String getValue() {
+                return "notification";
+            }
+        },///通知:消息送达到用户设备后,由友盟SDK接管处理并在通知栏上显示通知内容。
+        MESSAGE {
+            public String getValue() {
+                return "message";
+            }
+        };///消息:消息送达到用户设备后,消息内容透传给应用自身进行解析处理。
+
+        public abstract String getValue();
+    }
+
+    public enum AfterOpenAction {
+        go_app,//打开应用
+        go_url,//跳转到URL
+        go_activity,//打开特定的activity
+        go_custom//用户自定义内容。
+    }
+
+    // Set key/value in the rootJson, for the keys can be set please see ROOT_KEYS, PAYLOAD_KEYS,
+    // BODY_KEYS and POLICY_KEYS.
+    @Override
+    public boolean setPredefinedKeyValue(String key, Object value) throws Exception {
+        if (ROOT_KEYS.contains(key)) {
+            // This key should be in the root level
+            rootJson.put(key, value);
+        } else if (PAYLOAD_KEYS.contains(key)) {
+            // This key should be in the payload level
+            JSONObject payloadJson = null;
+            if (rootJson.containsKey("payload")) {
+                payloadJson = rootJson.getJSONObject("payload");
+            } else {
+                payloadJson = new JSONObject();
+                rootJson.put("payload", payloadJson);
+            }
+            payloadJson.put(key, value);
+        } else if (BODY_KEYS.contains(key)) {
+            // This key should be in the body level
+            JSONObject bodyJson = null;
+            JSONObject payloadJson = null;
+            // 'body' is under 'payload', so build a payload if it doesn't exist
+            if (rootJson.containsKey("payload")) {
+                payloadJson = rootJson.getJSONObject("payload");
+            } else {
+                payloadJson = new JSONObject();
+                rootJson.put("payload", payloadJson);
+            }
+            // Get body JSONObject, generate one if not existed
+            if (payloadJson.containsKey("body")) {
+                bodyJson = payloadJson.getJSONObject("body");
+            } else {
+                bodyJson = new JSONObject();
+                payloadJson.put("body", bodyJson);
+            }
+            bodyJson.put(key, value);
+        } else if (POLICY_KEYS.contains(key)) {
+            // This key should be in the body level
+            JSONObject policyJson = null;
+            if (rootJson.containsKey("policy")) {
+                policyJson = rootJson.getJSONObject("policy");
+            } else {
+                policyJson = new JSONObject();
+                rootJson.put("policy", policyJson);
+            }
+            policyJson.put(key, value);
+        } else {
+            if (key == "payload" || key == "body" || key == "policy" || key == "extra") {
+                throw new Exception("You don't need to set value for " + key + " , just set values for the sub keys in it.");
+            } else {
+                throw new Exception("Unknown key: " + key);
+            }
+        }
+        return true;
+    }
+
+    // Set extra key/value for Android notification
+    public boolean setExtraField(String key, String value) throws Exception {
+        JSONObject payloadJson = null;
+        JSONObject extraJson = null;
+        if (rootJson.containsKey("payload")) {
+            payloadJson = rootJson.getJSONObject("payload");
+        } else {
+            payloadJson = new JSONObject();
+            rootJson.put("payload", payloadJson);
+        }
+
+        if (payloadJson.containsKey("extra")) {
+            extraJson = payloadJson.getJSONObject("extra");
+        } else {
+            extraJson = new JSONObject();
+            payloadJson.put("extra", extraJson);
+        }
+        extraJson.put(key, value);
+        return true;
+    }
+
+    //
+    public void setDisplayType(DisplayType d) throws Exception {
+        setPredefinedKeyValue("display_type", d.getValue());
+    }
+
+    ///通知栏提示文字
+    public void setTicker(String ticker) throws Exception {
+        setPredefinedKeyValue("ticker", ticker);
+    }
+
+    ///通知标题
+    public void setTitle(String title) throws Exception {
+        setPredefinedKeyValue("title", title);
+    }
+
+    ///通知文字描述
+    public void setText(String text) throws Exception {
+        setPredefinedKeyValue("text", text);
+    }
+
+    ///用于标识该通知采用的样式。使用该参数时, 必须在SDK里面实现自定义通知栏样式。
+    public void setBuilderId(Integer builder_id) throws Exception {
+        setPredefinedKeyValue("builder_id", builder_id);
+    }
+
+    ///状态栏图标ID, R.drawable.[smallIcon],如果没有, 默认使用应用图标。
+    public void setIcon(String icon) throws Exception {
+        setPredefinedKeyValue("icon", icon);
+    }
+
+    ///通知栏拉开后左侧图标ID
+    public void setLargeIcon(String largeIcon) throws Exception {
+        setPredefinedKeyValue("largeIcon", largeIcon);
+    }
+
+    ///通知栏大图标的URL链接。该字段的优先级大于largeIcon。该字段要求以http或者https开头。
+    public void setImg(String img) throws Exception {
+        setPredefinedKeyValue("img", img);
+    }
+
+    ///收到通知是否震动,默认为"true"
+    public void setPlayVibrate(Boolean play_vibrate) throws Exception {
+        setPredefinedKeyValue("play_vibrate", play_vibrate.toString());
+    }
+
+    ///收到通知是否闪灯,默认为"true"
+    public void setPlayLights(Boolean play_lights) throws Exception {
+        setPredefinedKeyValue("play_lights", play_lights.toString());
+    }
+
+    ///收到通知是否发出声音,默认为"true"
+    public void setPlaySound(Boolean play_sound) throws Exception {
+        setPredefinedKeyValue("play_sound", play_sound.toString());
+    }
+
+    ///通知声音,R.raw.[sound]. 如果该字段为空,采用SDK默认的声音
+    public void setSound(String sound) throws Exception {
+        setPredefinedKeyValue("sound", sound);
+    }
+
+    ///收到通知后播放指定的声音文件
+    public void setPlaySound(String sound) throws Exception {
+        setPlaySound(true);
+        setSound(sound);
+    }
+
+    ///点击"通知"的后续行为,默认为打开app。
+    public void goAppAfterOpen() throws Exception {
+        setAfterOpenAction(AfterOpenAction.go_app);
+    }
+
+    public void goUrlAfterOpen(String url) throws Exception {
+        setAfterOpenAction(AfterOpenAction.go_url);
+        setUrl(url);
+    }
+
+    public void goActivityAfterOpen(String activity) throws Exception {
+        setAfterOpenAction(AfterOpenAction.go_activity);
+        setActivity(activity);
+    }
+
+    public void goCustomAfterOpen(String custom) throws Exception {
+        setAfterOpenAction(AfterOpenAction.go_custom);
+        setCustomField(custom);
+    }
+
+    public void goCustomAfterOpen(JSONObject custom) throws Exception {
+        setAfterOpenAction(AfterOpenAction.go_custom);
+        setCustomField(custom);
+    }
+
+    ///点击"通知"的后续行为,默认为打开app。原始接口
+    public void setAfterOpenAction(AfterOpenAction action) throws Exception {
+        setPredefinedKeyValue("after_open", action.toString());
+    }
+
+    public void setUrl(String url) throws Exception {
+        setPredefinedKeyValue("url", url);
+    }
+
+    public void setActivity(String activity) throws Exception {
+        setPredefinedKeyValue("activity", activity);
+    }
+
+    ///can be a string of json
+    public void setCustomField(String custom) throws Exception {
+        setPredefinedKeyValue("custom", custom);
+    }
+
+    public void setCustomField(JSONObject custom) throws Exception {
+        setPredefinedKeyValue("custom", custom);
+    }
+
+}

+ 6 - 0
src/main/java/com/izouma/nineth/push/App.java

@@ -0,0 +1,6 @@
+package com.izouma.nineth.push;
+
+
+public class App {
+
+}

+ 93 - 0
src/main/java/com/izouma/nineth/push/IOSNotification.java

@@ -0,0 +1,93 @@
+package com.izouma.nineth.push;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+public abstract class IOSNotification extends UmengNotification {
+
+	// Keys can be set in the aps level
+	protected static final HashSet<String> APS_KEYS = new HashSet<String>(Arrays.asList(new String[]{
+			"alert", "badge", "sound", "content-available"
+	}));
+	
+	@Override
+	public boolean setPredefinedKeyValue(String key, Object value) throws Exception {
+		if (ROOT_KEYS.contains(key)) {
+			// This key should be in the root level
+			rootJson.put(key, value);
+		} else if (APS_KEYS.contains(key)) {
+			// This key should be in the aps level
+			JSONObject apsJson = null;
+			JSONObject payloadJson = null;
+			if (rootJson.containsKey("payload")) {
+				payloadJson = rootJson.getJSONObject("payload");
+			} else {
+				payloadJson = new JSONObject();
+				rootJson.put("payload", payloadJson);
+			}
+			if (payloadJson.containsKey("aps")) {
+				apsJson = payloadJson.getJSONObject("aps");
+			} else {
+				apsJson = new JSONObject();
+				payloadJson.put("aps", apsJson);
+			}
+			apsJson.put(key, value);
+		} else if (POLICY_KEYS.contains(key)) {
+			// This key should be in the body level
+			JSONObject policyJson = null;
+			if (rootJson.containsKey("policy")) {
+				policyJson = rootJson.getJSONObject("policy");
+			} else {
+				policyJson = new JSONObject();
+				rootJson.put("policy", policyJson);
+			}
+			policyJson.put(key, value);
+		} else {
+			if (key == "payload" || key == "aps" || key == "policy") {
+				throw new Exception("You don't need to set value for " + key + " , just set values for the sub keys in it.");
+			} else {
+				throw new Exception("Unknownd key: " + key);
+			}
+		}
+		
+		return true;
+	}
+	// Set customized key/value for IOS notification
+	public boolean setCustomizedField(String key, String value) throws Exception {
+		//rootJson.put(key, value);
+		JSONObject payloadJson = null;
+		if (rootJson.containsKey("payload")) {
+			payloadJson = rootJson.getJSONObject("payload");
+		} else {
+			payloadJson = new JSONObject();
+			rootJson.put("payload", payloadJson);
+		}
+		payloadJson.put(key, value);
+		return true;
+	}
+
+	public void setAlert(String token) throws Exception {
+    	setPredefinedKeyValue("alert", token);
+    }
+
+    public void setAlert(String title ,String subtitle , String body) throws Exception{
+		JSONObject object = new JSONObject();
+		object.put("title" , title);
+		object.put("subtitle" , subtitle);
+		object.put("body" , body);
+		setPredefinedKeyValue("alert",object );
+	}
+	public void setBadge(Integer badge) throws Exception {
+    	setPredefinedKeyValue("badge", badge);
+    }
+	
+	public void setSound(String sound) throws Exception {
+    	setPredefinedKeyValue("sound", sound);
+    }
+	
+	public void setContentAvailable(Integer contentAvailable) throws Exception {
+    	setPredefinedKeyValue("content-available", contentAvailable);
+    }
+}

+ 102 - 0
src/main/java/com/izouma/nineth/push/PushClient.java

@@ -0,0 +1,102 @@
+package com.izouma.nineth.push;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+public class PushClient {
+	
+	// The user agent
+	protected final String USER_AGENT = "Mozilla/5.0";
+
+	// This object is used for sending the post request to Umeng
+	protected HttpClient client = HttpClientBuilder.create().build();;
+	
+	// The host
+	protected static final String host = "http://msg.umeng.com";
+	
+	// The upload path
+	protected static final String uploadPath = "/upload";
+	
+	// The post path
+	protected static final String postPath = "/api/send";
+
+	public boolean send(UmengNotification msg) throws Exception {
+		String timestamp = Integer.toString((int)(System.currentTimeMillis() / 1000));
+		msg.setPredefinedKeyValue("timestamp", timestamp);
+        String url = host + postPath;
+        String postBody = msg.getPostBody();
+        String sign = DigestUtils.md5Hex(("POST" + url + postBody + msg.getAppMasterSecret()).getBytes("utf8"));
+        url = url + "?sign=" + sign;
+        HttpPost post = new HttpPost(url);
+        post.setHeader("User-Agent", USER_AGENT);
+        StringEntity se = new StringEntity(postBody, "UTF-8");
+        post.setEntity(se);
+        // Send the post request and get the response
+        HttpResponse response = client.execute(post);
+        int status = response.getStatusLine().getStatusCode();
+        System.out.println("Response Code : " + status);
+        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+        StringBuffer result = new StringBuffer();
+        String line = "";
+        while ((line = rd.readLine()) != null) {
+            result.append(line);
+        }
+        System.out.println(result.toString());
+        if (status == 200) {
+            System.out.println("Notification sent successfully.");
+        } else {
+            System.out.println("Failed to send the notification!");
+        }
+        return true;
+    }
+
+	// Upload file with device_tokens to Umeng
+	public String uploadContents(String appkey,String appMasterSecret,String contents) throws Exception {
+		// Construct the json string
+		JSONObject uploadJson = new JSONObject();
+		uploadJson.put("appkey", appkey);
+		String timestamp = Integer.toString((int)(System.currentTimeMillis() / 1000));
+		uploadJson.put("timestamp", timestamp);
+		uploadJson.put("content", contents);
+		// Construct the request
+		String url = host + uploadPath;
+		String postBody = uploadJson.toString();
+		String sign = DigestUtils.md5Hex(("POST" + url + postBody + appMasterSecret).getBytes("utf8"));
+		url = url + "?sign=" + sign;
+		HttpPost post = new HttpPost(url);
+		post.setHeader("User-Agent", USER_AGENT);
+		StringEntity se = new StringEntity(postBody, "UTF-8");
+		post.setEntity(se);
+		// Send the post request and get the response
+		HttpResponse response = client.execute(post);
+		System.out.println("Response Code : " + response.getStatusLine().getStatusCode());
+		BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+		StringBuffer result = new StringBuffer();
+		String line = "";
+		while ((line = rd.readLine()) != null) {
+			result.append(line);
+		}
+		System.out.println(result.toString());
+		// Decode response string and get file_id from it
+		JSONObject respJson =   JSONObject.parseObject(result.toString());
+		String ret = respJson.getString("ret");
+		if (!ret.equals("SUCCESS")) {
+			throw new Exception("Failed to upload file");
+		}
+		JSONObject data = respJson.getJSONObject("data");
+		String fileId = data.getString("file_id");
+		// Set file_id into rootJson using setPredefinedKeyValue
+		
+		return fileId;
+	}
+
+}

+ 273 - 0
src/main/java/com/izouma/nineth/push/PushUtils.java

@@ -0,0 +1,273 @@
+package com.izouma.nineth.push;
+
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.push.android.*;
+import com.izouma.nineth.push.ios.*;
+
+public class PushUtils {
+    private String     appkey          = null;
+    private String     appMasterSecret = null;
+    private String     timestamp       = null;
+    private PushClient client          = new PushClient();
+
+    public PushUtils(String key, String secret) {
+        try {
+            appkey = key;
+            appMasterSecret = secret;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void sendAndroidBroadcast(String ticker, String title, String text, boolean production) throws Exception {
+        AndroidBroadcast broadcast = new AndroidBroadcast(appkey, appMasterSecret);
+        broadcast.setTicker(ticker);
+        broadcast.setTitle(title);
+        broadcast.setText(text);
+        broadcast.goAppAfterOpen();
+        broadcast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        // TODO Set 'production_mode' to 'false' if it's a test device.
+        // For how to register a test device, please see the developer doc.
+        if (production) {
+            broadcast.setProductionMode();
+        } else {
+            broadcast.setTestMode();
+        }
+        // Set customized fields
+//        broadcast.setExtraField("test", "helloworld");
+        //厂商通道相关参数
+//        broadcast.setChannelActivity("your channel activity");
+//        broadcast.setChannelProperties("abc");
+        client.send(broadcast);
+    }
+
+    public void sendAndroidUnicast(String ticker, String title, String text, String deviceToken) throws Exception {
+        AndroidUnicast unicast = new AndroidUnicast(appkey, appMasterSecret);
+        // TODO Set your device token
+        unicast.setDeviceToken(deviceToken);
+        unicast.setTicker(ticker);
+        unicast.setTitle(title);
+        unicast.setText(text);
+        unicast.goAppAfterOpen();
+        unicast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        // TODO Set 'production_mode' to 'false' if it's a test device.
+        // For how to register a test device, please see the developer doc.
+        unicast.setProductionMode();
+        // Set customized fields
+//        unicast.setExtraField("test", "helloworld");
+//        unicast.setChannelActivity("your channel activity");
+//        unicast.setChannelProperties("abc");
+        client.send(unicast);
+    }
+
+    public void sendAndroidGroupcast() throws Exception {
+        AndroidGroupcast groupcast = new AndroidGroupcast(appkey, appMasterSecret);
+        /*  TODO
+         *  Construct the filter condition:
+         *  "where":
+         *	{
+         *		"and":
+         *		[
+         *			{"tag":"test"},
+         *			{"tag":"Test"}
+         *		]
+         *	}
+         */
+        JSONObject filterJson = new JSONObject();
+        JSONObject whereJson = new JSONObject();
+        JSONArray tagArray = new JSONArray();
+        JSONObject testTag = new JSONObject();
+        JSONObject TestTag = new JSONObject();
+        testTag.put("tag", "test");
+        TestTag.put("tag", "Test");
+        tagArray.add(testTag);
+        tagArray.add(TestTag);
+        whereJson.put("and", tagArray);
+        filterJson.put("where", whereJson);
+
+        groupcast.setFilter(filterJson);
+        groupcast.setTicker("Android groupcast ticker");
+        groupcast.setTitle("中文的title");
+        groupcast.setText("Android groupcast text");
+        groupcast.goAppAfterOpen();
+        groupcast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        groupcast.setChannelActivity("your channel activity");
+        // TODO Set 'production_mode' to 'false' if it's a test device.
+        // For how to register a test device, please see the developer doc.
+        groupcast.setProductionMode();
+        //厂商通道相关参数
+        groupcast.setChannelActivity("your channel activity");
+        groupcast.setChannelProperties("abc");
+        client.send(groupcast);
+    }
+
+    public void sendAndroidCustomizedcast() throws Exception {
+        AndroidCustomizedcast customizedcast = new AndroidCustomizedcast(appkey, appMasterSecret);
+        // TODO Set your alias here, and use comma to split them if there are multiple alias.
+        // And if you have many alias, you can also upload a file containing these alias, then
+        // use file_id to send customized notification.
+        customizedcast.setAlias("alias", "alias_type");
+        customizedcast.setTicker("Android customizedcast ticker");
+        customizedcast.setTitle("中文的title");
+        customizedcast.setText("Android customizedcast text");
+        customizedcast.goAppAfterOpen();
+        customizedcast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        // TODO Set 'production_mode' to 'false' if it's a test device.
+        // For how to register a test device, please see the developer doc.
+        customizedcast.setProductionMode();
+        //厂商通道相关参数
+        customizedcast.setChannelActivity("your channel activity");
+        customizedcast.setChannelProperties("abc");
+        client.send(customizedcast);
+    }
+
+    public void sendAndroidCustomizedcastFile() throws Exception {
+        AndroidCustomizedcast customizedcast = new AndroidCustomizedcast(appkey, appMasterSecret);
+        // TODO Set your alias here, and use comma to split them if there are multiple alias.
+        // And if you have many alias, you can also upload a file containing these alias, then
+        // use file_id to send customized notification.
+        String fileId = client.uploadContents(appkey, appMasterSecret, "aa" + "\n" + "bb" + "\n" + "alias");
+        customizedcast.setFileId(fileId, "alias_type");
+        customizedcast.setTicker("Android customizedcast ticker");
+        customizedcast.setTitle("中文的title");
+        customizedcast.setText("Android customizedcast text");
+        customizedcast.goAppAfterOpen();
+        customizedcast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        // TODO Set 'production_mode' to 'false' if it's a test device.
+        // For how to register a test device, please see the developer doc.
+        customizedcast.setProductionMode();
+        //厂商通道相关参数
+        customizedcast.setChannelActivity("your channel activity");
+        customizedcast.setChannelProperties("abc");
+        client.send(customizedcast);
+    }
+
+    public void sendAndroidFilecast() throws Exception {
+        AndroidFilecast filecast = new AndroidFilecast(appkey, appMasterSecret);
+        // TODO upload your device tokens, and use '\n' to split them if there are multiple tokens
+        String fileId = client.uploadContents(appkey, appMasterSecret, "aa" + "\n" + "bb");
+        filecast.setFileId(fileId);
+        filecast.setTicker("Android filecast ticker");
+        filecast.setTitle("中文的title");
+        filecast.setText("Android filecast text");
+        filecast.goAppAfterOpen();
+        filecast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
+        //厂商通道相关参数
+        filecast.setChannelActivity("your channel activity");
+        filecast.setChannelProperties("abc");
+        client.send(filecast);
+    }
+
+    public void sendIOSBroadcast(String title, String text, boolean production) throws Exception {
+        IOSBroadcast broadcast = new IOSBroadcast(appkey, appMasterSecret);
+        //alert值设置为字符串
+        //broadcast.setAlert("IOS 广播测试");
+        //alert的值设置为字典
+        broadcast.setAlert(title, "", text);
+        broadcast.setBadge(0);
+        broadcast.setSound("default");
+        // TODO set 'production_mode' to 'true' if your app is under production mode
+        if (production) {
+            broadcast.setProductionMode();
+        } else {
+            broadcast.setTestMode();
+        }
+        // Set customized fields
+        broadcast.setCustomizedField("test", "helloworld");
+        client.send(broadcast);
+    }
+
+    public void sendIOSUnicast(String deviceToken) throws Exception {
+        IOSUnicast unicast = new IOSUnicast(appkey, appMasterSecret);
+        // TODO Set your device token
+        unicast.setDeviceToken(deviceToken);
+        //alert值设置为字符串
+        //unicast.setAlert("IOS 单播测试");
+        //alert的值设置为字典
+        unicast.setAlert("今日天气", "", "今日可能下雨🌂");
+        unicast.setBadge(0);
+        unicast.setSound("default");
+        // TODO set 'production_mode' to 'true' if your app is under production mode
+        unicast.setTestMode();
+        // Set customized fields
+        unicast.setCustomizedField("test", "helloworld");
+        client.send(unicast);
+    }
+
+
+    public void sendIOSGroupcast() throws Exception {
+        IOSGroupcast groupcast = new IOSGroupcast(appkey, appMasterSecret);
+        /*  TODO
+         *  Construct the filter condition:
+         *  "where":
+         *	{
+         *		"and":
+         *		[
+         *			{"tag":"iostest"}
+         *		]
+         *	}
+         */
+        JSONObject filterJson = new JSONObject();
+        JSONObject whereJson = new JSONObject();
+        JSONArray tagArray = new JSONArray();
+        JSONObject testTag = new JSONObject();
+        testTag.put("tag", "iostest");
+        tagArray.add(testTag);
+        whereJson.put("and", tagArray);
+        filterJson.put("where", whereJson);
+        System.out.println(filterJson.toString());
+
+        // Set filter condition into rootJson
+        groupcast.setFilter(filterJson);
+        //groupcast.setAlert("IOS 组播测试");
+        //alert的值设置为字典
+        groupcast.setAlert("今日天气", "subtitle", "今日可能下雨🌂");
+        groupcast.setBadge(0);
+        groupcast.setSound("default");
+        // TODO set 'production_mode' to 'true' if your app is under production mode
+        groupcast.setTestMode();
+        client.send(groupcast);
+    }
+
+    public void sendIOSCustomizedcast() throws Exception {
+        IOSCustomizedcast customizedcast = new IOSCustomizedcast(appkey, appMasterSecret);
+        // TODO Set your alias and alias_type here, and use comma to split them if there are multiple alias.
+        // And if you have many alias, you can also upload a file containing these alias, then
+        // use file_id to send customized notification.
+        customizedcast.setAlias("alias", "alias_type");
+        //customizedcast.setAlert("IOS 个性化测试");
+        //alert的值设置为字典
+        customizedcast.setAlert("今日天气", "", "今日可能下雨🌂");
+        customizedcast.setBadge(0);
+        customizedcast.setSound("default");
+        // TODO set 'production_mode' to 'true' if your app is under production mode
+        customizedcast.setTestMode();
+        client.send(customizedcast);
+    }
+
+    public void sendIOSFilecast() throws Exception {
+        IOSFilecast filecast = new IOSFilecast(appkey, appMasterSecret);
+        // TODO upload your device tokens, and use '\n' to split them if there are multiple tokens
+        String fileId = client.uploadContents(appkey, appMasterSecret, "aa" + "\n" + "bb");
+        filecast.setFileId(fileId);
+        //filecast.setAlert("IOS 文件播测试");
+        //alert的值设置为字典
+        filecast.setAlert("今日天气", "", "今日可能下雨🌂");
+        filecast.setBadge(0);
+        filecast.setSound("default");
+        // TODO set 'production_mode' to 'true' if your app is under production mode
+        filecast.setTestMode();
+        client.send(filecast);
+    }
+
+    public static void main(String[] args) {
+        try {
+            new PushUtils("62b5753a05844627b5c56bc8", "lynwia75tojrokcmqu9nyiukhjdrefof")
+                    .sendIOSBroadcast("test", "123", false);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+}

+ 87 - 0
src/main/java/com/izouma/nineth/push/UmengNotification.java

@@ -0,0 +1,87 @@
+package com.izouma.nineth.push;
+
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+public abstract class UmengNotification {
+	// This JSONObject is used for constructing the whole request string.
+	protected final JSONObject rootJson = new JSONObject();
+	
+	
+	// The app master secret
+	protected String appMasterSecret;
+	
+	// Keys can be set in the root level
+	protected static final HashSet<String> ROOT_KEYS = new HashSet<String>(Arrays.asList(new String[]{
+			"appkey", "timestamp", "type", "device_tokens", "alias", "alias_type", "file_id", 
+			"filter", "production_mode", "feedback", "description", "thirdparty_id" , "mipush" , "mi_activity" , "channel_properties"}));
+	
+	// Keys can be set in the policy level
+	protected static final HashSet<String> POLICY_KEYS = new HashSet<String>(Arrays.asList(new String[]{
+			"start_time", "expire_time", "max_send_num"
+	}));
+	
+	// Set predefined keys in the rootJson, for extra keys(Android) or customized keys(IOS) please 
+	// refer to corresponding methods in the subclass.
+	public abstract boolean setPredefinedKeyValue(String key, Object value) throws Exception;
+	public void setAppMasterSecret(String secret) {
+		appMasterSecret = secret;
+	}
+	
+	public String getPostBody(){
+		return rootJson.toString();
+	}
+	
+	protected final String getAppMasterSecret(){
+		return appMasterSecret;
+	}
+	
+	protected void setProductionMode(Boolean prod) throws Exception {
+    	setPredefinedKeyValue("production_mode", prod.toString());
+    }
+
+	///正式模式
+    public void setProductionMode() throws Exception {
+    	setProductionMode(true);
+    }
+
+    ///测试模式
+    public void setTestMode() throws Exception {
+    	setProductionMode(false);
+    }
+
+    ///发送消息描述,建议填写。
+    public void setDescription(String description) throws Exception {
+    	setPredefinedKeyValue("description", description);
+    }
+
+    ///定时发送时间,若不填写表示立即发送。格式: "YYYY-MM-DD hh:mm:ss"。
+    public void setStartTime(String startTime) throws Exception {
+    	setPredefinedKeyValue("start_time", startTime);
+    }
+    ///消息过期时间,格式: "YYYY-MM-DD hh:mm:ss"。
+    public void setExpireTime(String expireTime) throws Exception {
+    	setPredefinedKeyValue("expire_time", expireTime);
+    }
+    ///发送限速,每秒发送的最大条数。
+    public void setMaxSendNum(Integer num) throws Exception {
+    	setPredefinedKeyValue("max_send_num", num);
+    }
+
+    //厂商弹窗activity
+	public void setChannelActivity(String activity) throws Exception{
+       setPredefinedKeyValue("mipush", "true");
+       setPredefinedKeyValue("mi_activity",activity );
+	}
+
+	//厂商属性配置
+	public void setChannelProperties(String xiaoMiChannelId) throws Exception{
+		JSONObject object = new JSONObject();
+		object.put("xiaomi_channel_id" , xiaoMiChannelId);
+		setPredefinedKeyValue("channel_properties", object);
+	}
+
+}

+ 12 - 0
src/main/java/com/izouma/nineth/push/android/AndroidBroadcast.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.push.android;
+
+
+import com.izouma.nineth.push.AndroidNotification;
+
+public class AndroidBroadcast extends AndroidNotification {
+    public AndroidBroadcast(String appkey, String appMasterSecret) throws Exception {
+        setAppMasterSecret(appMasterSecret);
+        setPredefinedKeyValue("appkey", appkey);
+        this.setPredefinedKeyValue("type", "broadcast");
+    }
+}

+ 23 - 0
src/main/java/com/izouma/nineth/push/android/AndroidCustomizedcast.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth.push.android;
+
+
+import com.izouma.nineth.push.AndroidNotification;
+
+public class AndroidCustomizedcast extends AndroidNotification {
+	public AndroidCustomizedcast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "customizedcast");	
+	}
+	
+	public void setAlias(String alias,String aliasType) throws Exception {
+    	setPredefinedKeyValue("alias", alias);
+    	setPredefinedKeyValue("alias_type", aliasType);
+    }
+			
+	public void setFileId(String fileId,String aliasType) throws Exception {
+    	setPredefinedKeyValue("file_id", fileId);
+    	setPredefinedKeyValue("alias_type", aliasType);
+    }
+
+}

+ 16 - 0
src/main/java/com/izouma/nineth/push/android/AndroidFilecast.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.push.android;
+
+
+import com.izouma.nineth.push.AndroidNotification;
+
+public class AndroidFilecast extends AndroidNotification {
+	public AndroidFilecast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "filecast");	
+	}
+	
+	public void setFileId(String fileId) throws Exception {
+    	setPredefinedKeyValue("file_id", fileId);
+    }
+}

+ 17 - 0
src/main/java/com/izouma/nineth/push/android/AndroidGroupcast.java

@@ -0,0 +1,17 @@
+package com.izouma.nineth.push.android;
+
+
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.push.AndroidNotification;
+
+public class AndroidGroupcast extends AndroidNotification {
+	public AndroidGroupcast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "groupcast");	
+	}
+	
+	public void setFilter(JSONObject filter) throws Exception {
+    	setPredefinedKeyValue("filter", filter);
+    }
+}

+ 17 - 0
src/main/java/com/izouma/nineth/push/android/AndroidUnicast.java

@@ -0,0 +1,17 @@
+package com.izouma.nineth.push.android;
+
+
+import com.izouma.nineth.push.AndroidNotification;
+
+public class AndroidUnicast extends AndroidNotification {
+	public AndroidUnicast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "unicast");	
+	}
+	
+	public void setDeviceToken(String token) throws Exception {
+    	setPredefinedKeyValue("device_tokens", token);
+    }
+
+}

+ 13 - 0
src/main/java/com/izouma/nineth/push/ios/IOSBroadcast.java

@@ -0,0 +1,13 @@
+package com.izouma.nineth.push.ios;
+
+
+import com.izouma.nineth.push.IOSNotification;
+
+public class IOSBroadcast extends IOSNotification {
+	public IOSBroadcast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "broadcast");	
+		
+	}
+}

+ 22 - 0
src/main/java/com/izouma/nineth/push/ios/IOSCustomizedcast.java

@@ -0,0 +1,22 @@
+package com.izouma.nineth.push.ios;
+
+import com.izouma.nineth.push.IOSNotification;
+
+public class IOSCustomizedcast extends IOSNotification {
+	public IOSCustomizedcast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "customizedcast");	
+	}
+	
+	public void setAlias(String alias,String aliasType) throws Exception {
+    	setPredefinedKeyValue("alias", alias);
+    	setPredefinedKeyValue("alias_type", aliasType);
+    }
+		
+	public void setFileId(String fileId, String aliasType) throws Exception {
+		setPredefinedKeyValue("file_id", fileId);
+		setPredefinedKeyValue("alias_type", aliasType);
+	}
+
+}

+ 16 - 0
src/main/java/com/izouma/nineth/push/ios/IOSFilecast.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.push.ios;
+
+
+import com.izouma.nineth.push.IOSNotification;
+
+public class IOSFilecast extends IOSNotification {
+	public IOSFilecast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "filecast");	
+	}
+	
+	public void setFileId(String fileId) throws Exception {
+    	setPredefinedKeyValue("file_id", fileId);
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/push/ios/IOSGroupcast.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.push.ios;
+
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.push.IOSNotification;
+
+public class IOSGroupcast extends IOSNotification {
+	public IOSGroupcast(String appkey,String appMasterSecret) throws Exception {
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "groupcast");	
+	}
+	
+	public void setFilter(JSONObject filter) throws Exception {
+    	setPredefinedKeyValue("filter", filter);
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/push/ios/IOSUnicast.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.push.ios;
+
+
+import com.izouma.nineth.push.IOSNotification;
+
+public class IOSUnicast extends IOSNotification {
+	public IOSUnicast(String appkey,String appMasterSecret) throws Exception{
+			setAppMasterSecret(appMasterSecret);
+			setPredefinedKeyValue("appkey", appkey);
+			this.setPredefinedKeyValue("type", "unicast");	
+	}
+	
+	public void setDeviceToken(String token) throws Exception {
+    	setPredefinedKeyValue("device_tokens", token);
+    }
+}

+ 20 - 0
src/main/java/com/izouma/nineth/web/PushController.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.push.PushUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/push")
+public class PushController extends BaseController {
+
+    @PostMapping("/broadcast")
+    public void broadcast(@RequestParam String title, @RequestParam String text, @RequestParam boolean production) throws Exception {
+        new PushUtils("62b5753a05844627b5c56bc8", "dlo3bqifmuuvbpmodmcyc2xnh0b1fphi")
+                .sendAndroidBroadcast(title, title, text, production);
+        new PushUtils("62b5751a05844627b5c56b51", "lynwia75tojrokcmqu9nyiukhjdrefof")
+                .sendIOSBroadcast(title, text, production);
+    }
+}

+ 1 - 1
src/main/pc-space/src/components/ConsignmentInfo.vue

@@ -18,7 +18,7 @@
             <div class="border1"></div>
             <div class="title">寄售价格</div>
             <div class="price">
-                <el-form :model="form" :rules="rules" ref="numberValidateForm" label-width="85px" class="demo-ruleForm">
+                <el-form :model="form" :rules="rules" ref="numberValidateForm" label-width="85px" class="pushUtils-ruleForm">
                     <el-form-item label="寄售价格(元)" prop="price">
                         <el-input
                             placeholder="请设置寄售价格"

+ 1 - 1
src/main/pc-space/src/views/Send.vue

@@ -41,7 +41,7 @@
                     <el-button type="info" @click="copy" size="mini" plain>复制</el-button>
                 </div>
             </div> -->
-            <el-form :model="form" :rules="rules" ref="rules" label-width="85px" class="demo-ruleForm">
+            <el-form :model="form" :rules="rules" ref="rules" label-width="85px" class="pushUtils-ruleForm">
                 <div class="title">请输入交易密码,验证信息</div>
                 <div v-if="sets">
                     <el-form-item prop="password">

+ 1 - 1
src/main/resources/templates/EditViewTemplate.ftl

@@ -62,7 +62,7 @@
                                     <multi-upload v-model="formData.${field.modelName}"></multi-upload>
                                 <#elseif field.formType == 'fileUpload'>
                                     <el-upload
-                                            class="upload-demo"
+                                            class="upload-pushUtils"
                                             action="../upload/file"
                                             :on-change="handleChange"
                                             :file-list="fileList3">

+ 143 - 104
src/main/vue/src/router.js

@@ -669,8 +669,7 @@ const router = new Router({
                 {
                     path: '/companyTheme',
                     name: 'companyTheme',
-                    component: () =>
-                        import(/* webpackChunkName: "companyTheme" */ '@/views/company/CompanyTheme.vue'),
+                    component: () => import(/* webpackChunkName: "companyTheme" */ '@/views/company/CompanyTheme.vue'),
                     meta: {
                         title: '展厅主题'
                     }
@@ -1040,7 +1039,8 @@ const router = new Router({
                 {
                     path: '/companyCollectionEdit',
                     name: 'CompanyCollectionEdit',
-                    component: () => import(/* webpackChunkName: "companyCollectionEdit" */ '@/views/CompanyCollectionEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "companyCollectionEdit" */ '@/views/CompanyCollectionEdit.vue'),
                     meta: {
                         title: '藏品管理编辑'
                     }
@@ -1048,7 +1048,8 @@ const router = new Router({
                 {
                     path: '/companyCollectionList1',
                     name: 'CompanyCollectionList1',
-                    component: () => import(/* webpackChunkName: "companyCollectionList1" */ '@/views/CompanyCollectionList1.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "companyCollectionList1" */ '@/views/CompanyCollectionList1.vue'),
                     meta: {
                         title: '藏品管理'
                     }
@@ -1056,7 +1057,8 @@ const router = new Router({
                 {
                     path: '/companyBlindBoxEdit',
                     name: 'CompanyBlindBoxEdit',
-                    component: () => import(/* webpackChunkName: "companyBlindBoxEdit" */ '@/views/CompanyBlindBoxEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "companyBlindBoxEdit" */ '@/views/CompanyBlindBoxEdit.vue'),
                     meta: {
                         title: '盲盒编辑'
                     }
@@ -1064,7 +1066,8 @@ const router = new Router({
                 {
                     path: '/companyBlindBoxList',
                     name: 'CompanyBlindBoxList',
-                    component: () => import(/* webpackChunkName: "companyBlindBoxList" */ '@/views/CompanyBlindBoxList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "companyBlindBoxList" */ '@/views/CompanyBlindBoxList.vue'),
                     meta: {
                         title: '盲盒'
                     }
@@ -1082,273 +1085,309 @@ const router = new Router({
                     name: 'MetaUserEdit',
                     component: () => import(/* webpackChunkName: "metaUserEdit" */ '@/views/MetaUserEdit.vue'),
                     meta: {
-                        title: '元宇宙内测用户编辑',
-                    },
+                        title: '元宇宙内测用户编辑'
+                    }
                 },
                 {
                     path: '/metaUserList',
                     name: 'MetaUserList',
                     component: () => import(/* webpackChunkName: "metaUserList" */ '@/views/MetaUserList.vue'),
                     meta: {
-                        title: '元宇宙内测用户',
-                    },
+                        title: '元宇宙内测用户'
+                    }
                 },
                 {
                     path: '/metaBonusSceneList',
                     name: 'MetaBonusSceneList',
-                    component: () => import(/* webpackChunkName: "metaBonusSceneList" */ '@/views/MetaBonusSceneList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaBonusSceneList" */ '@/views/MetaBonusSceneList.vue'),
                     meta: {
-                        title: '元宇宙彩蛋',
-                    },
+                        title: '元宇宙彩蛋'
+                    }
                 },
                 {
                     path: '/metaAdvertRecordEdit',
                     name: 'MetaAdvertRecordEdit',
-                    component: () => import(/* webpackChunkName: "metaAdvertRecordEdit" */ '@/views/MetaAdvertRecordEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaAdvertRecordEdit" */ '@/views/MetaAdvertRecordEdit.vue'),
                     meta: {
-                        title: '元宇宙广告编辑',
-                    },
+                        title: '元宇宙广告编辑'
+                    }
                 },
                 {
                     path: '/metaAdvertRecordList',
                     name: 'MetaAdvertRecordList',
-                    component: () => import(/* webpackChunkName: "metaAdvertRecordList" */ '@/views/MetaAdvertRecordList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaAdvertRecordList" */ '@/views/MetaAdvertRecordList.vue'),
                     meta: {
-                        title: '元宇宙广告',
-                    },
+                        title: '元宇宙广告'
+                    }
                 },
                 {
                     path: '/metaTaskEdit',
                     name: 'MetaTaskEdit',
                     component: () => import(/* webpackChunkName: "metaTaskEdit" */ '@/views/MetaTaskEdit.vue'),
                     meta: {
-                        title: '元宇宙任务编辑',
-                    },
+                        title: '元宇宙任务编辑'
+                    }
                 },
                 {
                     path: '/metaTaskList',
                     name: 'MetaTaskList',
                     component: () => import(/* webpackChunkName: "metaTaskList" */ '@/views/MetaTaskList.vue'),
                     meta: {
-                        title: '元宇宙任务',
-                    },
+                        title: '元宇宙任务'
+                    }
                 },
                 {
                     path: '/metaUserGoldList',
                     name: 'MetaUserGoldList',
                     component: () => import(/* webpackChunkName: "metaUserGoldList" */ '@/views/MetaUserGoldList.vue'),
                     meta: {
-                        title: '元宇宙用户金币',
-                    },
+                        title: '元宇宙用户金币'
+                    }
                 },
                 {
                     path: '/metaUserGoldRecordList',
                     name: 'MetaUserGoldRecordList',
-                    component: () => import(/* webpackChunkName: "metaUserGoldRecordList" */ '@/views/MetaUserGoldRecordList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaUserGoldRecordList" */ '@/views/MetaUserGoldRecordList.vue'),
                     meta: {
-                        title: '元宇宙用户金币明细',
-                    },
+                        title: '元宇宙用户金币明细'
+                    }
                 },
                 {
                     path: '/metaTaskActivityEdit',
                     name: 'MetaTaskActivityEdit',
-                    component: () => import(/* webpackChunkName: "metaTaskActivityEdit" */ '@/views/MetaTaskActivityEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaTaskActivityEdit" */ '@/views/MetaTaskActivityEdit.vue'),
                     meta: {
-                        title: '元宇宙任务活动编辑',
-                    },
+                        title: '元宇宙任务活动编辑'
+                    }
                 },
                 {
                     path: '/metaTaskActivityList',
                     name: 'MetaTaskActivityList',
-                    component: () => import(/* webpackChunkName: "metaTaskActivityList" */ '@/views/MetaTaskActivityList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaTaskActivityList" */ '@/views/MetaTaskActivityList.vue'),
                     meta: {
-                        title: '元宇宙任务活动',
-                    },
+                        title: '元宇宙任务活动'
+                    }
                 },
                 {
                     path: '/metaUserTaskProgressList',
                     name: 'MetaUserTaskProgressList',
-                    component: () => import(/* webpackChunkName: "metaUserTaskProgressList" */ '@/views/MetaUserTaskProgressList.vue'),
+                    component: () =>
+                        import(
+                            /* webpackChunkName: "metaUserTaskProgressList" */ '@/views/MetaUserTaskProgressList.vue'
+                        ),
                     meta: {
-                        title: '元宇宙任务进度',
-                    },
+                        title: '元宇宙任务进度'
+                    }
                 },
                 {
                     path: '/metaTaskToUserList',
                     name: 'MetaTaskToUserList',
-                    component: () => import(/* webpackChunkName: "metaTaskToUserList" */ '@/views/MetaTaskToUserList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaTaskToUserList" */ '@/views/MetaTaskToUserList.vue'),
                     meta: {
-                        title: '元宇宙任务领取',
-                    },
+                        title: '元宇宙任务领取'
+                    }
                 },
                 {
                     path: '/metaDestroyActivityEdit',
                     name: 'MetaDestroyActivityEdit',
-                    component: () => import(/* webpackChunkName: "metaDestroyActivityEdit" */ '@/views/MetaDestroyActivityEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaDestroyActivityEdit" */ '@/views/MetaDestroyActivityEdit.vue'),
                     meta: {
-                        title: '元宇宙销毁任务编辑',
-                    },
+                        title: '元宇宙销毁任务编辑'
+                    }
                 },
                 {
                     path: '/metaDestroyActivityList',
                     name: 'MetaDestroyActivityList',
-                    component: () => import(/* webpackChunkName: "metaDestroyActivityList" */ '@/views/MetaDestroyActivityList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaDestroyActivityList" */ '@/views/MetaDestroyActivityList.vue'),
                     meta: {
-                        title: '元宇宙销毁任务',
-                    },
+                        title: '元宇宙销毁任务'
+                    }
                 },
                 {
                     path: '/metaResourceVersionEdit',
                     name: 'MetaResourceVersionEdit',
-                    component: () => import(/* webpackChunkName: "metaResourceVersionEdit" */ '@/views/MetaResourceVersionEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaResourceVersionEdit" */ '@/views/MetaResourceVersionEdit.vue'),
                     meta: {
-                        title: '元宇宙资源版本编辑',
-                    },
+                        title: '元宇宙资源版本编辑'
+                    }
                 },
                 {
                     path: '/metaResourceVersionList',
                     name: 'MetaResourceVersionList',
-                    component: () => import(/* webpackChunkName: "metaResourceVersionList" */ '@/views/MetaResourceVersionList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaResourceVersionList" */ '@/views/MetaResourceVersionList.vue'),
                     meta: {
-                        title: '元宇宙资源版本',
-                    },
+                        title: '元宇宙资源版本'
+                    }
                 },
                 {
                     path: '/metaMMOLoginInfoList',
                     name: 'MetaMMOLoginInfoList',
-                    component: () => import(/* webpackChunkName: "metaMMOLoginInfoList" */ '@/views/MetaMMOLoginInfoList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaMMOLoginInfoList" */ '@/views/MetaMMOLoginInfoList.vue'),
                     meta: {
-                       title: 'MMO登陆信息',
-                    },
+                        title: 'MMO登陆信息'
+                    }
                 },
                 {
                     path: '/photoAssetList',
                     name: 'PhotoAssetList',
                     component: () => import(/* webpackChunkName: "photoAssetList" */ '@/views/PhotoAssetList.vue'),
                     meta: {
-                        title: '相册',
-                    },
+                        title: '相册'
+                    }
                 },
                 {
                     path: '/publicScreenChatList',
                     name: 'PublicScreenChatList',
-                    component: () => import(/* webpackChunkName: "publicScreenChatList" */ '@/views/PublicScreenChatList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "publicScreenChatList" */ '@/views/PublicScreenChatList.vue'),
                     meta: {
-                       title: '公屏对话',
-                    },
-               },
+                        title: '公屏对话'
+                    }
+                },
                 {
                     path: '/metaItemEdit',
                     name: 'MetaItemEdit',
                     component: () => import(/* webpackChunkName: "metaItemEdit" */ '@/views/MetaItemEdit.vue'),
                     meta: {
-                       title: '元宇宙物品编辑',
-                    },
+                        title: '元宇宙物品编辑'
+                    }
                 },
                 {
                     path: '/metaItemList',
                     name: 'MetaItemList',
                     component: () => import(/* webpackChunkName: "metaItemList" */ '@/views/MetaItemList.vue'),
                     meta: {
-                       title: '元宇宙物品',
-                    },
-               },
+                        title: '元宇宙物品'
+                    }
+                },
                 {
                     path: '/metaShowRoomAssetList',
                     name: 'MetaShowRoomAssetList',
-                    component: () => import(/* webpackChunkName: "metaShowRoomAssetList" */ '@/views/MetaShowRoomAssetList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaShowRoomAssetList" */ '@/views/MetaShowRoomAssetList.vue'),
                     meta: {
-                       title: '元宇宙展厅藏品',
-                    },
-               },
+                        title: '元宇宙展厅藏品'
+                    }
+                },
                 {
                     path: '/metaGameCopyEdit',
                     name: 'MetaGameCopyEdit',
                     component: () => import(/* webpackChunkName: "metaGameCopyEdit" */ '@/views/MetaGameCopyEdit.vue'),
                     meta: {
-                       title: '元宇宙游戏副本配置编辑',
-                    },
+                        title: '元宇宙游戏副本配置编辑'
+                    }
                 },
                 {
                     path: '/metaGameCopyList',
                     name: 'MetaGameCopyList',
                     component: () => import(/* webpackChunkName: "metaGameCopyList" */ '@/views/MetaGameCopyList.vue'),
                     meta: {
-                       title: '元宇宙游戏副本配置',
-                    },
-               },
+                        title: '元宇宙游戏副本配置'
+                    }
+                },
                 {
                     path: '/metaZombieEdit',
                     name: 'MetaZombieEdit',
                     component: () => import(/* webpackChunkName: "metaZombieEdit" */ '@/views/MetaZombieEdit.vue'),
                     meta: {
-                       title: '元宇宙僵尸游戏配置编辑',
-                    },
+                        title: '元宇宙僵尸游戏配置编辑'
+                    }
                 },
                 {
                     path: '/metaZombieList',
                     name: 'MetaZombieList',
                     component: () => import(/* webpackChunkName: "metaZombieList" */ '@/views/MetaZombieList.vue'),
                     meta: {
-                       title: '元宇宙僵尸游戏配置',
-                    },
-               },
+                        title: '元宇宙僵尸游戏配置'
+                    }
+                },
                 {
                     path: '/metaGameProcessList',
                     name: 'MetaGameProcessList',
-                    component: () => import(/* webpackChunkName: "metaGameProcessList" */ '@/views/MetaGameProcessList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaGameProcessList" */ '@/views/MetaGameProcessList.vue'),
                     meta: {
-                       title: '元宇宙游戏进度',
-                    },
-               },
+                        title: '元宇宙游戏进度'
+                    }
+                },
                 {
                     path: '/metaGameStageAwardEdit',
                     name: 'MetaGameStageAwardEdit',
-                    component: () => import(/* webpackChunkName: "metaGameStageAwardEdit" */ '@/views/MetaGameStageAwardEdit.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaGameStageAwardEdit" */ '@/views/MetaGameStageAwardEdit.vue'),
                     meta: {
-                       title: '里程碑配置编辑',
-                    },
+                        title: '里程碑配置编辑'
+                    }
                 },
                 {
                     path: '/metaGameStageAwardList',
                     name: 'MetaGameStageAwardList',
-                    component: () => import(/* webpackChunkName: "metaGameStageAwardList" */ '@/views/MetaGameStageAwardList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaGameStageAwardList" */ '@/views/MetaGameStageAwardList.vue'),
                     meta: {
-                       title: '里程碑配置',
-                    },
+                        title: '里程碑配置'
+                    }
                 },
                 {
                     path: '/metaDestroyRecordList',
                     name: 'MetaDestroyRecordList',
-                    component: () => import(/* webpackChunkName: "metaDestroyRecordList" */ '@/views/MetaDestroyRecordList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaDestroyRecordList" */ '@/views/MetaDestroyRecordList.vue'),
                     meta: {
-                       title: '元宇宙销毁记录',
-                    },
+                        title: '元宇宙销毁记录'
+                    }
                 },
                 {
                     path: '/metaPlayerRoleClassifyEdit',
                     name: 'MetaPlayerRoleClassifyEdit',
-                    component: () => import(/* webpackChunkName: "metaPlayerRoleClassifyEdit" */ '@/views/MetaPlayerRoleClassifyEdit.vue'),
+                    component: () =>
+                        import(
+                            /* webpackChunkName: "metaPlayerRoleClassifyEdit" */ '@/views/MetaPlayerRoleClassifyEdit.vue'
+                        ),
                     meta: {
-                       title: '元宇宙角色配置编辑',
-                    },
+                        title: '元宇宙角色配置编辑'
+                    }
                 },
                 {
                     path: '/metaPlayerRoleClassifyList',
                     name: 'MetaPlayerRoleClassifyList',
-                    component: () => import(/* webpackChunkName: "metaPlayerRoleClassifyList" */ '@/views/MetaPlayerRoleClassifyList.vue'),
+                    component: () =>
+                        import(
+                            /* webpackChunkName: "metaPlayerRoleClassifyList" */ '@/views/MetaPlayerRoleClassifyList.vue'
+                        ),
                     meta: {
-                       title: '元宇宙角色配置',
-                    },
-               },
+                        title: '元宇宙角色配置'
+                    }
+                },
                 {
                     path: '/metaGameBoxPointsList',
                     name: 'MetaGameBoxPointsList',
-                    component: () => import(/* webpackChunkName: "metaGameBoxPointsList" */ '@/views/MetaGameBoxPointsList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "metaGameBoxPointsList" */ '@/views/MetaGameBoxPointsList.vue'),
                     meta: {
-                       title: '元宇宙GameBox积分',
-                    },
-               }
+                        title: '元宇宙GameBox积分'
+                    }
+                },
+                {
+                    path: '/push',
+                    name: 'push',
+                    component: () => import(/* webpackChunkName: "push" */ '@/views/Push.vue'),
+                    meta: {
+                        title: '推送通知'
+                    }
+                }
                 /**INSERT_LOCATION**/
             ]
         },

+ 1 - 1
src/main/vue/src/views/Admin.vue

@@ -12,7 +12,7 @@
                 :router="true"
                 :default-active="activeMenu"
                 style="border-right: 1px solid #545c64"
-                class="el-menu-vertical-demo"
+                class="el-menu-vertical-pushUtils"
             >
                 <sys-menu v-for="item in menus" :menu="item" :key="item.id"> </sys-menu>
             </el-menu>

+ 1 - 1
src/main/vue/src/views/MenuAuthority.vue

@@ -42,7 +42,7 @@
                 :unique-opened="true"
                 :router="true"
                 style="border-right: 1px solid #545c64"
-                class="el-menu-vertical-demo"
+                class="el-menu-vertical-pushUtils"
             >
                 <sys-menu v-for="item in previewMenus" :menu="item" :key="item.id" noRoute> </sys-menu>
             </el-menu>

+ 1 - 1
src/main/vue/src/views/PhotoProcessing.vue

@@ -3,7 +3,7 @@
         <el-upload
             action=""
             multiple
-            class="upload-demo"
+            class="upload-pushUtils"
             drag
             :file-list="fileList"
             list-type="picture"

+ 66 - 0
src/main/vue/src/views/Push.vue

@@ -0,0 +1,66 @@
+<template>
+    <div class="edit-view">
+        <page-title> </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form :model="formData" :rules="rules" ref="form">
+                    <el-form-item prop="title" label="标题">
+                        <el-input v-model="formData.title"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="text" label="内容">
+                        <el-input v-model="formData.text" type="textarea"></el-input>
+                    </el-form-item>
+                </el-form>
+                <el-button @click="push" :loading="pushing">发送</el-button>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name: 'Push',
+    data() {
+        return {
+            formData: {
+                title: '',
+                text: ''
+            },
+            rules: {
+                title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+                text: [{ required: true, message: '请输入内容', trigger: 'blur' }]
+            },
+            pushing: false
+        };
+    },
+    methods: {
+        push() {
+            this.$refs.form
+                .validate()
+                .then(res => {
+                    return this.$confirm('确认发送?');
+                })
+                .then(() => {
+                    this.pushing = true;
+                    this.$http
+                        .post('/push/broadcast', {
+                            ...this.formData,
+                            production:
+                                'raex.vip' === location.host ||
+                                'www.raex.vip' === location.host ||
+                                'admin.raex.vip' === location.host
+                        })
+                        .then(res => {
+                            this.$message.success('发送成功');
+                            this.pushing = false;
+                        })
+                        .catch(e => {
+                            this.$message.error(e.error);
+                            this.pushing = false;
+                        });
+                })
+                .catch(_ => {});
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>