Welcome to my blog. You can post whatever you like!

The blog post is using markdown, and here is a simple guide. Markdown Guide

LOGIN TO POST A BLOG

Life is Strange 评测

Satori

Author: Satori

Posted: 2017-09-05 23:10:30

Category: life_is_strange 游戏 评测

Views: 1242 Comments: 4

[Life is Strange][1]是一个很有意思的游戏,玩家通过选择选项以及利用女主回溯能力来与周围的人和物进行交互。 女主Max时隔5年从西雅图回到Arcadia Bay的Blackwell学习摄影。期间在洗手间偶遇一只蓝色蝴蝶,并把它拍了下来,不久后遇到富二代Nathan与一名女性进行争执,然后Nathan情急之下开了一枪,导致那名女性死亡。女主在一旁瞬间觉醒了时间回溯能力,又回到了上洗手间前上课的时刻。在女主确定自己拥有时间回溯能力之后,立马赶到洗手间,敲响火警铃声,使得那名女孩得救。 从这个时间段开始,女主Max就可以用回溯能力做很多事情。比如可以得知一些情报,然后回溯,改变既有事实。比如和别人打交道,对别人不太了解的时候,通过与他交流,再回溯时间,能提前说出别人想听到的信息,这样就更容易获得更多其他有用的信息,而这些信息都是后期至关重要的。 通过回溯时间,Max让他人免于意外,让原本固执难沟通的人那里获取很多情报。随着故事的发展,Max的能力越来越强。到了第二章最后,一直备受欺凌的女生Kate在宿舍楼要跳楼。但是Max赶到的时候已经跳下了,Max尝试回溯,但是也仅仅能回溯到Kate刚跳楼那一下。Max只能不断回溯,但这都不能救下Kate,而且对自身的消耗极大。但是最终Max爆发大招,时间暂停,她暂停了时间,然后一瘸一拐地来到了屋顶,此时时间暂停的效果刚好消失。此时Max发现自己暂时不能回溯时间了。于是接下来的对话只会进行一次,并不能回溯。这是游戏唯一一处不能回溯的地方。 这里是我认为游戏处理地非常好的一点。Kate这里大概会问6个问题,这些问题的难度取决于第一章和第二章前面玩家的种种选择,比如有没有擦掉侮辱她话的涂鸦,有没有接她的电话,有没有叫她去报警等等。如果这些行动恰当,那么Kate问的问题怎么回答都是对的,但是如果某个行动欠妥,那么就需要从4个答案当中选一个唯一正确的,选错Kate会退一步,当Kate退两步的时候Kate就会坠落死亡。 前面我唯一选择不太妥当的地方是当Kate遇到欺凌的时候我选择让她观察,而不是报警,同时Kate提出的那个问题我也回答错误了。但是这个游戏高就高在这样一个设定,当前5个问题Kate有往后退一步的举动后,会产生第六个问题,而这个问题非常坑爹,需要在之前的任务中仔细观看Kate的圣经,其中有两处地方有勾画,一个是马太福音11张28小节,另一个是箴言21章15小节,不过被Kate叉掉了。所以应该选择马太福音11:28来鼓励她 > Come to me, all you who are weary and burdened, and I will give you rest. 我就是在最后一个问题上答错了,因为我根本就没有留意之前去Kate房间里的时候那个圣经到底做了什么批注。所以真的是不了解Kate,最终导致了她死亡。说实话我玩到这里真的是郁闷了很久,要是我当时多关心一下她,多了解一下她,也不会让她孤立无援,最终跳了下去。 关于Chloe,女二,被刻画成一个前卫的朋克,叛逆的孩子,她在12岁的时候父亲因为一次意外出车祸去世,从此变得非常叛逆,经常逃课,那时候她没有朋友,没有父爱,非常孤独,这在后来的前传第一章觉醒里表现得非常好。最让她寒心的是童年玩伴Max无缘无故离开Arcadia Bay到西雅图求学,可以说是抛弃了Chloe。而之前被Nathan射杀的正是Chloe。当Max救下Chloe之后他们刷了一下对方的好感度,然后去寻找Chloe这5年里唯一的伙伴Rachel Amber,她已经失踪很久了,于是他们准备去寻找事情真相。 中间有一个情节,Max做客Chloe家中,Chloe母亲谈论Chloe父亲William去世的想法,她留给的信息是,William left us with best memories。不知为何,我对这个很有感触。虽然William在Chloe很早的时候离开了她,但是在他离开之前,带给了Chloe很多美好的回忆,虽然人已经走了,但是回忆依然是美好的,他带给Chloe快乐的感觉是真实存在过的,这就够了。 Max后来拥有看照片就能回溯到照片那个时候的能力,这样能够改变因果。她曾用这个方法去救William,结果真的成功了,她来到了平行时空。但是当Max来平行时空看Chloe时,发现Chloe躺在轮椅上。原来这个时空里虽然William没死,但是后来Chloe在一次驾驶William送她的车的时候出了事故,导致她直接成为一个植物人。Chloe表示她觉得自己这样活着特别没有尊严,而且自己的续命设备非常昂贵,加上她们家并不富裕,而且这样下去只能拖垮整个家,于是她想让Max拔掉呼吸管,有尊严地结束她的性命。玩到这里真的比较感慨,虽然救了Chloe父亲,以为这样就能让Chloe获得幸福,然而这样做依然只会带来不幸,Max不得不重新返回原来的时空。 在第四章最后,她们最后查到了真相,Rachel死于darkroom,当他们查到最终元凶是那个鬼畜老师Jefferson的时候,Chloe被射杀,Max被注射麻醉剂,然后被带到了dark♂room。 最终章的设计也是让我比较满意。为了躲避Jefferson的追杀,Max通过照片进行多次穿越,但是都不可避免Chloe的死亡,而且她还做了一个噩梦。在这一关Nightmare里面,把之前玩家痛恨,遗憾的地方全部都重现了一遍。比如Kate会说都怪你杀死了我。说实话当时害死Kate还是让我很难过的,现在重现这一段真的是很悲伤。然后是逃离Jefferson的追杀,每次被Jefferson逮到都会被一句大声的**Max**吓到。然后是逃离校长的监视,校长一直在说Max想杀死Rachel和Chloe。然后来到Locker♂Room,这里要逃离学校人的追捕。接着还需要躲避人去寻找5个酒瓶,这曾经在第二章Max刷好感的时候被Chloe要求做的事情,还是比较难找的。然后Max被关进一个雪球里面,静静地看着William迈向死神,而自己什么都做不了。最后来到餐厅,餐厅里的人乞求Max不要杀他们,然后和另一个自己对话,说出了自己根本不是超级英雄的事实。可以说这个游戏所有的矛盾和冲突点都在这一节上爆发了出来。 到了游戏的最后,得知Arcadia Bay会被暴风毁灭的种种原因就是自己救了Chloe之后所引起的蝴蝶效应后,会让玩家进行选择。这时候Chloe拿着第一章Max在Chloe遇害前拍的那个蓝色蝴蝶的照片,只需要穿越到那个时候不按响火警铃就行了。另外一种选择是撕掉这张照片,和Chloe一起逃离避难。 可以说我在这里纠结了很久,游戏制作者想要的结局应该是牺牲Chloe。游戏之前一直在暗示玩家,你之前做的行为都是拆东墙补西墙,你可能这次看似救了Chloe,实质上很可能在另一个地方也会害死Chloe。Chloe真正死亡的时间就应该是被Nathan射杀的时候。结合之前Chloe她妈说的那句,留下最重要的是Memories。在不存在的时空里,他们共同度过的时光是真实的,快乐的,拥有这份回忆就足够了,你不能渴求更多结果。就像游戏标题一样。Life is strange,当你想尽力避免一件事情的发生的时候,它总在另外一个时间点以另一个方式发生。可以说有些东西是不可避免,不可被改变的,我们要做的仅仅是享受当下,不留遗憾地活着。或许这就是这个游戏想要传达给我们的意思吧。 综合评价9.5/10 [1]: https://life-is-strange.wikia.com/wiki/Life_is_Strange_Wiki

Read More

消息提醒系统的实现

Satori

Author: Satori

Posted: 2017-08-22 10:54:10

Category: django 技术 消息提醒

Views: 1515 Comments: 4

