字节流动 2014-10-23
核心Service
源代码文件说明
序号 | 文件名 | 说明 | 操作 |
1 | readme.md | 说明文档 | 更新 |
2 | log4j.properties | 日志属性文件 | 未更新 |
3 | BaseMessage.java | 消息基类 | 未更新 |
4 | TextMessage.java | 文本消息类 | 未更新 |
5 | SignUtil.java | 取Token服务类 | 未更新 |
6 | MessageUtil.java | 消息处理工具类 | 未更新 |
7 | CoreServlet.java | 核心Servlet,增加doPost()方法 | 未更新 |
8 | CoreService.java | 核心服务类,处理前台传过来的请求,并返回响应 | 更新 |
9 | web.xml | Web项目配置文件(这里主要配置Servlet的信息) | 未更新 |
10 | index.jsp | 首页文件,显示时间信息,主要用来判断工程是否部署成功 | 未更新 |
11 | Logging.java | 日志实体类 | 新增 |
12 | LoggingDao.java | 日志操作类 | 新增 |
13 | LoggingDaoTest.java | 日志测试类 | 新增 |
14 | EntitiesHelper.java | 测试辅助类 | 新增 |
15 | jdbc.properties | 数据库配置文件 | 新增 |
16 | DBUtil.java | 数据库工具类 | 新增 |
17 | logging.xml | 测试数据文件 | 新增 |
18 | logging_add.xml | 测试数据文件 | 新增 |
日志实体类
Logging.java
package com.coderdream.bean; public class Logging { // `id` int(11) NOT NULL AUTO_INCREMENT, private int id; // `log_date` datetime DEFAULT NULL, // private Timestamp logDate; 由于MySQL不能存入带毫米数的Timestamp,这里直接存字符串 private String logDate; // `log_level` varchar(64) DEFAULT NULL, private String logLevel; // `location` varchar(256) DEFAULT NULL, private String location; // `message` varchar(1024) DEFAULT NULL, private String message; public Logging() { } public Logging(int id, String logDate, String logLevel, String location, String message) { this.id = id; this.logDate = logDate; this.logLevel = logLevel; this.location = location; this.message = message; } public Logging(String logDate, String logLevel, String location, String message) { this.logDate = logDate; this.logLevel = logLevel; this.location = location; this.message = message; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getLogDate() { return logDate; } public void setLogDate(String logDate) { this.logDate = logDate; } public String getLogLevel() { return logLevel; } public void setLogLevel(String logLevel) { this.logLevel = logLevel; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return "Logging [id=" + id + ", logDate=" + logDate + ", logLevel=" + logLevel + ", location=" + location + ", message=" + message + "]"; } }
日志操作类
LoggingDao.java
package com.coderdream.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.apache.log4j.Logger; import com.coderdream.bean.Logging; import com.coderdream.util.DBUtil; public class LoggingDao { private String location; public static String TAG = "LoggingDao"; private Logger logger = Logger.getLogger(LoggingDao.class); public LoggingDao() { } public LoggingDao(String location) { this.location = location; } public int addLogging(Logging logging) { logger.debug(TAG + "###0###"); String sql = "INSERT INTO logging (log_date, log_level, location, message) VALUES (?,?,?,?)"; int count = 0; Connection con = null; PreparedStatement ps = null; try { con = DBUtil.getConnection(); logger.debug(TAG + con); ps = con.prepareStatement(sql); ps.setString(1, logging.getLogDate()); ps.setString(2, logging.getLogLevel()); ps.setString(3, logging.getLocation()); ps.setString(4, logging.getMessage()); count = ps.executeUpdate(); logger.debug(TAG + "count: " + count); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(ps); DBUtil.close(con); } return count; } public int debug(String message) { if (!logger.isDebugEnabled()) { return 0; } logger.debug(TAG + "###0###"); String sql = "INSERT INTO logging (log_date, log_level, location, message) VALUES (?,?,?,?)"; int count = 0; Connection con = null; PreparedStatement pre = null; try { con = DBUtil.getConnection(); logger.debug(TAG + " ###2### Connection: " + con); pre = con.prepareStatement(sql); logger.debug(TAG + " ###3### PreparedStatement: " + pre); Date date = Calendar.getInstance().getTime(); SimpleDateFormat f_timestamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); String logTimestampStr = f_timestamp.format(date); pre.setString(1, logTimestampStr); pre.setString(2, "DEBUG"); pre.setString(3, location); pre.setString(4, message); count = pre.executeUpdate(); logger.debug(TAG + "###4### count: " + count); } catch (Exception e) { logger.debug(TAG + "###5### Exception: " + e.getMessage()); e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } return count; } public static int debug(String location, String message) { if (!Logger.getLogger(LoggingDao.class).isDebugEnabled()) { return 0; } String sql = "INSERT INTO logging (log_date, log_level, location, message) VALUES (?,?,?,?)"; int count = 0; Connection con = null; PreparedStatement pre = null; try { con = DBUtil.getConnection(); pre = con.prepareStatement(sql); Date date = Calendar.getInstance().getTime(); SimpleDateFormat f_timestamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); String logTimestampStr = f_timestamp.format(date); pre.setString(1, logTimestampStr); pre.setString(2, "DEBUG"); pre.setString(3, location); pre.setString(4, message); count = pre.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } return count; } public int error(String message) { logger.debug(TAG + "###0###"); String sql = "INSERT INTO logging (log_date, log_level, location, message) VALUES (?,?,?,?)"; int count = 0; Connection con = null; PreparedStatement pre = null; try { con = DBUtil.getConnection(); logger.debug(TAG + con); pre = con.prepareStatement(sql); Date date = Calendar.getInstance().getTime(); SimpleDateFormat f_timestamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); String logTimestampStr = f_timestamp.format(date); pre.setString(1, logTimestampStr); pre.setString(2, "ERROR"); pre.setString(3, location); pre.setString(4, message); count = pre.executeUpdate(); logger.debug(TAG + "count: " + count); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } return count; } public static int error(String location, String message) { String sql = "INSERT INTO logging (log_date, log_level, location, message) VALUES (?,?,?,?)"; int count = 0; Connection con = null; PreparedStatement pre = null; try { con = DBUtil.getConnection(); pre = con.prepareStatement(sql); Date date = Calendar.getInstance().getTime(); SimpleDateFormat f_timestamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); String logTimestampStr = f_timestamp.format(date); pre.setString(1, logTimestampStr); pre.setString(2, "ERROR"); pre.setString(3, location); pre.setString(4, message); count = pre.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } return count; } PreparedStatement pre; ResultSet rs; /** * @author help * * 显示所有记录 * @return */ public List<Logging> findLoggings() { String sql = "select * from logging order by id"; List<Logging> list = new ArrayList<Logging>(); // 获取prepareStatement对象 Connection con = null; try { con = DBUtil.getConnection(); pre = con.prepareStatement(sql); rs = pre.executeQuery(); while (rs.next()) { Logging logging = new Logging(); logging.setId(rs.getInt("id")); logging.setLogDate(rs.getString("log_date")); logging.setLogLevel(rs.getString("log_level")); logging.setLocation(rs.getString("location")); logging.setMessage(rs.getString("message")); list.add(logging); } } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } return list; } /** * @author help * * 显示所有记录 * @return */ public Logging findLogging(int id) { String sql = "select * from logging where id=?"; List<Logging> list = new ArrayList<Logging>(); // 获取prepareStatement对象 Connection con = null; try { con = DBUtil.getConnection(); pre = con.prepareStatement(sql); pre.setInt(1, id); rs = pre.executeQuery(); while (rs.next()) { Logging logging = new Logging(); logging.setId(rs.getInt("id")); logging.setLogDate(rs.getString("log_date")); logging.setLogLevel(rs.getString("log_level")); logging.setLocation(rs.getString("location")); logging.setMessage(rs.getString("message")); list.add(logging); } } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(pre); DBUtil.close(con); } if (null != list && 0 < list.size()) { return list.get(0); } return null; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } }
日志操作测试类
LoggingDaoTest.java
package com.coderdream.dao; import java.io.FileInputStream; import java.io.FileWriter; import java.sql.Connection; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; import org.dbunit.Assertion; import org.dbunit.DatabaseUnitException; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.database.QueryDataSet; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ITable; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlProducer; import org.dbunit.operation.DatabaseOperation; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; import com.coderdream.bean.Logging; import com.coderdream.util.DBUtil; import com.coderdream.util.EntitiesHelper; /** * <pre> * DBUnit使用步骤 * 1)下载地址为http://sourceforge.net/projects/dbunit/files/ * 2)导入DBUnit所需两个jar文件(dbunit.jar和slf4j-api.jar) * 3)创建DBUnit用到的xml格式的测试数据,xml文件名建议与表名相同 * 4)创建DBUnit的Connection和DataSet,然后开始进行各项测试工作 * * 使用注解@BeforeClass,在globalInit()执行打开数据库操作; * 使用注解@AfterClass,在globalDestroy()执行数据库关闭操作; * * 使用注解@Before,每次测试执行之前都先执行init()操作; * 使用注解@After,每次测试执行之后都会执行destroy()操作; * * DBUtil提供数据库操作方法。 * </pre> * * @author CoderDream * @date 2014年10月15日 * */ public class LoggingDaoTest { public static String TAG = "LoggingDaoTest"; private static final Logger logger = LoggerFactory.getLogger(LoggingDaoTest.class); private static Connection conn; private static IDatabaseConnection dbUnitConn; private static String DATA_BACKUP_FILE = "dataBackup_logging.xml"; private static String LOGGING_DATA_FILE = "logging.xml"; @BeforeClass public static void globalInit() { conn = DBUtil.getConnection(); System.out.println("DB-Unit Get Connection: " + conn); try { // DBUnit中用来操作数据文件的Connection需依赖于数据库连接的Connection dbUnitConn = new DatabaseConnection(conn); } catch (DatabaseUnitException e) { e.printStackTrace(); } } @AfterClass public static void globalDestroy() { DBUtil.close(conn); if (null != dbUnitConn) { try { dbUnitConn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 备份数据库中某一张或某几张表的数据,同时将xml文件中的数据插入到数据库中 */ @Before public void init() throws Exception { logger.debug("Before #### init"); // 通过QueryDataSet可以有效的选择要处理的表来作为DataSet QueryDataSet dataSet = new QueryDataSet(dbUnitConn); // 这里指定只备份t_logging表中的数据,如果想备份多个表,那就再addTable(tableName)即可 dataSet.addTable("logging"); FlatXmlDataSet.write(dataSet, new FileWriter(DATA_BACKUP_FILE)); } /** * 还原表数据 */ @After public void destroy() throws Exception { IDataSet dataSet = new FlatXmlDataSet(new FlatXmlProducer( new InputSource(new FileInputStream(DATA_BACKUP_FILE)))); DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet); } /** * <pre> * 测试查询方法 * DatabaseOperation类的几个常量值 * CLEAN_INSERT----先删除数据库中的所有数据,然后将xml中的数据插入数据库 * DELETE----------如果数据库存在与xml记录的相同的数据,则删除数据库中的该条数据 * DELETE_ALL------删除数据库中的所有数据 * INSERT----------将xml中的数据插入数据库 * NONE------------nothing to do * REFRESH---------刷新数据库中的数据 * TRUNCATE_TABLE--清空表中的数据 * UPDATE----------将数据库中的那条数据更新为xml中的数据 * </pre> */ @Test public void testFindLogging() throws Exception { IDataSet dataSet = new FlatXmlDataSet(new FlatXmlProducer(new InputSource(LoggingDaoTest.class.getClassLoader() .getResourceAsStream(LOGGING_DATA_FILE)))); DatabaseOperation.TRUNCATE_TABLE.execute(dbUnitConn, dataSet); DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet); // 下面开始数据测试 LoggingDao loggingDao = new LoggingDao(); Logging logging = loggingDao.findLogging(1); // 预想结果和实际结果的比较 EntitiesHelper.assertLogging(logging); } /** * 更新,添加,删除等方法,可以利用Assertion.assertEquals() 方法,拿表的整体来比较。 */ @Test public void testAddLogging() throws Exception { IDataSet dataSet = new FlatXmlDataSet(new FlatXmlProducer(new InputSource(LoggingDaoTest.class.getClassLoader() .getResourceAsStream(LOGGING_DATA_FILE)))); DatabaseOperation.TRUNCATE_TABLE.execute(dbUnitConn, dataSet); DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); String time = df.format(new Date(1413993830024l)); // Timestamp ts = Timestamp.valueOf(time); String logStr = "FirstLogging"; // 被追加的记录 Logging newLogging = new Logging(time, "DEBUG", TAG, logStr); // 执行追加 addLogging 方法 LoggingDao loggingDao = new LoggingDao(); int result = loggingDao.addLogging(newLogging); Assert.assertEquals(1, result); // 预期结果取得 IDataSet expectedDataSet = new FlatXmlDataSet(new FlatXmlProducer(new InputSource(LoggingDaoTest.class .getClassLoader().getResourceAsStream("logging_add.xml")))); ITable expectedTable = expectedDataSet.getTable("logging"); // 实际结果取得(取此时数据库中的数据) // Creates a dataset corresponding to the entire database IDataSet databaseDataSet = dbUnitConn.createDataSet(); ITable actualTable = databaseDataSet.getTable("logging"); // 预想结果和实际结果的比较 Assertion.assertEquals(expectedTable, actualTable); } }
测试辅助类
EntitiestHelper.java
package com.coderdream.util; import java.text.SimpleDateFormat; import java.util.Date; import org.junit.Assert; import com.coderdream.bean.Logging; public class EntitiesHelper { public static void assertLogging(Logging expected, Logging actual) { Assert.assertNotNull(expected); Assert.assertEquals(expected.getId(), actual.getId()); Assert.assertEquals(expected.getLogDate(), actual.getLogDate()); Assert.assertEquals(expected.getLogLevel(), actual.getLogLevel()); Assert.assertEquals(expected.getLocation(), actual.getLocation()); Assert.assertEquals(expected.getMessage(), actual.getMessage()); } public static void assertLogging(Logging expected) { String logStr = "InitLogging"; // Timestamp sDate = new Timestamp(1413993830024l); // 2014-10-23 00:03:50.024 // time: 1413993830024 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); String time = df.format(new Date(1413993830024l)); Logging baseLogging = new Logging(1, time, "DEBUG", "LoggingDaoTest", logStr); assertLogging(expected, baseLogging); } }
数据库工具类
DBUtil.java
package com.coderdream.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.Properties; public class DBUtil { public static Connection getConnection() { Connection con = null; Properties prop = new Properties();// 属性集合对象 InputStream fis; try { fis = DBUtil.class.getClassLoader().getResourceAsStream( "jdbc.properties"); prop.load(fis);// 将属性文件流装载到Properties对象中 String driver = prop.getProperty("driver"); String username = prop.getProperty("username"); String password = prop.getProperty("password"); String url = prop.getProperty("url");// 使用从库的域名 Map<String, String> envMap = System.getenv(); String os = envMap.get("OS"); // local if (null != os && "Windows_NT".equals(os.trim())) { username = prop.getProperty("local.username"); password = prop.getProperty("local.password"); url = prop.getProperty("local.url"); } // SAE else { username = prop.getProperty("sae.username"); password = prop.getProperty("sae.password"); url = prop.getProperty("sae.url"); } Class.forName(driver).newInstance(); con = DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } return con; } public static void close(Connection con) { try { if (null != con) { con.close(); } } catch (SQLException e) { e.printStackTrace(); } } public static void close(PreparedStatement ps) { try { if (null != ps) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); } } public static void close(ResultSet rs) { try { if (null != rs) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
核心服务类
CoreService.java
package com.coderdream.service; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Map; import org.apache.log4j.Logger; import com.coderdream.bean.Logging; import com.coderdream.dao.LoggingDao; import com.coderdream.model.TextMessage; import com.coderdream.util.MessageUtil; /** * 核心服务类 */ public class CoreService { public static String TAG = "CoreService"; private Logger logger = Logger.getLogger(CoreService.class); /** * 处理微信发来的请求 * * @param request * @return xml */ public String processRequest(InputStream inputStream) { logger.debug(TAG + " #1# processRequest"); //Timestamp sDate = new Timestamp(Calendar.getInstance().getTime().getTime()); Date date = Calendar.getInstance().getTime(); SimpleDateFormat f_timestamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); String logTimestampStr = f_timestamp.format(date); Logging logging = new Logging(logTimestampStr, "DEBUG", TAG, "#1# processRequest"); LoggingDao loggingDao = new LoggingDao(); loggingDao.addLogging(logging); // xml格式的消息数据 String respXml = null; // 默认返回的文本消息内容 String respContent = "未知的消息类型!"; try { // 调用parseXml方法解析请求消息 Map<String, String> requestMap = MessageUtil.parseXml(inputStream); // 发送方帐号 String fromUserName = requestMap.get("FromUserName"); // 开发者微信号 String toUserName = requestMap.get("ToUserName"); // 消息类型 String msgType = requestMap.get("MsgType"); String logStr = "#2# fromUserName: " + fromUserName + ", toUserName: " + toUserName + ", msgType: " + msgType; logger.debug(TAG + logStr); logging = new Logging(logTimestampStr, "DEBUG", TAG, logStr); loggingDao.addLogging(logging); // 回复文本消息 TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(MessageUtil.MESSAGE_TYPE_TEXT); logStr = "#3# textMessage: " + textMessage.toString(); logger.debug(TAG + logStr); logging = new Logging(logTimestampStr, "DEBUG", TAG, logStr); loggingDao.addLogging(logging); // 文本消息 if (msgType.equals(MessageUtil.MESSAGE_TYPE_TEXT)) { respContent = "您发送的是文本消息!"; } logStr = "#4# respContent: " + respContent; logger.debug(TAG + logStr); logging = new Logging(logTimestampStr, "DEBUG", TAG, logStr); loggingDao.addLogging(logging); // 设置文本消息的内容 textMessage.setContent(respContent); // 将文本消息对象转换成xml respXml = MessageUtil.messageToXml(textMessage); logStr = "#5# respXml: " + respXml; logger.debug(TAG + logStr); logging = new Logging(logTimestampStr, "DEBUG", TAG, logStr); loggingDao.addLogging(logging); } catch (Exception e) { e.printStackTrace(); } return respXml; } }
Maven工程文件
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderdream</groupId> <artifactId>wxquan</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>wxquan Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <junit.version>4.11</junit.version> <servlet.api.version>2.5</servlet.api.version> <jsp.api.version>2.1</jsp.api.version> <slf4j.version>1.7.5</slf4j.version> <dom4j.version>1.6.1</dom4j.version> <xstream.version>1.4.7</xstream.version> <mysql.version>5.1.17</mysql.version> <dbunit.version>2.4.9</dbunit.version> </properties> <dependencies> <!-- 测试的时候用到,打包的时候没有 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- 编译的时候用到,打包的时候没有 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>${servlet.api.version}</version> <scope>provided</scope> </dependency> <!-- 编译的时候用到,打包的时候没有 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>${jsp.api.version}</version> <scope>provided</scope> </dependency> <!-- 日志包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- 打包的时候剔除 xml-apis-1.0.b2.jar SAE中不支持 --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>${dom4j.version}</version> <exclusions> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> </exclusions> </dependency> <!-- 打包的时候剔除 xml-apis-1.0.b2.jar SAE中不支持 --> <!-- 这个jar必须用1.4.7的高版本,否则SAE不支持 --> <!-- 详细原因:http://blog.csdn.net/lyq8479/article/details/38878543 --> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>${xstream.version}</version> </dependency> <!-- 编译的时候用到,打包的时候没有,SAE已包含此jar --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>${dbunit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>wxquan</finalName> </build> </project>
将新增和修改过的代码上传到GitHub
将eclipse中的工程导出为wxquan.war档,上传到SAE中,更新已有的版本。
登录微信网页版:https://wx.qq.com/
输入“测试”,返回“您发送的是文本消息”。
查询SAE数据库,发现logging表中已写入相关数据:
完整源代码