国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

Jadual Kandungan
一、通訊機(jī)制
二、消息類型
1.接受消息類型
2.回復(fù)消息類型
1.微信地理位置消息
2.路名、標(biāo)志性建筑或是商場(chǎng)名稱
Rumah applet WeChat pembangunan WeChat 微信公眾平臺(tái)開(kāi)發(fā)系列

微信公眾平臺(tái)開(kāi)發(fā)系列

May 14, 2018 pm 03:28 PM
Platform awam WeChat

開(kāi)始微信公眾平臺(tái)的開(kāi)發(fā),我們首先要了解微信平臺(tái)可以幫助我們做哪些事情?

使用您的公眾賬號(hào)登陸http://mp.weixin.qq.com/,選擇菜單--高級(jí)功能-開(kāi)發(fā)模式--查看文檔,即能看到微信公眾平臺(tái)目前所能開(kāi)發(fā)的功能。

一、通訊機(jī)制

微信公眾平臺(tái)開(kāi)發(fā)系列

公眾平臺(tái)的主要內(nèi)容是

  • 接受用戶發(fā)送給您公眾賬號(hào)的消息

  • 給您的用戶回復(fù)消息

需要特別說(shuō)明的是,發(fā)送消息和回復(fù)消失是一個(gè)連貫的過(guò)程,只能在一個(gè)對(duì)話中完成。也就是說(shuō)您的用戶不找您說(shuō)話,您是不能主動(dòng)發(fā)送消息給你的客戶(群發(fā)是另外一種情況,有次數(shù)限制。你也可以申請(qǐng)付費(fèi)使用微信CRM平臺(tái))。所有的發(fā)送消息和接受消息,都需要微信平臺(tái)進(jìn)行中轉(zhuǎn)。

二、消息類型

下面介紹用戶能給您發(fā)送的消息類型,也就是目前接受到的消息類型。

1.接受消息類型

1.1文本消息:

這也是我們平時(shí)碰到最多的,可以根據(jù)文本中提到的一些關(guān)鍵字,進(jìn)行判斷,判斷用戶的含義,并進(jìn)行回復(fù)。

1.2圖片消息:

目前通過(guò)圖片理解用戶想表達(dá)的意思,還是有較大難度,因此多數(shù)的公眾賬號(hào),會(huì)選擇忽略圖片信息或選擇由人工來(lái)處理。只能說(shuō)一句:圖片很美,但是我看不懂。

1.3地理位置消息:

用戶把他的位置發(fā)給您,這對(duì)大多數(shù)公眾賬號(hào)來(lái)說(shuō),是一個(gè)重要的信息??梢蕴峁┮恍┗谖恢眯畔⒌姆?wù),比如酒店預(yù)訂公眾賬號(hào),可以給你推薦你周邊的酒店。 另外一個(gè)補(bǔ)充是,可以在文本消息中分析出位置信息,并加以利用。比如用戶輸入“南京路步行街”,可以提供用戶南京路步行街的相關(guān)商戶。

1.4鏈接消息:

目前還沒(méi)有看到開(kāi)發(fā)模式中特別有效的使用方法。使用比較多的可能會(huì)是購(gòu)物時(shí)或是咨詢時(shí),對(duì)所談?wù)摰膶?duì)象進(jìn)行明確。

1.5事件推送消息:

當(dāng)用戶進(jìn)入到和你對(duì)話的過(guò)程中,可以先和用戶打招呼等。這個(gè)消息目前只支持4.5版本,且暫時(shí)還沒(méi)有開(kāi)發(fā)。后續(xù)可想想的空間很大,比如用戶進(jìn)入到會(huì)話之后,搖一搖會(huì)發(fā)生什么呢?

2.回復(fù)消息類型

? 2.1文本消息
?? 這是我們平時(shí)發(fā)送最多的一類消息,當(dāng)只需要簡(jiǎn)單的文字即可回答用戶的消息時(shí),可用文本消息。文本消息中可以帶有鏈接地址。

微信公眾平臺(tái)開(kāi)發(fā)系列

?2.2圖文消息
????圖文消息,這是我們?cè)谕扑拖⒅薪?jīng)??吹降南⒏袷?。每項(xiàng)內(nèi)容可以點(diǎn)擊查看更詳細(xì)信息(當(dāng)然你也可以把鏈接設(shè)置為空,使其不能跳轉(zhuǎn))

微信公眾平臺(tái)開(kāi)發(fā)系列

