Ver Fonte

修改迁移

chejianzheng há 4 meses atrás
pai
commit
b13468bc5b

+ 3 - 1
src/main/java/com/pub/config/CustomInterceptorConfig.java

@@ -16,7 +16,9 @@ public class CustomInterceptorConfig implements WebMvcConfigurer {
 	@Override
 	public void addInterceptors(InterceptorRegistry registry) {
 		registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**").excludePathPatterns("/s/**",
-				"/framework-ui/**","/ws/**", "/error/**", "/index/login", "/index/initData","/fileUpload/upload");
+				"/framework-ui/**","/ws/**", "/error/**", "/index/login", "/index/initData","/fileUpload/upload"
+				,"/wx/callback","/wx/auth_url"
+		);
 	}
 
 	@Bean

+ 18 - 1
src/main/java/com/pub/util/MqttUtil.java

@@ -4,6 +4,8 @@ import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.util.*;
 
+import com.wxxcx.index.wxSendMessage;
+import com.wxxcx.user.WxRelatiion;
 import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
 import org.eclipse.paho.client.mqttv3.MqttCallback;
 import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -24,7 +26,7 @@ import com.wxxcx.user.MiniUserVO;
 import com.wxxcx.ws.PushMsgWebSocket;
 
 public class MqttUtil {
-//    private static String HOST = "tcp://119.45.12.173:1883";
+    //    private static String HOST = "tcp://119.45.12.173:1883";
     //	测试websocket
     private static String HOST = "tcp://43.137.10.199:1883";
     private static String clientId = UUID.randomUUID().toString();
@@ -225,6 +227,21 @@ public class MqttUtil {
                             DevVO dev = devs.get(0);
                             openids.add(dev.getUser_openid());
                             dev_name.append(dev.getDev_name());
+
+                            // dev_id dev_info == 拥有者 openId openId ==> userInfo ==> phone oppenId == > wx_relation 微信公众号id
+                            // 这里添加 微信服务号消息推送  String devName, String phoneNo, String fwhOpenId
+                            List<MiniUserVO> users = baseDAO.queryAllByCondition(MiniUserVO.class, "and openid = " + dev.getUser_openid(), null);
+                            String phoneNo = "";
+                            if (users != null && !users.isEmpty()) {
+                                phoneNo = users.get(0).getPhone();
+                            }
+                            List<WxRelatiion> wxRelatiions = baseDAO.queryAllByCondition(WxRelatiion.class, "and openid = " + dev.getUser_openid(), null);
+                            String fwhOpenId = "";
+                            if (wxRelatiions != null && !wxRelatiions.isEmpty()) {
+                                fwhOpenId = wxRelatiions.get(0).getFwhopenid();
+                            }
+                            System.out.println("发送微信公众号信息:devName=" + dev.getDev_name() + ", phoneNo=" + phoneNo + "fwhOpenId=" + fwhOpenId);
+                            wxSendMessage.queryAccessToken(dev.getDev_name(), phoneNo, fwhOpenId);
                         }
                         // 被分享者openid
 //						List<ShareVO> shares = baseDAO.queryAllByCondition(ShareVO.class, " and dev_id ='"+dev_id+"' ", null);

+ 30 - 0
src/main/java/com/wxxcx/WxMpConfig/WxMpConfig.java

@@ -0,0 +1,30 @@
+package com.wxxcx.WxMpConfig;
+
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class WxMpConfig {
+
+    @Value("${wx.mp.appId}")
+    private String appId;
+
+    @Value("${wx.mp.secret}")
+    private String secret;
+
+    @Bean
+    public WxMpService wxMpService() {
+        WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
+        config.setAppId(appId);
+        config.setSecret(secret);
+
+        WxMpService service = new WxMpServiceImpl();
+        service.setWxMpConfigStorage(config);
+        return service;
+    }
+}

+ 95 - 0
src/main/java/com/wxxcx/index/WxMpOauthController.java

@@ -0,0 +1,95 @@
+package com.wxxcx.index;
+
+import com.pub.jdbc.BaseDAO;
+import com.pub.util.JSONUtil;
+import com.pub.util.R;
+import com.wxxcx.user.WxRelatiion;
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.service.WxOAuth2Service;
+import me.chanjar.weixin.mp.api.WxMpService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+public class WxMpOauthController {
+
+    private final WxMpService wxMpService;
+
+    public WxMpOauthController(WxMpService wxMpService) {
+        this.wxMpService = wxMpService;
+    }
+
+    @Autowired
+    private BaseDAO baseDAO;
+
+    /**
+     * 自动跳转微信授权
+     * @return
+     */
+    @GetMapping("/wx/auth_url")
+    public R auth(HttpServletResponse response) throws IOException {
+        System.out.println("进来了");
+        R r = JSONUtil.getSuccessMsg(null);
+        String redirectUrl = "https://jkld.radar-power.cn/wx/callback"; // 微信后台配置的回调地址
+        String authUrl = wxMpService.getOAuth2Service()
+                .buildAuthorizationUrl(redirectUrl, "snsapi_userinfo", "state123");
+        response.sendRedirect(authUrl);
+        System.out.println(authUrl);
+        System.out.println(r+"这个是微信回调");
+
+        return r;
+    }
+
+    /**
+     * 微信回调接口
+     */
+    @GetMapping("/wx/callback")
+    public void callback(String code, String state, HttpServletResponse response) throws IOException {
+        try {
+            if (code == null || code.isEmpty()) {
+                response.getWriter().write("code 不存在,可能不是微信回调");
+                System.out.println("code 不存在,可能不是微信回调");
+                return;
+            }
+
+            WxOAuth2Service oauth2Service = wxMpService.getOAuth2Service();
+            WxOAuth2AccessToken accessToken = oauth2Service.getAccessToken(code);
+            WxOAuth2UserInfo userInfo = oauth2Service.getUserInfo(accessToken, null);
+
+            System.out.println("userInfo: " + userInfo);
+            System.out.println("nickname: " + userInfo.getNickname());
+            System.out.println("avatar: " + userInfo.getHeadImgUrl());
+            System.out.println("unionId: " + userInfo.getUnionId());
+
+            List<WxRelatiion> wxRelatiions = baseDAO.queryAllByConditionNocare(WxRelatiion.class, " and unionid='" + userInfo.getUnionId() + "' ", null);
+
+            if (wxRelatiions == null || wxRelatiions.isEmpty()) {
+                response.getWriter().write("当前用户未登录");
+                System.out.println("当前用户未登录");
+                return;
+            }
+
+            wxRelatiions.forEach(e -> {
+                baseDAO.updateSQL("update wx_relation set fwhopenid ='"+ userInfo.getOpenid()
+                        +"' where unionid ='"+
+                        e.getUnionid()+"'");
+            });
+
+            // 授权成功,跳转欢迎页面(带参数)
+            String welcomeUrl = "/welcome.html?nickname=" + userInfo.getNickname()
+                    + "&avatar=" + userInfo.getHeadImgUrl();
+            response.sendRedirect(welcomeUrl);
+
+        } catch (WxErrorException e) {
+            e.printStackTrace();
+            response.getWriter().write("微信回调失败:" + e.getMessage());
+        }
+    }
+}

+ 103 - 48
src/main/java/com/wxxcx/index/WxxcxIndexController.java

@@ -43,6 +43,8 @@ import com.wxxcx.room.RoomVO;
 import com.wxxcx.share.ShareVO;
 import com.wxxcx.targets.TargetVO;
 import com.wxxcx.user.MiniUserVO;
+import com.wxxcx.user.WxRelatiion;
+
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
@@ -83,6 +85,17 @@ public class WxxcxIndexController<AesException extends Throwable> {
                 null, false, null, null);
         JSONObject obj = JSONObject.parseObject(res);
         r.data.put("openid", obj.getString("openid"));
+        r.data.put("unionid", obj.getString("unionid"));
+        // 查询是否存在该 openid
+        List<WxRelatiion> list = baseDAO.getJdbcTemplate().query(
+                "SELECT * FROM wx_relation WHERE openid = ?",
+                new Object[]{obj.getString("openid")},
+                new BeanPropertyRowMapper<>(WxRelatiion.class)
+        );
+        if (list.size() <= 0) {
+            String insertSql = "INSERT INTO wx_relation (openid, unionid,fwhopenid) VALUES (?, ?,?)";
+            baseDAO.getJdbcTemplate().update(insertSql, obj.getString("openid"), obj.getString("unionid"), null);
+        }
         return r;
     }
 
