Dubbo 接口测试原理及多种方法实践总结

1、什么是 Dubbo

Dubbo 最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由 Java 开发,后来贡献给了 Apache 开源基金会组织。

下面以官网的一个说明来了解一下架构的演变过程,从而了解 Dubbo 的诞生原因:

  • 单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

  • 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web 框架(MVC)是关键。

  • 分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

  • 流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

2、Dubbo 架构简介

 

Dubbo 比较有特点的地方就是这个注册中心,平常我们测试较多的 HTTP 接口,直接请求接口,调用后端服务即可;而 Dubbo 是要先走注册中心获取服务的位置,下面来举个现实生活中的例子来说明。

现实举例

好比大家平常约朋友一起出去吃饭,听说川菜馆“赠李白”不错,然后需要找这家饭店在哪(用小蓝或小黄App),知道了具体的地址才出发,至于是走路,打车还是骑车,就随意了。

这里 App 就相当于注册中心(Registry),我们这群吃货就是消费者(Consumer),商家属于生产者(Provider)。商家把自己的信息注册在 App 上,消费者根据 App 查询到商家的信息,再根据信息找到商家进行消费。

2.1、Zookeeper 简介

之前经常有小伙伴问我 zk 干啥的?怎么用?下面就来简单了解一哈:

ZK,全称就是zookeeper,是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

下面的图示也可以清晰的说明zk的部署和工作的一些方式(具体的技术细节需要的话可以针对zk专门搜索学习):

  • Leader:集群工作的核心,事务请求的唯一调度和处理者,保证事务处理的顺序性。对于有写操作的请求,需统一转发给Leader处理。Leader需决定编号执行操作。
  • Follower:处理客户端非事务请求,转发事务请求转发给Leader,参与Leader选举。
  • Observer观察者:进行非事务请求的独立处理,对于事务请求,则转发给Leader服务器进行处理.不参与投票。
3、什么是 Dubbo 接口?

所谓的 Dubbo 接口,其实就是一个个 Dubbo 服务中的方法,而测试 Dubbo 接口就相当于我们测试人员充当消费者或者创造消费者去"消费"这个方法。

具体的方式有很多,代码、工具、命令皆可,在接下来的内容中来一一演示。

4、Dubbo 接口测试(创造消费者)
以下我将以本地的一个简单的 Dubbo 服务 demo 为例,演示 Dubbo 测试的各种方法。

interface只有两个,分别是OrderServiceUserService

OrderService


package com.qinzhen.testmall.service;


import com.qinzhen.testmall.bean.UserAddress;

import java.util.List;


public interface OrderService {


/**

* 初始化订单

* @param userID

*/

public List<UserAddress> initOrder(String userID);

}

UserService


package com.qinzhen.testmall.service;


import com.qinzhen.testmall.bean.UserAddress;

import java.util.List;


/**

* 用户服务

*/

public interface UserService {



/**

* 按照userId返回所有的收获地址

* @param userId

* @return

*/

public List<UserAddress> getUserAddressList(String userId);



/**

* 返回所有的收获地址

* @param

* @return

*/

public List<UserAddress> getUserAddressList();

}

JavaBean 对象 UserAddress 如下:


package com.qinzhen.testmall.bean;


import lombok.AllArgsConstructor;

import lombok.Data;

import java.io.Serializable;


@AllArgsConstructor

@Data

public class UserAddress implements Serializable {


private Integer id;

private String userAddress; //用户地址

private String userId; //用户ID

private String consignee; //收货人

private String phoneNum; //电话号码

private String isDefault; //是否为默认地址 Y-是 N-否


public UserAddress(){


}

}

创建一个provider 来实现UserService 的Interface :

实现方法中,根据 id 返回对应的用户地址信息即可:
···
package com.qinzhen.testmall.bootuserserviceprovider.service.impl;


import com.alibaba.dubbo.config.annotation.Service;

import com.qinzhen.testmall.bean.UserAddress;

import com.qinzhen.testmall.service.UserService;

import org.springframework.stereotype.Component;


import java.util.Arrays;

import java.util.Collections;

import java.util.List;


@Component

@Service //暴露服务