## 一、背景 消息系统是困扰我比较长时间的一个功能。试想了许多种方法,但是都不理想。最后经过一段时间的思考,总算找到了相对较好的解决方法。 首先需要知道什么是消息,消息就是用户感兴趣的内容有更新时的一个通知。用户感兴趣的内容目前看就两大类,一个是用户订阅的某作者发表了新的文章时,需要通知用户。另一个是当有人回复某用户时,包括回复用户的博客以及评论,通知用户。 可见消息和某一类信息源直接相关,另外用户希望知道哪些是未知消息,哪些是已读消息。有了这些需求之后,就可以设计数据结构来存储消息了。 ## 二、数据模型设计 对于消息而言,首先需要知道这条消息是给谁的,因此需要有一个外键指向用户。其次,要判断用户是否已经读过这条消息,因此需要一个布尔域记录这条消息是否被读过。然后需要知道这条消息的内容。这里选择使用一个generic foreign key来指向任意对象,同时需要额外的域来记录那个对象的类型以及id,因此整个消息的数据模型定义如下。 ``` class Notification(models.Model): unread = models.BooleanField(default=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') def __str__(self): return str(self.content_object) ``` ## 三、消息机制的实现 ### 1. 消息的生成 消息的生成是通过用户发博客和回复来触发的。当某人发了一条博客之后,需要查询谁关注了这名博主,然后向这些用户每人增添一条未读消息。同理,当某人回复某条博客或者评论的时候,需要给博客的作者或者评论的作者增添一条未读消息。 为了不改变原有逻辑,使用signal进行触发,将以上逻辑包装进处理函数中,再把对象和处理函数连接到一起,即可完成触发。 增添的逻辑如下。 首先是当有新回复的处理函数 ``` def post_save_comment_receiver(sender, instance, created, *args, **kwargs): if created: # for comment post to a blog if instance.parent is None and instance.blog.author != instance.author: Notification.objects.create(content_object=instance, user=instance.blog.author) # else if comment post to another comment elif instance.parent is not None and instance.parent.author != instance.author: Notification.objects.create(content_object=instance, user=instance.parent.author) ``` 然后是有新博客的处理函数 ``` def post_save_blog_receiver(sender, instance, created, *args, **kwargs): if created: # send notification to all followers author = instance.author relations = author.fan.all() fans = [relation.fan for relation in relations] for fan in fans: Notification.objects.create(content_object=instance, user=fan) ``` 然后将信号与对象连到一起 ``` post_save.connect(post_save_comment_receiver, sender=Comment) post_save.connect(post_save_blog_receiver, sender=Blog) ``` ### 2. 消息的阅读 当消息被阅读时将消息标记为已读,方法为发送ajax请求,触发条件为点击get detail,ajax请求如下。 ``` $(".read-reply").bind('click', function(event){ var notification = $(this).attr("id"); var notification_id = notification.split("_")[1]; $.ajax({ type: "GET", url: "{% url 'read_notification' %}", data: {"notification_id": notification_id}, success: function(result) { ajax_unread_replies_count(); get_notification_count(); ajax_comment_received(); $(this).click(); } }); }); ``` 最后收到响应后需要跳转到指定页面。 ### 3. 消息的显示 即显示有多少条消息,有两个地方需要显示,一个是导航栏,显示一共有几条消息,另一个是用户面板,显示回复的条数和订阅新博客的条数,也是通过发ajax请求得到数目,请求如下。 首先是未读回复消息数的ajax请求。 ``` function ajax_unread_replies_count() { $.ajax({ type: "GET", url: "{% url 'unread_comment_count' %}", success: function(result) { var count = result['count']; if (count > 0) { $('#unread-replies').html(count); } else { $('#unread-replies').html(""); } } }); } ``` 然后是新订阅博客消息数的ajax请求。 ``` function ajax_unread_blogs_count() { $.ajax({ type: "GET", url: "{% url 'unread_blog_count' %}", success: function(result) { var count = result['count']; if (count > 0) { $('#unread-blogs').html(count); } else { $('#unread-blogs').html(""); } } }); } ``` 具体的条目也是通过ajax请求来发送,收到的响应为具体的html代码。 先是收到回复内容的ajax请求。 ``` function ajax_comment_received() { $.ajax({ type: "GET", url: "{% url 'user_comment_received' %}", data: {"user_id": {{ request.user.id }} }, success: function(result) { $('#comment-received').html(result); }, error: function(jqXHR, exception) { $('#comment-received').html(exception); } }); } ``` 然后是新订阅博客内容的ajax请求。 ``` function ajax_subscribe_blogs_content() { $.ajax({ type: "GET", url: "{% url 'unread_blogs' %}", data: {"user_id": {{ request.user.id }} }, success: function(result) { $('#blog-subscriptions-div').html(result); update_marked_truncate(); $('pre code').each(function(i, block) { hljs.highlightBlock(block); }); }, error: function(jqXHR, exception) { $('#blog-subscriptions-div').html(exception); } }); } ``` ### 4. 消息的删除 虽然现在博客并不提供删除博客和评论操作(本来就没多少,要是能删除区不是更少QAQ), 但是以防万一还是需要处理博客或者评论被删除的情况。原理还是一样, 通过博客或者评论的id来筛选消息,然后将这些消息全部删除。删除需要用content_type, 可以使用动态的方法获得type, 再根据消息源的id就可以删除了,最后用信号将模型和函数进行连接,代码如下。 ``` def post_delete_notification_receiver(sender, instance, *args, **kwargs): ctype = ContentType.objects.get_for_model(instance) Notification.objects.filter(content_type=ctype).filter(object_id=instance.id).delete() post_delete.connect(post_delete_notification_receiver, sender=Comment) post_delete.connect(post_delete_notification_receiver, sender=Blog) ``` ## 四、消息处理的方法 前一节已经讲到了发送请求的部分,这一节讲如何实现ajax请求的url。 ### 1. 未读消息数目的实现 这个比较简单,主要是通过用户找到对应的消息,然后对消息进行filter, 找出未读的消息就行了。对于新订阅的博客,只需再filter一下,指定类型为博客,对于评论,则指定类型为评论就行了。 先是总未读消息的处理。 ``` @login_required def get_unread_notification_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` 然后是未读评论总数的处理。 ``` @login_required def get_unread_comment_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).filter(content_type__model='comment').count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` 最后是未读订阅博客数量的处理。 ``` @login_required def get_unread_blog_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).filter(content_type__model='blog').count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` ### 2. 标记消息为已读的实现 这个实现也比较简单,主要是通过消息id找到对应消息,然后将unread置为false就行了,不过需要验证请求用户是否是消息用户,不然就可以读别人的未读消息了,代码如下。 ``` @login_required def read_notification(request): if request.is_ajax(): notification_id = request.GET.get("notification_id") notification = get_object_or_404(Notification, id=notification_id) if request.user == notification.user: notification.unread = False notification.save() return JsonResponse({"success": True}) return JsonResponse({"success": False}) else: raise Http404("Page not found!") ``` ## 五、总结 消息系统是博客重要的组成部分。我通过具体的数据模型存储消息,并通过signal触发方式生成消息,通过ajax请求发送得到消息以及标记消息。这个解决方案的瓶颈在于当数据量足够大的时候,得到响应的速度会很慢,不过目前只能想到这个解决方法,以后如果想到更好的方法而且有足够动力的话会继续更新(多半不会)。

Read More

测试 API 发送

Satori

Author: Satori

Posted: 2017-08-12 13:33:54

Category: test 测试

Views: 774 Comments: 20

测试成功!

Read More

API 使用手册

Satori

Author: Satori

Posted: 2017-08-11 19:21:58

Category: 指南

Views: 1880 Comments: 0

