性能优化实例

偶然发现老板在使用公司内部的OA系统时,任务管理界面加载速度非常慢,经常刷新之后过个几秒才能展示完所有数据。作为产品经理,保证用户的体验是非常重要的,在经过与老板的沟通后,决定优化这个地方。

前端页面优化

幸好还有点前端开发的底子,面对性能优化这个问题也没有太犯怵。使用Chrome的Developer Tools可以轻松搞清楚哪些地方需要优化。
1. 减少http请求
减少http请求能够减少多次TCP连接占用的时间、多次DNS解析/缓存查找占用的时间。

  1. 使用缓存
    可以使用CDN缓存或浏览器缓存,将css、js、img等静态文件,可以参考浏览器与cdn缓存

  2. 压缩html、css、js
    现在的前端开发环境,一般都会设置生成build包时,合并压缩js、css文件,所以影响较小。

  3. 按需加载
    通过使用Developer Tools发现,现在的OA系统确实存在同时加载内容过多导致加载时间过长的问题,下一步会放懒加载或者将过多的内容用多个tab去呈现的方式优化

  4. 代码优化
    代码优化相对麻烦些,像那些导致页面重绘重排的代码,比如盒模型样式改变等,这些基本都是不可避免的,只能在满足需求的情况向尽量减少类似定位属性浮动属性等的使用

后端性能优化

  1. 网页开启gzip
    网页开启gzip压缩后,能够大大减少数据的大小,从而显著提高网站访问速度。网站是否开启gzip可以通过一个在线小工具查看网页GZIP压缩检测,也可以通过Chrome浏览器的Developer Tools查看。
    开启gzip
    在Response Headers中,如果有Contnet-Encoding:gzip,则说明后端开启了gzip

  2. 接口延迟时间优化异步打印日志
    waiting_1
    waiting_2
    上面两张图可以看到接口的延迟比较长,除了读写db外,可能也有额外的同步io,比如大量日志打印的情况。
    因为我们的OA系统有数据记录的需求,所以需要看一下是不是日志使用同步的方式打印的,如果是的话改成异步打印。

  3. db优化
    大数据量查询db时,需要考虑orm框架本身的缓存机制,一级sql语句的写法是否合理。

总结

以上就是关于公司内部OA系统性能优化方面的小总结

To C产品的会员等级积分体系(二)

电商平台会员及积分体系概况

电商平台特别依赖用户粘性,当用户从A平台购买某件商品后,一般不会再从不同平台购买相同商品,所以如何让用户在有购买需求的第一时间想起某个平台就显得至关重要。下面就调研分析一下电商平台的产品是如何设计其积分体系的:

分类

电商平台会员等级积分体系一般可以分为以下2种:
1. 成长值与积分并用的产品,如:京东(京享值 + 京豆)、淘宝(淘气值 + 天猫积分);
2. 只有成长值的产品,如:小红书。

电商平台的会员体系可以分为以下3种:
1. 只有付费会员,如:亚马逊;
2. 只有普通会员,如:国美、美丽说;
3. 普通会员与付费会员并存,如:京东、苏宁;
4. 普通会员与定向邀约会员并存,如:淘宝;

下面就整理一下各大电商平台的会员等级积分体系,暂不包括等级阈值的设定原因。


一、淘宝&天猫

天猫源自淘宝商城,会员体系使用淘宝的数据,额外增加了天猫积分。这里就将淘宝与天猫合并一起分析。

继续阅读To C产品的会员等级积分体系(二)

To C产品的会员等级积分体系(一)

大部分To C的产品为了增强用户粘性,都会有自己会员积分体系,所以在设计一款To C的App之前,必须要了解自己的产品适合使用什么样的用户等级积分体系。

用户等级积分关系

先放上一张图,下图是我总结的用户等级与积分关系图

用户等级积分体系