public class UserServiceImpl implements UserService {


private UserAddress userAddress1 = new UserAddress(1, "杭州市西湖区XX公司", "1", "qz", "12345678", "Y");

private UserAddress userAddress2 = new UserAddress(2, "杭州市西湖区花园", "2", "qz", "12345678", "N");


@Override

public List<UserAddress> getUserAddressList(String userId) {

if (userId.equals("1")){

return Collections.singletonList(userAddress1);

}

else if (userId.equals("2")){

return Collections.singletonList(userAddress2);

}

else {

return Arrays.asList(userAddress1, userAddress2);

}

}


@Override

public List<UserAddress> getUserAddressList(){

return Arrays.asList(userAddress1, userAddress2);

}

}

···

4.1 Java consumer 代码
下面我们编写  consumer代码,让服务消费者去注册中心订阅服务提供者的服务地址,以  RPC方式,获取远程服务代理,从而执行远程方法,代码也很简单,如下:
  • 代码结构:


实现场景就是实现OrderService 中的initOrder() 方法,初始化订单,初始化中直接调用userService 的getUserAddressLis(java.lang.String) 方法,具体代码如下:


package com.qinzhen.testmall.service.impl;


import com.qinzhen.testmall.bean.UserAddress;

import com.qinzhen.testmall.service.OrderService;

import com.qinzhen.testmall.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.List;


/**

* 1、讲服务提供者注册到注册中心(暴露服务)

* 1)导入dubbo依赖:操作zookeeper的客户端(curator)

* 2、让服务消费者去注册中心订阅服务提供者的服务地址

*/

@Service

public class OrderServiceImpl implements OrderService {


@Autowired

UserService userService;


public List<UserAddress> initOrder(String userId) {

//1.查询用户的收货地址

System.out.println("用户ID为:" + userId);

List<UserAddress> userAddressList = userService.getUserAddressList(userId);

return userAddressList;

}

}

  • consumer MainApplication

package com.qinzhen.testmall;


import com.qinzhen.testmall.bean.UserAddress;

import com.qinzhen.testmall.service.OrderService;

import com.qinzhen.testmall.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.stereotype.Service;


import java.io.IOException;

import java.util.List;



/**

* 1、将服务提供者注册到注册中心(暴露服务)

* 1)导入dubbo依赖:操作zookeeper的客户端(curator)

* 2、让服务消费者去注册中心订阅服务提供者的服务地址

*/

@Service

public class MainApplication {

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

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"consumer.xml"});

context.start();

OrderService orderService = context.getBean(OrderService.class); // 获取远程服务代理

List<UserAddress> userAddresses = orderService.initOrder("3");// 执行远程方法

System.out.println(userAddresses);

System.out.println("调用完成。。。");

System.in.read();

}

}

  • consumer.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


<context:component-scan base-package="com.qinzhen.testmall.service.impl"></context:component-scan>


<dubbo:application name="order-service-comsumer"></dubbo:application>


<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>


<!--声明需要远程调用远程服务的接口,生成远程服务代理-->

<dubbo:reference interface="com.qinzhen.testmall.service.UserService" id="userService"></dubbo:reference>


</beans>

  • 实例演示

首先确保provider已启动:

运行consumer,可以看到成功调用到dubbo方法,获取地址列表信息:

4.2 telnet+invoke
我们使用 telnet 命令可以直接访问对应的服务,但是前提是你需要知道服务对应的ip+端口。

如下配置文件,我们可以知道服务暴露在本地的20880端口


dubbo.application.name=boot-user-service-provider

dubbo.registry.address=127.0.0.1:2181

dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo

dubbo.protocol.port=20880

使用 telnet 命令进行访问,如下出现 Dubbo 字样时说明连接成功:


% telnet localhost 20880

Trying ::1...

Connected to localhost.

Escape character is '^]'.


dubbo>

Dubbo 内建的 telnet 命令的说明和用法如下

  • ls
    • ls: 显示服务列表
    • ls -l: 显示服务详细信息列表
    • ls XxxService: 显示服务的方法列表
    • ls -l XxxService: 显示服务的方法详细信息列表


dubbo>ls

com.qinzhen.testmall.service.UserService


dubbo>ls -l

com.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider&timestamp=1615088321885


