2014年8月12日星期二

记人人都是产品经理的线下活动

上周六参加了人人都是产品经理网站组织的一个线下活动,通知邮件里只给出了地址具体到某某楼第几层,没有具体房间号或者名字,开始还以为整层都是一个地方,结果到那找了一圈也没找到,后来找到一家公司门口,看门口没有明显标记于是犹豫了一下才进去问了问,结果发现还真是这,于是一直在里面坐等到了规定时间发现并没有像邮件中说的那样禁止迟到人员入场。
好了,进入正题,活动的主要内容是主办方出一个题目,然后几组人按照这个题目要求讨论出一个方案。每组6个人加一个网站工作人员组成,工作人员本意是引导大家顺利进行活动的,这次的题目是做一个计划类的手机APP,目的是解决不能按时完成计划人群的问题。很令人失望的一个题目。
我对产品经理狂拽那些傻逼名词的行为比较反感,什么大数据啊、互联网思维啊、痛点之类的,而那些每句话中夹杂着一两个英文单词的行为更令人感觉恶心,再吐槽一次,你要是整句英文或者有点有难度的英文不会翻译也行,全是nice、good、team、care,操你们大爷的敢说点5个字母以上的词吗?不幸的是这个活动里有不少同时兼备这两种傻逼行为的人,于是我痛苦的度过了几个小时。
组里有个所谓的人人都是产品经理兼职员工,坐进来时说自己是倒水的,开始后比谁都能说,而且丝毫没有教养,打断别人说话,别人不同意他的观点他就故意打击,比我若干年前在华为遇到的几个傻逼还有过之,起的队名和产品名都是极土,这种人平时肯定也是斗争高手,估计他们公司也是个有着极品公司文化的公司。
说说自己对题目的想法吧,你想让一个没有做计划的人去做计划本身就不切实际,想用一个app去改变别人的习惯那更是扯鸡巴蛋,像郭德纲说的,你本来就是那个货不是听段相声就能改的了的,而且互联网不是只有移动端,起码10年内web端也是主要的使用方式。如果非要让我做这样的一款app,我的想法是这样: 
1. 客户是B而不是C,因为C除了套VC以外赚不到什么钱 
2. web端要有配套产品 
3. 市场上有很多相似产品,傻逼产品黑话叫竞品,取胜的关键在技术水平(体现在速度、安全、容量、维护、扩展性等方面)和用户体验上 
4. 产品要足够简单,最小化功能
回头看看这些产品经理,思维已经固化了,只知道做移动app,客户永远是个人,丧心病狂的分享到朋友圈,不停的添加一些垃圾功能,全是一套固定的模式,听下来一点新意都没有。
组里有个已经创业的人,他有一个观点我很赞同,就是在基本功能的基础上重点突出一到两个亮点并做到极致,在demo day上也主要是讲自己的亮点,而不是全盘介绍功能,这样能让人更容易理解,而且容易吸引投资人的目光。
最后总结一下吧,参加了这次活动很失望,但是也学到了一点东西,同时也看到了希望,产品圈原来这么多傻逼,聪明人太少了,跟老罗一样观点了吗?以后有机会再吐槽一下老罗,还是带着产品思维写代码靠谱点。

2014年8月11日星期一

微信公众帐号开发 - 查歌词

想做一个歌词查询的工具有很长时间了,之前的想法一直是在web端,提供高质量的封面图,精美排版的歌词,作词作曲演唱者信息,如果可能再加入试听与购买链接,要是能再提供些背后的故事与独家采访就更牛逼了。
这样的想法需要大量的人力物力还有高超的技术才能运转的起来,起码目前我单枪匹马是无法完成的,所以一直拖着无法实现。昨天突然想起来我可以在微信公众平台上面建立一个公众号,提供查询功能,初期只接受文本消息,也只回复文本消息就好,简简单单,也有一定的实用性。
说做就做,昨天做出了第一个版本,这个版本里我只是在数据库中随便添加了几条数据,只有一个表一个字段,完全的LIKE匹配,这样的架构数据量大了之后性能一定歇菜,但是先不管了,于是想先做个爬虫到百度或者虾米上面去爬歌词,在google的时候发现虾米居然有api可以调用,这下爬起来简单多了,今天早上来到公司突然想起来虾米也有搜索功能,于是手动去搜了一下,url非常规范,返回的结果结构也很规范,于是决定修改架构,不自己保存数据了,直接去虾米上搜索后获得歌曲id,再到api去获取歌词的url路径,转而获取歌词内容,这也是第二个版本。
具体流程
  1. 用户通过微信向公众帐号发送文本消息
  2. 微信服务器向自己填写的api路径POST一个xml信息
  3. 解析获取关键词后到虾米搜索
  4. 抓取搜索结果获取歌曲id
  5. 到虾米api去获取歌曲详细信息
  6. 通过歌词url获取歌词内容
  7. 包装成xml信息返回给微信服务器
  8. 微信发送给用户结果
具体代码
1. 通过微信验证  微信只有在验证时是使用GET方式访问api,其余只有POST,"Hello, it works!"是为了测试方便加上去的
public void ProcessRequest (HttpContext context) {
    switch (context.Request.HttpMethod)
    {
        case "GET":
            context.Response.ContentType = "text/plain";
            context.Response.Write(context.Request["echostr"] == null ? "Hello, it works!" : context.Request["echostr"]);
            break;
        case "POST":
            WeiXinServlet.DoPost(context);
            break;
        default:
            break;
    }
}
2. 定义消息类  由于目前只有文本消息,所以我只定义了消息基类和文本消息类,回复的文本消息类还加了一个toXML()方法,用于返回给微信服务器
public class BaseMessage
{
    public string ToUserName { get; set; }
    public string FromUserName { get; set; }
    public string CreateTime { get; set; }
    public string MsgType { get; set; }
}