先解释一下这张图:
用户的行为决定了用户获得的成长值,成长值决定了用户所在的等级,不同的等级又决定了用户能够享有什么样的权益;
用户的行为决定了用户获得的积分,用户的积分决定了用户能够获得什么样的回报;
用户等级积分体系以层级分明的固化权益反过来驱动用户通过特定行为积累成长值提高等级,以积分来作为其它回报的兑换条件,驱动用户通过特定行为获取。

用户等级积分体系中的关键元素

如上图所示,整个用户等级积分体系中都避不开行为、成长值、等级、权益、积分、回报这六个元素:
– 行为:用户使用产品的行为,在这里特指能够获取成长值或积分的行为,如:完善资料、分享、签到、在线时长等;
– 成长值:用户等级体系中衡量等级的标准,如:淘宝的淘气值、京东的京享值等;
– 等级:用户在该产品中的会员等级,这里指的不是网络游戏中的角色等级,而是指该账号的会员等级。如:苏宁的新人、V1、V2、V3、V4等级,QQ的VIP等级;
– 权益:用户能够享受的奖励,能否享有权益严格按照用户的等级去衡量,即不同等级享有不同的权益。通常等级越高享有的权益越优;
– 积分:用户积累的用于兑换奖励的东西;
– 回报:积分兑换的奖励,与权益不同,积分对所有用户都是一样的,不会出现有等级限制的问题。如:信用卡的积分换购等。

行为与成长值/积分的关系

在这里,可以将行为粗略的分为两种:会对成长值/积分造成影响的行为和其它行为。这里拿淘宝&天猫举个栗子:
淘宝的淘气值(成长值)用来确定用户等级,是基于用户近12个月的行为计算出来的,包含购物行为、互动行为、基础信誉等。也就是说如果用户想让自己的淘气值变高,需要用户多进行购物、写评价、分享等行为;
淘宝的天猫积分是用以兑换积分权益,最终影响用户所受到的回报,跟等级没有关系。

成长值与等级的关系

等级升降的界定通常非常明确,成长值总额决定会员级别。为了能够保持用户活跃度,成长值通常还会有过期扣除或者按一定周期重新计算的处理。这里拿顺丰举个栗子:
顺丰达到金牌会员所需的成长值要 ≥ 1000,如果 200 < 成长值 < 1000,会员等级为银牌会员。而成长值的多少需要按照一个自然年内会员的实际消费金额决定的,如果没有达到所在等级的成长值要求,还会根据该自然年的实际获得成长值的多少重新划定等级。
至于成长值的门槛,则需要通过分析产品的用户群体,找到留存节点后,再进行计算得出。

等级与权益的关系

不同的等级所对应的权益也不同,一般会根据用户生命周期的类型分别给到不同的权益。
低等级用户通常作为平台的新用户,这时给到的权益一般是通过某些如满减券这样比较粗暴的方式给到;中等级用户作为平台的健康用户/待留存用户,获得的权益通常会比较丰富如极速退款、生日权益等;高等级用户作为平台的粘性用户,获得的权益通常较稀缺且能够彰显身份,如客服专线等。除此之外还有僵尸用户,这类用户在长期不使用产品后一般会享有一定的权益来刺激用户重新使用该产品。

积分与回报的关系

积分与回报通常会存在一定的兑换比例,这点在传统行业中比较明显,如航空公司的历程可以兑换机票,住酒店的积分可以兑换免费住宿等。积分与回报的设定一般还会按照不同的业务类型、不同的毛利去进行不同的设定。
积分按照能否在支付过程中使用分为金本位的硬通货积分和权益积分:
金本位硬通货积分如京豆、天猫积分,这类积分可以在支付过程中当做现金去使用,通常存在一定的兑换比例。平台类产品可以使用此类型的积分;
权益积分通常可以兑换成本不高的消费券类商品,在给用户提供回报的同时,也给其它产品做了引流的操作。

产品分类