相对于浏览器访问网页的形式,通过[API][1]来访问网络资源相对而言难度较大,但是通过API的方式能直接得到想要的数据,省去了浏览器加载各种静态资源的麻烦,因此效率和速度也要快不少。 # API的使用方法 ## 1. 获取API key 通过token的方式来验证,因此token很重要,获取token的方式是先访问[用户界面][2],选择*API key*选择项,点击*Generate new API key*按钮即可看到生成的API key,是40位长的字符串。 ## 2. 字段的填写 发送请求的方式有很多种,可以通过类似*Postman*的插件来发送,也可以通过curl来发送。 首先是请求头的填写,主要需要包含两个header,分别是content类型和authorization,其中部分api不需要authorization,后文详述。 一个是`Content-Type: application/json`,另一个是`Authorization: Token `**`API_key`**,其中**`API_key`**就是前面获取的40位长的API key,示例: `Authorization: Token 698e6123fa8444751f2344d5b89e7639f03866ed`,注意后文所有示例的API_key都是**无效**的,需要填写**你自己**的API_key! 然后是发送的方法,不同的api要求不同的方法,下文详述。 接着是发送的数据,不同api要求不同的数据,下文详述。 最后是请求的api地址,如果采用GET方法还需要在url后加上参数,整个url需要用引号括起来,下文详述。 ***特别注意,Windows系统下url地址需要用双引号括起来,而Linux系统单双引号都可以!*** ## 3. 请求的发送 将请求的头部填好,使用合适的方法讲数据发送到对应url就是整个发送的过程。对于不同的api,有些需要发送数据,有些则不需要,有些需要用POST方法发送,有些则需要GET方法。 以curl为例,使用`-X`指定发送方法,使用`-H`发送头部,使用`-d`发送数据。一个典型的api call如下。 ``` curl -X POST 'http://127.0.0.1:8000/api/blogs/post/' -H "Content-Type: application/json" -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -d '{"title": "This is a blog created by calling api", "text": "This is done via `curl`, authenticated via `token`", "categories": "test api"}' ``` 这个命令的解读如下。首先看`-X`,后面参数是POST,即使用POST方法来发送数据,然后是api地址,即`http://127.0.0.1:8000/api/blogs/post/`,然后是`-H` 后面接的就是头部**Content-Type**,然后又是`-H`,这次是api key验证字段,最后是`-d`字段,后面接了一个字典,也就是发送的数据字段。 # API参考手册 ## 1. 查看博客API ### 功能: 查看已经发布的博客 ### 地址: https://chongliu.me/api/blogs/ ### 发送方式: [GET] ### 发送数据: 无 ### 参数: [page, page_size] page表示当前的页码,page_size表示一页显示多少项目。这两个参数都是可选的。 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/?page=2&page_size=8' -H "Content-Type: application/json" ``` ### 返回字段: 1. count: 总共的博客条数 2. previous: 前一页的url 3. next: 后一页的url 4. results: 返回的结果 ## 2. 查看博客详细信息API ### 功能: 查看某一博客的具体信息 ### 地址: https://chongliu.me/api/blogs/archive/ *blog_id* / *blog_id*: 博客的id ### 发送方式: [GET] ### 发送数据: 无 ### 参数: 无 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/archive/105/' -H "Content-Type: application/json" ``` ### 返回字段: 1. id: 博客id 2. title: 博客的标题 3. author: 作者的名字 4. publish_time: 发布的时间 5. last_update_time: 最后修改时间 6. text: 博客正文 7. categories: 博客标签 ## 3. 更新博客API ### 功能: 更新某一博客的具体信息 ### 地址: https://chongliu.me/api/blogs/archive/ *blog_id* /edit/ *blog_id*: 博客的id ### 发送方式: [PUT] ### 发送数据: ``` { "title": "", "text": "", "categories": "" } ``` 其中三个数据段都是可选的。有哪个数据段就更新哪个数据段。其中categories字段将所有的category用空格隔开,如果categories字段留的字段是空字符串的话会移除所有标签。 ### 参数: 无 ### 权限: 仅限该博客的作者 ### 实例: ``` curl -X PUT 'https://chongliu.me/api/blogs/archive/19/edit/' -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -H "Content-Type: application/json" -d '{"title": "测试2 modified", "text": "test now!", "categories": "test 测试"}' ``` ### 返回字段: 1. title: 修改后的标题 2. text: 修改后的正文 3. categories: 修改后的标签 ## 4. 新建博客API ### 功能: 发表一条博客 ### 地址: https://chongliu.me/api/blogs/post/ ### 发送方式: [POST] ### 发送数据: ``` { "title": "", "text": "", "categories": "" } ``` 其中"title", "text"为必填字段,"categories"为选填字段。 ### 参数: 无 ### 权限: 所有登录用户 ### 实例: ``` curl -X POST 'https://chongliu.me/api/blogs/post/' -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -H "Content-Type: application/json" -d '{"title": "测试 API 发送", "text": "测试成功!", "categories": "test 测试"}' ``` ### 返回字段: 1. id: 新建博客的id 2. title: 新建博客的标题 3. text: 新建博客的正文 4. categories: 新建博客的标签 ## 5. 查看博客评论API ### 功能: 查看某一博客的所有评论 ### 地址: https://chongliu.me/api/blogs/archive/ *blog_id* /comment/ *blog_id*: 博客的id ### 发送方式: [GET] ### 发送数据: 无 ### 参数: 无 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/archive/2/comment/' -H "Content-Type: application/json" ``` ### 返回字段: 1. comments: 所有的评论,以json格式存储,为树形结构,每一个节点包含以下字段: id: 评论id, author: 评论作者, publish_time: 发布时间, last_update_time: 最后更新时间, text: 正文, children: 子节点。 ## 6. 查看评论API ### 功能: 查看某一评论及子评论 ### 地址: https://chongliu.me/api/blogs/comment/ *comment_id* / *comment_id*: 评论的id ### 发送方式: [GET] ### 发送数据: 无 ### 参数: 无 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/comment/260/' -H "Content-Type: application/json" ``` ### 返回字段: 1. id: 评论id 2. author: 作者的名字 3. publish_time: 发布的时间 4. last_update_time: 最后修改时间 5. text: 评论正文 6. children: 子节点。 ## 7. 回复博客API ### 功能: 对一条博客进行回复 ### 地址: https://chongliu.me/api/blogs/addComment/ *blog_id* / *blog_id*: 博客的id ### 发送方式: [POST] ### 发送数据: ``` { "text": "" } ``` "text"为必填字段,为回复正文。 ### 参数: 无 ### 权限: 所有登录用户 ### 实例: ``` curl -X POST 'https://chongliu.me/api/blogs/addComment/19/' -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -H "Content-Type: application/json" -d '{"text": "测试回复博客"}' ``` ### 返回字段: 1. id: 新回复的id 2. text: 新回复的正文 ## 8. 回复评论API ### 功能: 对一条评论进行回复 ### 地址: https://chongliu.me/api/blogs/addNestedComment/ *comment_id* / *comment_id*: 评论的id ### 发送方式: [POST] ### 发送数据: ``` { "text": "" } ``` "text"为必填字段,为回复正文。 ### 参数: 无 ### 权限: 所有登录用户 ### 实例: ``` curl -X POST 'https://chongliu.me/api/blogs/addNestedComment/268/' -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -H "Content-Type: application/json" -d '{"text": "测试回复评论"}' ``` ### 返回字段: 1. id: 新回复的id 2. text: 新回复的正文 ## 9. 更新评论API ### 功能: 更新某一评论的内容 ### 地址: https://chongliu.me/api/blogs/comment/ *comment_id* /edit/ *comment_id*: 评论的id ### 发送方式: [PUT] ### 发送数据: ``` { "text": "" } ``` "text"为必填字段,为回复正文。 ### 参数: 无 ### 权限: 仅限该评论的作者 ### 实例: ``` curl -X PUT 'https://chongliu.me/api/blogs/comment/268/edit/' -H 'Authorization: Token 78c94f35184bbdd0e36ed1dbc0dae490ebb86316' -H "Content-Type: application/json" -d '{"text": "updated comment"}' ``` ### 返回字段: 1. text: 更新后的回复正文 ## 10. 查看标签下的博客API ### 功能: 查看所有含某标签的博客 ### 地址: https://chongliu.me/api/blogs/category/ *name* / *name*: 标签名称 ### 发送方式: [GET] ### 发送数据: 无 ### 参数: [page, page_size] page表示当前的页码,page_size表示一页显示多少项目。这两个参数都是可选的。 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/category/test/?page=2&page_size=3' -H "Content-Type: application/json" ``` ### 返回字段: 1. count: 该标签下总共的博客条数 2. previous: 前一页的url 3. next: 后一页的url 4. results: 返回的结果 ## 11. 搜索博客API ### 功能: 查看所有在某字段含有指定搜索内容的博客 ### 地址: https://chongliu.me/api/blogs/search/ ### 发送方式: [GET] ### 发送数据: 无 ### 参数: search, [page, page_size, title, text, author, category, publish_from_date, publish_to_date, update_from_date, update_to_date] 其中search字段为搜索内容,是必选字段,其他都是可选字段 page表示当前的页码,page_size表示一页显示多少项目。 当参数含有title时(不必赋值),则会搜索所有标题含搜索字段的博客,同理text(正文字段),author(作者名字),category(标签名字)。需注意的是这4个人标签有同时出现的情况时,将会把所有的搜索结果合并,相当于取**或运算**,不是与运算。 当参数含有publish_from_date时,需要赋值,表示从某个日期开始发布的博客,格式**年-月-日**,如"2017-1-2","2017-03-09"等等。同理publish_to_date(发布时间在某日期之前), update_from_date(最后更新时间在某日期之后), update_to_date(最后更新日期在某日期之前)。需注意的是这四个标签同时出现的情况,会把所有条件合并选取,相当于**与运算**,不是或运算。同时还需注意如果要选择*2017年2月14日0时0分0秒*到*2017年2月14日23时59分59秒*所有发布的博客,参数应该是`?publish_from_date=2017-2-14&publish_to_date=2017-2-15`,*publish_to_date*应该是*2017-2-15*而不是*2017-2-14*。 ### 权限: 所有用户 ### 实例: ``` curl -X GET 'https://chongliu.me/api/blogs/search/?search=test&page=2&page_size=3&title&category&publish_from_date=2017-2-14&publish_to_date=2017-3-1' -H "Content-Type: application/json" ``` ### 返回字段: 1. count: 该搜索条件下总共的博客条数 2. previous: 前一页的url 3. next: 后一页的url 4. results: 返回的结果 [1]: https://en.wikipedia.org/wiki/Web_API [2]: https://chongliu.me/accounts/profile/

Read More

备份数据库很重要

Satori

Author: Satori

Posted: 2017-07-26 23:04:06

Category: git 教训 经验

Views: 663 Comments: 10

今天写了一天代码,头有点晕,准备把代码提交到服务器上。结果服务器上有一个样式加载有问题,看了看是没有读到对的静态文件。于是索性就在服务器上改代码。后来本地也改了另外一些东西,也提交了上去。在服务器上拉新代码时冲突了,当时git告诉我需要stash一下或者把冲突改过来。当时我一发蒙,居然用`git reset --hard`指令解决冲突,当时想的是反正都要合代码,返回上一个commit没问题。 但是当时忽略了一类重要的文件,也就是**工作区**中修改还没有提交的文件。一旦reset hard后工作区就被覆盖了,相当于我的文件修改就丢失了。而更要命的是这个文件不是其他文件,就是数据库文件。 其实数据库不应该放在git里面的,但我怕migrate文件太乱在线上migrate的时候会出问题,索性直接把数据库记录了。然而最近几次提交我都没有把数据库修改提交上去,导致我前几天辛辛苦苦写的技术博客就丢失了。 这真的令人唏嘘。还好有谷歌快照大法,让我追回一篇博客,但是另一篇就找不到了,只有重写。 真的是教训惨痛。要么我就根本别追踪数据库文件,要么每次我把修改都提交。以后使用reset之前就和使用rm一样先想一想有没有问题再操作。

Read More

利用K-means聚类压缩图片

Satori

Author: Satori

Posted: 2017-07-25 07:28:44

Category: K-means 技术 机器学习

Views: 2209 Comments: 0