dubbo>dubbo>ls com.qinzhen.testmall.service.UserService

getUserAddressList

getUserAddressList


dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService

java.util.List getUserAddressList(java.lang.String)

java.util.List getUserAddressList()
  • invoke
    • invoke XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法
    • invoke com.xxx.XxxService.XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用全路径服务的方法
    • invoke xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法(自动查找包含此方法的服务)
    • invoke xxxMethod({"name":"zhangsan","age":12,"class":"org.apache.dubbo.qos.legacy.service.Person"}) :当有参数重载,或者类型转换失败的时候,可以通过增加class属性指定需要转换类
    • 当参数为Map<Integer,T>key的类型为Integer时,建议指定类型。例如invoke com.xxx.xxxApiService({"3":0.123, "class":"java.util.HashMap"})

然后我们使用invoke 命令对dubbo方法getUserAddressList()进行调用,如下:


dubbo>invoke getUserAddressList()

[{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"},{"consignee":"qz","id":2,"isDefault":"N","phoneNum":"12345678","userAddress":"杭州市西湖区xx花园","userId":"2"}]


dubbo>invoke getUserAddressList("1")

[{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"}]

elapsed: 14 ms.

4.3 JMeter

对于 JMeter 测试 Dubbo 接口的方法,可参考往期文章:

4.4 Dubbo-admin

对于 Dubbo-admin 的安装调试,可参考文章:

《dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑》

4.5 泛化调用
测试 Dubbo 服务的时候,我们需要服务端的同学给我们提供 API,没有这个 API 我们是测不了的,而为了解决这个问题,Dubbo 官方又给我们提供了另外一个方法,就是泛化调用,来看看官方的解释:

 

  • 泛化调用的使用

Dubbo 给我们提供了一个接口GenericService,这个接口只有一个方法,就是$invoke,它接受三个参数,分别为方法名方法参数类型数组参数值数组

下面我们直接上代码演示:


import com.alibaba.dubbo.config.ApplicationConfig;

import com.alibaba.dubbo.config.ReferenceConfig;

import com.alibaba.dubbo.config.RegistryConfig;

import com.alibaba.dubbo.rpc.service.GenericService;

import org.junit.jupiter.api.Test;


public class TestDemo {


@Test

void testDubboGenericService(){

// 引用远程服务

// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存

ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();

// 弱类型接口名

reference.setApplication(new ApplicationConfig("order-service-consumer"));

reference.setInterface("com.qinzhen.testmall.service.UserService");

reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));


// 声明为泛化接口

reference.setGeneric(true);


// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用

GenericService genericService = reference.get();


Object result = genericService.$invoke("getUserAddressList", new String[] {"java.lang.String"}, new Object[] {"2"});

System.out.println(result);


}

}

运行后我们来看看结果,咦~也成功访问了:

 

  • 泛化调用的原理

我们通过 debug 跟入 Dubbo 的源码中,可以得到如下的调用链:

服务消费端:

 服务提供端:

从上面的调用链可以知道完成一次泛化调用,Dubbo 框架经历了很多过滤器,我们分别选取两端链路中的最后一步的 Filter 来简单了解一下泛化调用做了哪些事.

简化后的调用关系就如下:

先来看consumer 端的GenericImplFilter ,大概看下核心的处理步骤:


// 判断是否为泛化调用

if (invocation.getMethodName().equals(Constants.$INVOKE)

&& invocation.getArguments() != null

&& invocation.getArguments().length == 3

&& ProtocolUtils.isGeneric(generic)) {

// 获取泛化调用参数

Object[] args = (Object[]) invocation.getArguments()[2];

// 判断是否为nativejava方式

if (ProtocolUtils.isJavaGenericSerialization(generic)) {


for (Object arg : args) {

if (!(byte[].class == arg.getClass())) {

error(byte[].class.getName(), arg.getClass().getName());

}

}

// 判断是否为bean方式

} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {

for (Object arg : args) {

if (!(arg instanceof JavaBeanDescriptor)) {

error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());

}

}

}

// 设置为泛化调用方式

((RpcInvocation) invocation).setAttachment(

Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));

}

// 发起远程调用

return invoker.invoke(invocation);