接下来的几篇文章会按照产品的功能及本人对产品的使用习惯将要调研的产品分为几大类去调研分析:
1. 电商购物类:淘宝&天猫、京东、小红书、苏宁易购;
2. 网络视频类:爱奇艺、腾讯视频、优酷视频;
3. 短视频微视频类:抖音短视频、火山小视频、快手;
4. 网络社交类:微信、QQ、微博;
5. 生活服务类:支付宝、大众点评、汽车之家;
6. 出行类:百度地图、滴滴出行、去哪儿;

项目投资相关工具

最近公司有开发投资辅助工具的需求,再更投资部门的同事沟通需求后发现,项目投资的流程基本可以抽象为三步:1.投资经理搜集数据;2.投资经理产出报告(预审报告、立项报告等);3.上级主管决策。
投资经理们为了产生一篇篇的报告,需要使用文字/语言记录及数据搜寻的相关工具。下面整理一下跟同事们沟通及网上搜集到的工具。

金融数据终端

这个是最常用也是功能最强大的工具,可以查到股票、债券、基金、衍生品、指数、宏观行业等各类金融市场数据,通常需要付费并且安装到电脑上使用。常用的有:
1. Wind咨询金融终端
2. 慧博智能策略终端
3. Choice金融终端

企业数据

企业数据一般用于查询企业基本信息、历史沿革、实际控制人及其企业等信息。常用的有:
1. 天眼查
2. 企查查

市场

  1. 融资融券明细;
  2. 中登股票账户统计;
  3. 最新基金重仓股排行榜;
  4. 公募基金收益率排名;
  5. 新浪条件选股;
  6. 港股数据;
  7. 港股上市公司融资额查询;
  8. 美股明星股数据;

App数据、口碑等

查询互联网相关企业时,可以使用百度指数、App下载量等工具进行竞品数据的对比。
1. 百度指数;
2. 艾瑞App指数;
3. 艾瑞PC指数;
4. 应用雷达ios总榜;

法律法规

  1. 春晖投行在线

细分行业

新能源汽车

  1. 高工锂电;
  2. 动力电池网;
  3. 中国电动汽车网;

linux部署Jupyter notebook

一、安装python环境

1.下载python3

wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz

2.解压文件

tar -xvf Python-3.6.0.tgz

3.创建安装文件的路径

mkdir /usr/local/python3

4.编译

./configure --prefix=/usr/local/python3

5.安装

make
make install

二、安装配置Jupyter

1.安装Jupyter notebook

pip install jupyter notebook

继续阅读linux部署Jupyter notebook

好产品靠设计

人靠衣装,佛靠金装。在现在的快节奏的生活环境中,如果一个产品没有一眼打动用户的能力,那么可能会损失很多用户。

设计的重要性

“好的设计是将我们与竞争对手区分开的最重要方法。”

三星电子首席执行官尹钟龙先生用三星的成功证明,好的设计对一个产品的重要性。

在刚做产品的时候,因为公司没有UI,所以在原型制作的时候就需要一并把样式都考虑进去。虽然也能凑合的满足需求,但是产品看起来确实就比较普通。

设计VS技术

我们已经清楚的认识到了设计的重要性,但是对于一款好的产品来说,设计与服务都是非常重要,缺一不可的。但是如果非要选出一个最重要的,该如何选择呢?

小作坊与大品牌都能生产皮带,可为什么小作坊生产的皮带与大品牌的生产的皮带售价差距那么大呢?生产皮带可以说没什么技术难度,品控没问题的话,质量也应该差不多。造成售价差异巨大的是好的设计。这也与尹钟龙先生的观点不谋而合。

好产品靠设计

产品被创造出来是为公司盈利服务的。好的产品不仅可以给公司带来更多的收入,还能提高品牌的知名度。今后在产品经理的生涯中,除了本职工作以外,还要注意多与视觉设计师、交互设计师沟通,努力做出深受用户喜爱的、富有创意的产品!