?? 2.3音樂(lè)消息
?? 在你的答復(fù)中給用戶一個(gè)語(yǔ)音消息或是音樂(lè),可以獲得不少用戶的親睞。
了解了公眾平臺(tái)的通訊機(jī)制和消息類型,接下來(lái),我們開(kāi)始準(zhǔn)備開(kāi)發(fā)環(huán)境了

1.設(shè)置成為開(kāi)發(fā)者模式

登錄微信工作平臺(tái),選擇高級(jí)功能-進(jìn)入開(kāi)發(fā)模式,成為開(kāi)發(fā)者。需要做如下圖配置。URL配置的信息是指,微信的后臺(tái)服務(wù)器把您的用戶消息發(fā)送到該URL處理。Token是你和微信之間的一個(gè)密碼,用來(lái)驗(yàn)證消息是否是從微信的服務(wù)發(fā)送而來(lái),而不是其他來(lái)攻擊你的系統(tǒng)。

現(xiàn)在你還不能設(shè)置,在設(shè)置時(shí)微信會(huì)GET請(qǐng)求你設(shè)置的URL,已檢測(cè)接口是否可以使用。只有等你準(zhǔn)備好GET方法之后才可以進(jìn)行設(shè)置。

微信公眾平臺(tái)開(kāi)發(fā)系列

2.實(shí)現(xiàn)GET方法

從文檔中知道,我們需要實(shí)現(xiàn)POST和GET方法,GET方法用于驗(yàn)證微信和你的通訊驗(yàn)證,POST用于消息處理。

新建Servlet HelloWeChat,先實(shí)現(xiàn)其中的GET方法

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
        // TODO 為了簡(jiǎn)單起見(jiàn),先不對(duì)消息來(lái)源進(jìn)行校驗(yàn)
        response.setContentType("text/html;charset=UTF-8"); 
        PrintWriter pw = response.getWriter(); 
        String echo = request.getParameter("echostr"); 
        echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8"); 
        pw.println(echo); 
    }

可以在本地使用http://localhost:8080/QiyadengWeb/HelloWeChat?echostr=hello中文,先進(jìn)行測(cè)試,如果沒(méi)有問(wèn)題,可以部署到服務(wù)器上,然后在微信公眾平臺(tái)進(jìn)行設(shè)置了。

3.實(shí)現(xiàn)POST方法

POST方法首先接收到微信公眾平臺(tái)傳送過(guò)來(lái)的XML,從中提取消息發(fā)送人和消息內(nèi)容。更加消息發(fā)送內(nèi)容,你可以增加自己的處理邏輯,最后拼裝成回復(fù)消息XML,返回給微信公眾平臺(tái)。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
        response.setContentType("text/html;charset=UTF-8"); 
        PrintWriter pw = response.getWriter(); 
        String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8"); 
        WeChatTextMessage textMsg = null; 
        try { 
            textMsg = getWeChatTextMessage(wxMsgXml); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
        StringBuffer replyMsg = new StringBuffer(); 
        if(textMsg != null){ 
            //增加你所需要的處理邏輯,這里只是簡(jiǎn)單重復(fù)消息
            replyMsg.append("您給我的消息是:"); 
            replyMsg.append(textMsg.getContent()); 
        } 
        else{ 
            replyMsg.append(":)不是文本的消息,我暫時(shí)看不懂"); 
        } 
        String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName()); 
        pw.println(returnXml); 
    }

關(guān)于調(diào)試,這里推薦一個(gè)工具Fiddler,你可以模擬微信的POST消息到你的本地,而不必每次部署到服務(wù)器上進(jìn)行調(diào)試。關(guān)于Fiddler的POST數(shù)據(jù)使用方法,可以參考下圖標(biāo)注內(nèi)容。

微信公眾平臺(tái)開(kāi)發(fā)系列

4.部署并測(cè)試

完成第一步,并和你的公眾帳號(hào)好進(jìn)行對(duì)話,回復(fù)消息沒(méi)有問(wèn)題的話,那就恭喜你了。

微信公眾平臺(tái)開(kāi)發(fā)系列

5.依賴庫(kù)

使用maven的同學(xué),添加以下依賴即可。非maven用戶,找到這些庫(kù)添加到buider path中即可。

<dependency> 
    <groupId>joda-time</groupId> 
    <artifactId>joda-time</artifactId> 
    <version>2.2</version> 
</dependency> 
<dependency> 
    <groupId>org.apache.commons</groupId> 
    <artifactId>commons-io</artifactId> 
    <version>1.3.2</version> 
</dependency> 
<dependency> 
    <groupId>com.thoughtworks.xstream</groupId> 
    <artifactId>xstream</artifactId> 
    <version>1.4.3</version> 