K-means是一种用于非监督学习的集群方法。对于一组n维向量的数据,找到K个点,称为聚类中心,将数据分成K个类别,最终使得每个类中的向量相对于划分类的聚类中心的距离最短。 K-means具体操作步骤如下。 1. 在数据集中随机选择K个点作为聚类中心。 2. 对于数据集中的每一个点,找到离它最近的聚类中心。并被归为这一类。 3. 对于聚类中心划分的每一个类,求得类当中所有数据的平均值,并讲结果定义为新的聚类中心。 重复第2, 3步足够多次数,就能将数据集进行划分,得到这个数据集的某些特征。 而对于彩色图像而言,一般用RGB表示每个像素的色彩空间,每个占大小1B,一个像素就3B,但是如果将原图的色彩空间进行划分,比如取K=16,那么一个颜色就只有4bit,也就是0.5B,理论上大小会是原来的1/6. 这样就讲图片压缩了。相当于原来一个像素有2的24次方种颜色表示,现在只用16种颜色来表示。选取这16种颜色的做法是将该图的所有颜色空间进行聚类,找到16个“特征颜色”,然后给图中每个像素找到离它最近的特征颜色。 训练效果如下,首先是原图。 ![原图][1] 然后是K=16的压缩图像。 ![K=16][2] 然后是K=32的压缩图像。 ![K=32][3] 然后是K=64的压缩图像。 ![K=32][4] 可以看到,经过实验训练之后,虽然颜色空间被大大地压缩,不过令人欣喜的是图片依然能保留基本的色调,只不过很多细节都没了,随着K的增大,这个现象会好很多,不过可以看到,K=32时比K=16时,黄色更加丰富,肌肤的颜色更加丰润,背景蓝色更加自然。 而K=64时,衣服的红色也体现了出来,黄色更加丰富,肌肤失真更小。不过还是缺少原图的绿色。结果也不难理解,因为绿色毕竟很少,图整体色调是黄色和蓝色,因此绿色就被聚类到深青色上了。 理论上随着K的增大,图片效果会好很多,不过随着K的增大,训练的时间也大大增大,单机训练一张1280*720的图片,K=16时,用时50min,K=32,用时2h,可见速度确实非常慢。同时也应该看到,使用K-means算法聚类的问题是很容易受初始点的选取的影响。因此一般还需要多次设置初始点,取Cost最小的聚类方案,这也大大地提高了计算量。 总体而言,K-means聚类效果还是不错,可以看到K-means算法较为简单,效果比较好,是一种不错的聚类方法。 [1]: https://chongliu.me/media/user_1/6.jpg [2]: https://chongliu.me/media/user_1/res_16.png [3]: https://chongliu.me/media/user_1/res_32.png [4]: https://chongliu.me/media/user_1/res_64.png

Read More

Comment Thread 实现方法

Satori

Author: Satori

Posted: 2017-07-22 23:25:36

Category: comment-thread django django-mptt tree-structure 技术

Views: 2237 Comments: 0

对于博客的评论而言,结构可以非常复杂。我们不仅期望对博客能进行评论,也同时希望能对某些评论进行评论。因此,在一般情况下,评论的结构应该是树形结构。 然而树形结构的评论在关系型数据库中如何存储是一个较大的问题。难点主要有两个。 1. 每个评论都有一个对象,比如对博客进行的评论对象是博客本身,而对评论进行评论的对象是评论。如果用外键存评论的对象的化会导致不一致问题。对于博客而言,外键是博客,对于评论而言,外键是评论,无法做到统一。 2. 即使找到了某种方法得到了树形结构的评论对象,在前端模板又怎么样递归地将这些数据表示? 简单来说,需要遍历一个复合的列表,而django的template没有render这种复杂递归逻辑的能力。 对于第一个问题我在查阅Generic Foreign Key资料的时候得到了一点启发。构造评论对象的时候,使用一个外键指向自己,这样就能递归地构造评论。同时使用一个外键指向所选的博客,这样对于任意一条评论,使用指向自己的外键可以找到父评论(如果没有父评论就为空),使用指向博客的外键就能找到对应评论的博客。然后添加left和right域,它们也是外键,指向评论,作为找兄弟结点的域,方便进行层次遍历,找到某条评论的所有子评论。 但是对于第二个问题由于默认django模板的局限性,查了很多资料,最后使用**[`django-mptt`][1]**包实现树形数据的建立以及前端树形数据的递归表示。对于models而言,只要继承父类`MPTTModel`,并且定义一个parent字段,类型为`TreeForeignKey`,即指向自己类型的外键,并在META里定义插入排序方法`order_insertion_by`,就能自由地增删数据,而树形结构由它自己维护。同时django-mptt定义了一系列的模板tag,比如`{% recursetree nodes %}`,用于定义递归字段,`node`定义当前结点,`children`定义所有子节点。 这样前端代码结构类似如下。 ``` {% load mptt_tags %} <ul> {% recursetree nodes %} <li> {{ node.name }} {% if not node.is_leaf_node %} <ul class="children"> {{ children }} </ul> {% endif %} </li> {% endrecursetree %} </ul> ``` 其中node.xxx当中xxx是node的属性。这个代码意思是先load进mptt的tags,然后定义一个列表,使用`{% recursetree nodes %}`来递归遍历所有的结点。然后展示node的某些属性,然后判断这个结点是否为叶子结点,不是的话就递归构造它的子节点。 这样所有的结点能递归地进行展示。 然后定义的Comment数据结构如下。主要是定义了`parent`这个字段,这是构造递归树的基础。 ``` class Comment(MPTTModel): parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True) blog = models.ForeignKey(Blog, on_delete=models.CASCADE) author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) publish_time = models.DateTimeField(auto_now_add=True, auto_now=False) last_update_time = models.DateTimeField(null=True, blank=True) text = models.TextField() def __str__(self): return self.text """ rebuild order by running following command in the django shell from blogs.models import Comment Comment.objects.rebuild() """ class MPTTMeta: order_insertion_by = ['-publish_time'] ``` 同时定义两个view以供处理评论的生成,一个是直接评论博客的评论,另一个是评论评论的评论。只需要让它们分别提供博客id和评论id就可以了。对于前者而言,`parent`字段为空,对于后者而言,`parent`就是父评论。对于外键`blog`而言,前者直接用博客id就能找到博客,对于后者而言,可以通过父评论id找到父评论,再用父评论的blog字段找到对应的博客。 前者评论博客的view如下。 ``` def addComment(request, blog_id): form = BlogCommentForm(request.POST) blog = get_object_or_404(Blog, id=blog_id) if form.is_valid(): comment = Comment() comment.author = request.user comment.text = form.cleaned_data.get('text') comment.blog = blog comment.save() return HttpResponseRedirect(reverse('blogs:archive', args=(blog.id,))) ``` 后者评论评论的view如下。 ``` def addNestedComment(request, comment_id): form = BlogCommentForm(request.POST) parent_comment = get_object_or_404(Comment, id=comment_id) blog = parent_comment.blog if form.is_valid(): comment = Comment() comment.parent = parent_comment comment.author = request.user comment.text = form.cleaned_data.get('text') comment.blog = blog comment.save() return HttpResponseRedirect(reverse('blogs:archive', args=(blog.id,))) ``` view分别处理存储后都重定向到对应的博客页面就行了。 除此之外django-mptt还提供了`MPTTModelAdmin`,只要在admin注册,使用`admin.site.register(xxx, MPTTModelAdmin)`这样的语句就能在admin界面上以树形的结构看到评论的结构,其中xxx是想要注册admin的模型类。 如果想换一种排序方式,只需要重新建立树就好了,代码:`xxx.objects.rebuild()`。其中xxx是想要build的模型类。 总之`django-mtpp`是一个处理树形数据结构很有用的包,使用非常方便,快捷,是建立comment thread的不二选择。 [1]: https://github.com/django-mptt/django-mptt

Read More

交织together人声乐谱

Satori

Author: Satori

Posted: 2017-07-08 16:09:48

Category: 乐谱 交织Together 音乐

Views: 1131 Comments: 0

&emsp;&emsp;交织together最早出现在B站的拜年祭上,泠鸢小姐姐唱得确实好听,整个人都要融化了。花了大概两个小时的时间把人声谱扒下来了,看了一下应该要升C大调,用flstudio做了一个简易midi,然后用转换工具转成谱子,虽然是升C大调,不过记谱用的是降D大调。然后用自己的琴试了试,因为变调过多导致经常按错键,就索性直接移调到C大调上面,瞬间屏幕清静了许多,于是又能愉快玩耍了。 &emsp;&emsp;这里还是用的是升C大调的谱,毕竟原唱就是升C大调。 ![交织together1][1] ![交织together2][2] ![交织together3][3] ![交织together4][4] ![交织together5][5] [1]: https://chongliu.me/media/user_1/together-1.png [2]: https://chongliu.me/media/user_1/together-2.png [3]: https://chongliu.me/media/user_1/together-3.png [4]: https://chongliu.me/media/user_1/together-4.png [5]: https://chongliu.me/media/user_1/together-5.png

Read More

大学实习经历总结

Satori

Author: Satori

Posted: 2017-07-05 14:04:57

Category: 实习 总结

Views: 881 Comments: 6