最后摘录一段 Marty Cagan 在《启示录》中对设计重要性的阐述:

我从不认为富有创意的产品来自偶然。成功的产品都遵循一定的规律:
1. 产品经理的任务是探索产品的价值、可用性、可行性;
2. 探索(定义)产品需要产品经理、交互设计师、软件架构师通力合作;
3. 开发人员不擅长用户体验设计,因为开发人员脑子里想的是实现模型,而用户看重的是产品的概念模型;
4. 用户体验设计就是交互设计、视觉设计(对硬件设备来说,则是工业设计);
5. 功能(产品需求)和用户体验设计密不可分;
6. 产品创意必须尽早地、反复地接受目标用户的试用,以便获取有效的用户体验;
7. 为了验证产品的价值和可用性,必须尽早地、反复地请目标用户测试产品创意;
8. 采用高保真的产品原型是全体团队成员了解用户需求和用户体验最有效的途径;
9. 产品经理的目标是在最短的时间内把握复杂的市场/用户需求,确定产品的基本要求——价值、可用性、可行性;
10. 一旦认定产品符合以上基本要求,它就是一个完整的概念,去掉任何因素,都不可能达到预期的结果。

基于Vuejs、vue-router、iview的管理后台

上一篇分享的是使用angularjs及materialdesign的管理后台前端页面的实现。因为感觉input的输入框实在是太丑了,并且用户端的页面我早已开发完毕,就等着后端给接口了,所以就萌生出了使用心中一直念念不忘的Vuejs来实现一版管理后台。

这个使用了Vue-cli来构建vue架构,使用vue-router来搭建路由,使用iview来作为UI框架。

项目构建

  1. 安装nodejs
    nodejs安装及常用快捷键
    建议安装nvm来控制nodejs的版本,上面的链接里有详细的步骤,包括启动node服务常用的PM2等等…
  2. 全局安装webpack、vue-cli脚手架
    npm install -g webpack vue-cli;

  3. 创建工程
    vue init webpack-simple <project name>
    这里直接一路回车就可以了。
    注意:工程名不能是中文

  4. 安装项目依赖
    进入工程,输入npm install就可以了。

  5. 安装路由模块vue-router、网络请求模块vue-resource模块
    npm install --save vue-router vue-resource
    这个会安装到package.jsondependencies中。

  6. 安装UI框架 iView
    npm install --save iview

  7. 使用iView、vue-router、vue-resource

# main.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueResource from 'vue-resource';
import iview from 'iview';
import 'iview/dist/styles/iview.css';
import App from './App.vue';

Vue.config.debug = true;
Vue.use(VueRouter);
Vue.use(VueResource);
Vue.use(iview);
  1. 修改webpack.config.js配置文件
    因为使用iView需要引用其css,但是最初的webpack.config.js文件里面并没有配置读取css文件和字体文件或者有的设定的有问题,所以这里需要我们自行修改一下:
{
    test: /\.js$/,
    loader: 'babel-loader',
    exclude: /node_modules/
 },
 {
    test: /\.css$/,
    loader: "style-loader!css-loader"
 },
 {
    test: /\.(eot|woff|woff2|ttf)([\\?]?.*)$/,
    loader: "file-loader"
 },
 {
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'file-loader',
    options: {
      name: '[name].[ext]?[hash]'
    }
 }

继续阅读基于Vuejs、vue-router、iview的管理后台

基于angularjs与materialDesign的管理后台

最近换了个公司,乱七八糟的事儿比较多,更新耽误了些…
之前还看过一段时间的Vuejs,想着新公司如果做项目的话,用Vuejs去做。然而新公司的技术架构pc端更倾向于Angularjs,而且有专门的前端架构,架子什么的都搭的差不多了,所以来到新公司的第一个项目就用Angularjs做的。