</dependency>

6.完整的代碼

package com.qiyadeng.wechat; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.util.Date; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import org.apache.commons.io.IOUtils; 
import com.thoughtworks.xstream.XStream; 
import com.thoughtworks.xstream.io.xml.DomDriver; 
/**
 * Servlet implementation class HelloWeChat
 */
public class HelloWeChat extends HttpServlet { 
    private static final long serialVersionUID = 1L; 
    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWeChat() { 
        super(); 
    } 
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
        // TODO 為了簡(jiǎn)單起見(jiàn),先不對(duì)消息來(lái)源進(jìn)行校驗(yàn)
        response.setContentType("text/html;charset=UTF-8"); 
        PrintWriter pw = response.getWriter(); 
        String echo = request.getParameter("echostr"); 
        echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8"); 
        pw.println(echo); 
    } 
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
        response.setContentType("text/html;charset=UTF-8"); 
        PrintWriter pw = response.getWriter(); 
        String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8"); 
        WeChatTextMessage textMsg = null; 
        try { 
            textMsg = getWeChatTextMessage(wxMsgXml); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
        StringBuffer replyMsg = new StringBuffer(); 
        if(textMsg != null){ 
            //增加你所需要的處理邏輯,這里只是簡(jiǎn)單重復(fù)消息
            replyMsg.append("您給我的消息是:"); 
            replyMsg.append(textMsg.getContent()); 
        } 
        else{ 
            replyMsg.append(":)不是文本的消息,我暫時(shí)看不懂"); 
        } 
        String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName()); 
        pw.println(returnXml); 
    } 
    private WeChatTextMessage getWeChatTextMessage(String xml){ 
        XStream xstream = new XStream(new DomDriver()); 
        xstream.alias("xml", WeChatTextMessage.class); 
        xstream.aliasField("ToUserName", WeChatTextMessage.class, "toUserName"); 
        xstream.aliasField("FromUserName", WeChatTextMessage.class, "fromUserName"); 
        xstream.aliasField("CreateTime", WeChatTextMessage.class, "createTime"); 
        xstream.aliasField("MsgType", WeChatTextMessage.class, "messageType"); 
        xstream.aliasField("Content", WeChatTextMessage.class, "content"); 
        xstream.aliasField("MsgId", WeChatTextMessage.class, "msgId"); 
        WeChatTextMessage wechatTextMessage = (WeChatTextMessage)xstream.fromXML(xml);  
        return wechatTextMessage; 
    } 
    private String getReplyTextMessage(String content, String weChatUser){ 
        WeChatReplyTextMessage we = new WeChatReplyTextMessage(); 
        we.setMessageType("text"); 
        we.setFuncFlag("0"); 
        we.setCreateTime(new Long(new Date().getTime()).toString()); 
        we.setContent(content); 
        we.setToUserName(weChatUser); 
        we.setFromUserName("shanghaiweather");//TODO 你的公眾帳號(hào)微信號(hào)
        XStream xstream = new XStream(new DomDriver());  
        xstream.alias("xml", WeChatReplyTextMessage.class); 
        xstream.aliasField("ToUserName", WeChatReplyTextMessage.class, "toUserName"); 
        xstream.aliasField("FromUserName", WeChatReplyTextMessage.class, "fromUserName"); 
        xstream.aliasField("CreateTime", WeChatReplyTextMessage.class, "createTime"); 
        xstream.aliasField("MsgType", WeChatReplyTextMessage.class, "messageType"); 
        xstream.aliasField("Content", WeChatReplyTextMessage.class, "content"); 
        xstream.aliasField("FuncFlag", WeChatReplyTextMessage.class, "funcFlag"); 
        String xml =xstream.toXML(we); 
        return xml; 
    } 
}

位置識(shí)別這是實(shí)際應(yīng)用經(jīng)常應(yīng)用的消息,特別是很多商家,通過(guò)了解用戶位置,給用戶提供特別的產(chǎn)品或是商場(chǎng)的推薦。其中用戶可能發(fā)送兩種類型的消息:

1.微信地理位置信息

2.路名、標(biāo)志性建筑或是商場(chǎng)名稱

1.微信地理位置消息

認(rèn)識(shí)一下,微信地理位置消息,包含一些什么信息