由于大四的时候研究生的方向和导师都已经选好了,因此我准备来找实习来体验一下实际生产环境的编程是怎样的。毕竟我认为在学校里面的大作业之类的没有太多意思,写的东西无外乎又是什么系统,都是非常简单的增删改查的东西,没有太多新意。而是大作业很多都是我带队写的,自己对工程的代码特别熟悉,因此就想出去看看试试,见识一下真实生产环境,顺便赚一点小钱。 但是现实确实比较残酷,我前前后后投了5家公司的简历,其中一家公司连面试机会都没有给我,说实话还是有点受打击,第二家公司给了面试机会,面试的时候虽然有些问题答得不是很好但是我认为整体还是不错的,但是可惜最后还是没有要我,第三家和第四家公司面试都成功了,第五家公司在我选择第三家公司的时候就放弃面试机会了。主要当时第四家公司给我回得太晚了,导致我得到消息的时候已经在第三家公司入职了,而且说实话我感觉自己申请的software engineer intern感觉更有意思,所以,我就去了第三家公司,即AppAnnie。 顺便这里回顾一下面试的经过,当得知自己前面面试失败的时候还是非常沮丧的,一度丧失了信心,主要感觉自己对生产业务熟悉程度不够,有些基础知识掌握不是非常全面。因此也算得到了一点切身的体会,即真的要把自己的本事磨炼得非常高才有可能在面试的时候表现好,得到工作机会。 在Annie的面试非常有趣,在面试我的时候,面试官问的问题似乎和传统国内的互联网企业不太一样,他上来就把我一个网页扫雷小游戏项目的代码进行了回顾。令我印象特别深的是面试官真的是认认真真一行一行地看我的代码,先问我整体的框架模块是什么,各部分怎么联系的。然后深入每一个模块,问每个模块的每一个函数的内部实现是怎样的,甚至深入到了循环怎么写这种级别。在浏览的过程中对于一些游戏的核心逻辑让我描述过程,时不时跟我提建议,比如某些逻辑还可以抽出来供不同模块一起使用,同时也对我界面更新的算法提出了改进的意见,即可以把更新的区域进行记录,只刷新更新的部分,而不是每次全局地进行刷新等等。其中我印象特别深的是他在玩扫雷死的时候会爆发杠铃般的笑声,着实给面试增长不少乐趣。第二个面试官问我的问题就比较抽象了,主要是设计一个网站来实现员工工资的查看,他问需要知道哪些信息才能把这个系统设计出来。这个问题当时还是把我弄得比较懵,我尝试从规模,权限,安全,功能,验证,负载等方面进行了探讨,总之把自己能想到的东西都列出来之后,他满意地点了点头,最后询问我大概什么时候能将这个系统设计出来,我琢磨了一下,回答一周,最后面试官认为这是一个合理的时间。过了一天接到通知,让我1月4日到公司报道,总算,我的实习之旅就要开始了。 刚开始实习的时候真的是各种懵,早上开scrum会,我看周围的同事都在说几个point怎么样,某个数据又怎么从s3转移,某个es集群怎么样,在feature环境怎么测试什么的,真的是完全听不懂,无比慌张。后来组里的dod龙龙开会给我讲述了公司的开发流程是怎样的。简单说是用敏捷开发的思想,每次用一个sprint作为一个迭代周期,每一个sprint将一些要做的项目罗列出来,对于每一个项目,预估自己在这个sprint要做什么内容,每一个具体的内容就会建立一个具体的ticket进行记录。把要做的事情,大概的需求描述,srd文档,预估的投入精力等等进行评估,然后每个sprint根据所有人的精力点数加起来得到整个sprint的点数,然后估计工作量是否偏大,再动态地drag或drop一些ticket,使得在能完成这个sprint的情况下尽可能多地完成ticket。至此我总算明白了早上开那个会是干什么的了。 其实开会的时候还有很多,比如例行的会议有sprint大会和每天的scrum小会。sprint之前还有一个预估的会。除此之外还有根据具体的项目开会。比如和pm开会询问具体的需求,以及需求的变更。还有技术设计的会,比如出来一个新项目,讲这个项目解决方法的思路以及框架等等,然后大家对某些具体的细节进行challenge,提出自己的意见,然后再根据具体的情况进行修改,最有趣的还是大家一起愉快讨论的气氛。 刚开始给我印象最深的是公司全程用英语进行邮件沟通,早上开会的时候也是用英语。而且我们的PM在美国,所以比较有意思的是每次开会的时候都需要远程拨号到具体的会议室,然后用lifesize进行视频流的实时传输,而且后来据我观察还有自动美颜的功能,不能再赞了。对于在学校从来没有经历过这么国际化、大气的开会场景的我来讲,真的是非常新鲜,非常有意思。 工作的前两天主要是装各种环境。最让我意外的是所有的东西都要申请权限,或者叫提交一个IT request。这个request最厉害的地方在于几乎所有的权限都可以提。从开发环境IDE、代码库的权限、Jenkins权限、亚马逊aws权限到硬件设施比如显示屏、键盘等等。因为要等各种权限下来我第一天只是把该填的表格填好,将虚拟机装好(其实没有真正地装好,后面再详细地讲)。然后从别人那里拷贝一个代码库。因为代码库实在太大了,有大概4个G的样子,从github拉下来太费时间,而且更致命的是我现在根本没有代码库的权限。 后来我拿到了代码库的权限,非常荣欣的是之前实习生似乎都没有拿到库的权限,也就是说之前的实习生很可能都没有往库里面push过代码。这真的说明我的leader有点硬气,还真在security那边拿到了权限。至此我就能查看代码库提交pr了。 我拿到的第一个任务是写一个脚本自动记录scrum表。说白了就是从jira那边去拿数据,再将数据记录下来存到一个表格里面。最早我做的是本地版本的,也就是本地生成一个xlsx。但是当时发生了一个事情让我感到有点尴尬,令我记忆比较深刻。 刚来公司后,我第一个认识的小伙伴叫王总,他是我们小组的QA,他对刚来公司啥都不知道的我提供了各种指导,比如怎么开分支提pr,怎样写test case,怎样在github上触发jenkins的test case检查等等,可以说让我有了一点方向感,以至于我后来经常向他问各种问题。我当时刚接手那个scrum记录的任务,当时在jira验证的地方有一些不太明白的的地方。当我又准备找王总请教的时候这时候leader过来了,他用一种含蓄的方式表达了不满,他大概意思是王总本身特别忙,而且这个东西王总也不了解,也是现场查资料,而这些工作本来是我来做的,所以这样是耽误他人的工作。说实话当时我真的非常尴尬。这时候我想起了前女友曾经说过的话,“在工作上自己的事情尽量自己解决,真的到了实在不能解决的时候再去问他人”。我只好连声说“好好好”来掩饰我的慌张。至此之后,真的是遇到不会的东西,我才会找别人。这期间我掌握了各种找问题和解决问题的能力,也掌握了读文档的能力,而这在我实习之前这些都是我所忽略的技能。后来通过自己的不断实验总算找到了利用jira的api登录的方法。最后采取了basic auth而跳过了oauth方法。下一步应该是读数据整理数据了。 可惜我来实习的时间太过临近寒假放假了,因此我不得不请了一个多月的假,而且我还要期末考试,公司那时候恰好年会,因此我就暂时离开公司了。回到家里之后,我还是赶紧开始开发剩余的部分。总算做出来一个可用的版本。 开学之后,我继续这个项目的开发,原来生成一个本地文件要用不是很方便,还需要把文件上传到google doc上面,而这对于一个没有gui的机器来说确实比较困难。不过在此期间也和demien认识了,他好像开发了一个工具能在线上跑task,能把程序的输出存到数据库中,这样就解决了任务定时启动的问题。不过还是遇到了一点困难,xlsx文件是二进制文件,而之前那个工具是纯文本格式来存到数据库,导致二进制字段并不能存进去。不过后来好像用base64编码解决了这个问题。不过这时候我的重心还是转移到了直接写google doc上。 不得不说看google doc的文档是很费劲了,主要之前也没有做过看文档的事情,我花了好些时间将google api的oauth认证,google api的调用方法,以及api接口的定义看了好些遍,结合google自身给出的例子,反复实验。最终了解到api都是一个个的http请求,将要做的操作进行封装成数据段,然后发给服务器远端,然后就能进行修改。google的认证需要通过一个credential文件来认证,需要给google账户开通api权限,比较好的一点是这些api调用并不给钱。 经过一段时间的开发,我终于开发出了直接写入google doc的程序了。结果后面龙龙又加了一些需求,比如sprint时间可变,ticket的effort可以是小时或者点,而且动态变化适应不同的ticket数量,而且sprint可以加入新的ticket,也能做到对应条目的刷新。还好之前我代码写的鲁棒性还不错,这些任务都比较快速地写完了,不过effort那里不同的表示方式从jira取的时候字段还可能不一样,真的比较麻烦。 但是之后的问题让我很尴尬。龙龙在最后看我的代码的时候,觉得jira的认证用的是用户名和密码的方式不好,容易有安全方面的问题,就想我能不能用用jira的oauth方式验证。我不禁感慨一下放假之前我一直都是在干这个事情,还因为这个事情被批评了一下。当时确实没有搞明白怎么去认证。于是我又回到去看jira文档的时候了。 官方的文档真的是非常麻烦事情,经过自己的倒腾,终于能连上自己创建的jira了,但是有一个问题是使用oauth需要得到jira管理的最高权限,然后再认证,很明显security那边并不会给这个权限,于是这件事情就这样不了了之了。最心疼自己的一点是早知道要最高权限,当时就该问一问能不能申请到这个权限,这样就省去了后面自己各种调试代码各种做实验的effort,算是一个教训。 后来最后演示的时候发现google的oauth第一次认证的时候浏览器会弹出一个窗口,需要点allow才能认证,非常不便利,特别是线上的机器还不能点击。于是我又开始查看谷歌的文档,去了解其他oauth方式,结果发现方法比较复杂,最后认证不是很便利,就放弃了。不过同时我发现每次点allow后都会生成一个credential文件,只要把这个文件复制到.credential文件夹就能不用点击了,而且后来的使用发现似乎那个文件的有效期挺长的,用了半年都没有过期。最后龙龙把这个程序步到了一个mac上面,这件事总算告一段落,以后每个sprint我都利用这个程序生成track表来记录sprint完成情况。 这件事情做完后就是一些比较杂的各种小的事情了。比如从s3去拿数据export到csv文件,从代码里面统计机器的使用情况,做一个自动的curl工具,做工具连接本地postgresql,从公司原来的库里拆分出market部分等等。 基本上到了4月中下的时候我开始接触跟公司代码库相关的工作。第一个工作是写抓取google play app permission的spider。比较好的一点是之前我因为毕设的原因接触过spider的编写,所以上手不算很困难。不过这次permission的抓取和我之前抓的不太一样。我之前做的大多是网页的直接抓取,所有信息都在网页上有显示,但是我这个是要点击某个按钮才会显示出来。所以我需要研究一下数据在哪里。 令人沮丧的是页面里并没有permission的相关信息,于是我开始作死地试图从js入手去看它render的逻辑,结果我调试了2个小时都没有找到相关逻辑,而且这个过程十分瞎眼。之后向组里的spider专家于总请教。于总告诉我去看js逻辑是作死的,这种情况要不用phantomjs去模拟点击,要不找到它访问数据的源。我通过自己找一些资料发现页面的请求可能是一个ajax call。于是我重开浏览器,查看xhr请求,结果真的找到了ajax call的地址。不过看这个地址本身好像是一个静态的地址。但是我想那怎么区分不同app呢?我想到了这个请求应该是post请求,请求的app字段应该在post的字段里面,结果看了一下还真是,于是spider第一步,怎么拿到数据算是解决了。 接下来的工作难度还是大于我预期的那样。那个ajax call得到的json文件非常恶心,字段嵌套特别深,好不容易穿过六层嵌套得到了permission相关的字段。然而令人沮丧的是它的字段分为4个大的模块,每一块模块的解析都不一样。抓permission的同时需要得到permission group信息,而每个permission group得到的方式根本不一样,经过我多次观察和猜测,总算得到了一个映射关系,字段的解析就算完成了。 接下来的工作是存储到s3,这个通过看其他人写过的方法也比较快速地完成了,其中s3的key跟leader沟通后确定了,同时对于s3存储的schema格式,相关字段也进行了讨论和规定。本地验证抓一个app也成功了,本地redis里也得到mock了s3的数据。在这些完成之后,下面就是结合celery把抓取任务放到task queue里面然后写cronjob定时去起任务了。但是我对celery不太熟悉,看了一下公司关于task queue的文档,自己follow例子进行学习,了解了写法,简单来说,就是要写一个task来定义这个任务,再写一个producer来给这个task提供参数。其中task里面调用build spider的方法,然后本地修改celery的config文件,加入新加的任务,最后写一个cronjob,设定每天3点去抓3天以内的有新release的app(这个逻辑是后面加进去的)。同时加了monitor代码来监控这个spider的运行情况。总体来说这个开发的部分算是完结了,然而到了测试部分写ut的时候,却花了我不少的精力。 原则上每一个新提交或者新修改的代码都要写ut进行测试,这意味着我需要测试各种方法。最后好不容易补完了测试,但是在代码review的时候,龙龙告诉我s3存储的逻辑应该移到task里面而不是spider里面。这导致我又得全部重新写测试。而且由于逻辑改变了,需要重新验证task,但是task此时里面包含了s3的存储,但是写ut不应该真的去存储,得想办法绕过它,在小伙伴小鱼的指导下,我总算会了mock方法,将存储的方法mock掉,这样就能顺利完成了ut。 在本地测试的时候还遇到了task queue起不了的情况,在leader的指导下本地注释掉了因为没有权限起不起的任务列表,总算本地测试成功了,然后就到ci去跑ut。 经过无数磨难后,我的代码总算合进了master,然后就部署到线上的机器里。在公司的board上,我的名字第一次出现在了deploy板子上,当时真的是很兴奋,自己总算为公司真的是写了代码进去。还好第一次部署没有挂掉,理论上第二天我的代码就能运行起来了。 但是这件事情还远远没有结束。在后来的抓取中,spider挂掉的任务很多,仔细一看,出现了很多404和302的代码,但是线上的dashboard并不能看到具体是哪个app挂了,因为spider的请求是固定的地址,只是post的字段不一样,于是我就去修改spider的抛异常逻辑,增加了post字段,之后挂掉的app信息也能同时被暴露出来。再后来spider挂掉的任务还是不少,我又调低了任务的并发度来减少302,后来证明是有效的,最后就只剩404错误了,后来我又修改逻辑把404错误忽略,不过因为实习结束的原因这部分逻辑好像还是有点问题,残念。 通过spider项目的开发,我算是体验到了从需求分析到方法设计到代码编写到代码测试到后期维护的整个过程,这是我在学校里面真的是体会不到的。 后来我还接受了最后一个大的项目,也就是app detail api和web的增量开发。简单说app detail的api call需要增加一些字段,这些字段大多数都在数据库里面有存储。我通过看代码了解了整个逻辑,核心的部分在于detail manager去product去取字段或者利用product字段去组成想要的字段。然后product是通过一个mapper来绑定product的字段,然后product字段的增加在dto里面定义,而且不同market有不同的dto字段。mapper通过调用product service隐式地从数据库里取字段。 虽然整体的思路是清晰了但是实际操作还是遇到很多问题。比如有些字段在dto里面没有定义,需要补上,复杂一点的需要自己写sql来连接几张表选择相应的字段。最开始我想改product service。但是后来leader告诉我有其他的service可以调用,不用手动写sql,有一个top_inapp_purchase的service可以用。最后经过调用service的方法就能得到相应字段了。还有一个app的商店url没有任何地方存,我还需要强行拼出一个url。总的来说所有字段总算全部拿到了。但是还是像之前那样,写测试着实把我恶心到了。 写测试最烦的地方在于数据的准备,需要调用factory来造各种数据,但是有些字段并没有加入,我还需要扩展factory的功能,这个也花了我不少时间。最后准备完数据之后,我去跑ci,结果挂了一堆case,而且都不是我写ut的case,是一些别人的ut或者ft的case。原来以前的代码也要测试api的调用,现在我加了新的字段后当然以前的测试就会挂,因此我还一个个把原来的测试全部修改了一遍。通过一次次地跑ci我终于过了,满脸辛酸泪。 api开发完之后还要开发web端页面的显示。我也是从url找起,一步步深入内部逻辑,在about_widget找到要添加的字段,不过这个读懂逻辑后,找以前about信息的位置,再加入新的条目,这个我看起来还不算太困难。这次的测试好写了不少,短时间内还是很快搞定了。 这中间出了一个小插曲,不知怎的本地测试环境崩了,然后找了半天原因是本地调用pg的commit方法后就报错,简单来说就是本地数据库插入不了数据,错误好像是transaction的问题。不过我试了网上各种方法都没办法解决,无奈下我重装了虚拟机和重新拉了一下代码库,结果还是不行。问了很多人都觉得是setting_local的问题,但是我换了龙龙的setting_local好像也并没有用。我当时开始怀疑人生了。最后我去找了tom,tom非常仔细,从虚拟机重装都帮我看,结果每次装虚拟机都有3个报错。令我好奇的是每次装完虚拟机的时候Django版本都不是1.4.22,而是1.7,导致本地server不能起来,我就这个问题问了一下tom,tom说这个问题多半都是虚拟机没完全装成功,而虚拟机装完后的报错之前我一直都没管,因为本地之前各种开发测试都没有问题。结果tom怀疑是这个问题,帮我找了一下原因,结果是代码库里少了Django库,导致虚拟机后来没有还原到1.4.22版本。最后装上这个库之后总算本地测试能过了。当不再报那个transaction错误的时候我整个人都感动了。 我记得最早来公司搭环境的时候,虚拟机那块,文档强调必须没有任何报错才算成功,而我当时忽略了这个问题。而带来的影响居然在几个月之后体现了出来,耗费了我和其他人大量时间,这算是吸取了血的教训,不要忽视已有的问题,它可能为之后的开发埋下隐患。 在第一阶段的api和web开发完后,我又继续第二阶段的开发,这次是加入dna信息进去。当天和产品经理Stephen讨论web字段怎么加,加那些,怎么展示等等,这是我第一次一个人和PM对话,感觉还是挺好的,特别是他得知我这个能做,那个也能做之后特别高兴。会开完之后,我去问eddie有关dna相关的service,因为有了之前api的开发经验,我也很快开发完成了,我也非常开心。正准备开发web相关的部分的时候,发现web部分都是取product的字段,而dna信息不全,需要修改product service,最开始我想在product里面调用dna的service,但是经过考虑之后我放弃了这个方法,因为dna的service也引用了product service包,这样就会循环引用,无法通过,虽然可以通过改变import位置解决这个问题,但我觉得这种方法十分不优雅,我想的还是尽量改product service部分,让它有dna service的部分功能。于是我开始改,在龙龙的帮助下我注意到product service比较好的一点是它有一个fields域,决定要不要dna信息,因此我找到了product service的dna部分,手动改sql增加了几个字段,改一下dto,这样就能用product的成员来得到dna信息了。不过这样做整个api的代码也要改,于是我就开始改,重新跑ci,结果还是挂了一堆,而且不是很好解决。当天我回家加班到11点总算把所有的case都修了一遍,问题主要是准备数据这一块有些字段不全导致数据有问题,改了product service的测试,主要还是dna部分,改完之后第二天就是我上班的最后一天了。 当天测试的时候在mock那里出了一个问题,方法并没有被mock到,最后经过小鱼和龙龙的帮助后发现是mock的路径不对,小鱼也教会我mock返回对象的方法,以及对象成员的构造方法,真的是非常感谢他们。 最后一天我把web也很快做好了,因为改了统一接口之后就能方便地用接口得到数据了。赶在上午之前把api和web的开发全结束了,开始跑各种测试,包括smoke test和坑爹的web driver test 以及schema check。当所有测试几乎就要完的时候王总发现url出现了一点问题,主要是数据库的url不带http头,导致在跳转的时候出现了跳转错误,可惜这个错误我当天修不完了,有点可惜。不过当天也发现了google play的页面有一个url显示有问题,我去修,发现主要是有一个字段google play的子类没有定义,加了定义之后就修改好了。 至此,我为期近半年的开发也就告一段落了,整体任务算是完成了,可喜可贺。 除了日常工作外,工作外的生活也是留下了很深的印象。从中午12点到下午2点都是休息时间,这个时间里有各种兴趣小组。有弹琴小组,有三国杀小组,有dota小组,有fifa小组,大家中午都玩得不亦乐乎。我基本上每天中午也就玩玩狼人杀,和一群小伙伴不停地聊爆。每个常来的玩家给我留下很深的印象。比如连续3把悍跳脸不红心不跳的王总,以表情流和飘逸暴民玩法的肯尼,以狡猾玩法闻名以及初代狼人杀组织者demien,还有万年隐狼娜娜,当狼心理素质超好,当好人被怼变狼的于总,以及经常自high的左老板等等。太多了,留下了不少有趣的局。 偶尔和小鱼去学习弹琴。办公室餐厅里面有很多零食,一般饿的时候随便拿两个吃着玩玩。最喜欢的还是后期进的乐纯酸奶,基本上我一天要喝三盒,比较幸运的是几乎没有被admin小姐姐发现。每周五都有自助餐,一般而言大虾还有什么扇贝之类一下子都没了,还有烤鱼汉堡之类,真的很不错,一般我都拿2盘去吃。 我们小组也有活动,主要是早上去吃面,每周基本上最少吃一次面,每次于总都会很慷慨地把半碗面让给我,所以我特别喜欢去那里吃面。而且那边的辣椒都不错,每次都要很多,被肯尼嘲讽,说我来这边是吃辣椒的,并不是吃面的。 印象还比较深的是春天之后我和王总还有小鱼骑车回学校,那时候温度刚好,从东三环骑到北三环,基本上要骑一个多小时,路上时不时谈笑风生,经常聊爆,充满了欢乐的气氛。 总之在aa实习真的是收货颇丰,主要是看懂别人代码和文档的能力,以及自己编写文档的能力。以及建立了测试很重要的概念。同时也记住了踩过的不少坑。同时,还认识了很多有趣的小伙伴,真的是非常感谢你们,让我的实习生涯更加精彩。