@@ -723,33 +736,14 @@ public class WxxcxIndexController<AesException extends Throwable> {
     @ApiOperation(value = "读取房间信息")
     public R readRoom(String openid, String dev_id) {
         R r = JSONUtil.getSuccessMsg(null);
-        String dev_id_fix = dev_id.replace(":", "");
-        StringBuffer path = new StringBuffer("/mnt/");
-        path.append(dev_id_fix + ".json");
-
-        // 创建File对象
-        File file = new File(path.toString());
-        // 文件不存在,直接退出
-        if (!file.exists()) {
-            r = JSONUtil.getErrorMsg("文件不存在");
-            return r;
-        }
 
-        StringBuilder jsonStringBuilder = new StringBuilder("");
-        // 使用FileReader读取文件
-        try (BufferedReader reader = new BufferedReader(new FileReader(path.toString()))) {
-            String line;
-            while ((line = reader.readLine()) != null) {
-                jsonStringBuilder.append(line);
-            }
-        } catch (IOException e) {
-            r = JSONUtil.getErrorMsg(e);
+        DevRoomVO devRoomVO = baseDAO.queryByKey(DevRoomVO.class, "dev_id", dev_id);
+        if (devRoomVO == null || devRoomVO.getRoom_params() == null || devRoomVO.getRoom_params().length() == 0 ) {
+            r = JSONUtil.getErrorMsg("房间信息不存在");
             return r;
         }
 
-        // 将读取到的字符串转换为JSON对象
-        String jsonStr = jsonStringBuilder.toString();
-        JSONObject jsonObj = JSON.parseObject(jsonStr);
+        JSONObject jsonObj = JSON.parseObject(devRoomVO.getRoom_params());
 
         // 向前端发送数据
         r.data.put("data", jsonObj);
@@ -767,25 +761,10 @@ public class WxxcxIndexController<AesException extends Throwable> {
     public R saveRoom(@RequestBody RoomVO vo) {
         R r = JSONUtil.getSuccessMsg(null);
         String dev_id = vo.getDev_id();
-        System.out.println(vo);
-        StringBuffer path = new StringBuffer("/mnt/");
-        String dev_id_fix = dev_id.replace(":", "");
-        path.append(dev_id_fix + ".json");
-        File file = new File(path.toString());
-        // 获取文件的父目录(即包含文件的文件夹)
-        File parentDir = file.getParentFile();
-        // 如果父目录不存在,则创建它
-        if (!parentDir.exists()) {
-            if (!parentDir.mkdirs()) {
-                System.err.println("无法创建目录:" + parentDir.getAbsolutePath());
-                r = JSONUtil.getErrorMsg("无法创建目录");
-                return r;
-            }
-        }
-        try (FileWriter fileWriter = new FileWriter(path.toString())) {
+
+        try  {
             String jsonString = JSON.toJSONString(vo);
-            // 写入服务器
-            fileWriter.write(jsonString);
+
             // 写入数据库
             JSONObject obj = JSONObject.parseObject(jsonString);
             JSONArray roomParams = obj.getJSONArray("roomParams");
@@ -796,21 +775,97 @@ public class WxxcxIndexController<AesException extends Throwable> {
                 room.setDev_id(dev_id);
                 baseDAO.insertAll(room);
             }
-            StringBuffer sql = new StringBuffer("update dev_room set room_params=JSON_ARRAY(");
+
+            StringBuffer sql = new StringBuffer("update dev_room set room_params= JSON_OBJECT(");
+
+            sql.append("'openid','" + obj.getString("openid") + "',");
+            sql.append("'dev_id','" + obj.getString("dev_id") + "',");
+            sql.append("'start_x','" + obj.getString("start_x") + "',");
+            sql.append("'stop_x','" + obj.getString("stop_x") + "',");
+            sql.append("'start_y','" + obj.getString("start_y") + "',");
+            sql.append("'stop_y','" + obj.getString("stop_y") + "',");
+            sql.append("'roomParams'," +  "JSON_ARRAY(");
+
+            // 子区域插入sql
+            StringBuffer subBuffer = new StringBuffer(", sub_regions= JSON_ARRAY(");
+            // 家具插入sql
+            StringBuffer furBuffer = new StringBuffer(", furniture= JSON_ARRAY(");
             if (roomParams.size() > 0) {
+
+                boolean hasSub = false;
+                boolean hasFur = false;
                 for (int i = 0; i < roomParams.size(); i++) {
                     JSONObject itemObj = roomParams.getJSONObject(i);
                     sql.append("JSON_OBJECT('name','" + itemObj.getString("name") + "',");
                     sql.append("'type','" + itemObj.getString("type") + "',");
-                    sql.append("'width'," + itemObj.getString("widthToM") + ",");
-                    sql.append("'height'," + itemObj.getString("heightToM") + ",");
+                    sql.append("'width'," + itemObj.getString("width") + ",");
+                    sql.append("'height'," + itemObj.getString("height") + ",");
+                    sql.append("'heightToM'," + itemObj.getString("heightToM") + ",");
+                    sql.append("'left'," + itemObj.getString("left") + ",");
+                    sql.append("'rotate'," + itemObj.getString("rotate") + ",");
+                    sql.append("'top'," + itemObj.getString("top") + ",");
+                    sql.append("'widthToM'," + itemObj.getString("widthToM") + ",");
                     sql.append("'x'," + itemObj.getString("x") + ",");
                     sql.append("'y'," + itemObj.getString("y") + ",");
                     sql.append("'yuanX'," + itemObj.getString("yuanX") + ",");
                     sql.append("'yuanY'," + itemObj.getString("yuanY") + "),");
+
+                    String type = itemObj.getString("type");
+                    if (type.contains("area") || type.contains("otherArea")) {
+                        // 子区域
+                        subBuffer.append("JSON_OBJECT('name','" + itemObj.getString("name") + "',");
+                        subBuffer.append("'type','" + itemObj.getString("type") + "',");
+                        subBuffer.append("'width'," + itemObj.getString("width") + ",");
+                        subBuffer.append("'height'," + itemObj.getString("height") + ",");
+                        subBuffer.append("'heightToM'," + itemObj.getString("heightToM") + ",");
+                        subBuffer.append("'left'," + itemObj.getString("left") + ",");
+                        subBuffer.append("'rotate'," + itemObj.getString("rotate") + ",");
+                        subBuffer.append("'top'," + itemObj.getString("top") + ",");
+                        subBuffer.append("'widthToM'," + itemObj.getString("widthToM") + ",");
+                        subBuffer.append("'x'," + itemObj.getString("x") + ",");
+                        subBuffer.append("'y'," + itemObj.getString("y") + ",");
+                        subBuffer.append("'yuanX'," + itemObj.getString("yuanX") + ",");
+                        subBuffer.append("'yuanY'," + itemObj.getString("yuanY") + "),");
+
+                        hasSub = true;
+                    } else {
+                        // 家具
+                        furBuffer.append("JSON_OBJECT('name','" + itemObj.getString("name") + "',");
+                        furBuffer.append("'type','" + itemObj.getString("type") + "',");
+                        furBuffer.append("'width'," + itemObj.getString("width") + ",");
+                        furBuffer.append("'height'," + itemObj.getString("height") + ",");
+                        furBuffer.append("'heightToM'," + itemObj.getString("heightToM") + ",");
+                        furBuffer.append("'left'," + itemObj.getString("left") + ",");
+                        furBuffer.append("'rotate'," + itemObj.getString("rotate") + ",");
+                        furBuffer.append("'top'," + itemObj.getString("top") + ",");
+                        furBuffer.append("'widthToM'," + itemObj.getString("widthToM") + ",");
+                        furBuffer.append("'x'," + itemObj.getString("x") + ",");
+                        furBuffer.append("'y'," + itemObj.getString("y") + ",");
+                        furBuffer.append("'yuanX'," + itemObj.getString("yuanX") + ",");
+                        furBuffer.append("'yuanY'," + itemObj.getString("yuanY") + "),");
+
+                        hasFur = true;
+                    }
                 }
+
                 sql.setLength(sql.length() - 1);
-                sql.append(") where dev_id ='" + dev_id + "' ");
+                sql.append("))" );
+
+
+                if (hasSub) {
+                    subBuffer.setLength(subBuffer.length() - 1);
+                    sql.append(subBuffer).append(")");
+                }
+                if (hasFur) {
+                    furBuffer.setLength(furBuffer.length() - 1);
+                    sql.append(furBuffer).append(")");
+                }
+
+                sql.append(" where dev_id ='" + dev_id + "' ");
+
+                System.out.println(sql.toString());
+                // update dev_room set room_params= JSON_OBJECT('openid','oioMm7VtkWkvHFYBlzCR6couIENQ','dev_id','8CBFEA0B75D8','start_x','-200.0','stop_x','200.0','start_y','-200.0','stop_y','200.0',JSON_OBJECT('name','门','type','other','width',20,'height',80,'heightToM',80.0,'left',303,'rotate',false,'top',191,'widthToM',20.0,'x',-196.97,'y',198.09,'yuanX',0,'yuanY',0)) where dev_id ='8CBFEA0B75D8'
+
                 baseDAO.updateSQL(sql.toString());
                 //发送屏蔽区域的数据到服务器
                 // 创建 ObjectMapper 实例
@@ -1354,10 +1409,10 @@ public class WxxcxIndexController<AesException extends Throwable> {
 //	            }
 //			}
 //			StringBuffer sql = new StringBuffer("update dev_group set dev_list =JSON_ARRAY(");
-//			
+//
 //			// 列表不为空
 //			if(updatedArray.size()>0) {
-//				for(int i=0;i<updatedArray.size();i++) { 
+//				for(int i=0;i<updatedArray.size();i++) {
 //					sql.append("'"+updatedArray.getString(i)+"',");
 //				}
 //				sql.setLength(sql.length() - 1);
@@ -1741,7 +1796,7 @@ public class WxxcxIndexController<AesException extends Throwable> {
     public R getAccessTokenTwo() {
         R r = JSONUtil.getSuccessMsg(null);
         // 发送模板消息
-        wxSendMessage.queryAccessToken();
+        wxSendMessage.queryAccessToken("测试设备", "17356519496", "oWlo-6iXL0pQeYWZxEpwB8knv6D8");
         return r;
     }
 

+ 12 - 5
src/main/java/com/wxxcx/index/wxSendMessage.java

@@ -21,7 +21,7 @@ public class wxSendMessage {
     private static final String SECRET = "426effb7b58f9ba7c5f6dd1062db0074";
     private static final Gson gson = new Gson();
 
-    public static void queryAccessToken() {
+    public static void queryAccessToken(String devName, String phoneNo, String fwhOpenId) {
         // 1. 获取 access_token
         String accessToken = getAccessToken();
         if (accessToken == null) {
@@ -39,10 +39,16 @@ public class wxSendMessage {
         characterString1.put("value", "123456");
         data.put("character_string1", characterString1);
         Map<String, String> thing8 = new HashMap<>();
-        thing8.put("value", "测试设备");
+
+        // 设备名称
+//        thing8.put("value", "测试设备");
+        thing8.put("value", devName);
         data.put("thing8", thing8);
         Map<String, String> thing10 = new HashMap<>();
-        thing10.put("value", "17356519496");
+
+        // 用户手机号
+//        thing10.put("value", "17356519496");
+        thing10.put("value", phoneNo);
         data.put("thing10", thing10);
         Map<String, String> time3 = new HashMap<>();
         time3.put("value", currentTime);  // 时间字段需符合格式要求
@@ -51,8 +57,8 @@ public class wxSendMessage {
         const2.put("value", "设备运行异常,给用户发送推送");
         data.put("const2", const2);
         System.out.println(data);
-        String openid = "oWlo-6iXL0pQeYWZxEpwB8knv6D8";
-        boolean result = sendTemplateMessage(accessToken, openid, data);
+//        String openid = "oWlo-6iXL0pQeYWZxEpwB8knv6D8";
+        boolean result = sendTemplateMessage(accessToken, fwhOpenId, data);
         System.out.println("发送结果: " + (result ? "成功" : "失败"));
     }
 
@@ -68,6 +74,7 @@ public class wxSendMessage {
                 HttpEntity entity = response.getEntity();
                 String result = EntityUtils.toString(entity);
                 Map<String, Object> map = gson.fromJson(result, Map.class);
+                System.out.println(map.get("access_token"));
                 return (String) map.get("access_token");
             }
         } catch (IOException e) {

+ 13 - 4
src/main/java/com/wxxcx/room/DevRoomVO.java

@@ -25,7 +25,16 @@ public class DevRoomVO extends SuperVO{
 	private Timestamp update_time;
 	@ApiModelProperty(value="房间参数")
 	private String roomParams;
-	
+
+	@ApiModelProperty(value="房间参数")
+	private String room_params;
+
+	@ApiModelProperty(value="子区域")
+	private String sub_regions;
+
+	@ApiModelProperty(value="家具")
+	private String furniture;
+
 	/**
 	 * 获得表名
 	 */
@@ -33,7 +42,7 @@ public class DevRoomVO extends SuperVO{
 	public String getTableName() {
 		return "dev_room";
 	}
-	
+
 	/**
 	 * 获得关联子表的CascadeMetadata,没有直接null
 	 */
@@ -41,11 +50,11 @@ public class DevRoomVO extends SuperVO{
 	public CascadeMetadata[] getChildCascadeMetadata() {
 		return null;
 	}
-	
+
 	public String getDevID() {
 		return "dev_id";
 	}
-	
+
 	@Override
 	public String getPKFieldName() {
 		return "id";

+ 46 - 0
src/main/java/com/wxxcx/user/WxRelatiion.java

@@ -0,0 +1,46 @@
+package com.wxxcx.user;
+
+
+import com.pub.jdbc.CascadeMetadata;
+import com.pub.jdbc.SuperVO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+
+public class WxRelatiion extends SuperVO {
+    @ApiModelProperty(value="id")
+    private int id;
+    @ApiModelProperty(value="微信的openid")
+    private String openid;
+    @ApiModelProperty(value="微信平台的id")
+    private String unionid;
+    @ApiModelProperty(value="微信服务号的id")
+    private String fwhopenid;
+
+    /**
+     * 获取表名
+     */
+    @Override
+    public String getTableName(){
+        return "wx_relation";
+    }
+
+    /**
+     * 获得关联子表的CascadeMetadata,没有直接null
+     */
+    @Override
+    public CascadeMetadata[] getChildCascadeMetadata() {
+        return null;
+    }
+
+    /**
+     * 获得主键字段名称
+     */
+    @Override
+    public String getPKFieldName() {
+        return "id";
+    }
+
+
+}

+ 1 - 0
src/main/resources/static/MP_verify_y76kLVEQavXVgJN6.txt

@@ -0,0 +1 @@
+y76kLVEQavXVgJN6

+ 80 - 0
src/main/resources/static/welcome.html

@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <title>绑定成功</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <style>
+        html,
+        body {
+            margin: 0;
+            padding: 0;
+            height: 100%;
+            font-family: "Helvetica Neue", Arial, sans-serif;
+            background-color: #ffffff;
+        }
+
+        .card {
+            width: 100%;
+            height: 100%;
+            border-radius: 0;
+            background-color: #fff;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            text-align: center;
+        }
+
+        .avatar {
+            width: 120px;
+            height: 120px;
+            border-radius: 50%;
+            object-fit: cover;
+            margin-bottom: 20px;
+        }
+
+        .nickname {
+            font-size: 28px;
+            font-weight: bold;
+            color: #222;
+            margin-bottom: 10px;
+        }
+
+        .openid {
+            font-size: 18px;
+            color: #555;
+            word-break: break-all;
+            margin-bottom: 20px;
+        }
+
+        .success {
+            font-size: 20px;
+            color: #4CAF50;
+            font-weight: 500;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="card">
+        <img class="avatar" id="avatar" src="https://via.placeholder.com/120" alt="用户头像">
+        <div class="nickname" id="nickname">加载中...</div>
+        <div class="openid" id="openid"></div>
+        <div class="success">✅ 已成功绑定服务号通知</div>
+    </div>
+
+    <script>
+        const urlParams = new URLSearchParams(window.location.search);
+        const nickname = urlParams.get('nickname') || '用户';
+        const openid = urlParams.get('openid') || '';
+        const avatar = urlParams.get('avatar') || '';
+
+        document.getElementById("nickname").innerText = decodeURIComponent(nickname);
+        document.getElementById("openid").innerText = openid ? `OpenID: ${openid}` : '';
+        document.getElementById("avatar").src = avatar || "https://api.iconify.design/mdi:account-circle.svg?color=%23ccc&width=120";
+    </script>
+</body>
+
+</html>