通过这个项目,我对eslint、gitignore、proxy也算有了接触。毕竟以前做项目的时候也没关注过这些。这个项目是用gulp跟webpack配合使用的。也是直接用的旧有的架子,没做什么改动。
现在就说一下用Angularjs做的这个项目吧。

目录结构

  • app: 源代码
    • views: 一个页面一个文件夹
    • *.ctrl Controller
    • *.html Controller对应的页面
    • *.scss Controller对应页面的样式表
    • services: 服务,一个页面可能有多个服务,与页面一一对应
    • filters: 过滤器
    • app.js: 项目入口文件
    • index.html: index
  • dest: 编译后代码存放的位置,源码中不存在
  • .eslintrc.json: 语法检查
  • .gitignore: git忽略目录
  • .babelrc: babel编译配置
  • … 其他配置

入口文件 app.js

这个项目使用了angular-resource、angular-material等框架,因为Angularjs是依赖注入的,所以所有依赖的框架、插件甚至服务过滤器等等都需要import进来,并且注入到angular程序中。

Dependencies 项目依赖

import angular from 'angular';
import 'angular-material';
import 'angular-resource';
...

View 项目页面,包括其中的Controller及template等

import { Template, Controller } from './views/page1/page1.ctrl';
...

Service 服务

import IndexMenuResource from './services/index-menu.resource'

主程序,所有注入的内容都会在这里体现。

angular.module('app', [
    'ngResource', 'ngRoute', 'ngMaterial' ...
])
.controller('controller', Controller)
.service('IndexMenuResource', IndexMenuResource)
.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/', {
        template: Template,
        controller: Controller,
        controllerAs: 'ctrl'
        }) ...
    }]);

在这里,我每一个只写了一个,需要说明一点,使用controllerAs,在其他页面的对应的controller中,就可以不用$scope,转而使用this就可以了。然后需要在controller对应的template中,每一个变量前面都加上controllerAs的内容,也就是上文中的ctrl
这个地方也可以将路由模块抽离出来,但是,看着这个文件也不大,就放在这里吧,维护起来也还算好维护。

登录模块

登录模块是最后加的,原型图中也没有这个,但是感觉作为一个后台管理系统,如果没有这个模块,岂不是所有人知道网址就能随意修改内容了么…于是跟后端同事商量了一下,就把这个做上了。因为时间比较紧,项目经理就直接说,暂不需要权限管理,注册之类的,直接把这个东西在后台写死就好。但是后端同学还是为user单独建了张表,把权限也留出口来,以后如果有需要直接加上就好了。

继续阅读基于angularjs与materialDesign的管理后台

vue匹配模板简单实现

之前在学习新框架的时候,只是简单的跟着教程一步一步的敲demo,从来没有想过这个框架是如何实现的,简单的功能我能不能实现。最近打算开始学习Vue,觉得还是尝试一下能不能自己简单的实现其中的某些功能吧。

感觉模板引擎后端同学用的多一些,像jade、FreeMarker等,这个通常是在客户端发出HTTP请求之后,应用层的控制器(Controller)接收,通过应用层的服务器(service)访问数据库,然后封装到模型层(Model),再跳转到视图层(View),通过模板引擎生成HTML代码,最后返回给客户端浏览器。

但是Vuejs也是有模板的,他的模板引擎是在客户端浏览器的内存中处理生成的。所以我们用模板的时候,显示出来的是要渲染的数据。

Vue官网的第一个demo

<div id="app">
    {{ message }}
</div>
var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue!'
    }
});

看到这个部分,感觉应该可以通过正则来简单的实现一下:
html部分不变。

正则部分:

首先想到的是匹配两边的大括号,/\{\{\}\}/,注意转译;
然后我们需要匹配大括号中间的内容,[\w]+;
这里需要注意一个问题,在匹配的内容前后可能会有空格,所以这里内容部分我们需要修改成\s*[\w]+\s*;
但是如果需要替换的话,使用正则的分组用法$1会比较轻松的解决这个问题;
所以正则的最终版就变成了/(\{\{\s*([\w]+)\s*\}\})/g;

