zoukankan      html  css  js  c++  java
  • 微信公众号开发-配置开发环境02

    1.前言
      经过前面的配置,基本完成了一些基础配置。后面接下来就是一些开发流程了。


    2.配置pom.xml

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      4     <modelVersion>4.0.0</modelVersion>
      5 
      6     <groupId>com.wunaozai.wechat</groupId>
      7     <artifactId>WeChat</artifactId>
      8     <version>0.0.1</version>
      9     <packaging>jar</packaging>
     10 
     11     <name>WeChat</name>
     12     <description>微信公众号服务器</description>
     13 
     14     <parent>
     15         <groupId>org.springframework.boot</groupId>
     16         <artifactId>spring-boot-starter-parent</artifactId>
     17         <version>2.0.4.RELEASE</version>
     18         <relativePath/> <!-- lookup parent from repository -->
     19     </parent>
     20 
     21     <properties>
     22         <weixin-java-mp.version>3.1.0</weixin-java-mp.version>
     23         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     24         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     25         <java.version>1.8</java.version>
     26     </properties>
     27 
     28     <dependencies>
     29         <dependency>
     30             <groupId>org.springframework.boot</groupId>
     31             <artifactId>spring-boot-starter-web</artifactId>
     32         </dependency>
     33         
     34         <dependency>
     35             <groupId>org.springframework.boot</groupId>
     36             <artifactId>spring-boot-starter-thymeleaf</artifactId>
     37         </dependency>
     38         <dependency>
     39             <groupId>com.github.binarywang</groupId>
     40             <artifactId>weixin-java-mp</artifactId>
     41             <version>${weixin-java-mp.version}</version>
     42         </dependency>
     43         
     44         <dependency>
     45             <groupId>org.springframework.boot</groupId>
     46             <artifactId>spring-boot-starter-data-redis</artifactId>
     47         </dependency>
     48         <dependency>
     49             <groupId>org.springframework.boot</groupId>
     50             <artifactId>spring-boot-starter-data-mongodb</artifactId>
     51         </dependency>
     52         <dependency>
     53             <groupId>org.postgresql</groupId>
     54             <artifactId>postgresql</artifactId>
     55         </dependency>
     56         <dependency>
     57             <groupId>org.mybatis.spring.boot</groupId>
     58             <artifactId>mybatis-spring-boot-starter</artifactId>
     59             <version>1.3.2</version>
     60         </dependency>
     61         <dependency>
     62             <groupId>org.springframework.boot</groupId>
     63             <artifactId>spring-boot-starter-security</artifactId>
     64         </dependency>
     65         <dependency>
     66             <groupId>org.springframework.security.oauth</groupId>
     67             <artifactId>spring-security-oauth2</artifactId>
     68             <version>2.3.3.RELEASE</version>
     69         </dependency>
     70         <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
     71         <dependency>
     72             <groupId>com.github.pagehelper</groupId>
     73             <artifactId>pagehelper-spring-boot-starter</artifactId>
     74             <version>1.2.5</version>
     75         </dependency>
     76         <!-- MQTT -->
     77         <dependency>
     78             <groupId>org.springframework.boot</groupId>
     79             <artifactId>spring-boot-starter-integration</artifactId>
     80         </dependency>
     81         <dependency>
     82             <groupId>org.springframework.integration</groupId>
     83             <artifactId>spring-integration-stream</artifactId>
     84         </dependency>
     85         <dependency>
     86             <groupId>org.springframework.integration</groupId>
     87             <artifactId>spring-integration-mqtt</artifactId>
     88         </dependency>
     89 
     90         <dependency>
     91             <groupId>org.springframework.boot</groupId>
     92             <artifactId>spring-boot-devtools</artifactId>
     93             <scope>runtime</scope>
     94         </dependency>
     95         <dependency>
     96             <groupId>org.springframework.boot</groupId>
     97             <artifactId>spring-boot-starter-test</artifactId>
     98             <scope>test</scope>
     99         </dependency>
    100     </dependencies>
    101 
    102     <build>
    103         <plugins>
    104             <plugin>
    105                 <groupId>org.springframework.boot</groupId>
    106                 <artifactId>spring-boot-maven-plugin</artifactId>
    107             </plugin>
    108         </plugins>
    109     </build>
    110 
    111 
    112 </project>

      Thymeleaf 作为公众号页面前端
      weixin-java-mp 是微信公众号开发工具包
      Redis 主要用来集成OAuth2.0认证
      MongoDB 存一些JSON数据
      PostgreSQL 关系型数据,保存业务数据
      security-OAuth2 认证、权限控制
      spring-integration-mqtt MQTT客户端,用来与设备通讯

    3.配置文件application.properties

      一些项目配置

     1 spring.application.name=wechat
     2 
     3 server.port=8001
     4 
     5 logging.path=log
     6 logging.level.org.springframework.web=INFO
     7 logging.level.com.github.binarywang.demo.wx.mp=DEBUG
     8 logging.level.com.wunaozai.wechat=DEBUG
     9 
    10 wechat.mp.appId=wxc60******
    11 wechat.mp.secret=cfe2******
    12 wechat.mp.token=we****
    13 wechat.mp.aesKey=2Bq*******
    14 #微信号
    15 #wechat.mp.accountId=gh_8*****
    16 #上传临时目录
    17 wechat.mp.tmpDir=C:/tmp
    18 #系统语音存放目录
    19 project.voice.dir=D:/tmp
    20 #微信语音资源下载链接前缀
    21 project.resource.url=http://wechat.wunaozai.com/wx/v1/voice/recv
    22 
    23 #接口配置
    24 wechat.mp.url=http://wechat.wunaozai.com/wx/wechat
    25 #JS接口安全域名配置
    26 wechat.mp.js=wechat.wunaozai.com
    27 #网页授权域名配置
    28 wechat.mp.web.oauth=wechat.wunaozai.com
    29 wechat.mp.protocol=http
    30 
    31 spring.thymeleaf.prefix=classpath:/templates/
    32 spring.thymeleaf.suffix=.html
    33 spring.thymeleaf.mode=HTML5
    34 
    35 
    36 #postgres
    37 spring.datasource.url=jdbc:postgresql://172.16.23.202:5432/wechat
    38 spring.datasource.username=postgres
    39 spring.datasource.password=
    40 spring.datasource.driver-class-name=org.postgresql.Driver
    41 
    42 #mybatis
    43 mybatis.type-aliases-package=com.wunaozai.wechat.model
    44 mybatis.mapper-locations=classpath:com/wunaozai/wechat/mapper/*.xml
    45 
    46 #pagehelper
    47 pagehelper.helper-dialect=postgresql
    48 pagehelper.reasonable=true
    49 pagehelper.support-methods-arguments=true
    50 pagehelper.page-size-zero=true
    51 
    52 #redis
    53 spring.redis.database=2
    54 spring.redis.host=172.16.23.203
    55 spring.redis.port=6379
    56 spring.redis.password=f4e4********
    57 spring.redis.jedis.pool.max-active=8
    58 spring.redis.jedis.pool.max-wait=60
    59 spring.redis.jedis.pool.max-idle=8
    60 spring.redis.jedis.pool.min-idle=0
    61 spring.redis.timeout=10000
    62 
    63 #mongoDB
    64 spring.data.mongodb.uri=mongodb://www.wunaozai.com:27777/wechat
    65 
    66 #MQTT Client
    67 mqtt.client.host=tcp://mqtt.wunaozai.com:1883
    68 mqtt.client.clientid=*****admin
    69 mqtt.client.username=*****admin
    70 mqtt.client.password=*******
    71 mqtt.client.timeout=10
    72 mqtt.client.keepalive=20
    73 
    74 #tuling123
    75 api.tuling.url=http://openapi.tuling123.com/openapi/api/v2
    76 api.tuling.apiKey=******
    77 api.tuling.userId=******

    4.配置wechat-java-mp 工具包
      这个配置基本参考官网的Demo就可以了。我这里截图我自己的目录结构。

      从目录结构可以看出,一个 WechatMpConfiguration.java 作为全局配置及微信开发工具包入口。而下方的handler是对所有微信的事件进行封装。
      例如菜单事件、关注事件、取消关注事件、扫描事件、语音事件、对话事件等。每个事件都对应一个Handler。

      1 /**
      2  * 微信公众号开发 全局配置区
      3  * @author wunaozai
      4  * @date 2018-08-15
      5  */
      6 @Configuration
      7 @ConditionalOnClass(value=WxMpService.class)
      8 @EnableConfigurationProperties(WechatMpProperties.class)
      9 public class WechatMpConfiguration {
     10 
     11     @Autowired
     12     private WechatMpProperties properties;
     13     @Autowired
     14     private WechatMpLogHandler logHandler;
     15     @Autowired
     16     private WechatMpNullHandler nullHandler;
     17     @Autowired
     18     private WechatMpMenuHandler menuHandler;
     19     @Autowired
     20     private WechatMpMsgHandler msgHandler;
     21     @Autowired
     22     private WechatMpMsgVoiceHandler msgvoiceHandler;
     23     @Autowired
     24     private WechatMpUnsubscribeHandler unsubscribeHandler;
     25     @Autowired
     26     private WechatMpSubscribeHandler subscribeHandler;
     27     @Autowired
     28     private WechatMpScanHandler scanHandler;
     29     
     30     @Bean
     31     @ConditionalOnMissingBean
     32     public WxMpConfigStorage configStorage() {
     33         WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
     34         config.setAppId(properties.getAppId());
     35         config.setSecret(properties.getSecret());
     36         config.setToken(properties.getToken());
     37         config.setAesKey(properties.getAesKey());
     38         config.setTmpDirFile(new File(properties.getTmpDir()));
     39         return config;
     40     }
     41     
     42     @Bean
     43     @ConditionalOnMissingBean
     44     public WxMpService wxmpService(WxMpConfigStorage config) {
     45         WxMpService service = new WxMpServiceImpl();
     46         service.setWxMpConfigStorage(config);
     47         return service;
     48     }
     49     /*
     50     @Bean
     51     @ConditionalOnMissingBean
     52     public WxMpDeviceService wxMpDeviceService(WxMpService wxmpService) {
     53         WxMpDeviceService service = new WxMpDeviceServiceImpl(wxmpService);
     54         return service;
     55     }
     56     */
     57     
     58     @Bean
     59     public WxMpMessageRouter router(WxMpService wxmpService) {
     60         
     61         final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxmpService);
     62         
     63         //记录所有事件日志(异步执行)
     64         newRouter.rule().async(false).handler(this.logHandler).next();
     65         
     66         //当前项目Wifi故事机 只处理自定义菜单事件、关注/取关事件、文本和语音信息
     67         //自定义菜单事件
     68         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     69             .event(MenuButtonType.CLICK).handler(menuHandler).end();
     70         //关注事件
     71         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     72             .event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
     73         //取消关注事件
     74         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     75             .event(EventType.UNSUBSCRIBE).handler(unsubscribeHandler).end();
     76         //处理扫描事件
     77         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     78             .event(EventType.SCANCODE_WAITMSG).handler(scanHandler).end();
     79         
     80         //这里过滤掉所有事件
     81         newRouter.rule().async(false).msgType(XmlMsgType.EVENT).handler(nullHandler).end();
     82         
     83         //处理语音信息
     84         newRouter.rule().async(false).msgType(XmlMsgType.VOICE).handler(msgvoiceHandler).end();
     85         
     86         //这里过滤掉所有输入
     87         newRouter.rule().async(false).handler(msgHandler).end();
     88         
     89         /*
     90         //接收客服会话管理事件
     91         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     92             .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
     93             .handler(kfsessionHandler).end();
     94         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     95             .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
     96             .handler(kfsessionHandler).end();
     97         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
     98             .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
     99             .handler(kfsessionHandler).end();
    100             
    101         //门店审核事件
    102         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    103             .event(MenuButtonType.CLICK).handler(nullHandler).end();
    104         
    105         //自定义菜单事件
    106         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    107             .event(MenuButtonType.CLICK).handler(menuHandler).end();
    108         
    109         //点击菜单链接事件
    110         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    111             .event(MenuButtonType.VIEW).handler(nullHandler).end();
    112         
    113         //关注事件
    114         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    115             .event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
    116         
    117         //取消关注事件
    118         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    119             .event(EventType.UNSUBSCRIBE).handler(unsubscribeHandler).end();
    120         
    121         //上报地理位置事件
    122         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    123             .event(EventType.LOCATION).handler(locationHandler).end();
    124         
    125         //接收地理位置消息
    126         newRouter.rule().async(false).msgType(XmlMsgType.LOCATION)
    127             .handler(locationHandler).end();
    128         
    129         //扫码事件
    130         newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
    131             .event(EventType.SCAN).handler(nullHandler).end();
    132         
    133         //默认
    134         newRouter.rule().async(false).handler(msgHandler).end();
    135         */
    136         return newRouter;
    137     }
    138 }

    5. 关注事件举例
      我们在WechatMpConfiguration.java中注册了一个关注事件处理Handler。

    newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SUBSCRIBE).handler(subscribeHandler).end();
     1 /**
     2  * 用户关注事件
     3  * @author wunaozai
     4  * @date 2018-08-15
     5  */
     6 @Component
     7 public class WechatMpSubscribeHandler extends AbstractHandler {
     8 
     9     @Autowired
    10     private WechatUserService wechatuserService;
    11     
    12     @Override
    13     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
    14             WxSessionManager sessionManager) throws WxErrorException {
    15         //log.info("新关注用户 openid: " + wxMessage.getFromUser());
    16         //获取微信用户基本信息
    17         WxMpUser user = wxmpService.getUserService().userInfo(wxMessage.getFromUser(), null);
    18         if(user != null) {
    19             //可以添加关注用户到本地数据库
    20             WechatUserPoModel po = new WechatUserPoModel();
    21             po.setOpenid(user.getOpenId());
    22             po.setStatus(true);
    23             wechatuserService.selectOneAndInsertIt(po);
    24             
    25             po.setNickname(user.getNickname());
    26             po.setSex_desc(user.getSexDesc());
    27             po.setSex(user.getSex());
    28             po.setCity(user.getCity());
    29             po.setProvince(user.getProvince());
    30             po.setCountry(user.getCountry());
    31             po.setHead_img_url(user.getHeadImgUrl());
    32             wechatuserService.updateOneByOpenid(po);
    33             
    34         }
    35         try {
    36             String msg = "您好," + user.getNickname() + 
    37                     "!
    欢迎使用杰理故事机/:heart
    如果是第一次使用,请点击下方按钮,按提示步骤进行操作。";
    38             return new WechatMpTextBuilder().build(msg, wxMessage, wxmpService);
    39         } catch (Exception e) {
    40             log.error(e.getMessage());
    41         }
    42         return null;
    43     }
    44 }

    6. 扫码事件举例
      类似关注事件,扫码事件,也是需要在配置类,注册一个Handler。

    newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SCANCODE_WAITMSG).handler(scanHandler).end();
     1 @Component
     2 public class WechatMpScanHandler extends AbstractHandler {
     3 
     4     @Autowired
     5     private WechatUserCtrlService wechatuserctrlService;
     6     
     7     @Override
     8     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
     9             WxSessionManager sessionManager) throws WxErrorException {
    10         String scan_type = wxMessage.getScanCodeInfo().getScanType();
    11         String scan_result = wxMessage.getScanCodeInfo().getScanResult();
    12         String info = "非本公众号内二维码";
    13         try {
    14             if(scan_type.equalsIgnoreCase("qrcode")) {
    15                 ScanResult result = TranUtils.tranJSONObejct(scan_result, ScanResult.class);
    16                 String openid = wxMessage.getFromUser();
    17                 if(result.getK().equalsIgnoreCase("share")) {
    18                     //设备分享功能
    19                     try {
    20                         boolean flag = wechatuserctrlService.bindDeviceByShareKey(openid, result.getV());
    21                         if(flag == true) {
    22                             info = "绑定成功.";
    23                         }else {
    24                             info = "绑定失败.";
    25                         }
    26                     }catch (Exception e) {
    27                         info = e.getMessage();
    28                     }
    29                 }
    30             }
    31         }catch (Exception e) {
    32             e.printStackTrace();
    33         }
    34         return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
    35     }
    36 }

    7. 自定义菜单
      这个就分为创建自定义菜单和自定义菜单事件。创建自定义菜单,这个需要先构造自定义菜单格式,然后POST到微信。

     1 @RestController
     2 @RequestMapping("/wx/wechat")
     3 public class WechatMenuController implements WxMpMenuService {
     4 
     5     @Autowired
     6     private WxMpService wxmpService;
     7     @Value(value="${wechat.mp.url}")
     8     private String url;
     9     
    10     @GetMapping("/menu")
    11     public String menuCreateSample() throws WxErrorException {
    12         WxMenu menu = new WxMenu();
    13         WxMenuButton story = new WxMenuButton();
    14         story.setType(MenuButtonType.VIEW);
    15         story.setName("听故事");
    16         story.setKey(WechatMpMenuConfig.M_STORY);
    17         story.setUrl(url + "/story/index");
    18 
    19         WxMenuButton device_1 = new WxMenuButton();
    20         device_1.setType(MenuButtonType.VIEW);
    21         device_1.setName("联网配置");
    22         device_1.setKey(WechatMpMenuConfig.M_DEVICE_1);
    23         device_1.setUrl(url + "/airkiss");
    24         WxMenuButton device_2 = new WxMenuButton();
    25         device_2.setType(MenuButtonType.VIEW);
    26         device_2.setName("设备绑定");
    27         device_2.setKey(WechatMpMenuConfig.M_DEVICE_2);
    28         device_2.setUrl(url + "/scan_bind");
    29         WxMenuButton device_3 = new WxMenuButton();
    30         device_3.setType(MenuButtonType.SCANCODE_WAITMSG);
    31         device_3.setName("二维码扫描");
    32         device_3.setKey(WechatMpMenuConfig.M_DEVICE_3);
    33         WxMenuButton device_4 = new WxMenuButton();
    34         device_4.setType(MenuButtonType.VIEW);
    35         device_4.setName("我的设备");
    36         device_4.setKey(WechatMpMenuConfig.M_DEVICE_4);
    37         device_4.setUrl(url + "/story/index#/user");
    38         WxMenuButton device = new WxMenuButton();
    39         device.setType(MenuButtonType.CLICK);
    40         device.setName("设备功能");
    41         device.setKey(WechatMpMenuConfig.M_DEVICE);
    42         device.getSubButtons().add(device_1);
    43         device.getSubButtons().add(device_2);
    44         device.getSubButtons().add(device_3);
    45         device.getSubButtons().add(device_4);
    46         
    47         
    48         WxMenuButton other_1 = new WxMenuButton();
    49         other_1.setType(MenuButtonType.VIEW);
    50         other_1.setName("常见问题");
    51         other_1.setKey(WechatMpMenuConfig.M_OTHER_1);
    52         other_1.setUrl("https://mp.weixin.qq.com/s/YTGis2OK2Jtx-4NnnRqukw");
    53         WxMenuButton other_2 = new WxMenuButton();
    54         other_2.setType(MenuButtonType.VIEW);
    55         other_2.setName("意见反馈");
    56         other_2.setKey(WechatMpMenuConfig.M_OTHER_2);
    57         other_2.setUrl(url + "/feedback");
    58         WxMenuButton other_3 = new WxMenuButton();
    59         other_3.setType(MenuButtonType.VIEW);
    60         other_3.setName("官网");
    61         other_3.setKey(WechatMpMenuConfig.M_OTHER_3);
    62         other_3.setUrl("http://www.wunaozai.com/");
    63         WxMenuButton other_4 = new WxMenuButton();
    64         other_4.setType(MenuButtonType.CLICK);
    65         other_4.setName("接收新信息");
    66         other_4.setKey(WechatMpMenuConfig.M_OTHER_NEW_MSG);
    67         WxMenuButton other = new WxMenuButton();
    68         other.setType(MenuButtonType.CLICK);
    69         other.setName("更多");
    70         other.setKey(WechatMpMenuConfig.M_OTHER);
    71         other.getSubButtons().add(other_1);
    72         other.getSubButtons().add(other_2);
    73         other.getSubButtons().add(other_3);
    74         other.getSubButtons().add(other_4);
    75         
    76         
    77         menu.getButtons().add(story);
    78         menu.getButtons().add(device);
    79         menu.getButtons().add(other);
    80         
    81         wxmpService.getMenuService().menuCreate(menu);
    82         return "ok";
    83   }
    84   //...
    85 }

      自定义菜单事件Handler

     1 /**
     2  * 菜单功能
     3  * @author wunaozai
     4  * @date 2018-08-15
     5  */
     6 @Component
     7 public class WechatMpMenuHandler extends AbstractHandler {
     8 
     9     @Override
    10     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxmpService,
    11             WxSessionManager sessionManager) throws WxErrorException {
    12         log.info("微信公众号 菜单功能");
    13         String msg = String.format("Type: %s, Event: %s, Key: %s", 
    14                 wxMessage.getMsgType(), wxMessage.getEvent(), wxMessage.getEventKey());
    15         if(MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
    16             //如果是跳转类按钮的就不进行处理
    17             return null;
    18         }
    19         if(wxMessage.getEventKey().equalsIgnoreCase(WechatMpMenuConfig.M_OTHER_NEW_MSG)) {
    20             //TODO: 读取数据库,下发信息,并删除
    21             //如果没有信息的,提示没有未读信息
    22             String info = "暂无新消息.";
    23             return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
    24         }
    25         String info = "按下“"+wxMessage.getEventKey()+"”按钮,将跳转到指定的公众号页面.";
    26         return new WechatMpTextBuilder().build(info, wxMessage, wxmpService);
    27     }
    28 
    29 }

      自定义菜单注册

    newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(MenuButtonType.CLICK).handler(menuHandler).end();

      公众号后台-菜单分析

  • 相关阅读:
    细节决定成败,为什么他能挣15亿
    GLSL Notes
    Connecting Physics Bodies
    Pylint
    Physicals
    Advanced Scene Processing
    Working with Other Node Types II
    OperateParticleWithCodes
    SpriteParticle II
    Working with Other Node Types
  • 原文地址:https://www.cnblogs.com/wunaozai/p/9911089.html
Copyright © 2011-2022 走看看