Read More

大学感情经历总结

Satori

Author: Satori

Posted: 2017-07-03 02:51:26

Category: 总结 感情

Views: 10265 Comments: 0

大学四年以来我做过最错误的事情应该就是感情方面了,印象非常深刻,因此我将它作为大学回忆录的第一个部分进行回忆与思索。 可以说,因为我自身的缺陷深深伤害了两个女孩。而最让人遗憾的是我已经没有任何机会来补偿我过去所做的一切,真是令人唏嘘。 总结来说,我大学四年和三个女孩谈过恋爱,都没有成功,我将这三段经历记录下来,深刻反思自己的各种问题,希望将来不会再因为自己让别人在感情方面受到伤害。 先说第一个女孩,先叫她宁宁吧,那是大一刚进校的时候,那时候我们开校第一件事情是军训,她应该是那个时候认识我的,那时候我作为班级的“临时负责人”,去协调当时班级的事务,也算露了一个脸,之后她应该通过某种方式得到了我的联系方式,加了我的qq。 宁宁是一个非常淳朴的女孩子,来自青海西宁,长得比较娇小,不过说实话,单单从相貌来看不是很对我的胃口,但是本着所谓的“好意”,还是难以拒绝她的各种邀请,陪她各种出去玩,但这段时间的相处,让我产生了我们是不是能在一起的想法,毕竟这个女孩非常淳朴,而且很多时候真的是为我着想。虽然三年之内很多事情已经忘记了,但是有几个事情印象比较深刻。 第一个是我们去宏福公园的时候,那时候已经晚上12:00左右的时间了,我强行把她拉出来遛弯。当时我们的校区还在昌平,是一个比较偏远的地方,因此那个地方其实不太安全,但是当成试胆大会还是一起来玩了,全程她一直抓着我不放,让我第一次有了一种被依靠的感觉。 第二个是去世界公园那次,在一个比较偏远的地方,叫郭公庄,当时路都没怎么修好,我和她算是尝鲜去玩玩,拍了很多照,回来的时候坐公交车特别累,她把头靠在我的肩膀上睡着了,不过现在想来她一定是没有睡好,毕竟过程中她一直都在调整,想睡到一个舒服的位置,然而当时我太傻,还一个劲地乱动,不过还是初步尝到了朦胧爱情带来的甜蜜? 第三个是去奥林匹克森林公园,那次我第一次牵了她的手。 第四个是大一下的时候,那个时候有一个编程的大作业,我们实行组队制,那时候组队并没有把她拉上,也是我的失误。后来好像因为某一件事情我对班里有些同学的要求太严了,让他们很不舒服,最后是宁宁来跟我说这一些事情的,我才第一次认识到了自己对别人过于苛求了,但是我当时根本没注意到我对她也是过于苛求。 这是第五件事情了,发生在大一上的时候,那时候刚刚是半期考试完,高数成绩刚下来的时候,因为刚来不适应的缘故,她好像当时只考了30多分,以我多年以来的以成绩来评判人的标准,我当时狠狠嘲讽了她,当她哭出来的一瞬间我才意识到自己太过分了,都没有考虑她的心境安慰过她,之后再想怎么给她提出好的方法建议。现在反过来看那时候自己的情商简直低到了极点。一个刚来北京不久的女孩,面对未知的大学生活,当遭受困难来求助她当时比较憧憬的我的时候,我第一反应居然是嘲讽,她不哭出来才怪,真的现在想来都可怕。 第六件事情是14年夏天是世界杯的比赛,作为半个球迷,在考试结束后,我说我要去小扇形教室看世界杯,结果她愿意陪我。我记得当时比赛时间是凌晨,巴西打谁来着,当时她还拿了酸奶和吃的一起陪我看。但是显然她对足球并不感兴趣,中途好几次想要睡着了,但是我当时看到这个情况无动于衷,应该早点把她送回去,毕竟那么晚了,而且她当时从外面回来已经太累了,还要强行陪我看球,而我当时居然一点也不感动。 差不多也是大一的暑假吧,她和我的联系逐渐减少,最后我坐不住了,开始质问她,最后估计是实在受不了,她终于委托她们班另外一个同学给我发信息,总之意思就是分手,当时的我陷入了不可理解的状态。 这一段故事先聊到这里。现在我反过来回忆这段感情,我发现我最大的问题是对于一个我不是真心爱的人,没有早点结束这段暧昧的关系,却让对方一直付出,我去品尝我根本不应得到的甜蜜,同时不断地用行为和言语伤害她,让她感情一直得不到回报,甚至我连“我喜欢你”这句话都从来没有说过。我在和她暧昧的时候就已经觉得看不到未来,但是我还是厚着脸皮去回应她,给她幻想,而表现的行为根本没有回报,让她一次次痛苦,最终忍痛分手。现在想来她能忍我半年多真的是奇迹。说实话,我觉得那段时间的我就是渣男,只顾自己享受“爱情的甜蜜”而根本没有想过去回应别人对我的感情,可以说是践踏别人的感情,没有在一起的打算却想品尝在一起的甜蜜。我,真的是太糟糕了。 令人讽刺的是我当时居然没有看透自己为什么会“被分手”,觉得自己不公,为此还“苦恼”了半年。我的天,明明是自己的问题,而且那么明显,我居然没有看出来?还困惑了半年,导致下学期部分课程成绩有所下降。真心无法面对这段感情,因为我欠她,而且欠的实在太多了。 经过大二一年及大三上的忙碌期之后,我迎来了我第二任女友,这里称呼为01吧,她是高中的不同班级的同学,高中的时候有所接触,大学过后才因为友人A的介绍走到了一起。 第二段感情说实话,是我印象最深的,虽然只持续了5个月的时间,但是我很多第一次都是那个时候给的。第一次说爱一个人,第一次接吻,第一次抱女孩子,第一次和爱的人一起做饭等等。中间也不缺少甜蜜的回忆,但是这也是我最痛苦的感情,真的是因为这段感情,我有了很大的成长,也真正意识到了自己的很多问题。 我一直认为我是一个非常残忍的人,我把她从深渊里拉出来,然而拉到中间的时候,又把她丢入新的深渊。这得从她的经历讲起了。 在朋友介绍之前,她刚从一段长达4年的感情中出来。据她所说,她的前男友很想控制她,什么都在他的影响下,让她很受伤,所以就分手了。那时候她整个人状态非常不好,可以说收到了多方面的伤害。总之,朋友A也想让她从上一段感情尽早走出来,于是经过介绍,我们两个认识了。 从她的朋友圈来看,她是一个很喜欢电影读书的姑娘,和我认识之前,刚带她的奶奶一起去重庆玩了一下。我当时对她真的是产生了好感,真的觉得她是一个很细腻的女孩,很文艺,读过很多书,是我理想当中的女性。而现在回过头来看,她比我成熟,成熟太多了,虽然她还比我小4个月。 这次,我算把自己几乎所有的弱点都毫无保留地暴露给了她,其中最大的弱点是自私。具体的表现为想得到对方各种付出而自己不想付出。其次一些多年养成的坏毛病也显露了出来,而这些毛病追根溯源的话还是归为一点太以自我为中心。所有的事情出发点都是怎样对自己更好,更有利,再说的直白一点,还是自私。可以说我这段感情的失败,很大原因是因为它,而令人讽刺的是,似乎上一段感情原因也是它,经过接近两年的时间,我居然没有一点长进,我现在看来感到可悲和害怕。这些年来,我究竟因为自私伤害了多少人?虽然可以有很多理由可以开脱,但是,我觉得都站不住脚,因为生活中确实有生长环境和我差不多但是做得比我好得多的人比比皆是,我没有理由逃避。 这段感情,我忽视了感情是双方的事情,以一个人生活的状态去匹配两个人的生活状态,那结果只能是失败。她可是你的女朋友啊,你真的有好好考虑过她的感受吗? 然后再聊一下我们之间的事情,现在看来真的是黑历史,可以说是我真的不愿意去回想的历史。 按照时间线我回忆一下,第一次是和她见面,当时在339的罗斯餐厅吃饭,第一印象倒是不错,因为第一次见面或多或少都会注意一点,似乎没有太多不妥,下午和她一起去照花,似乎过得还算比较愉快。后来她要返校的时候我去送她,陪她买了一点给舍友的礼物,然后我给了她送别巧克力,路上还看了看合江亭那边的夜色,确实挺漂亮的,那个时候,我过得真的是很幸福。 之后我在2月17号跟她表了白,然后我们就在一起了。说实话那时候她还不知道我是一个什么样的人,不知道现在她后不后悔当初的选择。 因为她在武汉上学,我在北京上学,说白了就是刚在一起不久就开始异地恋了,而对于我这个根本不成熟的人来说异地恋真的是毫无经验。 3月初的时候,我坐车来到了武汉,陪她去看了东湖,给她拍了照,一起游走于暗黑的街道上一起听着我们喜欢的歌曲,早上去了长江大桥,感受到了江的辽阔和桥的壮阔。后来去江汉路去玩,吃了一些点心,真的是感觉无比幸福。 4月的时候,她也来北京来看我,我的问题差不多也是那个时候开始显露。我记得最深的事情有三件。最主要的一件事情是故宫的事情。当时她来北京,第一个特别想去的地方就是故宫,她好像六七年前来到过故宫,当时拍了一组照片,当时她对故宫各个建筑的构造特别入迷,回去之后还画了一张图纸,将每个宫殿的位置都标注上了,对故宫里面很多文物都有所了解,可以说是这方面的小专家。 而我听说她要去故宫的时候,我说了一句特别特别没脑子的话,我把这个归结于我的见识短浅。因为我对故宫相关的东西并不感兴趣,很多东西都不懂。对于我来说,偌大一个故宫就像一个特别无聊的地方,当时还是夏天,天气特别热,阳光特别晒,我当时就说了一句”故宫就是一堆破庙,有什么好看的“ 现在的我,打出这句话的时候都是无比羞耻的。这里就暴露了很多东西。首先常识性的错误,故宫之前是皇宫,和庙一点关系都没有。虽然我这么说是一句气话。 但是我没有想到的是,不喜欢去故宫,因为我对它不了解,不感兴趣。但是,这可是我女朋友想去的地方啊,而且这是她到北京之后第一个想去的地方,而且从她的状态可以看出她真的想去,而可悲的是我想的是我愿不愿意去,这就是问题的所在了。我还是考虑自身的感受而对自己的女朋友一点也不关心,即使我对故宫真的一点兴趣都没有,也应该陪她去看一看。 其实通过去故宫游览,我还是学到了不少知识,比如故宫里面为什么不种树,一方面是防刺客,另一方面树是木,皇宫是一个框,皇宫里面种树就是框加一个木就是”困“,皇帝怎么能困呢,所以不能种树。同时我也知道了怎么分辨石狮子的性别,爪子下是球的是公狮子,爪子下是小狮子的是母狮子。还有每个宫殿的屋檐上的神兽的位置以及个数代表的意义,琉璃瓦的高贵等等。可以说我真的学到了不少东西。而当时我那么一说直接拉开了我们之间的距离。 第二件事是出门吃饭的事情,她喜欢吃赛百味,但是我不喜欢吃,不太合我的胃口。所以听她说要吃赛百味的时候我直接把我的想法吐露出来。”卖那么贵的东西却难么难吃,不知道去吃的人怎么想的“。这个在我现在看来简直是爆炸式发言。还是同样的道理,根本没有考虑他人的感受。既然她说想吃,那么她一定很想吃,我不喜欢吃可以,但不能因为自己不喜欢吃而说出话来贬低她所喜欢的东西。这非常不给对方着想。 第三件事是晚上回去的时候和她辩论昆虫的向光性。她提出黑暗是一种物质,而有些昆虫喜欢黑暗这种物质。而在我看来黑暗是虚无,光才是“物质”,只有喜欢光这种”物质“的说法而并没有喜欢黑暗物质这种说法而和她吵得不可开交,最后以我强行结束话题结束。现在想来,自己的观点也不一定正确,不能以自己学过的东西和自己的认知作为绝对真理而排斥其他一切观点,这也是我性格的一个缺陷。 异地过程中还发生过一个不愉快的事情。因为我们是用一个高中,在排球活动月的时候我当过裁判,当时正好吹他们班的比赛,最后他们班获得了胜利。在聊天的时候她提到了这个事情,感谢我吹罚的时候偏向他们。但是我记忆中确实没有偏向任何一方,当时就自认清高,觉得她污蔑我。其实现在看来很不理智,毕竟她这么聊是为了增加我们之间的认同感和熟悉程度,而我仅仅认为是她是在污蔑我,出发点都没有找对,眼里进不了沙,真的是非常幼稚。 如果说之前这些事还算在接受范围内的话,那之后的事情真的是太过分了,我现在回想起都感觉心寒,可以说这是我一生的遗憾。 01在学期末尾的时候,找了一个北京的实习,为的就是和我在一起,结束异地生活。她过来当天正好是她的生日,可以说她真的特别期待这个生日和我重逢。然而我在那天做出的事情让她感到心寒。那天我去北京西站去接她,结果我走得不太早,在公交车上堵车了。这时候我真的是非常焦躁,为了方便尽快见面,我让她多坐几站车,但是她表示她行李很重不方便过来。 这时候我表现地非常毛躁,我只考虑了自己接她怎么怎么麻烦,但是丝毫没有考虑到她提着一个非常重的箱子,刚到北京,急需要人来接她,而我并没有安慰她,而居然向她表示了我的不满。这又是一个只考虑自己的一个例子,我现在想来真的是特别糟糕,丝毫不考虑他人感受,只考虑自己,太糟糕了。 然而更糟糕的事情还在后面。因为她的行李箱实在太重,我强行拖拽,在中途不小心把她的箱子的轮子弄坏了,导致我们留在路边,最后只能靠打车把箱子运回去,不得不说让她的生日过得很狼狈。然后更加让我现在感到羞愧的事情发生了,好不容易解决完了这些事情,最后我们来到了餐厅点餐庆祝她的生日。在她点菜的时候我居然让她省着点点菜。我的天,现在想起这件事情我真的是无地自容。在经历一天磨难后,不仅没关心她还让她省着点,我究竟是有多么一毛不拔?还真的是为她过生日吗?怎么这么糟心呢? 说来十分惭愧,之后我们还因为钱的事情真的是争执过很多次。现在想来全是我的问题。比如帮她充个公交卡不愿意啦,出去吃饭不想请啊之类的,发生过很多次,真的是感慨自己的自私自利。 反过来看她,在得知我们暑假没有空调后,主动邀请我过来住,别中暑,出去吃饭都是点最便宜的帮我省钱等等,可以说她为我考虑非常非常多,包括主动看我推荐的番剧,而我很少看她推荐的电影,等等。我们的付出根本就不平等。还记得她曾经说过,在外面,她是一个坚强的女子,而只有我才知道她的脆弱。然而可悲的是事实上我不仅不知道她的脆弱还雪上加霜。真的是令人唏嘘不已。 终于这些后果在一次事件之后彻底显露了。那是7月的时候,房东突然要涨房租了。她作为一个实习生每天只有100元的工资,即使全勤,也只有2200的工资,一个月的房租都2600。也就是说,她来北京的每一个月房租都交不起,在这种情况下,还要周末和我一起出去玩,一起吃吃吃,一起花销。加上交通费,一个月怎么也要4000左右。在这么困难的情况下,一向什么事都坚强扛的她第一次,也是最后一次示弱,让我一起分担房租。然而,我却拒绝了这个要求。 我居然,拒绝了,这个要求。 一个女孩,为了事业梦醒和喜爱的人,独自一人,远赴北京,受到这样的接待,生活在没有男朋友爱护的环境。独自一人,承受着事业和经济的压力。而我,不但没有分担任意一个,还在感情和经济上成为她的负担。面对给不起房租的她,我以一句”你好惨哦“来回应她。真的,之后她罕见地哭了,之前我从来没有见过她哭,哭得真的是特别伤心,就在我们宿舍楼下。 最近,她也回忆起这段往事,给出的是以下的感慨”那时候我觉得,可以没有男友没有爱情,实在不行我还可以请求爸妈的援助。但比起有男友时渴望有人分担忧愁时,却只是火上浇油要好得多。“ 真的,我当时真的是一个不折不扣的混蛋,自己感觉良好,实际上已经做出了不可挽回的事情。最后她用”逃“来描述她离开北京的时候。 我真的是做出了让别人逃离我这个选择,我究竟是有多么不济。 反观我身边的一个哥们,为了女朋友放弃行政保研这个非常好的机会,选择和她一起学习韩语远赴韩国留学。我跟他聊的时候真的是两眼泪汪汪,不说比得上他人,自己连最基本的事情都没有做到,给女朋友的是无尽的伤害,这样算什么男朋友? 第三个女孩是分手后01的学妹,她的故事不多讲了,因为我从她身上看到了过去我的影子,虽然她相比于那时候的我好得多,我们之间的问题还是在于遇到问题没有合理的方法进行沟通,简单说就是她遇到问题一直逃避,从来不肯正面面对问题,不想怎么解决问题,真的是感觉和她未来无望,选择了分手。 总体来说,我大学的恋爱经历是非常失败的,可以说是一无是处,把以自我为中心自私自利的弱点毫无保留地暴露了出来,给曾经的女朋友带来了无休无止的痛苦。 真的是让人唏嘘不已,这么多年来,自己替他人考虑这一点丝毫没有长进。我现在要做一个大的转变,思考问题的重心需要从自己落到他人身上,真的是什么事情都为他人考虑一点,别老想到自己。这可以说是这些感情经历带给我的收获,让我将来有所长进。 可惜的是过去造成的伤害我已经没有办法弥补,甚至前段时间01的生日我连写一个生日快乐都做不到。这真的是血的教训,真的是给我留下了足够深刻的印象。 不过也不能因此止步不前,我坚信我一定会有较大的改变去迎接新的人生,去认识新的人,给她带来快乐,而不是痛苦。

Read More