zoukankan      html  css  js  c++  java
  • 微信-服务器消息接受与回复-代码

    com.wtxy.weixin.service
      1 package com.wtxy.weixin.service;
      2 
      3 import java.util.Date;
      4 import java.util.Map;
      5 
      6 import javax.servlet.http.HttpServletRequest;
      7 
      8 import com.wtxy.util.MessageUtil;
      9 import com.wtxy.weixin.message.TextMessage;
     10 
     11 /**
     12  * 核心服务类
     13  * 
     14  * @author Ran
     15  * @date 2016-04-21
     16  */
     17 public class CoreService {
     18     /**
     19      * 处理微信发来的请求
     20      * 
     21      * @param request
     22      * @return xml
     23      */
     24     public static String processRequest(HttpServletRequest request) {
     25         // xml格式的消息数据
     26         String respXML = null;
     27         
     28         TextMessage textMessage = new TextMessage();
     29         // 默认返回的文本消息内容
     30         String respContent = "未知的消息类型!";
     31         try {
     32             // 调用parseXml方法解析请求消息
     33             Map<String, String> requestMap = MessageUtil.parseXML(request);
     34             // 发送方帐号
     35             String fromUserName = requestMap.get("FromUserName");
     36             // 开发者微信号
     37             String toUserName = requestMap.get("ToUserName");
     38             // 消息类型
     39             String msgType = requestMap.get("MsgType");
     40 
     41             // 回复文本消息
     42             textMessage.setToUserName(fromUserName);
     43             textMessage.setFromUserName(toUserName);
     44             textMessage.setCreateTime(new Date().getTime());
     45             textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
     46 
     47             // 文本消息
     48             if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
     49                 String content = requestMap.get("Content"); 
     50                 if (content.equals("开灯")){
     51                     respContent = "灯已开!";
     52                //respContent = "您发送的是文本消息!内容是:
    "+content;
     53                 }else if(content.equals("关灯")){
     54                     respContent = "灯已关!";
     55                 }
     56 
     57             }
     58             // 图片消息
     59             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
     60                 respContent = "您发送的是图片消息!";
     61             }
     62             // 语音消息
     63             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
     64                 respContent = "您发送的是语音消息!";
     65             }
     66             // 视频消息
     67             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {
     68                 respContent = "您发送的是视频消息!";
     69             }
     70             // 地理位置消息
     71             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {
     72                 respContent = "您发送的是地理位置消息!";
     73             }
     74             // 链接消息
     75             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
     76                 respContent = "您发送的是链接消息!";
     77             }
     78             // 事件推送
     79             else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
     80                 // 事件类型
     81                 String eventType = requestMap.get("Event");
     82                 // 关注
     83                 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
     84                     respContent = "欢迎关注 智能管家wtxy!";
     85                 }
     86                 // 取消关注
     87                 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
     88                     // TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复
     89                 }
     90                 // 扫描带参数二维码
     91                 else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {
     92                     // TODO 处理扫描带参数二维码事件
     93                 }
     94                 // 上报地理位置
     95                 else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {
     96                     // TODO 处理上报地理位置事件
     97                 }
     98                 // 自定义菜单
     99                 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
    100                     // TODO 处理菜单点击事件
    101                 }
    102             }
    103             // 设置文本消息的内容
    104             textMessage.setContent(respContent);
    105             // 将文本消息对象转换成xml
    106             respXML = MessageUtil.messageToXML(textMessage);
    107         } catch (Exception e) {
    108             e.printStackTrace();
    109         }
    110         return respXML;
    111     }
    112 }
    CoreService.java
    com.wtxy.servlet
     1 package com.wtxy.servlet;
     2 
     3 import java.io.IOException;
     4 import java.io.PrintWriter;
     5 
     6 import javax.servlet.ServletException;
     7 import javax.servlet.http.HttpServlet;
     8 import javax.servlet.http.HttpServletRequest;
     9 import javax.servlet.http.HttpServletResponse;
    10 
    11 import com.wtxy.util.SignUtil;
    12 import com.wtxy.weixin.service.CoreService;
    13 
    14 
    15 public class CoreServlet extends HttpServlet {
    16 
    17     private static final long serialVersionUID = 4440739483644821987L;
    18 
    19     /**
    20      * 请求校验(确认请求来自微信服务器)
    21      */
    22     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    23         // 微信加密签名
    24         String signature = request.getParameter("signature");
    25         // 时间戳
    26         String timestamp = request.getParameter("timestamp");
    27         // 随机数
    28         String nonce = request.getParameter("nonce");
    29         // 随机字符串
    30         String echostr = request.getParameter("echostr");
    31 
    32         PrintWriter out = response.getWriter();
    33         // 请求校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
    34         if (SignUtil.checkSignature(signature, timestamp, nonce)) {
    35             out.print(echostr);
    36         }
    37         out.close();
    38         out = null;
    39     }
    40 
    41     /**
    42      * 处理微信服务器发来的消息
    43      */
    44     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    45         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
    46                 request.setCharacterEncoding("UTF-8");
    47                 response.setCharacterEncoding("UTF-8");
    48 
    49                 // 接收参数微信加密签名、 时间戳、随机数
    50                 String signature = request.getParameter("signature");
    51                 String timestamp = request.getParameter("timestamp");
    52                 String nonce = request.getParameter("nonce");
    53 
    54                 PrintWriter out = response.getWriter();
    55                 // 请求校验
    56                 if (SignUtil.checkSignature(signature, timestamp, nonce)) {
    57                     // 调用核心服务类接收处理请求
    58                     String respXML = CoreService.processRequest(request);
    59                     out.write(respXML);
    60                 }
    61                 out.close();
    62                 out = null;
    63     }
    64 
    65 }
    CoreServlet.java
    com.wtxy.util
      1 package com.wtxy.util;
      2 
      3 import java.io.InputStream;
      4 import java.io.Writer;
      5 import java.util.HashMap;
      6 import java.util.List;
      7 import java.util.Map;
      8 
      9 import javax.servlet.http.HttpServletRequest;
     10 
     11 import org.dom4j.Document;
     12 import org.dom4j.Element;
     13 import org.dom4j.io.SAXReader;
     14 
     15 import com.thoughtworks.xstream.XStream;
     16 import com.thoughtworks.xstream.core.util.QuickWriter;
     17 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
     18 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
     19 import com.thoughtworks.xstream.io.xml.XppDriver;
     20 import com.wtxy.weixin.message.Article;
     21 import com.wtxy.weixin.message.ImageMessage;
     22 import com.wtxy.weixin.message.MusicMessage;
     23 import com.wtxy.weixin.message.NewsMessage;
     24 import com.wtxy.weixin.message.TextMessage;
     25 import com.wtxy.weixin.message.VideoMessage;
     26 import com.wtxy.weixin.message.VoiceMessage;
     27 
     28 public class MessageUtil {
     29     // 请求消息类型:文本
     30         public static final String REQ_MESSAGE_TYPE_TEXT = "text";
     31         // 请求消息类型:图片
     32         public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
     33         // 请求消息类型:语音
     34         public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
     35         // 请求消息类型:视频
     36         public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
     37         // 请求消息类型:地理位置
     38         public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
     39         // 请求消息类型:链接
     40         public static final String REQ_MESSAGE_TYPE_LINK = "link";
     41 
     42         // 请求消息类型:事件推送
     43         public static final String REQ_MESSAGE_TYPE_EVENT = "event";
     44 
     45         // 事件类型:subscribe(订阅)
     46         public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
     47         // 事件类型:unsubscribe(取消订阅)
     48         public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
     49         // 事件类型:scan(用户已关注时的扫描带参数二维码)
     50         public static final String EVENT_TYPE_SCAN = "scan";
     51         // 事件类型:LOCATION(上报地理位置)
     52         public static final String EVENT_TYPE_LOCATION = "LOCATION";
     53         // 事件类型:CLICK(自定义菜单)
     54         public static final String EVENT_TYPE_CLICK = "CLICK";
     55 
     56         // 响应消息类型:文本
     57         public static final String RESP_MESSAGE_TYPE_TEXT = "text";
     58         // 响应消息类型:图片
     59         public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
     60         // 响应消息类型:语音
     61         public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
     62         // 响应消息类型:视频
     63         public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
     64         // 响应消息类型:音乐
     65         public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
     66         // 响应消息类型:图文
     67         public static final String RESP_MESSAGE_TYPE_NEWS = "news";
     68 
     69         /**
     70          * 解析微信发来的请求(XML)
     71          * 
     72          * @param request
     73          * @return Map<String, String>
     74          * @throws Exception
     75          */
     76         //@SuppressWarnings("unchecked")
     77         public static HashMap<String, String> parseXML(HttpServletRequest request) throws Exception {
     78             // 将解析结果存储在HashMap中
     79             HashMap<String, String> map = new HashMap<String, String>();
     80 
     81             // 从request中取得输入流
     82             InputStream inputStream = request.getInputStream();
     83             // 读取输入流
     84             SAXReader reader = new SAXReader();
     85             Document document = reader.read(request.getInputStream());
     86             // 得到xml根元素
     87             Element root = document.getRootElement();
     88             
     89             recursiveParseXML(root,map);
     90 
     91             inputStream.close();
     92             inputStream = null;
     93 
     94             return map;
     95         }
     96         
     97         private static void recursiveParseXML(Element root,HashMap<String, String> map){
     98             // 得到根元素的所有子节点
     99             List<Element> elementList = root.elements();
    100             //判断有没有子元素列表
    101             if(elementList.size() == 0){
    102                 map.put(root.getName(), root.getText());
    103             }else{
    104                 //遍历
    105                 for (Element e : elementList){
    106                     recursiveParseXML(e,map);
    107                 }
    108             }
    109         }
    110 
    111         /**
    112          * 扩展xstream使其支持CDATA
    113          */
    114         private static XStream xstream = new XStream(new XppDriver() {
    115             public HierarchicalStreamWriter createWriter(Writer out) {
    116                 return new PrettyPrintWriter(out) {
    117                     // 对所有xml节点的转换都增加CDATA标记
    118                     boolean cdata = true;
    119 
    120                     //@SuppressWarnings("unchecked")
    121                     public void startNode(String name, Class clazz) {
    122                         super.startNode(name, clazz);
    123                     }
    124 
    125                     protected void writeText(QuickWriter writer, String text) {
    126                         if (cdata) {
    127                             writer.write("<![CDATA[");
    128                             writer.write(text);
    129                             writer.write("]]>");
    130                         } else {
    131                             writer.write(text);
    132                         }
    133                     }
    134                 };
    135             }
    136         });
    137 
    138         /**
    139          * 文本消息对象转换成xml
    140          * 
    141          * @param textMessage 文本消息对象
    142          * @return xml
    143          */
    144         public static String messageToXML(TextMessage textMessage) {
    145             xstream.alias("xml", TextMessage.class);
    146             return xstream.toXML(textMessage);
    147         }
    148 
    149         /**
    150          * 图片消息对象转换成xml
    151          * 
    152          * @param imageMessage 图片消息对象
    153          * @return xml
    154          */
    155         public static String messageToXML(ImageMessage imageMessage) {
    156             xstream.alias("xml", ImageMessage.class);
    157             return xstream.toXML(imageMessage);
    158         }
    159 
    160         /**
    161          * 语音消息对象转换成xml
    162          * 
    163          * @param voiceMessage 语音消息对象
    164          * @return xml
    165          */
    166         public static String messageToXML(VoiceMessage voiceMessage) {
    167             xstream.alias("xml", VoiceMessage.class);
    168             return xstream.toXML(voiceMessage);
    169         }
    170 
    171         /**
    172          * 视频消息对象转换成xml
    173          * 
    174          * @param videoMessage 视频消息对象
    175          * @return xml
    176          */
    177         public static String messageToXML(VideoMessage videoMessage) {
    178             xstream.alias("xml", VideoMessage.class);
    179             return xstream.toXML(videoMessage);
    180         }
    181 
    182         /**
    183          * 音乐消息对象转换成xml
    184          * 
    185          * @param musicMessage 音乐消息对象
    186          * @return xml
    187          */
    188         public static String messageToXML(MusicMessage musicMessage) {
    189             xstream.alias("xml", MusicMessage.class);
    190             return xstream.toXML(musicMessage);
    191         }
    192 
    193         /**
    194          * 图文消息对象转换成xml
    195          * 
    196          * @param newsMessage 图文消息对象
    197          * @return xml
    198          */
    199         public static String messageToXML(NewsMessage newsMessage) {
    200             xstream.alias("xml", NewsMessage.class);
    201             xstream.alias("item", Article.class);
    202             return xstream.toXML(newsMessage);
    203         }
    204     }
    MessageUtil.java
     1 package com.wtxy.util;
     2 
     3 import java.security.MessageDigest;
     4 import java.security.NoSuchAlgorithmException;
     5 import java.util.Arrays;
     6 
     7 /**
     8  * 请求校验工具类
     9  * 
    10  * @author Ran
    11  * @date 2016-04-21
    12  */
    13 public class SignUtil {
    14     // 与开发模式接口配置信息中的Token保持一致
    15     private static String token = "weixin";
    16 
    17     /**
    18      * 校验签名
    19      * 
    20      * @param signature 微信加密签名
    21      * @param timestamp 时间戳
    22      * @param nonce 随机数
    23      * @return
    24      */
    25     public static boolean checkSignature(String signature, String timestamp, String nonce) {
    26         // 对token、timestamp和nonce按字典排序
    27         String[] paramArr = new String[] { token, timestamp, nonce };
    28         Arrays.sort(paramArr);
    29 
    30         // 将排序后的结果拼接成一个字符串
    31         String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
    32 
    33         String ciphertext = null;
    34         try {
    35             MessageDigest md = MessageDigest.getInstance("SHA-1");
    36             // 对接后的字符串进行sha1加密
    37             byte[] digest = md.digest(content.toString().getBytes());
    38             ciphertext = byteToStr(digest);
    39         } catch (NoSuchAlgorithmException e) {
    40             e.printStackTrace();
    41         }
    42         
    43         // 将sha1加密后的字符串与signature进行对比
    44         return ciphertext != null ? ciphertext.equals(signature.toUpperCase()) : false;
    45     }
    46 
    47     /**
    48      * 将字节数组转换为十六进制字符串
    49      * 
    50      * @param byteArray
    51      * @return
    52      */
    53     private static String byteToStr(byte[] byteArray) {
    54         String strDigest = "";
    55         for (int i = 0; i < byteArray.length; i++) {
    56             strDigest += byteToHexStr(byteArray[i]);
    57         }
    58         return strDigest;
    59     }
    60 
    61     /**
    62      * 将字节转换为十六进制字符串
    63      * 
    64      * @param mByte
    65      * @return
    66      */
    67     private static String byteToHexStr(byte mByte) {
    68         char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    69         char[] tempArr = new char[2];
    70         tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
    71         tempArr[1] = Digit[mByte & 0X0F];
    72 
    73         String s = new String(tempArr);
    74         return s;
    75     }
    76 }
    SignUtil
    com.wtxy.weixin.message
     1 package com.wtxy.weixin.message;
     2 
     3 public class Article {
     4     private String Title;
     5     private String Description;
     6     private String PicUrl;
     7     private String Url;
     8     
     9     public String getTitle() {
    10         return Title;
    11     }
    12     public void setTitle(String title) {
    13         Title = title;
    14     }
    15     public String getDescription() {
    16         return Description;
    17     }
    18     public void setDescription(String description) {
    19         Description = description;
    20     }
    21     public String getPicUrl() {
    22         return PicUrl;
    23     }
    24     public void setPicUrl(String picUrl) {
    25         PicUrl = picUrl;
    26     }
    27     public String getUrl() {
    28         return Url;
    29     }
    30     public void setUrl(String url) {
    31         Url = url;
    32     }
    33     
    34     
    35 
    36 }
    Article.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class BaseMessage {
     4         // 接收方帐号(收到的OpenID)
     5         private String ToUserName;
     6         // 开发者微信号
     7         private String FromUserName;
     8         // 消息创建时间 (整型)
     9         private long CreateTime;
    10         // 消息类型(text/music/news)
    11         private String MsgType;
    12         
    13         
    14         public String getToUserName() {
    15             return ToUserName;
    16         }
    17         public void setToUserName(String toUserName) {
    18             ToUserName = toUserName;
    19         }
    20         public String getFromUserName() {
    21             return FromUserName;
    22         }
    23         public void setFromUserName(String fromUserName) {
    24             FromUserName = fromUserName;
    25         }
    26         public long getCreateTime() {
    27             return CreateTime;
    28         }
    29         public void setCreateTime(long createTime) {
    30             CreateTime = createTime;
    31         }
    32         public String getMsgType() {
    33             return MsgType;
    34         }
    35         public void setMsgType(String msgType) {
    36             MsgType = msgType;
    37         }
    38 
    39 
    40     }
    BaseMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class Image {
     4     private String Mediald;
     5 
     6     public String getMediald() {
     7         return Mediald;
     8     }
     9 
    10     public void setMediald(String mediald) {
    11         Mediald = mediald;
    12     }
    13 
    14 }
    Image.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class ImageMessage extends BaseMessage {
     4     private Image Image;
     5 
     6     public Image getImage() {
     7         return Image;
     8     }
     9 
    10     public void setImage(Image image) {
    11         Image = image;
    12     }
    13 
    14 }
    ImageMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class Music {
     4     private String Title;
     5     private String Description;
     6     private String MusicUrl;
     7     private String HQMusicUrl;
     8     private String ThumbMediaId;
     9     public String getTitle() {
    10         return Title;
    11     }
    12     public void setTitle(String title) {
    13         Title = title;
    14     }
    15     public String getDescription() {
    16         return Description;
    17     }
    18     public void setDescription(String description) {
    19         Description = description;
    20     }
    21     public String getMusicUrl() {
    22         return MusicUrl;
    23     }
    24     public void setMusicUrl(String musicUrl) {
    25         MusicUrl = musicUrl;
    26     }
    27     public String getHQMusicUrl() {
    28         return HQMusicUrl;
    29     }
    30     public void setHQMusicUrl(String hQMusicUrl) {
    31         HQMusicUrl = hQMusicUrl;
    32     }
    33     public String getThumbMediaId() {
    34         return ThumbMediaId;
    35     }
    36     public void setThumbMediaId(String thumbMediaId) {
    37         ThumbMediaId = thumbMediaId;
    38     }
    39     
    40     
    41 
    42 }
    Music.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class MusicMessage extends BaseMessage {
     4     private Music Music;
     5 
     6     public Music getMusic() {
     7         return Music;
     8     }
     9 
    10     public void setMusic(Music music) {
    11         Music = music;
    12     }
    13     
    14 
    15 }
    MusicMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 import java.util.List;
     4 
     5 public class NewsMessage extends BaseMessage {
     6     private int ArticleCount;
     7     private List<Article>Articles;
     8     
     9     public int getArticleCount() {
    10         return ArticleCount;
    11     }
    12     public void setArticleCount(int articleCount) {
    13         ArticleCount = articleCount;
    14     }
    15     public List<Article> getArticles() {
    16         return Articles;
    17     }
    18     public void setArticles(List<Article> articles) {
    19         Articles = articles;
    20     }
    21     
    22     
    23 
    24 }
    NewsMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class TextMessage extends BaseMessage {
     4     private String Content;
     5 
     6     public String getContent() {
     7         return Content;
     8     }
     9 
    10     public void setContent(String content) {
    11         Content = content;
    12     }
    13 
    14 }
    TextMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class Video {
     4     //媒体文件id
     5     private String MediaId;
     6     //
     7     private String Title;
     8     
     9     private String Description;
    10     
    11 
    12     public String getMediaId() {
    13         return MediaId;
    14     }
    15 
    16     public void setMediaId(String mediaId) {
    17         MediaId = mediaId;
    18     }
    19 
    20     public String getTitle() {
    21         return Title;
    22     }
    23 
    24     public void setTitle(String title) {
    25         Title = title;
    26     }
    27 
    28     public String getDescription() {
    29         return Description;
    30     }
    31 
    32     public void setDescription(String description) {
    33         Description = description;
    34     }
    35 }
    Video.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class VideoMessage extends BaseMessage {
     4     //语音
     5     private Video Video;
     6 
     7     public Video getVideo() {
     8         return Video;
     9     }
    10 
    11     public void setVideo(Video video) {
    12         Video = video;
    13     }
    14 
    15 }
    VideoMessage.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class Voice {
     4     //媒体文件id
     5     private String MediaId;
     6 
     7     public String getMediaId() {
     8         return MediaId;
     9     }
    10 
    11     public void setMediaId(String mediaId) {
    12         MediaId = mediaId;
    13     }
    14     
    15 }
    Voice.java
     1 package com.wtxy.weixin.message;
     2 
     3 public class VoiceMessage extends BaseMessage {
     4     private Voice Voice;
     5 
     6     public Voice getVoice() {
     7         return Voice;
     8     }
     9 
    10     public void setVoice(Voice voice) {
    11         Voice = voice;
    12     }
    13 
    14 }
    VoiceMessage.java
  • 相关阅读:
    Android ListView带CheckBox实现单选
    android 登录和设置IP/端口功能
    html5 10大html5前端框架
    Html5 8个强大的基于Bootstrap的CSS框架
    Android 探究 LayoutInflater setFactory
    Android onLoadFinished与onLoaderReset
    Android android.database.CursorIndexOutOfBoundsException:Index -1 requested, with a size of 1
    Android 中AIDL的使用与理解
    Android Studio查看android源码
    ArrayList和LinkedList的用法区别:
  • 原文地址:https://www.cnblogs.com/lantian729308328/p/5415924.html
Copyright © 2011-2022 走看看