再来看provider端的GenericFilter,大概的核心处理步骤如下:


package com.alibaba.dubbo.rpc.filter;


import ...


/**

* GenericInvokerFilter.

*/

@Activate(group = Constants.PROVIDER, order = -20000)

public class GenericFilter implements Filter {


@Override

public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {

// 判断是否为泛化请求

if (inv.getMethodName().equals(Constants.$INVOKE)

&& inv.getArguments() != null

&& inv.getArguments().length == 3

&& !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {

// 获取参数名称、参数类型、参数值

String name = ((String) inv.getArguments()[0]).trim();

String[] types = (String[]) inv.getArguments()[1];

Object[] args = (Object[]) inv.getArguments()[2];

try {

// 使用反射获取调用方法

Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);

Class<?>[] params = method.getParameterTypes();

if (args == null) {

args = new Object[params.length];

}

// 获取泛化引用方式使用的泛化类型

String generic = inv.getAttachment(Constants.GENERIC_KEY);

// 泛化类型为空的话就使用generic=true的方式

if (StringUtils.isEmpty(generic)

|| ProtocolUtils.isDefaultGenericSerialization(generic)) {

args = PojoUtils.realize(args, params, method.getGenericParameterTypes());

// 判断是否为generic=nativejava方式

} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {

for (int i = 0; i < args.length; i++) {

if (byte[].class == args[i].getClass()) {

try {

UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);

args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)

.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)

.deserialize(null, is).readObject();

} catch (Exception e) {

。。。

}

} else {

。。。

}

}

// 判断是否为generic=bean方式

} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {

for (int i = 0; i < args.length; i++) {

if (args[i] instanceof JavaBeanDescriptor) {

args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);

} else {

。。。

}

}

}

// 传递请求,执行服务

Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));

。。。

}

上面的代码很多,着重来提取一小段看一下:


Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);

Class<?>[] params = method.getParameterTypes();

从上面的代码中我们便可以得知原来泛化调用中使用了Java的反射技术来获取对应的方法信息完成调用的

4.6 用 Python 来测试 Dubbo
我们知道 Dubbo 是个 Java 项目,测试 Dubbo 就是模拟消费者去调用 Dubbo 的 Java 方法,那显而易见,用 Python 是肯定没法去直接调用Java的,但是在日常的工作中,很多小伙伴可能是 Pyhton技术栈的,或者因为一些测试条件限制亦或历史原因,必须要将 Dubbo 测试用 Python 实现以满足各种接口测试的一个组合。
  • 1. python-hessian库

Dubbo是支持hessian+http协议调用的,hessian是一种二进制序列化的方式。

了解到可以通过这种方式实现,具体没有尝试过,还需要开发在项目中将序列化的方式改为hessian,并且需要知道URL,有兴趣的小伙伴可以去了解一下。

  • 2. telnetlib库

telnetlib是Python3自带的一个库,可以调用telnet命令,其实也就相当于上面说到的使用telnet方式访问dubbo的方法

  • 3. 开发dubbo测试服务

我们可以使用 Java 来开发一个 Dubbo 测试的 Web 服务,实现上就可以使用 Dubbo 的泛化调用,然后我们再用 HTTP 访问的形式去访问这个服务,将我们的测试参数信息传过去,剩下的就交给 Java 去处理就好了。

这样经过封装设计后,可以实现 Python 端的使用者在访问 Dubbo 时就像在测试HTTP接口一样(例如 Python 的request库);另外服务的 IP、端口、注册中心等信息都不用出现在测试的工程中,只需要用环境标签做区分,在服务端进行请求转发即可,也保证了一定安全性。