<xml> 
<ToUserName><![CDATA[toUser]]></ToUserName> 
<FromUserName><![CDATA[fromUser]]></FromUserName> 
<CreateTime>1351776360</CreateTime> 
<MsgType><![CDATA[location]]></MsgType> 
<Location_X>23.134521</Location_X> 
<Location_Y>113.358803</Location_Y> 
<Scale>20</Scale> 
<Label><![CDATA[位置信息]]></Label> 
<MsgId>1234567890123456</MsgId> 
</xml>

包含的主要信息有經(jīng)度緯度和Label的位置??梢愿鶕?jù)label中描述的位置信息,提供給用戶對(duì)應(yīng)的服務(wù)。也可根據(jù)用戶的經(jīng)度緯度信息,提供你最近的產(chǎn)品或是有地域性的產(chǎn)品。

微信公眾平臺(tái)開(kāi)發(fā)系列

首先根據(jù)微信的地理位置信息,定義WeChatLocationMessage類,并能把Xml轉(zhuǎn)換為WeChatLocationMessage對(duì)象

public class WeChatLocationMessage { 
    private String toUserName; 
    private String fromUserName; 
    private String createTime; 
    private String msgType; 
    private String locationx; 
    private String localtiony; 
    private String scale; 
    private String label; 
    private String msgId; 
    public static WeChatLocationMessage getWeChatLocationMessage(String xml){ 
        XStream xstream = new XStream(new DomDriver()); 
        WeChatLocationMessage  message = null; 
        xstream.alias("xml", WeChatLocationMessage.class); 
        xstream.aliasField("ToUserName", WeChatLocationMessage.class, "toUserName"); 
        xstream.aliasField("FromUserName", WeChatLocationMessage.class, "fromUserName"); 
        xstream.aliasField("CreateTime", WeChatLocationMessage.class, "createTime"); 
        xstream.aliasField("MsgType", WeChatLocationMessage.class, "msgType"); 
        xstream.aliasField("Location_X", WeChatLocationMessage.class, "locationx"); 
        xstream.aliasField("Location_Y", WeChatLocationMessage.class, "localtiony"); 
        xstream.aliasField("Scale", WeChatLocationMessage.class, "scale"); 
        xstream.aliasField("Label", WeChatLocationMessage.class, "label"); 
        xstream.aliasField("MsgId", WeChatLocationMessage.class, "msgId"); 
        message = (WeChatLocationMessage)xstream.fromXML(xml); 
        return message; 
    } 
//getter and setter
}

本文利用百度的地圖API,查找最近的銀行做為示例。

public String getPalace(String query,String lat,String lng) throws ClientProtocolException, IOException{ 
    HttpClient httpClient = new DefaultHttpClient(); 
    String url = palceRequestUrl(query,lat,lng); 
    logger.log(Level.INFO, url); 
    HttpGet httpget = new HttpGet(url); 
    ResponseHandler<String> responseHandler = new BasicResponseHandler(); 
    String responseBody = httpClient.execute(httpget, responseHandler); 
    logger.log(Level.INFO,"baidu response:"+responseBody); 
    return responseBody; 
} 

public String palceRequestUrl(String query,String lat,String lng) throws UnsupportedEncodingException { 
    String url = WeChatConstant.BASEURL + "place/search?query=" + URLEncoder.encode(query,"UTF-8") + "&key="
            + WeChatConstant.MAPKEY +"&location="+lat+","+lng +"&radius=2000"+"&output=" + WeChatConstant.OUTPUTFORMAT; 
    return url; 
}

輸出的結(jié)果

<PlaceSearchResponse> 
    <status>OK</status> 
    <results> 
        <result> 
            <name>中國(guó)工商銀行東長(zhǎng)安街支行</name> 
            <location> 
                <lat>39.915891</lat> 
                <lng>116.41867</lng> 
            </location> 
            <address>東城區(qū)東長(zhǎng)安街1號(hào)東方廣場(chǎng)西三辦公樓1樓</address> 
            <uid>a025683c73033c35a21de987</uid> 
            <detail_url>http://api.map.baidu.com/place/detail?uid=a025683c73033c35a21de987&amp;amp;output=html&amp;amp;source=placeapi
            </detail_url> 
            <tag>銀行,王府井/東單</tag> 
        </result> 
      </results> 
</PlaceSearchResponse>

