在qq游戏中 hlyxhd的人头各代表什么

Struts2、Spring、Hibernate 高效率开发的最佳实践(转)_Yii框架相干说明_[转] 腾讯产品宝典:产品手写全纪录__脚本百事通
稍等,加载中……
^_^请注意,有可能下面的2篇文章才是您想要的内容:
Struts2、Spring、Hibernate 高效率开发的最佳实践(转)
Yii框架相干说明
[转] 腾讯产品宝典:产品手写全纪录
Struts2、Spring、Hibernate 高效率开发的最佳实践(转)
Struts2、Spring、Hibernate 高效开发的最佳实践(转)
SSH(Struts2+Spring+Hibernate)是最为 Java 业界熟知的 Java EE Web 组件层的开发技术。很多人提起 Java EE,甚至都会将其误认为就是 SSH。无论是书籍还是电子教程,大部分都已经千篇一律,讲解各种标签、配置的用法。许多人包括笔者在内,第一次使用 SSH 的时候,按照教程的介绍进行开发。繁琐的配置,重复的修改配置,不断定义的参数转换器,真的让笔者苦不堪言。本文对 SSH 的开发模式尝试了重新定义,按照规约优于配置的原则,利用 Java 反射、注解等技术,设计了新的一套 SSH 开发框架,应用该框架,可以大大提高开发效率,笔者多次将该开发框架应用在各种小型 SSH Web 应用系统之中,屡试不爽。
阅读文章之前,读者需要对 SSH 的结合开发有一些了解,最好是有实践的经验,特别是对 Struts2 需要较为了解,掌握 Struts2 自定义拦截器、自定义验证器等的开发。另外,读者还需要掌握一些前提技术,包括 Java 反射、Java 注解、理解事务隔离级别等。
文章首先进行框架的总体介绍,然后分点介绍各个部分的详细设计。另外,文章还将列举该框架中采用的技术特点及其相应目的,希望读者可以从中获益。
框架总体介绍
文章之所以说框架是通用的,因为它的思想适应任何的业务需求。按照文章介绍,按照介绍的框架搭建完代码架构后,可以屏蔽许多技术细节,让开发人员专注于业务逻辑的实现,这些繁琐的技术细节包括技术配置、权限控制、页面跳转、错误处理等等。框架大致的风格如图 1 所示,总体来说,该框架遵守了“规约优于配置”的原则。
图 1. 框架大致风格(查看大图 )
上图中,系统只有一个 action 配置。每个业务操作,不再对应一个 ActionSupport 子类,而是对应一个 ActionSupport 子类的类方法,利用 Struts2 的动态方法特性,使得业务方法摆脱了繁琐的配置,方便的增加和删除。全面的 result 配置,解决了各种页面跳转的问题。Action 方法的起名,遵守了权限与业务模型规约,让模型选择与权限控制交给框架来实现,同时访问的操作名就是【方法名】 .action。下面,将一一对框架的各部分进行讲解。
Struts2 的不动配置
该框架中,我们建议只定义少量的类继承于 ActionSupport,这样可以使得 struts.xml 的配置尽量减少,甚至只进行少量配置,而不用随着业务的增加而修改配置。清单 1 是笔者为一个教师考勤系统定义的 Action 配置。
清单 1. Struts2 的配置清单
&action name="*" method="{1}" class="Main"&
&interceptor-ref name="fileUpload"&
&param name="maximumSize"&&/param&
&/interceptor-ref&
&interceptor-ref name="myInterceptorStack"&&/interceptor-ref&
&result name="input"&/noDir/error.jsp&/result&
&result type="json" name="success"&&/result&
&result name="errorJson" type="json"&&/result&
&result type="json" name="error"&&/result&
&result type="stream" name="stream"&
&param name="contentType"&${contentType}&/param&
&param name="contentDisposition"&fileName="${inputFileName}"&/param&
&param name="inputName"&inputStream&/param&
&result name="dynamic"&/${url}&/result&
&result name="otherAction" type="redirectAction"&/${url}&/result&
&result name="red" type="redirect"&/${url}&/result&
清单 1 定义了很多规则。首先,该配置使用了动态方法调用技术,这可以使得许多的 Action 方法可以声明到一个类里,不用重复定义 Action 类,同时对业务的增加和删除可以简约到对 Action 类里方法增加和删除,增加的 Action 方法不需进行其他配置,如果业务被删除,则只需要将方法注释或者删除,非常方便。第二,配置的 package 继承于 json-default,也就是说这个包里的 action 是支持 Ajax 调用的,默认的,如果返回 ERROR、SUCCESS,则会将 Action 序列化为 JSON 返回到客户端。我们定义了各种的跳转类型,包括重定向到页面(具体的页面由 Action 里的 url 指定)、重定向到 Action、重定向到错误页面、流类型的返回等等,这为我们动态的选择返回结果数据提供了方便。那么使用上述的配置,对我们开发有什么好处呢?假设有一个新的业务,我们只需要在 Action 添加新的方法,如清单 2 所示。
清单 2. 添加新业务方法
public String business() throws Exception
…business process…
if (has error)
addFieldError(“error message”) return INPUT;
//url是action中定义的一个String变量,指定跳转地址
url = “business.jsp”; return “dynamic”;
调用这个新的业务方法,只需要调用这个链接:http://{host}:{port}/{webapp}/business.action。正如我们看到的,定义了新的业务逻辑方法,我们没有修改或添加任何配置,因为我们的配置是完整的,考虑到了各种跳转、错误情况,同时动态方法调用特性,让我们可以动态的指定业务方法。如果我们对清单 2 中的跳转代码进行抽取,清单 2 会更加简单。如清单 3 所示。
清单 3. 抽取基础方法后的业务方法
public String business() throws Exception
…business process…
if (has error)
return redirectToErrorPage("error message")
return redirectToPage("business.jsp");
上面的清单中,我们抽取了 redirectToErrorPage 和 redirectToPage 方法,这样就可以使得其他的业务方法可以重用这些跳转方法,整个业务过程变得清晰易懂。类似的我们还可以抽取出 redirectToAction(跳转到另一个业务方法)、redirectStream(流类型的跳转)、redirectToAnotherPage(用于重定向的跳转)、redirectToJson(Ajax 的跳转)等等。这样这些公共方法就可以让其他的开发人员一起使用。程序员可以从跳转、错误提示、重复配置 Action 的痛苦中解救出来,专注于编写业务逻辑。
ModelDriven 的规约
有了上面的配置,我们还不能做到完全脱离配置。比如,我们定义了一个 Action 类继承于 ActionSupport,我们知道使用 ModelDriven 可以将用户上传的数据封装到一个业务 Bean 里,而不用直接在 Action 里声明变量,这很重要。我相信有很多读者遇到过这个问题。当业务 Bean 不同时,也就是需要用户上传的数据不同时,我们就要随之添加新的 Action,这直接导致修改 struts.xml 的配置,然后还要修改 applicationContext.xml 的事务配置、Bean 的配置,哪天我们不要这个业务了,又要重复的修改删除配置,笔者开始时为此事近乎抓狂,为什么我们不能只定义一个 Action,而 ModelDriven 的模型动态改变呢?
仔细考虑 Struts2 的机制,Struts2 将客户端的参数装配到 ModelDriven 的模型里,是通过“装配拦截器”装配的。只要我们在装配拦截器执行前,改变 ModelDriven 里的模型对象就行了。这就需要我们自定义一个拦截器,struts2 提供了这个机制。在自定义的拦截器里,我们根据用户调用的 Action 方法,新建一个模型,并且将模型设置到 Action 中,这样模型就可以是动态的了,记住,这个拦截器需要放在 defaultStack 的前面。
同样,新的问题是如何根据 Action 方法动态的选择业务模型呢?难道重复的写 if 方法吗?当然不能这样,动态的模型,就应该来自于动态的方法。因此定义的 action 方法需要有规约,笔者在自己的程序中,是这样定义规约的。Action 方法是这样组成:_$_,使用美元符隔开(美元符是 Java 方法合法的标识符),前部分是操作名,可以任意取,后部分是业务模型的类名。同时,所有的业务模型,都放到指定的一个包里,假设该包名为 com.dw.business。那么在自定义的拦截里,我们获得用户调用的 Action 方法名,按美元符隔开获得后半部分的类名,指定的包名(这里是 com.dw.business)+ 类名就是业务模型类的全路径,使用 Java 反射机制动态生成一个空的业务模型对象(所以,业务模型类必须有一个无参的构造函数),设置到 Action 里,再交给装配器的时候,装配器会自动组装这个模型。该拦截器的核心代码如清单 4 所示。
清单 4. 拦截器清单
public String intercept(ActionInvocation ai) throws Exception {
ai.addPreResultListener(this);
Main action = (Main)ai.getAction();
//业务方法名
String name = ai.getInvocationContext().getName();
int lastIndex = name.lastIndexOf("$");
if (lastIndex != -1)
String head = "com.dw.business.";
String className = name.substring(lastIndex+1, name.length());
//关键:动态设置业务模型
action.setModel(Class.forName(head).newInstance());
} catch (Exception e) {}
return ai.invoke();
有了上面的配置,基本可以做到屏蔽大多数技术细节开发了,但是我们还有一个问题,当在业务方法中,意外抛出了异常,struts2 默认的是返回 INPUT,按照 清单 1 的配置,发生错误将跳转到 noDir/error.jsp 页面,显示错误信息,这适合在非 Ajax 的处理。但是有时候我们希望他返回错误是 JSON 类型,因为 Action 的一些方法是 Ajax 的调用方式,也就是 Action 方法的执行结果需要返回 errorJson。我的解决方法是利用 Java 注解技术,定义一个新的注解,名为 IfErrorReturnToJson,它的代码如清单 5 所示。
清单 5. IfErrorReturnToJson 的代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IfErrorReturnToJson {
该注解作用在方法上,也就是 Action 的业务方法,如果我们希望某业务方法在发送错误时,就返回 JSON 类型,那么我们只需要在该方法上加上这个注解。现在,我们如何动态的修改返回值呢。清单 2 的第二行,为 ActionInvocation 添加了 PreResultListener,这个监听器是在返回结果前进行一些处理,正符合我们的需求。我们为自定义拦截器类实现了 PreResultListener 接口,实现了接口方法,如清单 6 所示。
清单 6. PreResultListener 的代码
public void beforeResult(ActionInvocation ai, String result) {
if (Main.INPUT.equals(result) || Main.ERROR.equals(result))
String methodName = ai.getInvocationContext().getName();
Method method = Main.class.getMethod(methodName);
if (method != null && method.getAnnotation(
IfErrorReturnToJson.class) != null)
ai.setResultCode(Main.ERROR_JSON);
} catch (Exception e) {
可以看到,我们在返回结果前,如果当前的返回结果是 INPUT 或者 ERROR,我们会使用反射机制,检查该方法是否加了 IfErrorReturnToJson 的注解,如果加了注解,则调用 ai.setResultCode(Main.ERROR_JSON); 方法,修改返回值,使得结果为 JSON 数据类型。
事务隔离级别的规约
在 SSH 开发中,需要重点考虑的是 Spring 的事务配置。根据不同的业务,定义的事务隔离级别就不同,比如对隔离级别要求高的,就要用到 Spring 的序列化读的隔离配置;有一些方法只是为了权限控制,用于页面跳转,则不需要用到事务控制;一些操作目的是搜索,那么该事务就是只读的。精细的事务配置,可以提高业务处理的代码效率。这时候,业务方法的命名规约就可以起到隔离级别的控制作用,比如方法名是 *begin*( 方法名中包含 begin),则该方法没有事务控制;方法名是 *search* 的,则该方法拥有只读的事务;方法名是 *seri*,则用序列化读事务控制,提高并发安全级别。这个事务配置清单如所示。
在一些应用中,涉及到简单的权限管理。读者可能会想到一些开源的中间件,如 ralasafe 这样的开源权限组件,虽然很全面,但是学习起来需要一定的时间,熟练掌握,并配合 SSH 开发则更需要细致的学习。当我们只是简单的权限控制时,我们完全可以用到一些规约,来完成用户的权限控制。在我设计的规约中,需要在数据库中定义一张 Permission 表,代表权限 , 它与用户表是多对一的关系,它至少有两个字段,一个是 permissionName,一个是 permissionPrefix。permissionName 用于描述该权限,而 permissionPrefix 是我们所关心的。我们设计 Action 的方法,遵守 permissionPrefix_actionMethodName$BussinessBean 这样的原则,permissionPrefix 前缀代表数据库中权限表的 permissionPrefix 值 , 使用下划线隔开。当用户登录后,会将该用户所拥有的权限列表存放到 session 之中。当用户试图访问一个 Action Method 时,会使用 ModelDriven 的规约 里一样的方法,截取方法名获得该方法允许的访问权限,如果用户的权限列表中包含了该方法的权限,则允许调用,否则不允许调用。这样设计,虽然无法做到数据的访问权限,却可以满足一大部门的功能权限控制。这里需要记住,权限描述可以修改,但是权限的 permissionPrefix 不能修改,如果方法名中没有权限标识,则代表任何用户都可以访问。类似的,如果一个方法多个权限都可以访问,则可以这样设计方法 p1_p2_p3_methodName$BussinessBean,使用多个下划线分割。
合理利用 Struts2 的传参技术
基于 HTTP 协议的特殊性,上传到 Web 服务器的数值都是字符串,而我们的业务模型都是对象类型,有一些还是复杂的业务对象。Struts2 提供了 Conveter 的机制,允许程序员将 String 转换成复杂的业务对象。但是笔者刚用这个机制的时候,开始的确兴奋,但是随着业务的修改,业务模型的修改,这些 Conveter 的管理着实让人头疼。因此,本人认为 Conveter 技术应该尽量少用。我提出了一个新的方法,假设一个业务模型中,里面包含一个 List&People& pids 属性对象,People 里有一个 id 属性,我们需要客户端上传 People 的 id 列表,放入 List&People& pids 之中。如果使用 Conveter,我们在客户端需要定义规则,如客户端上传 pids=1,2,3,4,6,8。然后服务器端使用 Conveter 执行分割字符串、新建 People 对象、设置 ID、添加到列表等一系列操作。听着都头晕不是吗 ? 而且这相对并不安全,对程序员解析字符串的功底要求很高。我们感谢 Struts2 的传参机制,如果我们这样传参:&input name=”pids[0].id”/&&input name=”pids[1].id”/&。这两个 input 的上传,Struts2 会根据上传数据的 name,自动组装成 List&People& 对象,并且赋值给 pids,pids[0] 代表列表的第 0 个元素,这用到了 OGNL 表达式的传参规则,它会自动识别 pids 是数组还是列表,如果 pids 默认是空,它还会自动新建一个空的数组或列表。笔者可以查看 OGNL 的相关教程。合理利用该技术,可以大大缩减传参的难度。
合理使用 Struts2 的标签
Struts2 提供了众多的标签,这些标签大致包括了 UI 标签、数据标签以及逻辑标签。这里,我们需要理解 UI 标签与服务器端的数据交互格式,比如 checkboxlist 标签,传到服务器端就是一个数组,该数组存放的是选定的 checkbox 列表的数值 , 这比使用 iterator 标签生成 checkbox 列表容易获得数据。Doubleselect 标签,用于生成 2 级的级联 select,经常使用的是部门 - 用户的级联。Optiontransferselect 标签,用于批量的迁移,比如用于批量的为部门分配用户。使用 struts2 的标签可以大大减少用户界面的开发,以及使得用户界面到服务器的数据传输方式变得非常简单。
Action 的模型验证
Struts2 具有一套完善的验证机制,在 ActionSupport 类里,可以将模型验证方法写在 validate* 方法里。如果重写了 ActionSupport 的 validate 方法,这个 validate 会在执行所有 Action Method 前调用,执行验证。而自定义的 validate*(* 是方法名),比如方法名是 add,则这个方法名就是 validateAdd),它只会在执行 add 方法前执行验证。对于数据的验证,笔者建议使用 validater 文件验证,它更加容易配置和修改,利用现有的验证器,可以减少硬编码验证的痛苦。在上面提到的框架中,由于使用的是动态方法机制,我们需要在 Action 类所在的包里,新建 validator 的 XML 文件,名字是 { 类名 }-{ 方法名 }-validation.xml(如果不是动态的方法,则 { 类名 }-validation.xml 就足够了)。最后的结果如图 2 所示。
图 2. Validator 文件配置结果
具体的配置内容,读者可以搜索 Struts2 的 validation 框架教程。
自定义业务模型验证器
Struts2 提供的验证器,包括 date、required、requiredstring 等等,这些可以归于数据验证。而对于特定的业务模型验证,则比较复杂,因此,在该框架中,可以自定义一个业务验证器,这是 struts2 支持的,它负责对业务模型进行验证,比如可以验证用户上传的用户 ID 的用户是否存在,这可以在很大程度上保证系统的安全性。验证器的代码清单 7 如下面所示。
清单 7. 业务验证器代码
public class BusinessValidator extends FieldValidatorSupport {
private String property =
public String getProperty() {
public void setProperty(String property) {
this.property =
public void validate(Object exist) throws ValidationException {
String fieldName = getFieldName();
Object fieldValue = getFieldValue(fieldName, exist);
if (fieldValue != null && fieldValue instanceof Integer &&
((Integer)fieldValue) &= 0)
addFieldError("message", "上传的ID,该数据是不存在!");
} else if (fieldValue == null)
addFieldError("message", "上传的ID,该数据是不存在!");
if (exist != null && exist instanceof Main &&
((Main)exist).getModel() instanceof IChecker)
Main pa = (Main)
IChecker e = (IChecker)pa.getModel();
boolean isRight = e.checkOk(property, pa);
if (!isRight)
addFieldError("message", "上传的ID,该数据是不存在!");
pa.getPubDao().getHibernateTemplate().clear();
清单 7 中,用到一个 IChecker 接口,需要验证的业务模型需要实现 IChecker 接口,在接口实现方法中实现业务验证过程,错误的话返回 false。
在 src 目录(或者 WEB-INF 下的 class 目录),添加 validater.xml 文件,在其他自带的验证器后,添加业务验证器配置,命名为 check。
清单 8. 验证器的配置
&validators&
&validator name="check" class="com.attendance.action.BusinessValidator"/&
&/validators&
接下来就可以使用这个验证器,在图 2 中的 validation 文件里,添加如清单 9 的配置。
清单 9. 验证器的使用
&field-validator type="check" short-circuit="true"&
&param name="property"&department&/param&
&message& 该部门不存在 !&/message&
&/field-validator&
利用 JEE Eclipse 生成 Hibernate 的 JPA 模型
IBM 提供了 JEE 版的 Eclipse,专门用于开发 Java EE 的应用,它提供了从数据库中生成符合 JPA 规范的数据模型的能力。Hibernate3 以后,支持了 JPA 规范(不是完全支持,但是已经较为全面),利用 JEE Eclipse 的生成能力,就可以避免繁琐的 hbm 文件的配置。同时,JPA 的规范里,还可以提供 NamedQuery、OrderBy 等注解,对于复杂的数据库,该规范可以减少开发时间。
Struts2 的 action 标签,用于调用某个 action。这个标签笔者认为非常有用,尤其体现在 UI 重用中,比如用户管理中,在很多个界面里,都允许用户信息修改和用户删除,那么我们就可以将用户信息修改和用户删除的页面代码以及 JavaScript 放在一个 JSP 里,同时定义一个 Action 方法 ( 名为 A),为这个 JSP 执行初始化。接下来在允许进行用户修改和删除的界面,都用 s:action 标签调用这个 A 方法,设置 executeResult 为 true,将用户修改和删除的代码包含在页面里。这样就实现了 Action 的重用。如果重用的界面不需要服务器的初始化,这直接使用 jsp:include 或 s:include 引用重用的 JSP。
本文通过一系列的讲解,讲述了如何搭建一个通用的 SSH 开发框架,陈述了其中的设计思想,由于篇幅限制,文章只介绍了框架中的重要部分和重要思想,希望读者可以从中获益。由于笔者水平有限,如有错误,请联系我批评指正。
Yii框架相干说明
Yii框架相关说明
db组件 'schemaCachingDuration'=&3600, 为什么不起做用?需要开缓存
如何在页面下边显示sql的查询时间在log组件的routes中加入
array('class'=&'CProfileLogRoute','levels'=&'error, warning',)
同时在db组件中加入'enableProfiling'=&true,同时在这种情况下,可以用CDbConnection::getStats() 查看执行了多少个语句,用了多少时间
如何知道某一个程序段运行需要的时间配置好CProfileLogRoute后,在需要测试的地方加上
Yii::beginProfile('blockID');//程序段Yii::endProfile('blockID');
'enableParamLogging'=&true,的作用是?在日志的bind的参数后边跟数的值
如何在页面底部显示所有的db相关的日志同上,配置log组件的routes中加入
array('class'=&'CWebLogRoute','levels'=&'trace, info, error, warning','categories' =& 'system.db.*',//'showInFireBug' =& true, 将在firebug中显示日志),
把日志记录到数据库
array('class'=&'CDbLogRoute','logTableName'=&'applog','connectionID'=&'db',),
运行时表applog会自动生成,如果不能生成,参照api自已建立
如何记录$_GET,$_SESSION等信息,在以上的routes中各个配置中加上
'filter'=&'CLogFilter',
log配置中的level设置不对,可能会得不到日志信息另外level,category的值可以随便写,只要在用yii::Log("","自定义level","自定义的category")时对应起来即可
如何记录更详细的信息,能记录stack?
在入口文件中加上define('YII_TRACE_LEVEL',10);数字越大,记当的越详细,结果如下[15:31:57.226][trace][system.db.CDbCommand] Querying SQL: SHOW COLUMNS FROM `Bangdan` in E:\APMServ5.2.6\www\\protected\models\Bangdan.php (21)in E:\APMServ5.2.6\www\\protected\components\HotBangdan.php (21) in E:\APMServ5.2.6如果在调试时,终止程序运行且看到日志,不能用die及用application::end,即Yii::app()-&end(),其会触发onEndRequest事件,日志就是在这个事件中记录的
如何发布一个资源文件并引用$css=Yii::app()-&getAssetManager()-&publish(dirname(__FILE__)."/aa.css");yii::app()-&clientScript-&registerCssFIle($css);如果改变activelable中默认的标题重写方法attributeLabels
过滤不良代码
$purifier=new CHtmlP$purifier-&options=array("HTML.Allowed"=&"div");$content=$purifier-&purify($content);
beginWidget('CHtmlPurifier'); ?&...display user-entered content here...endWidget(); ?&
如何防止重复提交?提交后
Ccontroler-&refresh();
如何在成功后显示一个提示,用户刷新页时去掉提示
Cwebuser-&setFlash();getFlash();
如何防止重复提交, 并在提交成功后给出提示?控制器中
Yii::app()-&user-&setFlash('submit','thanks');$this-&refresh();
if(Yii::app()-&user-&hasFlash('submit')){echo Yii::app()-&user-&getFlash('submit');}
一般我们是跳转到列表页,或用redirect跳到编辑页,就不需要了,如果还是要显示当前页,以上就有用了,比如在当前时显示,编辑或添加新的记录
如何分页itemCount总记录条数CPagination代表分页信息,有多少页,每页几条记录等CLinkPager生成分页的代码,自定义css可以给属性cssFile一个值
$criteria=new CDbCriteria();$pages=new CPagination("数据库中的总记录数");$pages-&pageSize=2;$pages-&applyLimit($criteria);//给$criteria-&limit offset等符值$posts=Post::model()-&findAll($criteria);$this-&widget('CLinkPager',array('pages'=&$pages));
列表如何排序
$criteria=new CDbCriteria();$sort = new CSort('Post');$sort-&defaultOrder=" status asc";$sort-&applyOrder($criteria);$posts=Post::model()-&findAll($criteria);
应用时用$sort-&link('字段名')实际是生成一个带参数的url,然后在在applyOrder时应用这些参数修改$criteria,得到相应的查寻结果
如何生成并验证验证码:基本用法&?php $this-&widget('CCaptcha'); ?& 具体参数查手册原理CCaptcha这个widget会在run时调用当前控制器的$captchaAction='captcha'方法,这个方法指到一个类CCaptchaAction其会生成验证码图象,并记入到session中
如何显示静态页重写actions
'help'=&array('class'=&'CViewAction','basePath'=&'help', //指定目录名'defaultView'=&'default','viewParam'=&'help' //get参数),
假定当前控制器是post那么可以能过/post/help/help/content访问help目录下的content.php可以建立子目录比如help/reigterhelp/content.那可以通过/post/help/help/registerhelp.content来访问用CViewAction的好处时,可以与其它的view共享layout
关于没有权限访问跳转的url相关当没有权限时调用CAccessControlFilter类中的accessDenied,其调用CwebUser中的loginRequired(),记录当前的returnurl后跳转到CWebUser配置中的loginurl,在此处登陆后,可以通过redirect跳转到returnurl(Yii::app()-&request-&redirect(Yii::app()-&user-&returnUrl);)当强制显示登陆表单,比如判断用户是guest就一直列出登陆表单,不会调用loginRequired, 就得不到returnurl,这时候想跳回去,参见cookbook上相关贴子
registerCoreScript在framework/web/js/package.php中列出的才是
多对多关联条件
$criteria-&addInCondition("categorys.id",$in);$criteria-&addSearchCondition('Shop.name',$keyword);$shops=Shop::model()-&with(array("categorys"=&array('together'=&true)))-&findAll($criteria);
同时要在Shop模型中加入alias="categorys" ,另外together=true放在模型的关联中也可
YII中的RBAC权限,用数据库存item,在system/web/auth下找到相应的sql导放到数据库中配置'authManager' =& array('class' =& 'CDbAuthManager','connectionID' =& 'db',),如果在sql中导入的三个表的表名不是默认的,需要在这上边的配置中配置,具体的看api
$auth=Yii::app()-&authM//$auth-&createOperation("post",'postpost');//$auth-&createTask("post","posts");$auth-&createRole("post","post");auth-&assign("post",'demo');if(Yii::app()-&user-&checkAccess("post")){echo "yes";else{echo "no";}
这种情况下三者是一样的
如何获得上一页的url以返回
Yii::app()-&request-&urlR
accessControl 是Ccontroller中内置的过滤方法,其它的还有ajaxOnly postOnly
CMaskedTextField此组件用于限制用户的输入,对应的jquery插件/projects/masked-input-plugin/
在一对多,多对多查询时,the eager loading 联合所有的表生成一条语句,如果主表有limit的查询选项,那么他将单独执行,然后再执行与关联表有关的语句,返回相关表的数据对象,这就是为什么在做大优惠时,以中间表为查询条件出错的原因,解决办法with()返回 CActiveFinder对象,其方法together(),既使主表中有LIMIT/OFFSET 也是返回一条
多对多查询时,分页有时候页中显示的条数不正确,因为有重复的项,加上$criteria-&group = true即可
模型的rules中,验证某个字段不能重复,array('name', 'unique','message' =& '有重复的名子'),
CStatePersister是yii的核心组件,提供了基于文件的数据保存方式,可以不在同的请求中使用
COutputCache 即是一个组件,又是一个filter,前者的时候用于在view中缓存内容,后者的时候用于在controller中缓存就是说片段缓存,是把COutputCache当一个widget来用,页面缓存把COutputCache当作一个filter来用
动态缓存,用CController的一个方法 renderDynamic($callback);
COutputCache几个属性,duration,dependency 另外还有几个,可以通称为Variation, 有什么作用呢?在beginCache是需要手工指定一个id,Variation的作有就是自动给生成这个id
在布署模式的时候,有错误不会有stack样的提示,会显示一个errorxxx的错误
如何在程序有错的时候跳到指定的action在components中设置
'errorHandler'=&array('errorAction'=&'site/error',),
在此action中可以能过Yii::app()-&errorHandler-&error获得错误信息
把字符串分解成数组,并去掉空值
preg_split('/\s*,\s*/','this , is , , a test',-1,PREG_SPLIT_NO_EMPTY )
CActiveRecord::exits();判断有没有这样的记录,一般用于添加时,判断某字段有没有重复
CActiveDataProvider 一个基于ActiveRecord的数据提供源常用的用法
$dataProvider=new CActiveDataProvider('Post', array('criteria'=&array(),'pagination'=&array(),'sort'=&array(), ));
上如'sort'=&array('defaultOrder'=&'status, update_time DESC',),ClistView同上结合使用,其中的_view中可以用一个$data的变量,代表当前的model数据如果dataProvider中的pagination,sort设为false,则CliveView中对应的部分也无法使用
$this-&widget('zii.widgets.ClistView',array('dataProvider' =& $dataprovider,'itemView' =& '_view','template' =& '{items}{sorter}{pager}','sortableAttributes' =& array(),));
CGridView的使用也结合$dataprovider,用的时候主要是对columns的配置,主要有CDataColumn, CLinkColumn, CButtonColumn and CCheckBoxColumn.具体用法看api总的说来CgridView没有ClistView灵活
插入meta信息
Yii::app()-&clientScript-&registerMetaTag('keywords','关键字');Yii::app()-&clientScript-&registerMetaTag('description','一些描述');
CMap::mergeArray() 比array_merge更智能的合并数组,yii中配置的合并用这个
CClipWidget 通过ob_start ob_getconent生成一段不显示的内容,可以能过CController::clips访问,如
$this-&beginWidget('CClipWidget',array('id'=&'name','renderClip'=&true));
可以通过$this-&clips['name']来显示,其中的renderClip如果为false,则在当前位置不显示内容
获得服务器时间
$_SERVER['REQUEST_TIME']
维护程序时,这样子所有的请求转发到一个地方
'catchAllRequest'=&array('site/all'),
根据二级域名缓存
array('COutputCache + search','duration' =& 120,'varyByParam' =& array('q','page'),'varyByExpression' =& "app()-&request-&hostInfo",),
有多个分站时,同步登陆,
基于cookie
'user'=&array('identityCookie'=&array('domain'=&'.'),'allowAutoLogin' =& true,)
如果是基本于session
'session' =& array( 'cookieParams' =& array('domain' =& '.dayouhui', 'lifetime' =& 0),'timeout' =& 3600,),
如何使用theme在main.php中配置
'theme'=&'classic',
如何得到前前使用的主题
Yii::app()-&theme
得到主题名字
Yii::app()-&theme-&
themes文件夹和protected是同级的,其下边某个theme的目录结果同protected/views下一样
关于skin用theme改变view的外观,skin是用来改变widgets的外观的skin是健值对用于初始化一个widget的属性要对widget使用skin,需要做以下几步1:配置'widgetFactory'=&array('enableSkin'=&true,),2:在views下建立skins目录3:在skins目录下建立与Widget名子一样的php文件,返回数组,即能用于widget的初始配置4:在php文件中,如果有defautl的配置,会先找这个skin5:如果应用了theme,程序会先去对应的theme目录下的skins中找配置文件6:如果只是想给widget统一一个skin,建议用Customizing Widgets Globally
如果防止post跨站攻击
'request'=&array('enableCsrfValidation'=&true,),
这时候生成的表单要用CHtml::form(),其会写一段代码在cookie中
防止Cookie攻击
'request'=&array('enableCookieValidation'=&true,),
同时生成与得到cookie是要用 CHttpCookie
如何让表单验证不驼过的提示为中文在main.php的配置中加上
'language' =& 'zh_CN',
如何实现仿google的自动完成功能
widget('CAutoComplete', array('name'=&'xxx','url'=&array('suggestTags'),'multiple'=&false,'htmlOptions'=&array('size'=&50),)); ?&
然后在url指定的地址中的方法中如下输出,即可echo "a\nb\nc"
CGridView详解这东西在后台比较有用,能加速开发的速度,值得一看CGridView用表格的方式显示数据项每一行代表一个数据项,一列通常代表数据项的一个属性CGridView支持排序和分页,可以用ajax或普通的方式CgridView必序和data provider一起使用最简单的用法
$dataprovider = new CActiveDataProvider('Post');$this-&widget('zii.widgets.grid.CGridView',array('dataProvider'=&$dataprovider,));
这会用表格的方式显示每一条数据项,每一列是Post的一个属性在显示中带了分页和排序我们可以自定义CgridView::columns属性,以自定义表格列的显示方式这个cloumns如何配置呢?其是一个数组,每一个数组元素对应着一列的配置,可以是字符串或数组1、如果是字符串,格式是name:type:header 后两者是可选的,根据这三个值,创建一个CdatColumn实例其中type参见CFormatter2、如果是数组,其可以实例化CDdataColumn、ClinkColumn,CButtonColumn,CCheckBoxColumn实例,具体实例化哪个由数组中的class指定,默认是CDataColumn2.1,如果class=&'CDataCloumn'则可以指定name或者value,如果指定以value优先
用CDataColumn时如何以关联表的数据序列?代码如下:表示可以post关联的author中的username排序列
$dataprovider = new CActiveDataProvider('Post',array('criteria'=&array('with'=&'author',),'sort'=&array('attributes'=&array('title','create_time','author_id'=&array('asc'=&'author.username asc','desc'=&'author.username desc','label'=&'作者'))),));$this-&widget('zii.widgets.grid.CGridView',array('dataProvider'=&$dataprovider,'columns'=&array('title','create_time',array('name'=&'author_id','value'=&'$data-&author-&username'),),));
另外CDataColumn还有一个filter属性,如果是空,那么生成一个textfield,如果是数组(键值),则生成一个dropDownlist在当前列的上部,供搜索2.2:如果class=&"CLinkColumn"
array('class'=&'CLinkColumn','label'=&'查看用户','url'=&Yii::app()-&createURL('user/edit'))
则生成一个连接2.3:如果class="CCheckBoxColumn"
array('class'=&'CCheckBoxColumn','name'=&'title','id'=&'select'),
可以生成一个checkbox供选择,且只能选一个可以配置CGridView::selectableRows 如果是0,则不能选,如果 1,只选一个如果是2或其它值,则可以选多个代码如下:
array('class'=&'CCheckBoxColumn','name'=&'title','id'=&'select'),
2.3:如果class="CButtonColumn"
array('class'=&'CButtonColumn','updateButtonUrl'=&'Yii::app()-&createUrl("post/edit",array("id"=&$data-&id));',),
修改updateButtonUrl为编辑贴子
如何用gridview生成一个代搜索的管理列表1、在Model的rules 设定可以搜索的属性
array('title, status, create_time', 'safe', 'on'=&'search'),
2、在Model中,添加搜索时的方法
public function search(){$criteria=new CDbC$criteria-&compare('title',$this-&title,true);$criteria-&compare('status',$this-&status);$criteria-&compare('create_time',$this-&create_time);return new CActiveDataProvider('Post', array('criteria'=&$criteria,'sort'=&array('defaultOrder'=&'status, update_time DESC',),));}
3、 在Controler中,写接受搜索用到的表单的值的方法
public function actionAdmin(){$model=new Post('search');if(isset($_GET['Post']))$model-&attributes=$_GET['Post'];$this-&render('admin',array('model'=&$model,));}
4、在view中用CGridView显示设置好&?php $this-&widget('zii.widgets.grid.CGridView', array('dataProvider'=&$model-&search(),'filter'=&$model,'columns'=&array(),)); ?&以上代码大部分是yii自动生成的,只要做少量修改即可有时候会出现,搜索后页面为空的清况,原因可能是layout/main.php中echo $content外层无div,就是说main.php中必须有一个div包含$content//CListView详解其用列表的形式显示数据,不象CGridView一样,用表格显示数据,CListView用一个 view模板来显示每一条数据其支持排序与分页常用的代码如下&?php$dataProvider = new CActiveDataProvider('Post',array('pagination'=&array('pageSize'=&2),));$this-&widget('zii.widgets.CListView',array('dataProvider'=&$dataProvider,'itemView'=&'_view','template'=&' {summary} {items} {pager}{sorter}','sortableAttributes'=&array('title','create_time'=&'Post Time',),));
CActiveForm详解快速生成表单,支持ajax验证,对于比较复杂的验下最好是自己生成表单,写验证方法常用代码,在Controller中public function actionForm(){$post = new Post();if(isset($_POST['ajax']) && $_POST['ajax']==='post'){echo CActiveForm::validate($post);Yii::app()-&end();}if(isset($_POST['Post'])){$post-&attributes = $_POST['Post'];if($post-&save()){echo '存成功了';}}$this-&render('form',array('post'=&$post));}在view中&?php$form = $this-&beginWidget('CActiveForm',array('id'=&'post',//这里与Controller中的ajax对应'enableAjaxValidation'=&true,));?&&?php echo CHtml::errorSummary($post); ?&&?php echo $form-&labelEx($post,'title');?&&?php echo $form-&textField($post,'title')?&&?php echo $form-&error($post,'title'); ?& error一定要写上,要不不会触发ajax验证&?php echo $form-&labelEx($post,'content');?&&?php echo $form-&textField($post,'content')?&&?php echo CHtml::submitButton($post-&isNewRecord ? 'Create' : 'Save'); ?&&?php $this-&endWidget(); ?&//CBreadcrumbs常用代码&?php $this-&widget('zii.widgets.CBreadcrumbs', array('links'=&$this-&breadcrumbs,'homeLink'=&'&span&&a href=""&shouye&/span&','separator'=&'&&&')); ?&其中breadcrumbs中Controller中的一个属性,如果要出现导航,就要在view中给此属性附值生成的html如下&div &&span&&a href=""&shouye&/span&&&&&span&Managde Posts&/span&&&&&span&b&/span&&&&&span&c&/span&
所以如果网站用到导航的时候,美工最好把导航代码定义如上//CDetailView 用在仅仅是为了查看数据时,还是比较有用的,比如用在后台
如何在提交后显示一段提示在控制器中if(isset($_POST['name'])){Yii::app()-&user-&setFlash('success','you are success');$this-&refresh();}在view中if (Yii::app()-&user-&hasFlash('success')){echo 're is'.Yii::app()-&user-&getFlash('success');}else{echo 'no';}
如何得到当前域名app()-&request-&hostInfo
activeDropDownList,给出提示,并有值array('empty'=&array(0=&'选择分组')&input type="submit" value="提交" /&
验证码如何生成及验证Controller中public function actions(){return array('captcha'=&array('class'=&'CCaptchaAction','backColor'=&0xFFFFFF,'maxLength'=&4,'minLength'=&4,),);}View中&?php echo CHtml::activeTextField($user, 'verifyCode');?&&?php $this-&widget('CCaptcha',array('captchaAction' =& '/site/captcha','showRefreshButton' =& false,'clickableImage' =& true,'imageOptions' =& array('align'=&'top', 'title'=&'重新获取'),));?&Model中array('verifyCode', 'captcha', 'captchaAction'=&'site/captcha', 'message' =& '输入的验证码不正确'),set_time_limit(0);//禁止角本超时
如何想把手工的东西记录的数据库main.php中配置logarray('class'=&'CDbLogRoute','levels'=&'info','logTableName'=&'Log','connectionID'=&'db',),应用时Yii::log('信息','info');deleteAllByAttributes(array("phone"=&$phones)直接接受一个数组,可以删除数组中符合条件的记录YII_BLOG STUDY重新看了一遍yii blog,有些记录会与上边的重复YII:Trace() 在debug模式是才记录信息,同时在main.php中的Log中的配置中的levels中要有trace,至于记录多少栈由index.php中的YII_TRACE_LEVEL决定
配置Gii'modules'=&array('gii'=&array('class'=&'system.gii.GiiModule','password'=&'123',),),
获得客户端IPif($_SERVER['HTTP_CLIENT_IP']){ $ip = $_SERVER['HTTP_CLIENT_IP']; }elseif($_SERVER['HTTP_X_FORWARDED_FOR']){ $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; }else{ $ip = $_SERVER['REMOTE_ADDR']; }
CActiveForm还是比较强大的,建议在以后的项目中form都用这个来实现layout/中的视图是可以继承的&?php $this-&beginContent('/layouts/main'); ?&然后在中间出现$content即可&?php $this-&endContent(); ?&create,update最好是分开放在两个action中,共用一个form,中间可以加一层view,以在头尾显示不同的东西成段的完成一个功能的代码尽量拿出来放到一个方法中$this-&beginWidget('CMarkdown', array('purifyOutput'=&true));echo $data-&$this-&endWidget();linkButton,在删除时需要用js提示,可以看下这此组件中的comfirm而且他们的提交方式都是post,是因为在jquery.yii.js写死了具体的以在源文件中低部找到那段js中的ajaxsubmit,所在的js看下filter是在执行action之前或之后执行的一段代码,要应用filters必须得写CController::filters()方法为什么在filters方法写上return array('accessControl', // perform access control for CRUD operations);能进行crud验证呢?accessController是CContronller内置的filter,其调用accessRules,得到验证规定,所以也要重写对应的accessRules,返回一个验证规则的数组成部分if the application uses modules,a root alias is also predefined for each module ID and refers to the base path of the corresponding module如:echo YiiBase::getPathOfAlias('bbs');得到module bbs的路径关于CUrlManager'模式'=&'route'matchValue是指,对于一个url规则,正常情况下是只看参数的名子是否一样就应用规则如果matchValue=true,则也要看值如,规则'index-/&id:\d+&'=&array("book/index",'matchValue'=&false),$this-&createUrl('book/index', array('id'=&'abcd'));可以应用以上规则的,如果规则中的matchValue=true,则就不能应用了XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行renderPartial()render()后者会把需要的js,css等嵌入前者可以通过把最后一个参数设置成true完成一样的功能addInCondition 不用考虑数组是空的情况yii会自动处理如何得到当前url?Yii::app()-&request-&ctype_开始的几个函数,用于检查字任串是不是符合要求,代替了简单的正则表达式CController中的setPageState可以保存同一页中的POST的表单状态如何通过BEhavior修改CActiveRecord?写类文件继承自class LLog extends CActiveRecordBehavior{public function beforeDelete($event){$model = get_class($this-&Owner);//做要做的事,比如日志或修改模型字段内容}}然后修改模型文件public function behaviors(){return array(// Classname =& path to Class'LLog'=&'application.behavior.LLog',);}如何在应用程序处理请求之前执行一段操作?在main.php中配置'onBeginRequest' =& 'function'当然这个function方法要存在也可以写在放口文件index.php中,代码改成如下$app = Yii::createWebApplication($config);$app-&onbeginRequest = 'begin';$app-&run();function begin(){echo 'yyyyydddyyyyyy';}为什么在CActiveRecordBehavior中用beforesave就可以代表了事件onBeforeSave注意基为中最上边的events方法中返回的对应关系'onBeforeSave'=&'beforeSave'在调用attacth(CBehavior中)的时候,$owner-&attachEventHandler($event,array($this,$handler));就指定了事件onBeforeSave的处理函数是用本类中的beforeSaveYII中的CComponent,CEvent与Behavior及CActiveRecordBehavior个人理解这一块教程少,今天个人理解了下,写了个小例子,有助于理解完成如下功能,一个JTool类,继承CComponent,当其长度改变时,调用事件,输出"change me".JTool.php在protected/components 下&?phpclass JTool extends CComponent{private $_public function getWidth(){return $this-&_width ? $this-&_width : 1;}public function setWidth($width){if($this-&hasEventHandler('onChange')){$this-&onChange(new CEvent());}$this-&_width = $}public function onChange($event){$this-&raiseEvent('onChange', $event);}}OK,功能已经实现了,找个控制器,执行$j = new JTool();$j-&onChange = "showChange"; //给事件绑定handle showChange$j-&width = 100; //调用setWidth,解发绑定的事件showChangefunction showChange(){echo 'changed me';}现在我们想给JTool添加一个功能,返回长度的100倍,我们可以继承JTool.php写一个方法class JToolSub extends JTool{public function get100width(){return $this-&width*100;}}OK,功能实现了,这个执行就简单了new JToolSub调用方法即可上边的这两种办法,就是仅完成功能,下边演示Behavior及events来实现如何用Behavior来实现上边的增加一个方法,返回长度的100倍的功能呢?写类JBeJBe.php在protected/behavior 下class JBe extends CBehavior{public function get100width(){return $this-&Owner-&width*100;}}OK,功能已经实现了,找个控制器,执行$j = new JTool();$j-&attachBehavior('JBe', 'application.behavior.JBe');echo $j-&get100width();如何用Behavior实现JTool中的长度改变时,调用一个事件的功能呢?写类JBeclass JBe extends CBehavior{public function events(){return array_merge(parent::events(),array('onChange'=&'change',));}public function change(){echo 'changed';}public function get100width(){return $this-&Owner-&width*100;}}OK,功能实现随便找个控制器,执行$j = new JTool();$j-&attachBehavior('JBe', 'application.behavior.JBe');$j-&width = 100;这里的要点是events方法返回的数组array('onChange'=&'change')定义了事件(event)和对应的事件处理方法(event hander)事件是是Compents(JTool中)定义的,即JTool中的onChange处理方法同由Behavior(JBe中)类定义的,即JBe中的change这样子再看CActiveRecordBehavior,其是绑定给CActiveRecord 这个组件的,绑定方法重写behaviors()CActiveRecordBehavior中的events() 方法返回事件及事处理函数的对应,如:'onBeforeSave'=&'beforeSave'即组件CActiveRecord中的onBeforeSave这个事件对应的处理函数是CActiveRecordBehavior中的beforeSave方法这样子CActiveRecord在调用save()时,触发事件onBeforeSave,调用CActiveRecordBehavior对应的处理函数beforeSave我们只要写一个CActiveRecordBehavior的子类,重写其中的beforeSave,执行一些操作,然后给CActiveRecord绑定即可如果你自己有个目录下有些类或文件常用,可以在main.php的最上边定义一个路径别名Yii::setPathOfAlias('local','path/to/local-folder');如果是多个可以在main.php中的array中加一个配置'aliases'=&array('local'=&'path/to/local/'),如何得到proteced目录的物理路径?YII::app()-&basePwidget是发布资源$url = Yii::app()-&getAssetManager()-&publish(Yii::getPathOfAlias('ponents.homeuserlived'));cs()-&registerCoreScript('jquery');cs()-&registerScriptFile($url.'/location.js' ,CClientScript::POS_HEAD);cs()-&registerScriptFile($url.'/YLChinaArea.js' ,CClientScript::POS_HEAD);cs()-&registerCssFile($url.'/style.css');如何写application component, 即在main.php可配置"my"=&array('')可以通过Yii::app()-&my来访问?继承CApplicationComponent即可,并可以自带Behavior等yii中读写session的两种方法$session = Yii::app()-&$session['terry'] = 30;var_dump($session['key']);Yii::app()-&user-&setState('tom', '40');var_dump(Yii::app()-&user-&getState('key', 'default'));==========================================分隔线===================================soap非yii教程,意思是不用yii框架的时候要对象提供webservice的写法分两种WSDL模式,和非WSDL模式,先看后者这个也比较简单,服务器端server.php&?phpini_set('soap.wsdl_cache_enabled',0);class Student {public function getInfo($name,$age){if($age == 20){throw new SoapFault(-1, 'Cannot divide by zero!');}$xml = "&root&&name&".$name."&/name&";$xml .= "&age&".$age."&/age&&/root&";return $} }$soapS = new SoapServer(null,array('uri' =& ''));$soapS-&setClass('Student');$soapS-&handle();?&客户端client.php&?php$soap = new SoapClient(null,array('location'=&"http://localhost/mysoap/index.php",'uri'=&'inadex.php'));echo $soap-&getInfo('a','b');这样子即可=============================================yii,Componnts那快,忘了,写了个小例子回忆了下是写一个可以写在main.php中的Components并绑定行为,事件======================================class ExtWindow extends CApplicationComponent{private $title = 'title';public $public function getTitle(){return $this-&title ? $this-&title : 'old title&br /&';}public function setTitle($title){echo '=='.$this-&oldtitle.'==';$this-&oldtitle = $this-&$this-&title = $if($this-&hasEventHandler('onTitleChange')){$event =new CEvent($this);$this-&raiseEvent('onTitleChange', $event);}}//必须有这么个方法,其和raiseEent中的事件一样,具体看代码public function onTitleChange($event){}}===========================&?phpclass Window extends CBehavior{public function events(){return array_merge(parent::events(),array('onTitleChange'=&'titleChange',));}public function titleChange($event){echo $event-&sender-&echo 'event TitleChange is handled in Behavior&br /&';echo $this-&owner-&}public function titleOld(){echo '&br /&old title is is '.$this-&owner-&}}==============================main.php中的写法'ExtWin'=&array('class' =& 'ExtWindow','oldtitle'=&'我是旧的','behaviors'=&array('win'=&'application..behavior.Window')=============================================一对多,多对多的关联时最后的参数 together说明如果为false,分开查多个语句如果为true,强制生成一个语句如果没有设置,分页页生成多个语句,不分页时生成一个语句),多对多时,查询时,中间表的名子叫 (关联名_关联名)with选项的作用是eager loadingtogether的作用是 要不要形成一个语句当是一个sql语句是记录会有重复,这时候分页分出现相同的记录,加上group=&true即可,只要弄明白了,你生成的sql是一条还是多条sql就明白在多对多查询时的结果了两个表不是用主键关联'user' =& array(self::BELONGS_TO, 'OaskUser', '','on'=&'name=userName', 'select'=&'TrueName'),
[转] 腾讯产品宝典:产品手写全纪录
[转] 腾讯产品宝典:产品手记全纪录
一款产品,从脑袋里的一个想法变成真正让千万用户使用并创造价值的产品,往往要经过一个繁杂和漫长的过程,移动终端的产品遵循一个“快”字。项目周期要短一些,但只是相对时间缩短,完整的流程和环节却一个也不能少:立项--需求--开发--测试--上线。即便产品发布成功,依然有数据监控,用户反馈,产品运营推广,厂商内置合作等多个环节需要跟进。
如果将每个环节细化开来,又可以分出:概念设计--产品定位--需求定义--美术风格设计--交互定稿--UI输出--程序开发--测试—debug--发布前准备--上线……
只有亲自成功地把整个过程经历一遍,事无巨细,遇到问题解决问题,这样沉淀下来的经验,才是真正可以帮助我们成长的,这些经验胜过任何一本产品经理手册。
下面以手机QQ游戏大厅为例,详细和大家共同探讨。
【例】手机QQ游戏大厅是腾讯手机游戏的承载平台,自2009年上线以来,受到广大用户青睐,目前已经发展成为中国最大的手机休闲游戏娱乐平台。手机QQ游戏大厅具备休闲游戏、单机游戏、社区游戏、MMO网游等多种游戏类型的接入能力,覆盖Symbain、Kjava、Android等多个操作系统,随着iPhone及Pad版本的推出,2011年将实现手机操作系统的全平台覆盖并对外全面开放,相信在不断推陈出新,优化功能及用户体验的过程中,手机QQ游戏大厅会进一步巩固自己的领先优势,让更多的用户在手持移动终端上也能享受到PC电脑般的游戏体验,成为继手机QQ之后,移动平台上又一个明星产品。
1.概念设计:如何在一款海量用户的历史产品中寻求突破?
在接到Android版本的产品设计任务时,手机QQ游戏大厅已经成功发布了Symbain V3/V5、Kjava等多个版本,得到用户广泛好评且积累了大量的用户数据和反馈。Android版本可以说是手机QQ游戏大厅产品在中低端平台取得成功后,向智能机高端平台进军的一次尝试,预期是在延续之前版本的优秀品质和特性的同时,在设计和功能上有所突破。
在这种情况下,如何做一次概念设计,使之能够符合产品承前启后的预期?拍脑袋不行,一味的听取用户意见,最大限度的满足用户的愿望似乎也不可取。我们都有过这样的经验:因为晚到,中途才进入一个会议,这时候如果在不知道前人观点的情况下,就胡乱发表意见,总是欠妥的,正确的做法是选择倾听。没有充分了解问题的本质,就急于发表意见,积极主动的态度虽然不错,但意见往往会欠缺专业。所以我将之前发布的各个大厅版本的概念设计稿,需求文档,交互稿,各方的建议,用户的反馈……能找到的通通找出来,强迫自己不要急于动手,耐心的全部看一遍。同时,收集了市场上已有的竞争对手的作品,所谓的竞品分析。这个过程花了几天的时间,但非常非常值得!
关键词:寻求突破、倾听、积极学习历史文档、研究竞争对手
1.可以从历史版本的演变过程中,看到这个产品发展的轨迹,以及它可能的走向,对未来版本的产品趋势判断非常有帮助。
2.可以从被淘汰的概念设计中,看到淘汰的原因,哪些处理方式是不被推荐的,最终胜出的设计击中了哪些核心诉求。
3.可以从大量的用户反馈中轻易的找到目前版本还没有实现但是用户迫切需要的刚性需求。
4.可能发现自己想出来的很天才的想法,前人早就已经尝试过了,你几乎立刻就能知道这个想法是否有被采用,问题在哪里,而不用重复人力物力去再次求证。
5.可能发现前人的问题,做得不好的地方,而那正是你在新版本里需要解决的问题。
6.当然,我们要做的是全新的设计,而上面这些工作会让设计更靠谱,更有信心。
2.产品定位:定位是否清晰明确且正确?这个问题可能决定一款产品的命运
对历史版本有了充分的理解后,清楚了大厅功能的取舍原因,以及受到哪些技术上和规划定位上的限制。心里面对于大厅的产品特性,需求,未来版本的预期都有了比较具体的认识,再来做产品定位和版本规划就比闭门造车和拍脑袋要靠谱很多了。
我得到的结论是,尽管Android系统较Symbain/Kjava无论在硬件还是软件性能上都更加出色,给了大厅的设计提供了更多的可能性,但就目前的发展规划和实现进度而言,一个我们脑袋里面的完美大厅还需要相当长的时间分阶段分步骤的来实现,不可能一步到位。很多产品经理,尤其是新人,容易犯的一个错误是,一上来就使劲的加功能,把所有觉得很炫的功能都一股脑提成需求,最后发现进度Delay又掉回头来删啊删,最后的结果肯定是产品形态不完整,偏离了设计预期,因为欠缺整体规划,导致想加新功能的时候发现很多地方都要改。后续的可扩展性也比较差。
基于这样的结论,我给Android游戏大厅按实施顺序分为三个版本进行了定义:3 R2 `% w2 G- Z; }* f5 Y3 v
关键词:从全局出发,尝试站在更高的层面上,为产品做出长期规划
3.需求定义:做加法还是做减法?
产品定位清楚之后,就开始严格按照产品定位,对概念设计时罗列出来繁多的功能进行筛选和排序,将他们过滤并分配至每个小版本里面去,有理有据,也就对自己的设计更有把握和信心了。, b. B6 j/ q4 ?
一款成功产品的需求文档,不可能是一人之力完成的,一定是团队智慧的结晶。需求评审这里个人有一个经验,每次评审之前最好跟所有人都先沟通一下,解决大部分问题,这样可以让评审更顺利和高效,换句话说,就是要给自己机会,认真的倾听每一个伙伴的想法。4 l6 ^( n1 a# S
[! L+ u: O$ D
需求也不是一成不变的,在开发过程中,难免遇到这样那样的问题,有时候我们会由于技术或成本限制,出现暂时无法解决的问题,那就需要产品经理来从产品侧寻求解决方案。只要当需求发生变更时,及时知会到相关的开发和测试人员就好。
当然,有时候分歧也是在所难免的,大多数情况下可以通过沟通解决,Android大厅交互设计时就遇到过一个问题,基于android系统具有物理的返回键这一特性,是否应该在屏幕内提供全部虚拟按键,设计和产品据理力争,也没有得出结论,我们最终通过CE/灰度发布收集用户反馈的方式,解决了这个问题。不知道听谁的?听用户的!, @: {0 {( D. ?) X, A
关键词:数据最诚实,帮你拨开迷雾,击中用户最核心的诉求
4.美术风格设计:华丽和简约谁更美丽?
此次Android游戏大厅的美术风格设计几易其稿,前后历经三次改版,才最终呈现在用户面前,获得了用户的好评。
产品形态和视觉风格是相互影响的,这一点在无线的产品设计上体现尤为明显,无线业务系统总裁Tel曾经说过:“无线的资源永远都是有限的。”手机终端不但屏幕空间小,不同操作系统的用户都有其固有的操作习惯,同时还要兼顾速度和效率,对视觉和交互都有更高的要求。美术人员不了解这些特性,就很难做出符合用户需求的设计,相反,产品经理也需要在视觉上听取专业人士的意见,好的设计一定是建立在有效的沟通之上的。
关键词:美术风格是为产品服务的,产品好才是真的好!
5.开发与测试:又爱又恨的Bug!
a& E/ R# e) `/ L& l0 B* z
本次Android大厅在技术上有很多新的尝试,例如首次实现了游戏免安装,绕过了系统的安装步骤,即下即玩,无需安装,极大的提升了用户体验。
使用下载器的形式,根据用户屏幕分辨率适配最优版本,从根本上解决了用户经常安装错误版本,体验不好的问题,得到了用户的好评。5 Y
`( d3 A% V! t2 S2 c, K) A
在印象里产品经理和开发人员总是一对矛盾体,产品人员使劲想功能,搞的开发非常辛苦,但是一款产品从立项一直到最终上线,这两个角色也是最核心的,他们能否有效并高效的合作,可以说是一款产品能否最后成功的关键因素。于是,沟通变得非常重要,从产品的角度,沟通要有时效性,能当时给出结论的一定要当时给出,做一个有解决方案的产品经理。同时,在做产品设计时,要充分参考开发的意见,不懂可以问,但一定要和开发确认清楚,在考虑功能和需求的同时,也要考虑到后台实现,在技术上产品人员有很多不足,所以一定要积极的向开发人员请教。让开发人员理解你的真正意图,在开发动手之前就达到一致,把不一致的地方解决掉,从而降低了之后反复修改的机率。
每个新产品经理一定都记得产品第一次提测时的紧张心情,很怕出问题,其实这是个错误的心态,测试是为了尽可能降低发布风险,测出的问题越多,发布后可能的问题就越少,所以虽然产品人员和测试人员的视角和思维是不同的,但在工作上却是极大的互补,专业测试人员缜密的思维可以帮助产品经理快速的发现遗漏的问题,甚至于在提测前,每次测试用例的撰写都是一次补充和完善产品需求的机会。
项目的成败不会取决于需求文档,但高品质的需求文档却是一定是好项目的一部分,产品经理在做需求时考虑的不仔细全面,不了解技术实现,对整个团队包括测试和开发人员,都会带来很大的负面影响以及重复劳动,不断地需求变更,推翻,和无法实现的功能,迭代安排问题,都将导致项目的拖延甚至最终的延期。
关键词:每时每刻的去体验,问题发现的越早越好!
6.团队的力量:如何减少分歧,最大化团队优势?
在整个产品策划过程中,一直离不开团队的帮助,从最开始看的文档,就是团队成员的智慧结晶,到产品定义好,出来见人的时候,也要和团队成员一次又一次的开评审会,听取大家的意见,团队成员的经验可以让产品少走弯路。当然,意见出现分歧,PK也在所难免,但就在一次次的碰撞中,产品慢慢得到修正和完善。8 V% ~3 P
P/ j& A8 H( {7 O
除了组内的合作,Android大厅在开发过程中,还遇到了跨部门,跨BU,甚至跨城市的合作,目前已经上线的Android手机QQ游戏大厅 Beta 1版本就是由CDC的同学们操刀完成。困难的问题大家一起想办法,出现问题及时沟通,出现分歧从产品本身出发,产品好才是真的好。
很多新产品经理容易犯的一个错误是,太强调自己!交互、美术……统统都要自己主导,有想法是很好的,适当的坚持也是必须的,但更重要的一点,是要充分的信任你的团队,相信每个人在他各自的领域,都是最专业的。相信专业人士的意见,并学习,培养相关的专业感觉,我相信是对产品经理更有意义的事情。
我们知道,一款产品,无论有多优秀,都不可能满足所有用户的所有需求。即使明确了目标用户最核心的诉求,同样无法满足所有目标用户的预期,这是一定的,从产品人员的角度,我们能做的永远都是,优化自己的产品,一点一点尝试满足再多一点人的预期。所以,产品的体验和质量,永远有再提高的空间。) j* ]8 D; E5 D( w! J
Android游戏大厅目前已进入Beta 2版本的开发,计划在6月会有新的版本上线,相对Beta 1版本又做出了相当程度的改变和优化,这是一个渐变的过程。我相信所有的好产品,对用户负责的产品,都一定要经历这样的一个过程,从最初产品经理脑袋里面的一个构想,变成可以用的软件,逐渐发现更多用户诉求,针对用户的意见和使用习惯做改进,慢慢的将产品变好。
关键词:充分信赖你的伙伴,他们都是最专业的!
感谢一起奋斗的队友,感谢一起走过的岁月,感谢而未来更长!
以下是腾讯流传一篇老文章,但常看常新,每一次看都不一样的收获。-----------------------昨天听了pony在峰会上的讲座,收获颇丰,晚上回家后把记录的笔记整理了一下,先放上来和大家分享一下。整理时间较短,如有不周全之处,大家谅解:) - ~- Q% B
o# m- M( |, Y5 ?2 S背景:pony是公司的首席体验官、首席产品经理。这次在产品峰会上pony将自己平时经验的积累与大家交流,体验较细。这次分享研发管理部,设计中心整理了些材料。主要的案例是qqmail和qq影音的内容。以此为demo来讲解。 3 O+ [; f$ O6 ^# z5 U" }' Rpony的讲解主要分为三大部分:产品设计、产品运营、交互设计。 . L) n* N' n" o! L5 ]5 V, T在开场首先提到,互联网同类产品竞争激烈,只有抓住用户的心才能持续走下去。产品要赢得用户的心,要从一些小的点来赢得用户。 # v
\2 y: J- \# N/ B) f% Z第一部分:产品设计 这个部分,感受最深的是两个词:核心能力、口碑。这部分还着重提到了pony对产品经理素质、开发人员心态的期望。 核心能力 任何产品都有核心功能,能帮助到用户,解决用户某一方面的需求,如节省时间、解决问题,提升效率等等。 很多产品经理对核心能力的关注不够,不是说完全没有关注,而是没有关注到度。核心能力不仅仅是功能上也,也包括性能上的。对于技术出身的产品经理,特别是做后台出来的,对于性能的关注,如果自己有能力、有信心做到对核心能力的关注,肯定会渴望将速度、后台做到极限。现在很多产品都没做好,一抓问题一大堆。如,前阵子网页速度优化,好多东西可以优化,一下提速好多,之前不知道都做什么去了。之前用户忍受了很久,同时浪费时间、浪费我们的资源。不抓,都没人理,很说部过去。要在性能方面放入更多精力。 ! E5 P5 }+ s( M+ F
}+ q3 S. R谈到核心的能力,首先要有技术突破点。如做影音的时候,不是要做人家有我也有的东西。以前公司做的你有我有的东西,总是排在第二第三,虽然也有机会,但缺乏第一次出来亮相失去用户的认同感。 第一要关注你的产品的硬指标,在设计和开发的时候要考虑到外部会将对它与竞争对手做评测。如播放能力,占用内存。qq影音的核心性能和速度直接超越暴风影音。这样就能看到用户很多的好评和口碑。所以之后如果qq影音不出大问题,发展的势头将会很好。 / H0 Y/ K% E0 V, z% @! p硬指标评测cpu占用、高清加速,当时也有很多发展方向,如网络播放啊、交流啊、分享啊,也是思路。现在都砍掉,就是要做播放器,是用户的需求,纯用户需求不需要多少钱的。高清的,并不是很多人需要的,但是是高端用户的需求(这个后面口碑创造会再提到)。只有硬指标满足了,用户说,我这个破机器,暴风影音不能放,qq影音能放。这句话说出来,这样口碑就出来了。用户知道你行,差异化出来了。口碑要有差异性。 ' @3 K* i& C8 ^; Y4 q& N) U$ p, E4 i3 Y1 m核心能力要做到极致。要多想如何通过技术实现差异化,人家做不到,或者通过半年一年才能追上来。 - ?! Y9 H0 N% K0 M5 u8 [- x如,用户总评论qq的时候说用qq唯一的理由是传文件快,有群。于是这些就是我们的优势,那我们就要将优势发挥到极致。我们需要更加深入的去想,要想到要不要做传输速度、中转啊。离线传文件在邮件体现就是一个中转站,超大文件,也不难,就是要去做。产品部门很快的去做,去测试。用户用的量也不一定大,但几个月用一次,口碑就来了。用户会说,我要传大文件,找了半天找不到可以传的地方,万般无赖之下用了很烂的qqmail,居然行了。于是我们的口碑就来了。做了很多测试、逐步放量,看变化,因为到期就删掉,成本也没提升多少。 , F6 h3 V" H- I+ y要做大,要考虑到如何做到极致让人家想到也追不上,我们这么多年在idc上的功力不能浪费,需要我们去做。高速上传、城域网中专站,支持高速地上传……,又发现问题,如不在邮件,在im做怎么体验,这个我们在后面要逐步考虑到做起来。我们的目的是要让用户感到超快、飞快,让用户体验非常好。这些都需要大量技术和后台来配合。 $ U( t7 D" M. K) X产品的发展都需要产品经理来配合。现在我们产品经理有是做研发出身的不多。而很多产品和服务是需要大量技术背景的,目前我们希望的产品经理是非常资深的,做过前端、后端开发的技术研发人员晋升而来的,刚毕业的人员来做产品经理很人担心。好的产品最好交到一个有技术能力的、有经验的产品人员手上,会让大家更加放心。如果产品人员太烂,让很多兄弟陪着干,结果发现方向错误是非常浪费和挫伤团队士气的。 产品最难的是订优先级和先后次序。要看哪个是用户最核心的。功能好不好不是说有用户用了,用量多少了,写个报告统计下流量证明是好。这个是很错误的,好不好要看用户是不是要用这个功能,用户要用的实时出现。腾讯很多产品经理的激情还不够,做出来的产品比较大路货。虽然挑剔不出很不对的东西,但放出去用户也没有感觉,最后就不了了之。pony有时候很痛心,希望大家在产品设计之初就想的透彻一点。产品经理需要投入更多的关注度,关注度不一样,结果出来的很不一样。 $ a5 ]4 X: B) n$ x' ]$ Q, Z口碑 做产品要做口碑,要关注高端用户、意见领袖关注的点。以前的思路是抓大放小,满足大部分小白用户的需求。但是高端用户这块是真正可以拿口碑的。 ; ^* N' Z
Z/ ]8 v5 C) M* E如何提高口碑,看最高端用户的关注,这个是在基础功能比较好的情况下考虑。如邮件搜索啊,rss啊,这些是很炫的用户会在博客和论坛里面提及的。做起来也不难,在有能力的情况下保证。在产品已经成型的情况下,要考虑到,对高端用户的心态要不一样。如果想要获得高端用户的口碑,还需要在产品的设计上大气些。如,让用户在我们的qqmail上使用别的邮箱的地址,而不带任何自己qqmail的尾巴。之前我们做的时候不会自动保存别的邮箱的地址,自己心里打个小九九,让别人不方便使用外部邮箱地址,好使用我们的。这些小九九,高端用户都是看的出来,反倒不好。所以要改掉,要做到真正的方便到用户。 改变用户习惯要让他信任你,改变有过程的,需要通过我们的努力让用户慢慢改过来。如,关闭数字帐号,发现很多bug,拍拍都不知道改。如,独立密码,之前不是双密码,而是改整体密码。 ' \4 Q9 t. ~# s6 |, v! L需要满足高端用户,让他不要怀疑你、bs你。如浏览器到兼容,可能你会考虑很多浏览器的覆盖率不高而不去做,但在高端用户来看,这是个态度问题,如果你的产品连这个都没考虑,其他的我就都怀疑了。你这个产品团队的意识好不好。再如同文件夹是否对齐,是否会引起杀毒软件的报警,都是小事但要关注。 个性化服务,并不是大众化服务,也是拿口碑的。 , p$ V3 U/ W9 j0 D0 H! D5 z+ d一个产品在没有口碑的时候,不要滥用平台,如要im带呀,投入营销资源呀,要marking联系pr公司投放广告呀,广告位提要求……等着人家砍,想一半也够了。产品经理精力好像分布的很好50% 产品、30%营销、20%……。如果你在基础处控制的好,也可以。但90%的时候第一点都做不好。如果你的实力和胜算不到70-80%,那么把精力放在最核心的地方。在已经获得良好口碑,处于上升期的产品才考虑这些。 . L0 Y% x4 W8 X# D. T产品经理关注最最核心、获得用户口碑的战略点,如果这块没做透,做营销只是告诉用户过来,失望,再花更多的精力弥补,是得不偿失的。当用户没有自动在增长(用户会主动给朋友推荐来使用我们的产品的时候),看着用户的增长,否则不要去打扰用户,否则可能是好心办坏事。这个时候,每做一件事情,每加一个东西要很慎重的考虑,真的是有建设性的去增加产品的一个口碑。当用户口碑坏掉后,再将用户拉回来很难。 2 O
s# \& n' y% t# Y加功能,在管理控制功能上也要有技巧。在核心功能做好后,常用功能是要逐步补齐的。产品在局部、细小之处的创新需要永不满足。作为一个有良好口碑的产品,每加一个功能都要考虑清楚,这个功能给10%的用户带来好感的时候是否会给90%的用户带来困惑。如果有冲突的要聪明点,分情况避免。每个功能不一定要用的多才是好,而是用了的人都觉得好就是好。 2 u0 z4 D" E* O
C4 }( r1 n, b) y( W* |$ C. I, j做产品开发的时候需要有较强的研发机制保证,这样可以让产品开发更加敏捷更加快速。有些需求,提一下都可以得到很快反应。qqmail也会每天排好规划,为什么能很快反应,如文件加锁。有些产品做个东西写ppt、做汇报……,人家顺手就做了。很多产品不敏捷,大家要敏捷点、就算是大项目也要灵活。不能说等3个月后再给你个东西看,这个时候竞争对手都不知道跑到好远了。 9 V# B& M2 h9 _3 S% m3 a* c/ b6 c' k开发人员的心态要关注产品,不要是公事公办的态度。你要知道用户、同行会关注你的产品,在这种驱动下开发人员要自动去完成。不能说什么都要产品做好后,流水线样的送过来我才做。开发人员要参与,40-50%左右的产品最终体验应该是由开发人员决定的。产品人员不要嫉妒有些工作是是开发人员设计的,只有这样才是团队共同参与的。如果都是产品想的就完蛋了,那么这个team做这个产品没有什么机会,必然会产生产品迭代慢的效果。这样一个格局太不行了。 + d, _* {& R7 G0 E3 t运营式管理 1 t9 F. H9 w. l( {& e! O/ U这个部分感受最深的一个词:天天用。这部分还谈到了pony眼中产品经理的一些基本要求。 7 L5 N( Z# n9 B# B2 N, J( c我们的产品不是单机版,需要有强的用户感和技术功底外,很重要的是服务。我们要关注很多很复杂的内容,如架构啊,应用啊,产品需要有更好的架构,这个是需要花很多精力,常态下可能看不出来。所以需要高层从kpi上考虑。这个是考功力,谁做的好,总办领导是看得到的。设计的好的架构不会手乱脚乱。如把核心的东西做成组件模块分发。 / e* f1 O; `! f' K+ p& b( \2 ~4 W# t/ q1 u/ J发现产品的不足,最简单的方法就是产品天天用。天天去看,去论坛,去博客、去订阅。产品经理要敏感点,找出你的产品不足之处。有的产品经理说找不出来很奇怪,上线的时候坚持三个月天天用,问题是有限的,一天发现一个,解决掉,这样慢慢的已经开始逼近你那个很有口碑的点了。不要因为工作没有技术含量就不去做,很多好的产品都是靠这个方法做出来的。对于高层来说,不仅仅是安排下面的人去做就可以了,一定要自己做。这些都不难,关键要坚持。意识要提高。你要做到每个周末,都心痒痒要去做。心里一定要想着,这个周末不试,肯定出事。这样坚持,到一个产品基本成型,就可以去看下个产品了。 6 E% ~( E" ]; e从哪个地方找,论坛啊、博客啊,rss订阅啊。高端用户不屑于去论坛提,在博客提,需要产品经理自己去追出来。如qqmail、影音的产品经理自己去查、去搜,然后主动和用户接触,解决,有些确实是用户搞错了,有些是我们的问题。产品经理心态要很好,希望用户能找出问题我们再解决掉。哪怕再小的问题解决了也是完成一件大事。有些事情做了,见效很快。运营方面要天天去看的,产品经理要关注多个方面,比如说你的产品慢,用户不会管你的idc烂或者其他原因,只是知道你慢。产品经理要全面,服务器端哪个方面的问题能找出来。跟踪用户定位问题。如果pony都能搜索到的问题,没看到产品经理出现,那么就是你没做到位。 ( |, l
I/ V% V% A# B$ r. a( R1 |1 n( }交互设计 ' y$ e2 P7 L1 L: T3 V4 L3 r) i6 H/ Y. R5 s) u- P- s& U交互要求我们细致,视觉简洁清爽。 产品经理要想到自己是个挑剔的用户,想像自己是个笨用户,复杂的看不懂。 ! |* b$ d: q2 K5 \" a) D* E5 m产品人员的精力有限,交互内容很多,所以要抓最常见的一块。流量、用量最大的地方都要考虑。规范到要让用户使用的舒服。要在感觉、触觉上都有琢磨,有困惑要想到去改善。如鼠标少移动、可快速点到等等。 ) e+ `" Q. q( F+ S像邮箱的一个按钮“返回”放在哪儿,上线测,放右边还是左边,大家都会多放琢磨,怎么放更好,并上线尝试,现在的方案折中比较好。如输入邮箱密码出错,输入框内的内容select上,不用用户清楚可以直接输出。这些都是对用户体验的优化。 如对同个用户发信,在此用户有多个邮箱的情况下会默认选最近用的一个帐号。这些需求都小,但你想清楚,用户就会说好,虽然用户未必说的出好在哪儿。 产品的使用要符合用户的习惯,如写邮件的时候copy东西,更多人习惯用键盘来操作。虽然有些技术难度,但也可以解决。交互,对鼠标反馈的灵敏性,便捷性。 9 {# Q1 l9 T9 E: H1 |0 C不强迫用户,如点亮图标。如qqmail,不为1%的需求骚扰99%的用户 4 }# S( _5 S3 B* s+ u操作便利,如qq音乐,新旧列表,两者都要兼顾到,如qq影音的快捷播放,从圆形到方形,最后因为影响性能而放弃。 2 |/ S) V' ^# g
j7 }美术、淡淡的,点到即止,如qqmail,qqmail在ui上的启发,不用太重也能做的很好。后来用在大量的产品,如hummer、影音。有图案和简洁不矛盾。 2 C4 K; m1 `# X7 z+ Y
R4 E* W. U# E重点突出,防止不必要的低龄化,还提到了一些内容,如产品成功关键点等等,这些在pony的ppt上有,没有记下来,大家可以在之后腾讯峰会吧(/group/forum)直接看pony的ppt。
E- g/ Y4 `7 l1 ^0 ?( R# A* P; Y+ J) X最后pony谈了一下有些产品的态度问题——态度很好,不解决问题。只做表面功夫,与其花一段时间写个长长的报告,不如实实际际的去解决问题。 2 [% N6 b
O+ q- i9 B1 J外部也有很多优秀的产品可以学习,学习不是学皮毛,学样子,要学会。 0 F; ?
@6 L外部可以学习的优秀产品,web类的,google、yahoo、facebook、apple,非web类的没有记录下来。 9 t+ Y, i) [* l, |$ }" Q# }+ C5 R1 d6 x5 v2 b总结一下: 1、核心功能要做透,做的人家追不上,自己的优势要尽量的发挥; 2、产品口碑要建立,要关注高端用户,要调整自己心态; 3、敏捷、快,产品迭代要快,快速实现、快速响应,要做到真正的迭代; 4、产品人员要全面,要能找出核心需求,要关注技术(架构、服务是不是好),要关注产品(天天用),要关注用户(还需要出去寻找问题并解决); & N. d7 d) v* M. w' d5、开发人员心态要好,要有参与感,不要被动的等; - _" z( t& [5 U5 a6、交互设计简洁,关注要点,当自己是个挑剔的、笨的用户; 7、想办法利用公司的资源,如pony等人都是大家的公共资源,要争取到pony对自己产品的关注,会给你的产品带来很多好的指导和创意。(这个不是pony 说的,是后来jeff提到的,不过很实在,pony做过这么多的产品,有这么好的产品感觉,如果给你的产品提出建议,是对产品很大的帮助)
如果您想提高自己的技术水平,欢迎加入本站官方1号QQ群:&&,&&2号QQ群:,在群里结识技术精英和交流技术^_^
本站联系邮箱:

我要回帖

更多关于 在qq游戏中 hlyxhd 的文章

 

随机推荐