对团队成员的放权

###讨论背景
团队刚刚来了两个新人,加上之前的一个工作一年的员工总共四个人;新的项目刚下来,时间比较紧,新人对于业务都不甚熟悉,而且我对于新人能力也不甚熟悉。

####项目开始阶段:把控所有细节

项目在一开始的时候会有很多方向性的事情和细节节点需要确定,而这个时候新人还都处于了解项目的阶段,而老员工因为个人原因对于项目也很难有整体把控的能力;所以这个阶段,需要团队的负责人来确定所有细节,包括对团队成员的分工。当团队成熟之后,可以根据成员的兴趣,个人能力和性格等特点分配不同的功能,但是以现在的团队而言还是需要负责人将功能拆分之后细分至每一个人。

#####问题
这个阶段因为对于团队和细节控制的太细,而且因为抓紧时间,因此不能让每个成员都能像自己一样熟练掌握项目细节。所以在实施之后,成员对功能点的不熟悉导致需要大量的沟通时间,而且决策都集中在一个人,会导致决策者称为项目推进的瓶颈,而且作为决策者会变的很疲惫,对项目进展也不利。

#####收获
这个阶段是对项目整体把握最好阶段,因为这个阶段会决定项目对基本架构和大致方向,也是非常锻炼项目架构能力的时候。

项目中期:逐渐放权

随着项目的推进,团队成员渐渐进入开发状态,对项目对了解也逐渐深入;由于他们是承担开发任务最多的,所以他们应该对项目的各个细节甚至节点有一定的决定权;如果团队成员每个人工作都很积极,而且能力也很好,那调动他们的主动性将对项目的推动有极大的意义。

怎样才能最大程度的调动成员的主动性?通过这次项目的实施发现,给予他们足够的自主权是很有效的做法。自主权也就意味着决策权,他们可以决定项目实施的细节该怎样实现,采用何种方式能够最快的交付功能。这时候负责各个功能的人,才能真正感觉到这个功能是自己的。拥有权利,再要求他们负担起对应的义务也就理所当然——义务与权利是对等的。

#####问题
将项目实施放权给成员之后,也需要承担相应的风险,因为一旦因为开发经验等问题而导致项目跑偏,最后吃亏对是团队所有对人。所以这个时候项目负责人可以相应监督项目实施,听取他们对决策之后,如果发现存在一定对风险,需要及时提出来,并讨论一个可行的解决方案。但是整个讨论过程,项目负责人更多是一个引导者或者是问题的提出者,具体的解决应该由实施项目的人来提出并大家讨论的。

#####收获
当每个人都有自主权之后,决策就会细化到每个人,也就去除了决策的中心化,项目进展就不会被处于中心的人左右了。这样项目负责人可以抽出足够的时间来把控项目整体,能更全面的考虑项目中存在的问题,从另外更全面的视角来思考项目,与项目成员的思路形成互补。
另外,项目成员切实承担了项目压力,对个人成长也有很多的帮助。

###决策中心化还是分散化?

这个案例就引申出一个问题:项目决策(推进)是应该中心化模式还是分散化模式?
以我个人的理解,项目的大方向、架构和项目中的风险判断应该采用中心化的决策模式,这样才能更好的统一,而且能够更快速。当然中心化的决策应该充分吸收其他人的意见,但是无论讨论如何激烈,最后拍板决定的还应该是项目的负责人。这也符合了义务和权利对等的原则。

当项目细化为task并分配到每个人的时候,决策权也应该分配到每个人,这样他们能够根据实际来作出判断,也更好的推进项目。

Clojure ns require use import

ns

ns用来定义当前的命名空间,可以配合import,require,use进行使用。

1
2
3
4
5
6
(ns foo.bar
(:refer-clojure :exclude [ancestors printf])
(:require [clojure.contrib sql sql.tests])
(:use [my.lib this that])
(:import [java.util Date Timer Random]
(java.sql Connection Statement)))

require

require 是将clojure load进入当前的命名空间,会跳过已经load进来的lib。 :require可以配合 :as, :only, :include, :exclude, :refer, :all等等一起使用。