接下來(lái),把百度地圖反映出來(lái)的最近位置信息,以圖文消息的格式展示給微信用戶

    public static String getWeChatReplyNewsMessageByBaiduPlace(List<BaiduPlaceResponse> placeList, double lat, double lng,String userName, int size){ 
        WeChatReplyNewsMessage newsMessage = new WeChatReplyNewsMessage(); 
        List<Item> items = new ArrayList<Item>(); 
        StringBuffer strBuf = new StringBuffer(); 
        logger.log(Level.INFO,"placeList count="+placeList.size()); 
        newsMessage.setItems(items); 
        if(placeList.size()>size){ 
            newsMessage.setArticleCount(size); 
        } 
        else{ 
            newsMessage.setArticleCount(placeList.size()); 
        } 
        logger.log(Level.INFO,"article count="+newsMessage.getArticleCount()); 
        newsMessage.setCreateTime(new Date().getTime()+""); 
        newsMessage.setMsgType("news"); 
        newsMessage.setFuncFlag("0"); 
        newsMessage.setToUserName(userName); 
        newsMessage.setFromUserName(WeChatConstant.FROMUSERNAME); 
        for(int i = 0;i <newsMessage.getArticleCount();i++){ 
            BaiduPlaceResponse place = placeList.get(i); 
            Double distance = GeoUtil.DistanceOfTwoPoints(Double.valueOf(place.getLng()), Double.valueOf(place.getLat()), lng, lat, GaussSphere.Beijing54); 
            Item item = new Item(); 
            item.setTitle(place.getName()+"["+distance+"米]"+"\n"+place.getAddress()+"\n"+place.getTelephone()); 
            item.setPicUrl(""); 
            item.setUrl(place.getDetailUrl()); 
            item.setDescription(""); 
            items.add(item); 
        } 
logger.log(Level.INFO,"newMessage="+newsMessage.toString()); 
        strBuf = strBuf.append(getWeChatNewsMessage(newsMessage)); 
        return strBuf.toString(); 
    } 
    public static String getWeChatNewsMessage(WeChatReplyNewsMessage newsMessage){ 
        XStream xstream = new XStream(new DomDriver()); 
        xstream.alias("xml", WeChatReplyNewsMessage.class); 
        xstream.aliasField("ToUserName", WeChatReplyNewsMessage.class, "toUserName"); 
        xstream.aliasField("FromUserName", WeChatReplyNewsMessage.class, "fromUserName"); 
        xstream.aliasField("CreateTime", WeChatReplyNewsMessage.class, "createTime"); 
        xstream.aliasField("MsgType", WeChatReplyNewsMessage.class, "msgType"); 
        xstream.aliasField("ArticleCount", WeChatReplyNewsMessage.class, "articleCount"); 
        xstream.aliasField("Content", WeChatReplyNewsMessage.class, "content"); 
        xstream.aliasField("FuncFlag", WeChatReplyNewsMessage.class, "funcFlag"); 
        xstream.aliasField("Articles", WeChatReplyNewsMessage.class, "items"); 
        xstream.alias("item", Item.class); 
        xstream.aliasField("Title", Item.class, "title"); 
        xstream.aliasField("Description", Item.class, "description"); 
        xstream.aliasField("PicUrl", Item.class, "picUrl"); 
        xstream.aliasField("Url", Item.class, "url"); 
        return xstream.toXML(newsMessage); 
    }

2.路名、標(biāo)志性建筑或是商場(chǎng)名稱

對(duì)路名、標(biāo)志性建筑等信息,方法還是通過(guò)第三方地圖信息,確定輸入的位置信息的經(jīng)度緯度。

本文使用百度地圖API,確定所查找的位置的經(jīng)度和緯度。

確定了經(jīng)度和緯度,問(wèn)題就變成和第1種消息類型一致了,根據(jù)經(jīng)度緯度去做相應(yīng)處理。

微信公眾平臺(tái)開(kāi)發(fā)系列

public String getGeoCode(String query) throws ClientProtocolException, IOException{ 
        HttpClient httpClient = new DefaultHttpClient(); 
        String url = geoCodeRequestUrl(query); 
        logger.log(Level.INFO, url); 
        HttpGet httpget = new HttpGet(url); 
        ResponseHandler<String> responseHandler = new BasicResponseHandler(); 
        String responseBody = httpClient.execute(httpget, responseHandler); 
        logger.log(Level.INFO,"baidu response:"+responseBody); 
        return responseBody; 
    } 
    public String geoCodeRequestUrl(String query) throws UnsupportedEncodingException{ 
        String url = WeChatConstant.BASEURL + "geocoder?address=" + URLEncoder.encode(query,"UTF-8") + "&key="
                + WeChatConstant.MAPKEY + "&output=" + WeChatConstant.OUTPUTFORMAT; 
        return url; 
    }

更多微信公眾平臺(tái)開(kāi)發(fā)系列?相關(guān)文章請(qǐng)關(guān)注PHP中文網(wǎng)!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)