怎样注册工程咨询师报考条件成i安全的服务对象?

在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象。ASP.NET Core中的DI容器体现为一个实现了IServiceProvider接口的对象。
ServiceProvider与ServiceDescriptor服务的注册与提供&&& 利用ServiceProvider来提供服务&&& 提供一个服务实例的集合&&& 获取ServiceProvider自身对象&&& 对泛型的支持 一、ServiceProvider与ServiceDescriptor 我一直觉得优秀的设计首先应该是简单的设计,至少是看起来简单的设计,这就是我们所谓的大道至简。作为一个服务的提供者,ASP.NET Core中的DI容器最终体现为一个IServiceProvider接口,我们将所有实现了该接口的类型及其实例统称为ServiceProvider。如下面的代码片段所示,该接口简单至极,它仅仅提供了唯一个GetService方法,该方法根据提供的服务类型为你提供对应的服务实例。
1: public&interface IServiceProvider
3:&&&& object GetService(Type serviceType);
ASP.NET Core内部真正使用的是一个实现了IServiceProvider接口的内部类型(该类型的名称为“ServiceProvider”),我们不能直接创建该对象,只能间接地通过调用IServiceCollection接口的扩展方法BuildServiceProvider得到它。IServiceCollection接口定义在“Microsoft.Extensions.DependencyInjection”命名空间下,如果没有特别说明,本系列文章涉及到的与ASP.NET Core依赖注入相关的类型均采用此命名空间。 如下面的代码片段所示,IServiceCollection接口实际上代表一个元素为ServiceDescriptor对象的集合,它直接继承了另一个接口IList&ServiceDescriptor&,而ServiceCollection类实现了该接口。
1: public&static&class ServiceCollectionExtensions
3:&&&& public&static IServiceProvider BuildServiceProvider(this IServiceCollection services);
6: public&interface IServiceCollection : IList&ServiceDescriptor&
9: Public class ServiceCollection: IServiceCollection
11:&&&& //省略成员
体现为DI容器的ServiceProvider之所以能够根据我们给定的服务类型(一般是一个接口类型)提供一个能够开箱即用的服务实例,是因为我们预先注册了相应的服务描述信息,这些指导ServiceProvider正确实施服务提供操作的服务描述体现为如下一个ServiceDescriptor类型。
1: public&class ServiceDescriptor
3:&&&& public ServiceDescriptor(Type serviceType, object instance);
4:&&&& public ServiceDescriptor(Type serviceType, Func&IServiceProvider, object& factory, ServiceLifetime lifetime);
5:&&&& public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
7:&&&& public Type&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ServiceType {& }
8:&&&& public ServiceLifetime&&&&&&&&&&&&&&&&&&&& Lifetime {& }
10:&&&& public Type&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ImplementationType {& }
11:&&&& public&object&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ImplementationInstance {& }
12:&&&& public Func&IServiceProvider, object&&&&&& ImplementationFactory {& }&&&&&
ServiceDescriptor的ServiceType属性代表提供服务的生命类型,由于标准化的服务一般会定义成接口,所以在绝大部分情况下体现为一个接口类型。类型为ServiceLifetime的属性Lifetime体现了ServiceProvider针对服务实例生命周期的控制方式。如下面的代码片段所示,ServiceLifetime是一个美剧类型,定义其中的三个选项(Singleton、Scoped和Transient)体现三种对服务对象生命周期的控制形式,我们将在本节后续部分对此作专门的介绍。
1: public&enum ServiceLifetime
3:&&&& Singleton,
4:&&&& Scoped,
5:&&&& Transient
对于ServiceDescriptor的其他三个属性来说,它们实际上是辅助ServiceProvider完成具体的服务实例提供操。ImplementationType属性代表被提供服务实例的真实类型,属性ImplementationInstance则直接代表被提供的服务实例,ImplementationFactory则提供了一个创建服务实例的委托对象。ASP.NET Core与依赖注入相关的几个核心类型具有如图10所示的关系。
由于ASP.NET Core中的ServiceProvider是根据一个代表ServiceDescriptor集合的IServiceCollection对象创建的,当我们调用其GetService方法的时候,它会根据我们提供的服务类型找到对应的ServiceDecriptor对象。如果该ServiceDecriptor对象的ImplementationInstance属性返回一个具体的对象,该对象将直接用作被提供的服务实例。如果ServiceDecriptor对象的ImplementationFactory返回一个具体的委托,该委托对象将直接用作创建服务实例的工厂。
如果这两个属性均为Null,ServiceProvider才会根据ImplementationType属性返回的类型调用相应的构造函数创建被提供的服务实例。至于我们在上面一节中提到的三种依赖注入方式,ServiceProvider仅仅支持构造器注入,属性注入和方法注入的支持并未提供。
二、服务的注册与提供
ASP.NET Core针对依赖注入的编程主要体现在两个方面:其一,创建一个ServiceCollection对象并将服务注册信息以ServiceDescriptor对象的形式添加其中;其二,针对ServiceCollection对象创建对应的ServiceProvider并利用它提供我们需要的服务实例。
在进行服务注册的时候,我们可以直接调用相应的构造函数创建ServiceDescriptor对象并将其添加到ServiceCollection对象之中。除此之外,IServiceCollection接口还具有如下三组扩展方法将这两个步骤合二为一。从下面给出的代码片段我们不难看出这三组扩展方法分别针对上面我们提及的三种针对服务实例的生命周期控制方式,泛型参数TService代表服务的声明类型,即ServiceDescriptor的ServiceType属性,至于ServiceDescriptor的其他属性,则通过方法相应的参数来提供。
1: public&static&class ServiceCollectionExtensions
3:&&&& public&static IServiceCollection AddScoped&TService&(this IServiceCollection services) where TService: class;
4:&&& //其他AddScoped&TService&重载
6:&&&& public&static IServiceCollection AddSingleton&TService&(this IServiceCollection services) where TService: class;
7:&&& //其他AddSingleton&TService&重载
9:&&&& public&static IServiceCollection AddTransient&TService&(this IServiceCollection services) where TService: class;
10:&&&& //其他AddTransient&TService&重载
对于用作DI容器的ServiceProvider对象来说,我们可以直接调用它的GetService方法根据指定的服务类型获得想用的服务实例。除此之外,服务的提供还可以通过IServiceProvider接口相应的扩展方法来完成。如下面的代码片段所示,扩展方法GetService&T&以泛型参数的形式指定服务的声明类型。至于另外两个扩展方法GetRequiredService和GetRequiredService&T&,如果ServiceProvider不能提供一个具体的服务实例,一个InvalidOperationException异常会被抛出来并提示相应的服务注册信息不足。
1: public&static&class ServiceProviderExtensions
3:&&&& public&static T GetService&T&(this IServiceProvider provider);
4:&&&& public&static&object GetRequiredService(this IServiceProvider provider, Type serviceType);
5:&&&& public&static T GetRequiredService&T&(this IServiceProvider provider);
利用ServiceProvider来提供服务
接下来采用实例演示的方式来介绍如何利用ServiceCollection进行服务注册,以及如何利用ServiceCollection创建对应的ServiceProvider来提供我们需要的服务实例。我们创建一个ASP.NET Core控制台程序,并在project.json中按照如下的方式添加针对 “Microsoft.Extensions.DepedencyInjection”这个NuGet包的依赖。
2:&& "dependencies": {
3:&&&& "Microsoft.Extensions.DependencyInjection": "1.0.0-rc1-final"&
我们接下来定义四个服务接口(IFoo、IBar、IBaz和IGux)以及分别实现它们的四个服务类(Foo、Bar、Baz和Gux)如下面的代码片段所示,IGux具有三个只读属性(Foo、Bar和Baz)均为接口类型,并在构造函数中进行初始化。
1: public&interface IFoo {}
2: public&interface IBar {}
3: public&interface IBaz {}
4: public&interface IGux
6:&&&& IFoo Foo { }
7:&&&& IBar Bar { }
8:&&&& IBaz Baz { }
11: public&class Foo : IFoo {}
12: public&class Bar : IBar {}
13: public&class Baz : IBaz {}
14: public&class Gux : IGux
16:&&&& public IFoo Foo { private }
17:&&&& public IBar Bar { private }
18:&&&& public IBaz Baz { private }
20:&&&& public Gux(IFoo foo, IBar bar, IBaz baz)
22:&&&&&&&& this.Foo =
23:&&&&&&&& this.Bar =
24:&&&&&&&& this.Baz =
现在我们在作为程序入口的Main方法中创建了一个ServiceCollection对象,并采用不同的方式完成了针对四个服务接口的注册。具体来说,对于正对服务接口IFoo和IGux的ServiceDescriptor来说,我们指定了代表服务真实类型的ImplementationType属性,而对于针对服务接口IBar和IBaz的ServiceDescriptor来说,我们初始化的则是分别代表服务实例和服务工厂的ImplementationInstance个ImplementationFactory属性。由于我们调用的是AddSingleton方法,所以四个ServiceDescriptor的Lifetime属性均为Singleton。
1: class Program
3:&&&& static&void Main(string[] args)
5:&&&&&&&& IServiceCollection services = new ServiceCollection()
6:&&&&&&&&&&&& .AddSingleton&IFoo, Foo&()
7:&&&&&&&&&&&& .AddSingleton&IBar&(new Bar())
8:&&&&&&&&&&&& .AddSingleton&IBaz&(_ =& new Baz())
9:&&&&&&&&&&&& .AddSingleton&IGux, Gux&();
11:&&&&&&&& IServiceProvider serviceProvider = services.BuildServiceProvider();
12:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IFoo&(): {0}",serviceProvider.GetService&IFoo&());
13:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IBar&(): {0}", serviceProvider.GetService&IBar&());
14:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IBaz&(): {0}", serviceProvider.GetService&IBaz&());
15:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IGux&(): {0}", serviceProvider.GetService&IGux&());
接下来我们调用ServiceCollection对象的扩展方法BuildServiceProvider得到对应的ServiceProvider对象,然后调用其扩展方法GetService&T&分别获得针对四个接口的服务实例对象并将类型名称其输出到控制台上。运行该程序之后,我们会在控制台上得到如下的输出结果,由此印证ServiceProvider为我们提供了我们期望的服务实例。
1: serviceProvider.GetService&IFoo&(): Foo
2: serviceProvider.GetService&IBar&(): Bar
3: serviceProvider.GetService&IBaz&(): Baz
4: serviceProvider.GetService&IGux&(): Gux
提供一个服务实例的集合
如果我们在调用GetService方法的时候将服务类型指定为IEnumerable&T&,那么返回的结果将会是一个集合对象。除此之外, 我们可以直接调用IServiceProvider如下两个扩展方法GetServeces达到相同的目的。在这种情况下,ServiceProvider将会利用所有与指定服务类型相匹配的ServiceDescriptor来提供具体的服务实例,这些均会作为返回的集合对象的元素。如果所有的ServiceDescriptor均与指定的服务类型不匹配,那么最终返回的是一个空的集合对象。
1: public&static&class ServiceProviderExtensions
3:&&&& public&static IEnumerable&T& GetServices&T&(this IServiceProvider provider);
4:&&&& public&static IEnumerable&object& GetServices(this IServiceProvider provider, Type serviceType);
值得一提的是,如果ServiceProvider所在的ServiceCollection包含多个具有相同服务类型(对应ServiceType属性)的ServiceDescriptor,当我们调用GetService方法获取单个服务实例的时候,只有最后一个ServiceDescriptor才是有效的,至于其他的ServiceDescriptor,它们只有在获取服务集合的场景下才有意义。
我们通过一个简单的实例来演示如何利用ServiceProvider得到一个包含多个服务实例的集合。我们在一个控制台应用中定义了如下一个服务接口IFoobar,两个服务类型Foo和Bar均实现了这个接口。在作为程序入口的Main方法中,我们将针针对服务类型Foo和Bar的两个ServiceDescriptor添加到创建的ServiceCollection对象中,这两个ServiceDescriptor对象的ServiceType属性均为IFoobar。
1: class Program
3:&&&& static&void Main(string[] args)
5:&&&&&&&& IServiceCollection serviceCollection = new ServiceCollection()
6:&&&&&&&&&&&&& .AddSingleton&IFoobar, Foo&()
7:&&&&&&&&&&&&& .AddSingleton&IFoobar, Bar&();
9:&&&&&&&& IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
10:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IFoobar&(): {0}", serviceProvider.GetService&IFoobar&());
12:&&&&&&&& IEnumerable&IFoobar& services = serviceProvider.GetServices&IFoobar&();
13:&&&&&&&& int index = 1;
14:&&&&&&&& Console.WriteLine("serviceProvider.GetServices&IFoobar&():");
15:&&&&&&&& foreach (IFoobar foobar in services)
16:&&&&&&&& {
17:&&&&&&&&&&&& Console.WriteLine("{0}: {1}", index++, foobar);
18:&&&&&&&& }
22: public&interface IFoobar {}
23: public&class Foo : IFoobar {}
24: public&class Bar : IFoobar {}
在调用ServiceCollection对象的扩展方法BuildServiceProvider得到对应的ServiceProvider对象之后,我们先调用其GetService&T&方法以确定针对服务接口IFoobar得到的服务实例的真实类型就是是Foo还是Bar。接下来我们调用ServiceProvider的扩展方法GetServices&T&获取一组针对服务接口IFoobar的服务实例并将它们的真是类型打印在控制台上。该程序运行后将会在控制台上生成如下的输出结果。
1: serviceProvider.GetService&IFoobar&(): Bar
2: serviceProvider.GetServices&IFoobar&():
获取ServiceProvider自身对象
对于ServiceProvider的服务提供机制来说,还有一个小小的细节值得我们关注,那就是当我们调用GetService或者GetRequiredService方法的时候若将服务类型设定为IServiceProvider,那么得到的对象实际上就是ServiceProvider自身这个对象。与之同理,调用GetServices方法将会返回一个包含自身的集合。如下所示的代码片段体现了ServiceProvider的这个特性。
1: class Program
3:&&&& static&void Main(string[] args)
5:&&&&&&&& IServiceProvider serviceProvider = new ServiceCollection().BuildServiceProvider();
6:&&&&&&&& Debug.Assert(object.ReferenceEquals(serviceProvider, serviceProvider.GetService&IServiceProvider&()));
7:&&&&&&&& Debug.Assert(object.ReferenceEquals(serviceProvider, serviceProvider.GetServices&IServiceProvider&().Single()));
对泛型的支持
ServiceProvider提供的服务实例不仅限于普通的类型,它对泛型服务类型同样支持。在针对泛型服务进行注册的时候,我们可以将服务类型设定为携带具体泛型参数的“关闭泛型类型”(比如IFoobar&IFoo,IBar&),除此之外服务类型也可以是包含具体泛型参数的“开放泛型类型”(比如IFoo&,&)。前者实际上还是将其视为非泛型服务来对待,后者才真正体现了“泛型”的本质。
比如我们注册了某个泛型服务接口IFoobar&,&与它的实现类Foobar&,&之间的映射关系,当我们指定一个携带具体泛型参数的服务接口类型IFoobar&IFoo,IBar&并调用ServiceProvider的GetService方法获取对应的服务实例时,ServiceProvider会针对指定的泛型参数类型(IFoo和IBar)来解析与之匹配的实现类型(可能是Foo和Baz)并得到最终的实现类型(Foobar&Foo,Baz&)。
我们同样利用一个简单的控制台应用来演示基于泛型的服务注册与提供方式。如下面的代码片段所示,我们定义了三个服务接口(IFoo、IBar和IFoobar&T1,T2&)和实现它们的三个服务类(Foo、Bar个Foobar&T1,T2&),泛型接口具有两个泛型参数类型的属性(Foo和Bar),它们在实现类中以构造器注入的方式被初始化。
1: class Program
3:&&&& static&void Main(string[] args)
5:&&&&&&&& IServiceProvider serviceProvider = new ServiceCollection()
6:&&&&&&&&&&&& .AddTransient&IFoo, Foo&()
7:&&&&&&&&&&&& .AddTransient&IBar, Bar&()
8:&&&&&&&&&&&& .AddTransient(typeof(IFoobar&,&), typeof(Foobar&,&))
9:&&&&&&&&&&&& .BuildServiceProvider();
11:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IFoobar&IFoo, IBar&&().Foo: {0}", serviceProvider.GetService&IFoobar&IFoo, IBar&&().Foo);
12:&&&&&&&& Console.WriteLine("serviceProvider.GetService&IFoobar&IFoo, IBar&&().Bar: {0}", serviceProvider.GetService&IFoobar&IFoo, IBar&&().Bar);
16: public&interface IFoobar&T1, T2&
18:&&&& T1 Foo { }
19:&&&& T2 Bar { }
21: public&interface IFoo {}
22: public&interface IBar {}
24: public&class Foobar&T1, T2& : IFoobar&T1, T2&
26:&&&& public T1 Foo { private }
27:&&&& public T2 Bar { private }
28:&&&& public Foobar(T1 foo, T2 bar)
30:&&&&&&&& this.Foo =
31:&&&&&&&& this.Bar =
34: public&class Foo : IFoo {}
35: public&class Bar : IBar {}
在作为入口程序的Main方法中,我们创建了一个ServiceCollection对象并采用Transient模式注册了上述三个服务接口与对应实现类型之间的映射关系,对于泛型服务IFoobar&T1,T2&/Foobar&T1,T2&来说,我们指定的是不携带具体泛型参数的开放泛型类型IFoobar&,&/Foobar&,&。利用此ServiceCollection创建出对应的ServiceProvider之后,我们调用后者的GetService方法并指定IFoobar&IFoo,IBar&为服务类型。得到的服务对象将会是一个Foobar&Foo,Bar&对象,我们将它的Foo和Bar属性类型输出于控制台上作为验证。该程序执行之后将会在控制台上产生下所示的输出结果。
1: serviceProvider.GetService&IFoobar&IFoo, IBar&&().Foo: Foo
2: serviceProvider.GetService&IFoobar&IFoo, IBar&&().Bar: Bar
阅读(...) 评论()你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
查看Apple的图表。
在步骤#9中,服务器如何知道它实际上正在与有权购买的iPhone通话,并且Eve没有使用不诚实获得的收据进行重播?
收据可能有效,但不能证明发件人是授权方。
iPhone上是否有可用于签署收据的设备证书?
有没有办法将收据绑定到设备,或将收据绑定到iTunes帐户和设备,以便服务器可以验证?
Apple提供的易受攻击的方法
服务器可以通过验证购买:
iPhone应用程序在购买后收到。让iPhone 对其进行编码(您可以使用此开源)并将其发送到您的服务器。 (您甚至可以按原样发送它,并让服务器base64在验证之前对其进行编码。)
让您的服务器使用HTTP POST将单个密钥receipt-data和base64编码的发送到https://buy.itunes.apple.com/verifyReceipt的JSON请求。 (有关如何在各种服务器端语言中执行此操作的说明)
服务器将使用带有两个键的JSON对象进行响应:status是一个整数,receipt是重复的收据。
如果status为零,则应接受收据有效,非零值表示收据无效。
安全添加到Apple的方法
但是,有一些安全隐患。用户可以使用其他用户的收据,因为设备不与收据绑定,或者用户可以使用其他产品的收据,因为服务器不验证收据的产品ID。为确保不会发生这种情况,您还应该执行以下操作:
当您第一次收到应用程序中的收据时,立即通过HTTPS或SSL套接字等安全通道将其与一起发送到您的服务器。不要将它存放在任何地方,留在内存中。
在您的服务器上,将UUID和收据对存储在数据库中。
当设备发送UUID和收据对时,请向您的数据库确认收据尚未使用,并通过检查确保收据实际上是您的产品。收据只是一个JSON对象,因此您的服务器可以通过解码来自base64的收据来读取内容。
通过安全通道向设备返回响应,告知其购买是否:
以新身份验证(不在数据库中且有效)
过去进行过身份验证(相同的UUID和收据对已在DB中)
由于错误的产品ID而被拒绝
由于已将收据与另一个UUID一起使用而被拒绝。
由于收据仅存储在设备的内存中,并且您的应用程序使用设备的UUID(可以是,请参阅注释),并且所有购买的产品都会以安全的方式使用设备的UUID记录在服务器上;用户无法使用其他用户的收据来验证购买,也无法使用其他产品的收据,因为您检查了该收据。
如果要验证交易的其他详细信息,还可以验证收据中的其他字段。例如,如果您的产品是订阅,那么您也希望查看交易日期。
此外,用户不能假装是您的服务器,因为他们没有您的SSL证书,因此将设备置于具有与您相同名称的主机的专用网络上。
失败考虑因素
由于在用户的设备收到收据并与服务器进行验证之间可能会发生故障(例如,如果用户失去连接,或者您的服务器因维护而关闭),您也应该让用户“重新授权”。重新授权应从商店获取收据(使用)并将其重新发送到服务器,就像这是一次新购买一样。这应该很少需要使用,但应该可以保存用户在网络出现故障时必须重新购买产品。
多设备考虑因素
这意味着如果用户想要在多个设备上使用应用程序,他们将不得不多次购买该产品。这可能是期望的效果,但您可能应该在购买之前通知用户,因为他们可能希望能够在与其帐户关联的设备上使用内容。
如果收据还包含iTunes帐户信息,则身份验证可以使用该信息允许用户在其所有设备(但不是他们的朋友)之间共享内容。
我不认为可以将收据绑定到设备。
我的理解是允许您在多个设备上安装应用程序而无需额外费用。将其绑定到设备意味着如果您升级/更换手机,则需要再次购买所有应用。
我相信如果你无法读取用户的苹果ID,那么你唯一的防盗版保护措施就是保持每个transaction_id的下载请求数量(当然是服务器端),并限制它们超过一定值。
因此,如果您将其限制为50,则为用户提供合理的余地,以便在多个设备上部署应用程序及其内容并进行多次还原,但是对于想要分发带有效收据的盗版版本的人来说很难实现无限制恢复。当然,他们可以只发布一个包含所有内容的版本,但是你无能为力,至少他们不会对你的服务器征税。
UDID不再起作用
Beniot的答案很棒,但是,正如Joe D'Andrea所提到的,UDID已被弃用,我上一次尝试时,使用该调用获取UDID的应用程序在上传到iTunes时无法通过验证。
作为收据计数器替代的限时收据
要添加到hloupyhonza的答案,除了为特定收据设置“下载请求”计数器之外,您还可以按时间限制收据有效性。我发现任何合理的12到24小时之间的东西。
此方法还允许购买者在他拥有的任何其他设备上使用购买,只要他使用相同的Apple ID登录App Store即可。注意:每次恢复购买完成后,Apple都会返回一个全新的收据(包含原始收据的详细信息) - 这样可以使购买恢复超过我们为特定收据设置的时间限制。
防止“现成的”黑客
为了防止典型的“谷歌搜索”黑客攻击解决方案(我的数据显示这几乎构成了所有IAP黑客攻击尝试),我使用校验和(选择你最喜欢的算法,除非你想让它不漏,否则无关紧要)以下连接:
receipt-data json string
验证成功状态代码。
该应用程序将验证我们的验证服务器返回的校验和。这不是防水的,因为黑客可能会从你的应用程序的二进制文件中检索共享密钥。但它已经阻止了迄今为止所有“现成的”黑客攻击,这对我的使用来说已经足够了。
要回复问题请先或
关注: 0 人组织/团体登录
还不是志愿者?
青少年服务
注册志愿者 (人)
志愿服务时长 ({{ timeType }})
志愿服务组织 (个)
志愿服务团体 (个)
{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-")
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
地点:{{ item.province }}{{ item.city }}{{ item.area }}{{ item.address }}
开始时间:{{ new Date(item.startDate).toLocaleDateString().replace(/\//g, "-") + " " + new Date(item.startDate).toTimeString().substr(0, 8) }}
活跃志愿者
{{item.preview}}
{{item.preview}}
{{index+1}}{{item.districtName}}
{{item.countPass}}
志愿服务时长
{{index+1}}{{item1_2.districtName}}
{{ parseInt(item1_2.sumServicetime/60) }}

我要回帖

更多关于 注册工程咨询师报考条件 的文章

 

随机推荐