js部分:
function vue(options) {
    var context = document.querySelector(options.el);
    var html = context.innerHTML;
    var regex = /(\{\{\s*([\w]+)\s*\}\})/g;
    while(regex.test(html)) {
        html = html.replace(RegExp.$1, options.data[RegExp.$2]);
    }
    context.innerHTML = html;
}

$1是最先匹配到的{% raw %}{{}}{% endraw %}
包括其内的部分,$2是匹配的要替换的对象。
使用方法跟vue的使用方法一样。
这样第一个demo我们就简单的实现了。当然相比vue有非常多的缺陷,比如不可以通过app.message来修改数据…

继续阅读vue匹配模板简单实现

前端构建工具的简易实现

虽然说之前用过gulp、webpack这种前端构建工具,但是自己也只是单纯的使用,从来没想过这些东西到底是怎样实现的。最近看了一节公开课,觉得很受启发,现在就说一下我的这个前端构建工具是如何实现的。

功能

开发环境

zst dev 实现保存代码自动刷新的功能。

生产环境

zst dist 这个实现的功能较多:
1. 自动生成dist目录
2. 合并压缩js、css添加时间戳输出到dist目录
3. 将favicon.ico文件复制到dist目录
4. 处理index.html文件,删除注释
5. 如果开发的是移动端页面,还会在index.html中插入移动端meta及处理rem的代码

用到的模块:

express、watch、command、fs、path、uglifyjs、uglifycss、cheerio、open、package、http、socket.io

开发

config

我们需要一个config对象来作为项目的配置

const config = {
    server: {
        ip: "http://localhost",
        port: 3000
    },
    input: "./src",
    output: "./dist",
    info: "INFO  ",
    isPhone: true, //是否是手机
};

server字段设置的是服务的ip跟端口,input字段是输入路径,output字段是输出路径,info字段是打印log的日志头,isPhone字段是是否是移动端。

开发环境

  1. 首先,我们需要创建一个http服务在3000端口,这里我们使用express这个框架来实现。
var app = require('express');
var server = require('http').createServer(app);
server.listen(config.server.port, function (req, res) {
    log('server start at '+config.server.ip+":"+config.server.port);
});
app.get('/', function (req, res) {
    res.send(getHtml(config.isPhone,config.input));
});

function getHtml(isPhone,path) {
    var path = path || './src';
    var devHtml = fs.readFileSync(path +'/index.html', 'utf-8') + fs.readFileSync('./socket.xml');
    if(isPhone) return fs.readFileSync('./isphone.xml') + devHtml;
    else return devHtml;
}

在这段代码中,server.listen是在config.server.port端口开启一个服务器,cb会在服务开启后执行。
app.get()是express的路由,当客户端访问到/时,返回给客户端什么内容。
getHtml方法最终返回的是加上处理自动刷新代码的index.html。
如果要开启服务器后自动打开客户端,可以使用open插件:

var open = require('open'); //自动打开浏览器
server.listen(config.server.port, function (req, res) {
    log('server start at '+config.server.ip+":"+config.server.port);
    open(config.server.ip+":"+config.server.port);
});
  1. 使用socket.io及watch
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
    watch.watchTree(config.input, function (f, curr, prev) {
        if (typeof f == "object" && prev === null && curr === null) {
            // Finished walking the tree
        } else { // f was changed
            socket.emit('file-change');
        }
    });
    log('client connected');
});

socket.io是websocket长链接通信的封装,之前在egret游戏中使用了websocket通信,但是没有使用socket.io。
这个就是当socket连接后,执行的操作,也就是使用watch插件进行文件的监控。使用方式都有注释,也可以从github上进行搜索。最后一个else中,socket.emit是自定义的事件名,文件改变。

继续阅读前端构建工具的简易实现