1
2
3
4
5
6
7
;; alias clojure.java.io as iouser=> (require '[clojure.java.io :as io])nil

user=> (io/file "Filename")#<File Filename>

;; alias clojure.java.io as io using prefixesuser=> (require '(clojure.java [io :as io2])nil

user=> (io2/file "Filename")#<File Filename>

use

use与require作用类似,但除了将libs load进入之外,还使用clojure.core/refer refer他们的namespaces。可以配合:exclude, :only, and :rename使用。

1
2
3
4
5
6
(ns some.namespace
(:require [clojure.contrib.json :as json])
(:use [clojure.string :only [trim lower-case split]]
[clojure.contrib.shell-out]
[clojure.pprint]
[clojure.test]))

import

import主要用来加载java类。

1
2
3
4
5
6
7
(import & import-symbols-or-lists)
import-list => (package-symbol class-name-symbols*)
(ns foo.bar
(:import (java.util Date
Calendar)
(java.util.logging Logger
Level)))

Clojure 读写文件

Clojure读取文件

####简单读文件(slurp)

最简单的读取文件可以采用slurp,主要作用是读取文件并以字符串的形式返回文件内容,因为其将文件直接存入内存,此方法适用于小文件的读取。

1
(slurp "test.file")

在repl里面可以直接打印出字符串内容。

Clojure IO标准库

Clojure中的文件io包装在标准库clojure.java.io包中,我们用reader进行文件读取。

1
2
3
4
5
6
7
8
9
10
(ns reader.read-file
(:require [clojure.java.io :as io]))

(def testfile (io/file "E:/hack/clojure-cook/src/reader/snake.clj"))
(with-open [rdr (io/input-stream testfile)]
(loop [c (.read rdr)]
(if (not= c -1)
(do
(+ x c)
(recur (.read rdr))))))

这段代码里用到了两个重要的函数,file和reader。file函数主要获取java.io.File对象。可以使用以下方法来获取file对象。

1
2
3
4
5
6
7
8
(ns reader.file-demo
(:require [clojure.java.io :as io]))

(def f (io/file "E:/hack/clojure-cook/src/reader/snake.clj"))

(def data-f
(io/file
(io/resource "snake.clj)))

其中resource是从项目读取资源,需要在project.clj中配置资源的文件夹:

1
:resource-paths ["E:/hack/clojure-cook/src/reader"]

使用java io

除了读取本地文件,也可以读取网络信息,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
(ns reader.file-demo
(:require [clojure.java.io :as io])
(:import (java.net URL)
(java.io InputStreamReader BufferedReader)))


(defn fetch-url[address]
(with-open [stream (.openStream (URL. address))]
(let [buf (BufferedReader.
(InputStreamReader. stream))]
(apply str (line-seq buf)))))

(fetch-url "http://www.baidu.com")

##写文件

####简单写文件(split

1
2
3
4
(ns reader.file-demo
(:require [clojure.java.io :as io]))
(def out (io/file "demo.file"))
(spit out "test file")

spit是和slurp相对应的函数,只能进行极小且简单的文件处理,如果进行复杂的文件处理还需要使用Clojure IO库,或者java IO库。

Clojure IO 写文件

1
2
3
4
5
6
7
8
9
10
11
(ns reader.file-demo
(:require [clojure.java.io :as io]))


(def out (io/file "demo.file"))

(defn write-file [content]
(with-open [w (io/writer out)]
(.write w content)))

(write-file "test file second")

2015新年计划

为什么不写年终总结呢?

其实自己已经好几年没有写过新年计划了,也就没有太多想以此总结。没写计划的这几年,生活过得还算不错,年年回想起来都觉得收获不少。

今年与往年不同,结婚成家了,在上海买了房子,基本稳定在上海了,生活进入了另一个阶段,需要好好计划一下了。

一个月读一本书

不管是什么类型的书,小说可以,社科可以,甚至技术类的书籍也可以。

每天虽然很忙,但是零碎休闲的时间还算不少,每天上下班的地铁有半个小时,加上其他的时间,一天也有一个小时可以认真读书。

出去旅游一次

不算上结婚旅游,今年还要自己出去旅游一次。这些年因为经济原因,自己没有自主出去旅游过,错过了很多美好的假期。今年打算去国内的某个地方,认真看看,体验一下不同地方的生活。

###学习一门新的编程语言###

去年一直在看Clojure,但一直未能沉下心研究,也就没能深入学习。

今年,要认真学习一门新语言,可能是Clojure也可能是其他,达到熟练使用,理解原理,同时对其社区有贡献的程度。

###研究一个开源项目###

前年深入研究过Netty,收获颇丰,可惜没能有代码贡献.几年继续寻找一个优秀的开源项目研究,达到贡献代码的程度。

###工作上再升级###

不管最后职级上自己能不能升上去,但能力上要达到升级的程度,基本能够胜任大型平台的项目架构设计,同时掌握项目管理技能以及少量的人员管理。

总共五个目标,一年只有12个月,除去旅游,剩下的四个目标,平均时间也只有三个月,还是有压力的。拭目以待吧。

memcache优化使用tips

本文主要讨论了在使用memcache时的一些技巧,以及这些技巧的优缺点。

###1. 缓存的数据

缓存存储的数据按照计算(读取)的结果类型,可以分为

  1. 存储直接从数据库中读取的数据,降低对数据库的压力,可称为数据源型缓存
  2. 存储从数据库读取并且业务处理完毕的数据,也可称为 最终结果型缓存
  3. 存储进行了部分业务处理的数据,称为中间结果型缓存

数据源型缓存可以直接降低对DB的压力,扩展性高,这也是缓存应用很普遍的形式。但是如果这些数据需要经过复杂而消耗资源的处理,则数据源缓存只能解决了部分的性能问题,这个时候就可以考虑最终结果性缓存,也就是存储经过算法处理之后的结果,这样,每次程序直接根据key从缓存读取结果即可,能够节省大量的时间。

最终结果性缓存,比较适合需要消耗大量计算时间的程序,但是其缺点是缓存的扩展和可移植性很差,一旦程序的key有变化,就会导致所有的缓存都不再适合使用;而且在缓存失效的情况下,会导致程序的时间曲线出现毛刺。

中间结果性缓存,是出于数据源缓存与最终结果型缓存的一种形式,中和了两种缓存的优缺点,但是会使得缓存变多,一次计算需要读取多个缓存。

在实际使用过程中,优先使用数据源型缓存。

###2.缓存有效期设置
memcached中所有的缓存都有缓存时间,最长有效时间为72小时。

缓存有效期时间越长,其命中率也就越高,越能有效降低对DB的压力,同时提高程序的处理性能;但是其数据的有效性也就会降低,而且有效期很长的缓存导致程序bug之后,会等到很久之后才会被发现,在问题定位及debug都会造成较大的困难。

原则上在业务允许的范围内,可尽量将有效期设置更长;在使用过程中,综合程序的可测性和线上bug处理的便利性,一般缓存的有效期设置时间会小于2个小时。

###3.缓存更新策略
我们在程序中通常的方式是,首先从缓存中读取数据,如果缓存中的数据已经过期,则我们会直接向DB请求数据,并将正确的数据存储到缓存中。这种更新策略也就做被动式更新策略

在程序中需要的缓存的数据一般是更新频率很低的数据,我们可以在数据发生改变时,主动的将改变后的程序更新到缓存中,这样缓存数据的有效期就可以基本处于有效的状态中,这是主动式更新策略,可以将这种缓存的有效期设置的长些。但是这种方式会使得缓存在两处的程序中更新。

对于一些可预见的高QPS的缓存,我们可以在程序启动的时候将这部分缓存加载(缓存预热),这样就可以避免由于冷启动而对DB造成瞬时过大压力。

###4.使用热点缓存
在某些场景之下,cache的qps极高,会出现一瞬间缓存miss,导致过多的请求直接压到数据库中,造成数据库雪崩,这也叫做stampeding herd问题。我们可以采用热点缓存的方式来解决。

热点缓存在缓存失效时,总共有n个请求在读取缓存,对其中n-1个请求返回刚刚过期的缓存数据,只允许其中一个请求直接访问DB,并更新缓存。这样可以有效降低对数据库的访问数量,同时对于业务也不会造成太大的影响。

###5.批量读取缓存(multiget)
在一些情况,需要一次读取多个key的缓存。这个时候可以使用批量读取的方式一次向memcached请求多个key的缓存,这样可以有效避免循环读取单个key缓存的网络问询时间。

批量缓存导致过multiget 无底洞问题。可以考虑把multiget的key分布到一个节点上,来避免这个问题,这样就需要自己定制memcache的客户端,按一定的规则(比如:相同的前缀)把一类key分布到同一个节点上,来避免这个问题,同时这样也可以提高性能,不用在多个节点之间等待数据。

###6.缓存内容大小
multiget主要为了解决网络轮训的时间,但是网络传输时间还包括了传输数据量的大小,需要传输的数据量越大,需要的传输时间也就越大。

所以,我们在设计缓存的时候,尽量将缓存的value变小,比如采用自己序列化value的方式,这样可以有效降低网络传输时间。有效避免网络的拥堵。

另外还存在一个问题:当某个key的QPS过高,会出现短时间内网络拥堵的情况,例如value的大小为1k,而其QPS 为100000,而服务器内网的最大带宽只有1000M,这时候就会出现网络拥堵的情况。实际上,因为缓存的网络带宽是很多key共用的,所以在热点key QPS还没有达到临界点的时候,就已经超时了。

所以,缩小缓存value不仅可有效减低网络传输时间,同时也可以降低发生网络拥堵的几率。

##总结
缓存的使用有很多小技巧,但是很多技巧都有其优缺点,我们在实际的业务中根据现状选择适合自己的使用方式,才能有效提高网站的性能。

##Reference
也谈如何构建高性能服务端程序
Memcached应用总结

Spring中实现Proxy模式

Spring中使用Proxy模式很多,经典就是AOP中的代理了,但是这里想讨论的是我们在自己的代码中实现代理模式的问题。

在上一篇Spring实现factory模式中,我们需要使用Person里面的方法,但是type是程序运行的过程中才知道是多少,如果要使用Person接口需要在代码里面动态的判断到底使用哪种对象,这时候使用Factory模式就很难实现这种了。
撇开配置,我们最直接的想法是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface Handler{
int handler( int type);
}

public class Handler1 implements Handler{
int handler(int type){
return type +1;
}
}

public class Handler2 implements Handler{
int handler(int type){
return type +2;
}
}

public class TestHandler{
private Handler handler;

public void testhandler(){
int type = (int) random();
if(type == 0){
handler = new Handler1();
}else{
handler = new Handler2();
}
handler.handler(type);
}
}

这种代码使用Proxy模式是合适的。

Proxy模式[http://en.wikipedia.org/wiki/Proxy_pattern] :所谓的代理者是指一个类型可以作为其它东西的接口。
我们可以将PersonHandler 里面的person使用prox的方式来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HandlerProxy implements Handler{
int handler(int type){
int type = (int) random();
if(type == 0){
handler = new Handler1();
}else{
handler = new Handler2();
}
return handler.handler(type);
}
}

public class TestHandler{
private Handler handler = new HandlerProxy();

public void testhandler(){
int type = (int) random();

handler.handler(type);
}
}

这样在配时就可以采用如下配置

1
2
<bean id="handler1" class="Handler1" />
<bean id="handler2" class = "Handler2"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HandlerProxy implements Handler{

@Autowired
private Handler handler1
@Autowired
private Handler handler2
int handler(int type){

int type = (int) random();
if(type == 0){
return handler1.handler(type);
}else{
return handler2.handler(type);
}

}
}

Spring中实现Factory模式

Facory Pattern(工厂模式)

工厂模式分为工厂方法模式(也可以成为简单工厂)和抽象工厂模式
例如工厂方法模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
    public interface Person{
String getName();
}
public Class Male implements Person{
String getName(){
return "Male";
}
}

public Class Female implements Person{
String getName(){
return "Female";
}
}

public class PersonFacory{
public static createPerson(int type){
if(type==1){
return new Male();
}else{
return new Female();
}
}
}
```

工厂方法模式相对比较简单,主要是接受不同的参数组装不同的bean。

抽象工厂模式,参见如下代码:
```java
interface BlackPerson extends Person(){
void handler(int type);
String getColor();
}

interface YellowPerson extends Person{

String getColor();
}
public class BlackMale extends BlackPeron{
void handler(int type){
}
public String getColor(){
return "black";
}
public String getName(){
return "blackMale";
}
}

public class BlackFemale extends BlackPeron{
void handler(int type){
}
public String getColor(){
return "BlackGirl";
}
public String getName(){
return "BlackFemale";
}
}

public class YellowMale extends YellowPeron{
public String getColor(){
return "Yellow";
}
public String getName(){
return "YellowMale";
}
}

public class YellowFemale extends YellowPeron{
public String getColor(){
return "YellowGirl";
}
public String getName(){
return "YellowFemale";
}
}

abstract ColorPersonFacory{
BlackPerson createBlackPerson(int type);
YellowPerson createYellowPerson(int type);
}
class MaleFactory implements ColorPersonFacory{
BlackPerson createBlackPerson(int type){
return new BlackMale();
}

YellowPerson createYellowPerson(int type){
return new YellowMale();
}
}
```


###Srping中实现工厂模式###
Spring中其实实现的是工厂方法模式,例如需要配置一个Male:

```xml
<bean id="male" class="PersonFacory"
factory-method="createPerson">
<constructor-arg value="1" />
<property></property>
</bean>

其中constructor-arg的参数即是createPerson的参数,新建出bean之后,通过property对bean做属性设置。
Spring中虽然是一种工厂方法模式,但是通过配置也同样可以支持抽象工厂模式,例如通过工厂模式获取BlackMale,可以如下配置

1
2
3
4
5
<bean id="blackMale" class="BlackPersonFacory" 
factory-method="createPerson">

<constructor-arg value="1" />
<property></property>
</bean>

Spring管理junit transaction

OTP

我们在日常的开发中,很大部分ut 都是测试数据库的增删改查功能,重复的ut常常因为数据库不一致而失败,例如第一次测试的时候刚刚插入了一条数据,再运行同样的ut因为数据重复就会失败。因此最好的测试方式是自己mock数据。每次ut在结束的时候都将数据库状态恢复到初始状态。这里就介绍一种spring使用transaction实现的方式。

  1. 在spring配置文件中配置数据库事务管理:
    1
    2
    3
    4
    <tx:annotation-driven transaction-manager="txManager"/>
    <bean id="txManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="testDataSource" />
    </bean>

其中dataSource是需要纳入管理的数据库的数据源配置。

  1. 所有的测试类都需要继承AbstractTransactionalJUnit4SpringContextTests类。

  2. UT类中设置数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Autowired
    @Qualifier("dpMainDataSource")
    private DataSource dpMainDataSource;
    @Override
    public void setDataSource(DataSource dataSource) {
    this.simpleJdbcTemplate = new SimpleJdbcTemplate(dpMainDataSource);
    }
    ```

    4. 配置标签类
    以上的配置完成之后,该UT所有的@Test方法执行结果都会RollBack,
    如果有特殊需求可以设置@Rollback(false)
    ```java
    @TransactionConfiguration(defaultRollback = true,transactionManager="txManager")

5、下面给出一个示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:/config/spring/common/appcontext-*.xml",
"classpath*:/config/spring/local/appcontext-*.xml",
"classpath*:/config/spring/membercard/appcontext-*.xml" })
@TransactionConfiguration(defaultRollback = true, transactionManager = "txManager")
public class MemberCardUserProfileDemotionServiceObjectTest extends
AbstractTransactionalJUnit4SpringContextTests{

@Autowired
@Qualifier("dpMainDataSource")
private DataSource dpMainDataSource;

@Autowired
private MemberCardUserProfileDemotionService memberCardUserProfileDemotionService;

@Autowired
private MemberCardUserDao memberCardUserDao;

@Override
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dpMainDataSource);
}

@Test
public void testOne(){
//to test in here
}

@Test
@Rollback(false)
public void testTwo(){
// to test in here

}
}

###总结
采用这种方式做ut,无论代码是在开发环境还是qa测试环境,只要每次代码修改只需要ut测试就可以看出代码是否正确,再也不会因为数据库的问题导致ut结果变化。

银行里的动态密码

OTP

最近用了中国银行的网银,虽然整体体验很烂,但是动态密码还是很有趣,细思量下来,里面有很多门道。

动态密码(OTP)

动态密码是一种实时变化的密码,银行里所用的是同步的密码技术,其同步方案又分为基于时间的同步方案和基于事件的同步方案。基于事件的密码生成需要两个基本输入:原定的密码种子和用户的特定事件(如登录,或者某个生成密码的操作等)。在用户的生成密码之后,服务器这一端会根据相同的事件和密码种子生成密码,来验证用户的输入是否正确。但是基于事件的密码是需要与服务器交互的,使用起来并不方便。在这里就不多介绍了。

###基于时间同步的动态密码。
现在中行的密码动态口令卡就是这种技术,简单方便,不需要与服务器有交互,看起来是一个很神奇的东西。
基于时间同步的动态密码,原理上类似于一次性密码本ONP(one-time pad ),根据时间变化来生成不同的密码。在银行的使用过程中会发给用户一个专门硬件,这个硬件采用与服务器相同的密码生成算法,根据时间的变化生成密码。当需要用户输入一次性密码时,服务器会查询此时应该生成的密码,从而将用户输入密码与服务器密码相比对。基于时间同步的动态密码技术,其中一个关键在于用户的硬件与服务器的时间必须保持一致,否则便会出现所生成的密码不一致的问题。目前时间同步令牌一般通过增大偏移量的技术( 前后10mins) 来进行远程同步, 确保其能够继续使用, 降低对应用的影响; 但对于超出默认的时间同步令牌( 共20mins) , 将无法继续使用或进行远程同步, 必须送回服务器端另行处理。

###相关实现
目前关于时间同步的的算法已经有OATH的TOTP规范也有相关的开源项目实现。

图片编辑的web端解决

最近项目里面需要给用户提供一个图片裁剪和压缩的功能,这些图片是要推送到手机端,为了不过多浪费用户的流量,所以必须要压缩到某个阈值范围内。由于网站面向的用户都是偏低端的用户,而且他们绝大多数都是实用ie7,8的,所以不能使用html5来实现了。所以目前可用的方案只有两个:

  • 前端使用flash程序编辑图片后提交至后台保存;
  • 或 后端接收原始图片,在服务器端对图片进行裁剪和压缩等编辑操作。

经过调研,如果前端使用flash程序处理图片可以使用第三方的api(如美图秀秀apihttp://open.web.meitu.com/)或者前端重新开发一个图片处理的flash插件。后端处理图片可能的方案是在服务器端开发程序做图片处理,或者接入点评的图片中心以实现编辑的功能。

###面临的问题

  1. 首先,前端使用美图秀秀api可以满足用户图片编辑的需求,但是存在跨域问题和UI不一致的问题。因为美图秀秀api需要调用美图的服务器,所以需要在服务器端配置跨域,但跨域存在安全性和稳定性的风险,而且商户端的网站是挂在在商户中心下,跨域之后会使得商户中心网站及其挂载在商户中心下的网站都面临安全性的问题,可能在使用的过程中受到恶意攻击。

  2. 其次,如果前端重新开发flash,时间会比较长,而且前端可以开发flash的资源较少。

  3. 第三,如果后端在服务器端直接处理图片,会导致服务器内存消耗过大,网站稳定性下降厉害,会付出较大的代价。而且使用java实现图片的压缩,效果并不好,这在测试程序中也可看出。因此在服务器端直接处理图片是一件吃力不讨好的方案。

  4. 最后一种方案就是新建一个服务器,专门用于图片处理的功能,这种服务器做完,也就基本做了一个图片云服务的服务器。这样做是一个长期可行的方案,但是要解决眼前的问题,耗费的时间太长。

所以想来想去,为了尽快的解决问题,还是采用美图秀秀的api,但是使用美图秀秀是需要跨域的,跨域毕竟存在安全性风险和稳定性风险。

###问题的解决

要解决稳定性,必须要考虑美图秀秀服务器挂了该怎么办

美图秀秀服务器的稳定性,不是我们所能控制的,所以必须要在美图秀秀挂了的情况下,自己的网站依然能上传图片。为了解除这种依赖,我们在程序中增加了一个标志位配置,以区别是否实用美图秀秀。如果这个标志位是false的时候,我们就使用简单的上传图片功能。如果为true的话,就继续使用美图秀秀。

解决安全性问题

跨域的安全风险还是很多的,特别是容易遭到CSRF的攻击。为了规避这种风险,最好的方式是将跨域的这部分独立到另一个服务器上。因为图片上传的功能对服务器性能消耗不大,所以找一个低性能的虚拟机完全可以满足这个需求。

现在flash是一个日渐式微的技术,会做flash的人越来越少了,Html5的实现又有ie在拦着,所以这一段时间内将简单的图片编辑放到服务器才是最稳定的方案了。