大体上的思路流程如下:

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Doe 发布 [V1.0.0] 前段时间排查某问题的时候,想要快速知道某些dubbo接口(三无)的响应结果,但不想启动项目(因为这些项目不是你负责的,不会部署而且超级笨重),也不想新建一个dubbo客户端项目(占地方),也不想开telnet客户端连接口(麻烦而且有限制)。所以扣了dubbo的netty模块源码,封装了个收发客户端集成一个工具,可以快速调试dubbo接口。源码地址:https://github.com/VIPJoey/doe 极简模式 普通模式 目录结构 mmc-dubbo-api 接口项目,主要用于测试。 mmc-dubbo-provider dubbo提供者项目,主要用于测试。 mmc-dubbo-doe 主项目,实现dubbo接口调试。 deploy 部署文档 功能特性 极简模式:通过dubbo提供的telnet协议收发数据。 普通模式:通过封装netty客户端收发数据。 用例模式:通过缓存数据,方便下一次操作,依赖普通模式。 增加依赖:通过调用maven命令,下载jar包和热加载到系统,主要用来分析接口方法参数,主要作用在普通模式。 依赖列表:通过分析pom文件,展示已经加载的jar包。 其它特性 springboot 整合 redis,支持spring el 表达式。 springboot 整合 thymeleaf。 springboot 整合 logback。 netty rpc 实现原理。 开发环境 jdk 1.8 maven 3.5.3 dubbo 2.6.1 lombok 1.16.20 idea 2018 windows 7 安装步骤 安装jdk 安装maven,并设置好环境变量,仓库目录。 进入mmc-dubbo-api目录,执行mvn clean install命令,省api的jar包。 进入mmc-dubbo-doe目录,执行mvn clean install 命令,在target目录生成dubbo-doe-1.0.0-RELEASE.jar 在F盘(可以任意盘)创建目录F:\app\doe 把dubbo-doe-1.0.0-RELEASE.jar拷贝到F:\app\doe 把deploy目录中的所有文件拷贝到F:\app\doe 如果您电脑安装了git bash,可以在bash窗口运行 ./deploy.sh start,否则如果没有安装git bash,只能打开cmd切换到F:\app\doe目录,然后执行java -jar dubbo-doe-1.0.0-RELEASE.jar --spring.profiles.active=prd 打开浏览器,访问地址:http://localhost:9876/doe/home/index 全剧终
基于微信小程序的家政服务预约系统采用PHP语言和微信小程序技术,数据库采用Mysql,运行软件为微信开发者工具。本系统实现了管理员和客户、员工三个角色的功能。管理员的功能为客户管理、员工管理、家政服务管理、服务预约管理、员工风采管理、客户需求管理、接单管理等。客户的功能为查看家政服务进行预约和发布自己的需求以及管理预约信息和接单信息等。员工可以查看预约信息和进行接单。本系统实现了网上预约家政服务的流程化管理,可以帮助工作人员的管理工作和帮助客户查询家政服务的相关信息,改变了客户找家政服务的方式,提高了预约家政服务的效率。 本系统是针对网上预约家政服务开发的工作管理系统,包括到所有的工作内容。可以使网上预约家政服务的工作合理化和流程化。本系统包括手机端设计和电脑端设计,有界面和数据库。本系统的使用角色分为管理员和客户、员工三个身份。管理员可以管理系统里的所有信息。员工可以发布服务信息和查询客户的需求进行接单。客户可以发布需求和预约家政服务以及管理预约信息、接单信息。 本功能可以实现家政服务信息的查询和删除,管理员添加家政服务信息功能填写正确的信息就可以实现家政服务信息的添加,点击家政服务信息管理功能可以看到基于微信小程序的家政服务预约系统里所有家政服务的信息,在添加家政服务信息的界面里需要填写标题信息,当信息填写不正确就会造成家政服务信息添加失败。员工风采信息可以使客户更好的了解员工。员工风采信息管理的流程为,管理员点击员工风采信息管理功能,查看员工风采信息,点击员工风采信息添加功能,输入员工风采信息然后点击提交按钮就可以完成员工风采信息的添加。客户需求信息关系着客户的家政服务预约,管理员可以查询和修改客户需求信息,还可以查看客户需求的添加时间。接单信息属于本系统里的核心数据,管理员可以对接单的信息进行查询。本功能设计的目的可以使家政服务进行及时的安排。管理员可以查询员工信息,可以进行修改删除。 客户可以查看自己的预约和修改自己的资料并发布需求以及管理接单信息等。 在首页里可以看到管理员添加和管理的信息,客户可以在首页里进行家政服务的预约和公司介绍信息的了解。 员工可以查询客户需求进行接单以及管理家政服务信息和留言信息、收藏信息等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值