public class ContentMessage : BaseMessage
{
    public string Content { get; set; }
}

public class ReturnContentMessage : ContentMessage
{
    public virtual string toXML()
    {
        return XmlHelper.ObjectToXml(this);
    }
}
3. toXML()方法  .NET提供了直接从类序列化为xml的方法,很方便
public static string ObjectToXml(object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType());
    using(MemoryStream stream = new MemoryStream())
    {
        XmlTextWriter xmlTW = new XmlTextWriter(stream, new UTF8Encoding(false));
        xmlTW.Formatting = Formatting.Indented;
        serializer.Serialize(xmlTW, obj);
        xmlTW.Flush();
        xmlTW.Close();
        UTF8Encoding uTF8 = new UTF8Encoding(false, true);

        //直接序列化底层node name会以类名命名,转换为xml
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(uTF8.GetString(stream.ToArray()));
        XmlDocument docNew = new XmlDocument();
        XmlElement newRoot = docNew.CreateElement("xml");
        docNew.AppendChild(newRoot);
        newRoot.InnerXml = doc.DocumentElement.InnerXml;
        return docNew.OuterXml;
    }
}
4. doPOST()方法  微信这点不太方便,所有访问你的api的都扔到这一个地址来,你只能自己做判断然后分发处理,同样我只做了文本消息(text)的处理。
public static void DoPost(HttpContext context)
    {
        StreamReader reader = new StreamReader(context.Request.InputStream, Encoding.UTF8);
        string postStr =  reader.ReadToEnd();
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(postStr);
        switch(doc.SelectSingleNode("/xml/MsgType").InnerText)
        {
            case "text":
                ContentMessage content = new ContentMessage
                {
                    ToUserName = doc.SelectSingleNode("/xml/ToUserName").InnerText,
                    FromUserName = doc.SelectSingleNode("/xml/FromUserName").InnerText,
                    CreateTime = doc.SelectSingleNode("/xml/CreateTime").InnerText,
                    MsgType = "text",
                    Content = doc.SelectSingleNode("/xml/Content").InnerText
                };
                string lyric = Lyrics.ChaGeCi(content.Content);

                ReturnContentMessage returnContent = new ReturnContentMessage
                {
                    ToUserName = content.FromUserName,
                    FromUserName = content.ToUserName,
                    CreateTime = DateTime.Now.ToString(),
                    MsgType = "text",
                    Content = lyric
                };
                string result = XmlHelper.ObjectToXml(returnContent);
                context.Response.ContentType = "text/xml";
                context.Response.Write(result);
                break;
        }
    }
5. 具体歌词逻辑获取  目前只取搜索结果的第一条数据,这样最简洁快速。另外引用了外部抓取库HtmlAgilityPack,顺便吐槽一下,这个库的网站上真的是什么文档都没有啊,要想弄懂得去读源码…,在筛选结果时还用到了一点正则表达式
public static string ChaGeCi(string twoChar)
    {
        string lyric = "";
        string keyword = twoChar.Replace(" ", "+");

        //1.搜索,获取第1条结果的
        HtmlWeb web1 = new HtmlWeb();
        HtmlDocument doc1 = web1.Load("http://www.xiami.com/search/song-lyric/?key=" + keyword);

        //取第1条结果
        HtmlNode node = doc1.DocumentNode.SelectSingleNode("//body//div//div//div//div//div//div//div//div//ul//li[1]//table//tbody//tr//td//a[@href]");
        if(node != null)
        {
            string sid = node.Attributes["href"].Value;
            sid = Regex.Replace(sid, @"[^\d]*", "");

            //2.获取歌曲信息,HtmlAgilityPack库无法读取网络的xml文件
            Uri uri2 = new Uri("http://www.xiami.com/song/playlist/id/" + sid);
            HttpWebRequest req2 = HttpWebRequest.Create(uri2) as HttpWebRequest;
            string songPage = "";
            using(HttpWebResponse res2 = req2.GetResponse() as HttpWebResponse)
            {
                StreamReader reader2 = new StreamReader(res2.GetResponseStream());
                songPage = reader2.ReadToEnd();
            }

            if(songPage.Length > 1)
            {
                XmlDocument xml = new XmlDocument();
                xml.LoadXml(songPage);
                //歌名和演唱者,放在文本消息结尾方便判断结果对错
                string title = xml.GetElementsByTagName("title")[0].InnerText;
                string artist = xml.GetElementsByTagName("artist")[0].InnerText;
                string lyric_url = xml.GetElementsByTagName("lyric")[0].InnerText;

                //3.根据lyric_url下载歌词
                HttpWebRequest req3 = HttpWebRequest.Create(new Uri(lyric_url)) as HttpWebRequest;
                using(HttpWebResponse res3 = req3.GetResponse() as HttpWebResponse)
                {
                    StreamReader reader3 = new StreamReader(res3.GetResponseStream());
                    lyric = reader3.ReadToEnd();
                    lyric = Regex.Replace(lyric, @"\[.*?\]", "");
                    lyric += "\n" + title + " - " + artist;
                }
            }

        }
        return lyric == "" ? "抱歉,没有找到相关歌词~" : lyric;
    }

总结
  • 搜索可能要加上网易云音乐的接口
  • 如果有可能的话,后续还可以加上语音的搜索