2010年06月6日 10:43 上午 fisher 分类: 技术

Web Service技术内幕

–Web Service的详细教程和协议分析

2010/2/25      蒋彪 于南京

1. Web Service的介绍

1.1 Web Service到底是什么?

研究一下当前的应用程序开发,你会发现一个绝对的倾向:人们开始偏爱基于浏览器的瘦客户应用程序。这当然不是因为瘦客户能够提供更好的用户界面,而是因为它能够避免花在桌面应用程序发布上的高成本。发布桌面应用程序成本很高,一半是因为应用程序安装和配置的问题,另一半是因为客户和服务器之间通信的问题。

传统的Windows富客户应用程序使用DCOM来与服务器进行通信和调用远程对象。配置好DCOM使其在一个大型的网络中正常工作将是一个极富挑战性的工作,同时也是许多IT工程师的噩梦。事实上,许多IT工程师宁愿忍受浏览器所带来的功能限制,也不愿在局域网上去运行一个DCOM。在我看来,结果就是一个发布容易,但开发难度大而且用户界面极其受限的应用程序。极端的说,就是你花了更多的资金和时间,却开发出从用户看来功能更弱的应用程序。不信你问问你的会计师对新的基于浏览器的会计软件有什么想法:绝大多数商用程序用户希望使用更加友好的Windows用户界面。

关于客户端与服务器的通信问题,一个完美的解决方法是使用HTTP协议来通信。这是因为任何运行Web浏览器的机器都在使用HTTP协议。同时,当前许多防火墙也配置为只允许HTTP连接。

许多商用程序还面临另一个问题,那就是与其他程序的互操作性。如果所有的应用程序都是使用COM或.NET语言写的,并且都运行在Windows平台上,那就天下太平了。然而,事实上大多数商业数据仍然在大型主机上以非关系文件(VSAM)的形式存放,并由COBOL语言编写的大型机程序访问。而且,目前还有很多商用程序继续在使用C++、Java、Visual Basic和其他各种各样的语言编写。现在,除了最简单的程序之外,所有的应用程序都需要与运行在其他异构平台上的应用程序集成并进行数据交换。这样的任务通常都是由特殊的方法,如文件传输和分析,消息队列,还有仅适用于某些情况的的API,如IBM的”高级程序到程序交流(APPC)”等来完成的。在以前,没有一个应用程序通信标准,是独立于平台、组建模型和编程语言的。只有通过Web Service,客户端和服务器才能够自由的用HTTP进行通信,不论两个程序的平台和编程语言是什么。

Web Service

对这个问题,我们至少有两种答案。从表面上看,Web Service 就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。这就是说,你能够用编程的方法通过Web来调用这个应用程序。我们把调用这个Web Service 的应用程序叫做客户。例如,你想创建一个Web Service ,它的作用是返回当前的天气情况。那么你可已建立一个ASP页面,它接受邮政编码作为查询字符串,然后返回一个由逗号隔开的字符串,包含了当前的气温和天气。要调用这个ASP页面,客户端需要发送下面的这个HTTP GET请求:

http://host.company.com/weather.asp?zipcode=20171

返回的数据就应该是这样:

这个ASP页面就应该可以算作是Web Service 了。因为它基于HTTP GET请求,暴露出了一个可以通过Web调用的API。当然,Web Service 还有更多的东西。

下面是对Web Service 更精确的解释: Web Services是建立可互操作的分布式应用程序的新平台。作为一个Windows程序员,你可能已经用COM或DCOM建立过基于组件的分布式应用程序。COM是一个非常好的组件技术,但是我们也很容易举出COM并不能满足要求的情况。

Web Service平台是一套标准,它定义了应用程序如何在Web上实现互操作性。你可以用任何你喜欢的语言,在任何你喜欢的平台上写Web Service ,只要我们可以通过Web Service标准对这些服务进行查询和访问。

新平台

Web Service平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,Web Service平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。在传统的分布式系统中,基于界面(interface)的平台提供了一些方法来描述界面、方法和参数(译注:如COM和COBAR中的IDL语言)。同样的,Web Service平台也必须提供一种标准来描述Web Service,让客户可以得到足够的信息来调用这个Web Service。最后,我们还必须有一种方法来对这个Web Service进行远程调用。这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。下面几个小节就简要介绍了组成Web Service平台的这三个技术。

XMLXSD

可扩展的标记语言(XML)是Web Service平台中表示数据的基本格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关的。无关性是比技术优越性更重要的:软件厂商是不会选择一个由竞争对手所发明的技术的。

XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。例如,整形数到底代表什么?16位,32位,还是64位?这些细节对实现互操作性都是很重要的。W3C制定的XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,并给出了一种语言来扩展这套数据类型。Web Service平台就是用XSD来作为其数据类型系统的。当你用某种语言(如VB.NET或C#)来构造一个Web Service时,为了符合Web Service标准,所有你使用的数据类型都必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。在第二章中,我们将深入XSD,学习怎样转换自定义的数据类型(例如类)到XSD的类型。

SOAP

Web Service建好以后,你或者其他人就会去调用它。简单对象访问协议(SOAP)提供了标准的RPC方法来调用Web Service。实际上,SOAP在这里有点用词不当:它意味着下面的Web Service是以对象的方式表示的,但事实并不一定如此:你完全可以把你的Web Service写成一系列的C函数,并仍然使用SOAP进行调用。SOAP规范定义了SOAP消息的格式,以及怎样通过HTTP协议来使用SOAP。SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。第三章我们会讨论SOAP,并结识SOAP消息的各种元素。

WSDL

你会怎样向别人介绍你的Web Service有什么功能,以及每个函数调用时的参数呢?你可能会自己写一套文档,你甚至可能会口头上告诉需要使用你的Web Service的人。这些非正式的方法至少都有一个严重的问题:当程序员坐到电脑前,想要使用你的Web Service的时候,他们的工具(如Visual Studio)无法给他们提供任何帮助,因为这些工具根本就不了解你的Web

service。解决方法是:用机器能阅读的方式提供一个正式的描述文档。Web Service描述语言(WSDL)就是这样一个基于XML的语言,用于描述Web Service及其函数、参数和返回值。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的Web Service生成WSDL文档,又能导入WSDL文档,生成调用相应Web Service的代码。

一句话总结:Web Service就是高级版的DCOMCORBA

1.2 Web Service的体系结构图

通过上图,我们能看出来,WebService的运行机制有点类似于CORBA。

1.通过WSDL描述WebService

2.通过SOAP在互联网环境内传递数据

3.通过JAXB实现Java和XML之间的互相转换。

具体的Web Service的体系结构可以参照以下文件:

======================================================

深入浅出JAX-WS 2.0

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/11/5306034.aspx

SOA研究之 JAX-WS

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/10/5305049.aspx

手把手教你在Interstage上部署WebService

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/22/5317356.aspx

======================================================

.

1.3 一个简单的WebService运行实例

WebService代码:

package stock.server;

@javax.jws.WebService

public class StockQuoteProvider {

public StockQuoteProvider () {

}

public float getLastTradePrice (String tickerSymbol) {

return “abc”.equals(tickerSymbol)? 1234.0f : 0.0f;

}

}

客户端代码:

import java.lang.annotation.Annotation;

import stock.server.*;

import javax.xml.ws.Service;

public class StockQuoteClient {

public static void main(String[] args) throws Exception {

StockQuoteProviderService service = new StockQuoteProviderService();

StockQuoteProvider port = service.getStockQuoteProviderPort();

System.out.println(port.getLastTradePrice(args[0]));

}

}

2. Web Service技术研究的环境准备

2.1 安装运行环境

推荐安装GlassFish服务器,具体安装方法可以参见以下文章:

手把手教你 怎么 安装 GlassFish

http://blog.csdn.net/nanjingjiangbiao/archive/2010/01/28/5264913.aspx

2.2 下载WebService API(JAX-WS)的源代码

具体的下载URL参见以下地址:

https://jax-ws-sources.dev.java.net/source/browse/jax-ws-sources/

2.3 部署服务器端

1.把1.3中的服务器端部署到GlassFish中

2.用wsimport命令生成stub中间程序

3.把1.3中的客户端代码和stub代码,以及2.2下载的WebService源代码部署到Eclipse工程中,就可以DEBUG了。

具体的可以参照以下文章:

SOA研究之 JAX-WS

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/10/5305049.aspx

3. Web Service的技术内幕

3.1 客户端是如何和WSDL建立关系的?

1. 首先,我们看到客户端程序中有以下这样一行

StockQuoteProviderService service = new

StockQuoteProviderService();

2. 这行代码,会调用到

public StockQuoteProviderService() {

super(STOCKQUOTEPROVIDERSERVICE_WSDL_LOCATIONnew QName(“http://server.stock/”, “StockQuoteProviderService”));

}

3. 这行代码,会调用到com.sun.xml.ws.spi.ProviderImpl的

@Override

public ServiceDelegate createServiceDelegate( URL wsdlDocumentLocation, QName serviceName, Class serviceClass) {

return new WSServiceDelegate(wsdlDocumentLocation, serviceName, serviceClass);

}

4. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

/**

@param serviceClass

*      Either {@link Service}.class or other generated service-derived classes.

*/

public WSServiceDelegate(@Nullable Source wsdl, @NotNull QName serviceName, @NotNull final Class<? extends Service> serviceClass) {

~省略~

}

5. 而其中最关键的就是以下这段,解析阅读WSDL的代码,这段代码主要是用XMLStream来构建WSDL代码,不足为奇。

WSDLServiceImpl service=null;

if (wsdl != null) {

try {

URL url = wsdl.getSystemId()==nullnullnew URL(wsdl.getSystemId());

WSDLModelImpl model = parseWSDL(url, wsdl);

service = model.getService(this.serviceName);

if (service == null)

throw new WebServiceException(

ClientMessages.INVALID_SERVICE_NAME(this.serviceName,

buildNameList(model.getServices().keySet())));

// fill in statically known ports

for (WSDLPortImpl port : service.getPorts())

ports.put(port.getName(), new PortInfo(this, port));

catch (MalformedURLException e) {

throw new WebServiceException(ClientMessages.INVALID_WSDL_URL(wsdl.getSystemId()), e);

}

}

this.wsdlService = service;

3.2 客户端是如何和SEI建立关系的?

1. 首先,我们看到客户端程序中有以下这样一行

StockQuoteProvider port = service.getStockQuoteProviderPort();

2. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

private <T> T getPort(WSEndpointReference wsepr, QName portName, Class<T> portInterface,

WebServiceFeature… features) {

SEIPortInfo spi = addSEI(portName, portInterface, features);

return createEndpointIFBaseProxy(wsepr,portName,portInterface,features, spi);

}

3. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

/**

* Creates a new pipeline for the given port name.

*/

private Tube createPipeline(PortInfo portInfo, WSBinding binding) {

//Check all required WSDL extensions are understood

checkAllWSDLExtensionsUnderstood(portInfo,binding);

SEIModel seiModel = null;

if(portInfo instanceof SEIPortInfo) {

seiModel = ((SEIPortInfo)portInfo).model;

}

BindingID bindingId = portInfo.bindingId;

TubelineAssembler assembler = TubelineAssemblerFactory.create(

Thread.currentThread().getContextClassLoader(), bindingId);

if (assembler == null)

throw new WebServiceException(“Unable to process bindingID=” + bindingId);    // TODO: i18n

return assembler.createClient(

new ClientTubeAssemblerContext(

portInfo.targetEndpoint,

portInfo.portModel,

this, binding, container,((BindingImpl)binding).createCodec(),seiModel));

}

在这段代码中,使用了TUBE技术,把客户端和服务器之间建立了关系。

4. 这行代码,会调用到com.sun.xml.ws.util.pipe.StandaloneTubeAssembler的

@NotNull

public Tube createClient(ClientTubeAssemblerContext context) {

Tube head = context.createTransportTube();

head = context.createSecurityTube(head);

if (dump) {

// for debugging inject a dump pipe. this is left in the production code,

// as it would be very handy for a trouble-shooting at the production site.

head = context.createDumpTube(“client”, System.out, head);

}

head = context.createWsaTube(head);

head = context.createClientMUTube(head);

head = context.createValidationTube(head);

return context.createHandlerTube(head);

}

在以上代码中,开始和SOAP绑定,准备发送请求和调用函数等等。

3.3 客户端是如何发送请求的?

1. 首先,我们看到客户端程序中有以下这样一行

port.getLastTradePrice(args[0])

2. 这行代码,会调用到com.sun.xml.ws.client.sei.SEIStub的

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {

MethodHandler handler = methodHandlers.get(method);

if (handler != null) {

return handler.invoke(proxy, args);

else {

// we handle the other method invocations by ourselves

try {

return method.invoke(this, args);

catch (IllegalAccessException e) {

// impossible

throw new AssertionError(e);

catch (IllegalArgumentException e) {

throw new AssertionError(e);

catch (InvocationTargetException e) {

throw e.getCause();

}

}

}

3. 这行代码,会调用到com.sun.xml.ws.client.stub的(中间省去了2层不重要的代码)

/**

* Passes a message to a pipe for processing.

* <p>

* Unlike {@link Tube} instances,

* this method is thread-safe and can be invoked from

* multiple threads concurrently.

*

@param packet         The message to be sent to the server

@param requestContext The {@link RequestContext} when this invocation is originally scheduled.

*                       This must be the same object as {@link #requestContext} for synchronous

*                       invocations, but for asynchronous invocations, it needs to be a snapshot

*                       captured at the point of invocation, to correctly satisfy the spec requirement.

@param receiver       Receives the {@link ResponseContext}. Since the spec requires

*                       that the asynchronous invocations must not update response context,

*                       depending on the mode of invocationthey have to go to different places.

*                       So we take a setter that abstracts that away.

*/

protected final Packet process(Packet packet, RequestContext requestContext, ResponseContextReceiver receiver) {

configureRequestPacket(packet, requestContext);

Pool<Tube> pool = tubes;

if (pool == null)

throw new WebServiceException(“close method has already been invoked”); // TODO: i18n

Fiber fiber = engine.createFiber();

// then send it away!

Tube tube = pool.take();

try {

return fiber.runSync(tube, packet);

finally {

// this allows us to capture the packet even when the call failed with an exception.

// when the call fails with an exception it’s no longer a ‘reply’ but it may provide some information

// about what went wrong.

// note that Packet can still be updated after

// ResponseContext is created.

Packet reply = (fiber.getPacket() == null) ? packet : fiber.getPacket();

receiver.setResponseContext(new ResponseContext(reply));

pool.recycle(tube);

}

}

4. 这行代码,会调用到com.sun.xml.ws.api.pipe. __doRun的(中间省去了3层不重要的代码)

这段代码开始发送打成数据包的http请求

/**

* To be invoked from {@link #doRun(Tube)}.

*

@see #doRun(Tube)

*/

private Tube __doRun(Tube next) {

final Fiber old = CURRENT_FIBER.get();

CURRENT_FIBER.set(this);

// if true, lots of debug messages to show what’s being executed

final boolean traceEnabled = LOGGER.isLoggable(Level.FINER);

try {

while(!isBlocking() && !needsToReenter) {

try {

NextAction na;

Tube last;

if(throwable!=null) {

if(contsSize==0) {

// nothing else to execute. we are done.

return null;

}

last = popCont();

if(traceEnabled)

LOGGER.finer(getName()+’ ‘+last+”.processException(“+throwable+’)’);

na = last.processException(throwable);

else {

if(next!=null) {

if(traceEnabled)

LOGGER.finer(getName()+’ ‘+next+”.processRequest(“+packet+’)’);

na = next.processRequest(packet);

last = next;

else {

if(contsSize==0) {

// nothing else to execute. we are done.

return null;

}

last = popCont();

if(traceEnabled)

LOGGER.finer(getName()+’ ‘+last+”.processResponse(“+packet+’)’);

na = last.processResponse(packet);

}

}

~省略~

5. 这行代码,会调用到com.sun.xml.ws.encoding. StreamSOAPCodec的(中间省去了无数层不重要的代码)的

public ContentType encode(Packet packet, OutputStream out) {

if (packet.getMessage() != null) {

XMLStreamWriter writer = XMLStreamWriterFactory.create(out);

try {

packet.getMessage().writeTo(writer);

writer.flush();

catch (XMLStreamException e) {

throw new WebServiceException(e);

}

XMLStreamWriterFactory.recycle(writer);

}

return getContentType(packet.soapAction);

}

大家可以看到,他发出请求了。

3.4 服务器端是如何接受请求的?

1. 首先,服务器端有一个名叫JAXWSServlet的Servlet常驻服务器,监听请求。所以,请求会首先被转发给com.sun.enterprise.webservice.JAXWSServlet的

protected void doPost(HttpServletRequest request,

HttpServletResponse response)

throws ServletException ,IOException{

/**

* This requirement came from the jbi team. If the WebServiceEndpoint

* is a jbi endpoint which is private throw an error whenever a get

* or a post request is made

*/

Endpoint endpt =

wsEngine_.getEndpoint(request.getServletPath());

~省略~

2.最后,代码会调转到以下com.sun.xml.ws.transport.http.HttpAdapter的

final class HttpToolkit extends Adapter.Toolkit {

public void handle(WSHTTPConnection con) throws IOException {

boolean invoke = false;

try {

Packet packet = new Packet();

try {

packet = decodePacket(con, codec);

invoke = true;

catch(ExceptionHasMessage e) {

LOGGER.log(Level.SEVERE, “JAXWS2015: An ExceptionHasMessage occurred. ” + e.getMessage(), e);

packet.setMessage(e.getFaultMessage());

catch(UnsupportedMediaException e) {

LOGGER.log(Level.SEVERE, “JAXWS2016: An UnsupportedMediaException occurred. ” + e.getMessage(), e);

con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);

catch(Exception e) {

LOGGER.log(Level.SEVERE, “JAXWS2017: A ServerRtException occurred. ” + e.getMessage(), e);

con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);

}

if (invoke) {

try {

packet = head.process(packet, con.getWebServiceContextDelegate(),

packet.transportBackChannel);

catch(Exception e) {

LOGGER.log(Level.SEVERE, “JAXWS2018: An Exception occurred. ” + e.getMessage(), e);

if (!con.isClosed()) {

writeInternalServerError(con);

}

return;

}

}

encodePacket(packet, con, codec);

finally {

if (!con.isClosed()) {

con.close();

}

}

}

}

以上代码分为三块大的处理,分别是

packet = decodePacket(con, codec);

packet = head.process(packet, con.getWebServiceContextDelegate(),

packet.transportBackChannel);

encodePacket(packet, con, codec);

用来解析数据包,调用服务,发送回复数据包。这里就不详细介绍了。

4. 总结

希望能够通过这样的文章,更清晰的,直白的把WebService的详细技术内幕公布给大家,为开源软件运动作一份自己的贡献。

##以上##

发表于 @ 2010年02月25日 11:44:00 | 评论( 87 ) 举报收藏

2010年06月6日 9:27 上午 fisher 分类: 产品

手把手教你估算软件项目成本

2010-3-4 蒋彪 于南京

[背景]

软件项目一般来说可以分成两种:

A.     客户定制系统

B.     研发产品化系统

目前,国内绝大多数的都是在做A类型的客户定制系统,从接客户的单,到做客户的需求,拿到客户的合同,做开发,做实施,做后期维护之类的工作。

另外一种B类的,做产品研发的工作,国内涉及的人不多,而且它的项目估算里面涉及的问题很多,这里就不展开谈了。

做一个正常的软件项目,作为经营者和管理者,都想清楚地知道,这个软件项目有多大,要花掉多少成本,我能拿到的利润有多少,所以能不能准确地估算出软件项目的规模就显得很重要的。

下面我们来剖析一个小小的软件项目的规模估算。

[项目的需求文档]

假设现在,我们接到了一个项目,项目的名称是×××会员综合管理平台,决定采取传统的B/S架构来设计,我们首先要干的事情就是具体的分析这个项目的需求文档,只有在熟悉需求的情况下才能知道整体的规模。

具体的需求文档参见:

附件—系统的需求文档

[项目规模的概算]

我们大家都知道,正常的软件开发模式,比如瀑布开发模式的话,会分成

A.     需求分析

B.     基本设计

C.     详细设计

D.     Codeing

E.     UT

F.      CT

G.     RT

H.    后期维护

这么多阶段和步骤。但是根据,我所了解到的,国内除了少部分对日的大型公司会严格按照这种流程来做事情之外,绝大多数的国内公司还是随着自己的性子来。其中不乏,东软,联创之类的著名企业。所以我在制定项目概算的时候,还是按照国内的开发步骤来做:

大项目 中项目 小项目 人日
系统设计 数据库设计(大概10张表左右) —— 6
系统结构设计 —— 6
画面demo —— 10
系统开发框架搭建 —— 3
开发作业 会员管理子模块 会员开卡画面 1.5
会员开卡确认画面 0.5
会员信息检索画面 1
会员信息修改画面 1
会员休息修改确认画面 0.5
批量生成卡号 1
会员积分输入和修改 2
会员卡延期画面 2
会员卡挂失画面 2
商品管理子模块 商品录入画面 1
商品录入确认画面 0.5
商品检索画面 1
商品信息维护画面 1
库存管理 库存检索画面 1
库存新建画面 1
库存修改画面 1
库存信息确认画面 0.5
~省略~
测试作业 测试数据和计划的准备 —— 3
分模块测试 分画面测试 ~省略~
后期维护 系统上线安装 硬件安装,布线 1
环境安装,项目部署 1
简单的客户培训 3
维护 日常数据的维护 4
BUG的修正 5
总计 大约7人月以上

[结论]

软件公司在算钱的时候有几种方法:

A.       国内的比如联创之类,用项目分段方法收钱,做到哪一个阶段,或者完成了一个模板的上线就算前

B.       外包公司一般采用一个人月多少钱来收钱,比如对日外包一般是1万~2万一个人月。

对于老板而言,他要计算出项目的成本,也要这样算,比如以下:

(总人月:7人月) 项目成本 对客户收费
总价 7(市价:1/人月) >=8

# 为什么项目成本里面,一个人月会有1万呢

因为如果我们假设项目的成员构成如下:

职位 月工资
PM 60,00
SE 45,00
PG(5人) 25,00×6
公司日常运营费用(包括文职人员,会计,场地租金,旅游福利,公司上层的工资,电脑设备,和客户打交道的关系费—–) 500,00

于是我们就能得到:

月开销合计 75,500
平均一个人月 10,786

# 为什么项目最后的售价一定会大于8万呢

在今天的IT市场上,一般来说作客户定制系统的公司,利润率只有10%~20%,厉害一点的比如联创,日恒一般也就15%。

特别是现在每年5%的通货膨胀率,如果一个企业不拿到10%以上的利润,那这个公司一定会完蛋。

所以,7万×(最起码的利润率)10%>=8万。

证明完毕

—–以上——

以下软件管理相关文章,欢迎大家访问

========================================================

《对日外包项目 管理十日谈》

http://blog.csdn.net/nanjingjiangbiao/archive/2010/01/31/5274307.aspx

对日外包项目管理十日谈 之 第一日 接活

http://blog.csdn.net/nanjingjiangbiao/archive/2010/03/10/5364523.aspx

========================================================

【附件—系统的需求文档】

系统需求:

模块名 处理机能 机能详细
会员管理子模块 会员卡类型管理:分为储值型返现型、计次型、普通型。 储值型返现型属于预付费型会员卡,例如充100实到帐120。

计次型属于预付费型会员卡,例如500块/20次。

普通型分为两种:一种属于预付费型会员卡,在开卡之际需要充入一定的现金;还有一种仅是用于代表用户拥有某个商户的会员身份,仅用于积分或打折使用。

每种卡类型都有相对应的积分与消费折扣率。

会员卡管理:包括会员开卡、会员信息维护、批量生成卡号等功能。 会员开卡:会员首次办理会员卡时需录入会员的信息并生成相应的卡信息与会员信息对应。

会员信息维护:会员信息的查询,会员卡、会员身份信息的修改。

批量生成卡号:可以事先生成一批卡号,当用户需办理卡时,直接录入即可。无论是单独生成还是批量生成卡号,都需屏蔽不吉利的号码。

充值管理:有储值的会员卡在金额消费完毕后,需进行续费,若未续费,则会员卡暂不可用。 储值型返现型、计次型为开卡前一次性充值。使用完毕即结束,再次充值时,所充金额按卡类型的限止进行充值。

普通消费型:可充入金额不等,具体金额由商家自行确定。

会员积分 会员积分是一个可以灵活配置的功能。例如开卡送多少积分,不同类型的会员卡在消费时增加多少积分,在兑换礼品时减少多少积分等等。
会员卡延期 无论是哪种类型的会员卡,在建卡之初都会设置相应的结束时间,在结束时间到来时,若尚有余额未使用,用户可以申请延期,延期具体时间由商家自行决定。
会员卡挂失:用户在无意中丢失卡片后可以向办理卡片时的商户申请挂失。 挂失:用户凭办理时输入的密码与证件进行挂失。

取挂:用户若找到了丢失的卡片,可以取消挂失。

补卡:用户在挂失一段时间后,可以申请补卡。补卡时用户的会员卡号有可能会变,但会员卡编号是唯一的,不可变的。

商品管理子模块 商品类别管理:商家为自己的商品创建相应的类别。商品的类别分为真实商品与虚拟商品两种。 真实商品是现实中存在的商品,例如:香烟、酒、饮料等。

虚拟商品为空间或时间上的概念。

真实商品管理: 商品信息录入:各商家自行录入商品信息。

商品信息维护:包括商品信息的查询、修改、删除等功能。

虚拟商品管理: 商品管理:例如某个球场。3小时/100元。某种服务,100元/1次。
库存管理 库房管理 创建、维护、查询、删除本商家的库房信息。
供应商管理 创建、维护供应商信息。供应商名称,电话,具体联系人,销售产品等。
入库管理 新进商品的入库操作。商品的名称,数量,对应的供应商,存储的库房,保持期,最低库存告警点等。
出库管理 商品销售过程中,系统会对商品的数量进行自动的减少。
库存告警 当某种商品库存量低于设定的水平时,给予明确的告警。
消费管理子模块 预订管理 用户以电话的形式联系商家,并预订下到达的时间和所消费的服务。商家通过系统创建预订单,预订单中包含用户的联系信息或会员卡号、计划消费的服务、使用的场地等信息。
消费单生成 用户来到商家消费后,若是事先有预定则此时转化为相应的消费单,若是当场消费,则现场生成消费单。消费单中保存了用户在商户的一切消费行为,当最终进行费用结算时,若用户是会员则可将消费单与会员卡对接。
添加真实商品 为已正式生成的消费单添加商品,包括商品的数量,单价,消费时间等。
增加虚拟商品 为已正式生成的消费单添加虚拟的商品,虚拟的商品不同于真实商品,未必以数量为单位,可能是以时间或次数为单位。系统会详细记录会员消费的起始时间或次数,到会员结帐时自动根据记录计算出结果。
费用结算管理 系统会根据各商户所生成的消费单上的内容进行结算。这包括真实商品的数量与单价的乘积,虚拟商品所用时间或次数的计算结果,或者是二者之和。在计算出结果后,若用户持有会员卡,系统会根据会员卡的类型、商品的类型等进行打折、积分。
联合结帐 在上面结帐管理的基础上,可以将不同的消费单关联,并设置其中一张消费单为主结算单进行费用结算。
商家自助管理子模块 商家信息管理 对商家自身信息的管理、维护。商家充值功能。
员工管理 新建、维护员工。包括员工登陆系统的帐号,初始密码,有效期等。
员工销售情况统计 查看每个店内员工的商品或服务销售情况,可以借此衡量员工的业绩。
员工操作日志 查看每个店内员工的操作行为记录。
交班管理 员工与员工之间交班时的一种操作,主要是对上一班员工的各类数据的一个总结,新一班员工数据的重新开始录入。
提醒管理 分为两种提醒,一种是程序控制的提醒,在某些点上加入,到达限定条件即提醒(待议);一种是可配置的提醒,如,某年某月某日要做些什么。
短信群发申请 商家编辑短信的内容提交至管理员处统一发送。
邮件群发管理 可以从数据库中随机掏出指定人数用户向其发送邮件。
公告管理 针对店内员工的公告信息
计量单位管理 每个商家可以添加属于自己的计量单位,例如:个,次。这种仅限于页面展示,与价格换算无关联。
密码修改 对登陆系统密码的修改
统计报表 待定
系统管理 角色权限管理 平台中有众多商家,他们所包含的员工都有相应的角色,不同的角色所看见的功能不一样,角色由管理员统一创建。
商家管理 所有商家皆由此添加,在有效期到来之前,商家均可正常登陆系统进行操作。
地市信息管理 系统初始数据,一般不做变更,主要包含江苏省13个地市的信息。
提醒管理 分为两种提醒,一种是程序控制的提醒,在某些点上加入,到达限定条件即提醒(待议);一种是可配置的提醒,如,某年某月某日要做些什么。
短信群发管理 可以从数据库中随机取出指定人数用户向其发送短信。审批后,因按短信的条数扣除从商家的帐户上扣除一定的金额,若金额不够则不能审批。
邮件群发管理 可以从数据库中随机掏出指定人数用户向其发送邮件
公告管理 向所有的商家发布公告信息
密码修改 对登陆系统密码的修改
统计报表 待定

发表于 @ 2010年03月04日 18:05:00 | 评论( 202 ) 举报收藏

2010年05月31日 10:03 下午 fisher 分类: 后端

上次我看到建硕Blog这一片文章时还真没看懂。现在才明白,reCaptcha每次都拿出两个词,其实你只要输对第一个词就可以,第二个词你其实无须准确输入。这也太绝了点。

Google也在使用这个聪明的主意。除了Image LabelerSearchWiki这样基于兴趣和自愿的工具,我觉得最绝的一步棋是收购reCaptcha项目。reCaptcha是卡内基梅隆大学的spin-off项目。原本是为了帮助网站防止spam。Google收购的原因,确实为了帮助识别图书扫描中遇到的不可识别的扭曲的文字。每次用户会看到两个词。一个词是reCaptcha已经知道正确结果的词,另外一个词是不知道结果的。只有答对第一个词,就可以证明你不是机器,而第二个词的答案,如果和另外一个或者更多用户一样,就把这一答案作为正确答案,放入正确答案库里。就此轮回。可想而知,每天有多少人基于自己的目的(比如发个邮件,发个帖子等)来帮Google解决计算机解决不了的问题。

想想看,现在有多少网站使用reCaptcha的验证码服务?嘿,原来如此。

2010年05月30日 11:30 上午 fisher 分类: 后端

Blogger : mail2blog : [Tiger虎牙] 试试WP的mail2blog功能

插件基础 http://codex.wordpress.org/Blog_by_Email
中文hack http://blog.cnzz.com/?p=88

真正好用的,还是postie插件,具有很好的扩展支持。

2010年05月28日 10:05 下午 fisher 分类: 用户体验UE

图1 elya总结的UED流程

全文:产品UED流程及交付物
来自:落花流水——elya妞╰_╯

2010年05月3日 1:31 下午 fisher 分类: 电商

2007年7月27日,有一篇网志:http://iamfisher.blog.sohu.com/57105322.html 。这篇网志是keso斗牛士一篇网志后我的一篇回复的复制。keso那篇网志,从Google Adwords与Ali Golden Supplier的回报效率谈起,涉及电商界业态,不少朋友在keso博文后面一起聊,挺有价值。今天偶尔又去看,发现2009年有一篇新的回复,值得一看:

后来在訂閱文章中有提到該篇,於是遛達過來,看到博主的此段話很有意思,也跟貼說說自己的淺見。

【Google的价值到底是什么?。。。也不是简单的流量,Google的流量一直没有超过Yahoo!。Google的最大价值,或许就在于通过它所创造的价值。】

—-換言之,google到底是什麼類型的公司或組織(firm)?以經濟社會學的視角觀之,酷狗透過搜索一腳立足于【消費場域】,一手卻如千手觀音藉由抓住N多生產商之手法穿越【生產場域】。此乃其力量所在–因為其已經成為目前或今后很長一段時間里【交換場域】之王者。

—絕非簡單的廣告公司所可以定義。

—而隨著技術的創新,今后的壟斷公司大多是這類【雙性人】!

2010年03月28日 8:24 下午 fisher 分类: 技术

mount /dev/brain || tail -f /var/log/thoughts >> /pub/www
DEAN LEE:/DEV/BLOG

互联网永远都不乏追随者和抄袭者。因为创新太难,也太容易被如法炮制,所以很少有人会去啃这块骨头。而我们恰恰就是啃骨头的“傻子“:我们要做的不是中国制造,而是中国创造。这也是为什么我们做了那么多”傻事“的原因:我们总是想在老套的应用里,尝试一些新鲜的东西;总是想对这个似乎已经定型的世界说一声不:产品可以是另一个样子,世界也无需一成不变。(公开信全文)

技术理想主义的坚持,迹近于苦行者的坚贞。推荐访问:openid.35.com

2010年03月14日 5:40 下午 宝宝 分类: 新传

地球一小时——2010年3月27日,星期六,20:30

行动起来,关灯一小时,应对气候变化

2010年3月27日20:30,“地球一小时”活动将在全球开展。加入我们的行列,熄灭灯光,向全世界发出“紧急行动,应对气候变化”的倡议。

地球一小时2010活动将号召每个人承担责任,为确保一个可持续的未来发挥自己的作用。

地球一小时2010年的目标:全球6000多个城市、超过10亿人参与活动,政府、企业、社区积极加入,让“地球一小时”成为世界上规模最大的环保行动。 更多…

2010年03月11日 3:45 下午 i新传 分类: 技术, 电商

独立网店系统USHOP18,包括:优店体验优店导购USHOP18优店中国这四块。首页界面有五种(深蓝,亮蓝,灰,渐变灰,橙)不同的颜色效果,随机显示,因为我们各自喜欢的颜色不一样,然后程序员干脆都放上去了,让它随机显示。 更多…

2010年03月11日 3:24 下午 i新传 分类: 新传

这两天将blog做了一次不算大的改动,换上了简洁的新装,从原来的不定宽两栏结构改成现在的定宽两栏结构,加上一些细节的修改, 感觉清爽了很多。

第一次改WP的模板,虽然不会php,但之前有研究过zen cartecshop的模板修改,进行得还算顺利。目前就内容上还不够丰富,更新速度也比较慢,这还得我们努力呢。如果大家对这个模板有什么好的建议就给我们留言吧……欢迎提意见。