[转载]Getting started with ASP.NET Web API 2.2 for OData v4.0 - .NET Web Development and Tools Blog - Site Home - MSDN Blogs

mikel阅读(1035)

[转载]Getting started with ASP.NET Web API 2.2 for OData v4.0 – .NET Web Development and Tools Blog – Site Home – MSDN Blogs.

A few weeks ago we started publishing nightly builds for our initial support in ASP.NET Web API for the OData v4.0 protocol. Our OData v4.0 support is based on the OData Library for OData v4.0 that has been released in the past few months. The OData v4.0 protocol introduces a lot of changes and new features that allow more flexibility in the way to model services and improvements over many features from the past versions of the protocol.

In addition to this, the OData protocol has been recently ratified as an OASIS standard which will help bolster the adoption of the protocol by many companies and services all over the internet. If you want to know more about OData you can check the official site at www.odata.org where you can find the complete specification of the protocol and the features, the different formats supported and information about existing OData clients you can use in your apps. If you want to take a sneak peak at the new features and changes in the v4.0 version, you can do it here.

During the past few months, the Web API team has been working on the initial support for the v4.0 version. Many of the existing changes in the current nightly build deal with protocol and format changes from the v3.0 to the v4.0 version, but we have managed to add some interesting features to our current OData support. This list of features include:

1. OData attribute routing: This feature allows you to define the routes in your controllers and actions using attributes.

2. Support for functions: This feature allows you to define functions in your OData model and bind them to actions in your controller that implement them.

3. Model aliasing: This feature allows to change the names of the types and properties in your OData model to be different than the ones in your CLR types.

4. Support for limiting allowed queries: This feature allows the service to define limitations on the properties of the model that can be filtered, sorted, expanded or navigated across.

5. Support for ETags: This feature allows to generate an @odata.etag annotation based on some properties of the entity that can be used in IfMatch and IfNoneMatch headers in following requests.

6. Support for Enums: We’ve improved our support for Enums and now we support them as OData enumerations.

7. Support for $format: We’ve also added support for $format, so clients are able to specify the desired format of the response in the URL.

Important changes in this version

The OData v4.0 protocol includes a lot of new features and many changes to existing ones that improve the protocol and the modeling capabilities for the services implementers, but at the same time, those changes make difficult to support multiple versions of the protocol in a single implementation.

For that reason, we have decided to create a new assembly to support the v4.0 version of the protocol while maintaining the current assembly for those people who want to implement services based on previous versions.

One of the important goals with this new implementation has been to support the side by side scenario where customers can have v3 and v4 services running on the same application. To that effect, we had to make some changes in the current naming of some classes and methods to allow for a reasonable user experience. Here are the most important changes:

1. The package ID for the v4.0 is Microsoft.AspNet.OData.

2. The assembly name and the root namespace are now System.Web.OData instead of System.Web.Http.OData.

3. All the extension methods have been moved to System.Web.OData.Extensions.

4. We have removed all the extension methods that used to exist for HttpRequestMessage like GetODataPath or GetEdmModel and we have added a single extension method, ODataProperties that returns an object containing the common OData properties that were accessible by the old extension methods, like the IEdmModel of the service or the ODataPath of the request.

5. MapODataRoute has been changed to MapODataServiceRoute.

6. QueryableAttribute has been changed to EnableQueryAttribute.

For the sake of consistency between versions, we have done the same set of changes in the Microsoft.AspNet.WebApi.OData to achieve a similar development experience. Only the namespace remains System.Web.Http.OData in this version. The current methods and class names can still be used with the System.Web.Http.OData (OData v3.0), but we have marked them as obsolete, and they are not available in the new assembly.

Enough talking, let’s write an OData v4.0 service!

We’ll start our new OData v4.0 service by creating a simple web application that we’ll call ODataV4Service. We’ll chose to use the Web API template that will install the default Web API packages required for our application.

Once the basic application has been created, the first thing we need to do is update the existing Web API packages to use the nightly versions hosted on MyGet. In order to do that, right click on “References” in the project we have just created on the solution explorer, click on “Manage Nuget Packages” and expand the Updates section on the left.

image

Check that there is a source for WebStack Nightly, and if not, just proceed to add it by clicking the Settings button on the left bottom corner of the window and adding the source in the windows that appears after clicking, as shown in the following figure.

clip_image004

As you can see from the image, the URL for the nightly ASP.NET packages is http://www.myget.org/f/aspnetwebstacknightly/ and you can see all the different published packages on https://www.myget.org/gallery/aspnetwebstacknightly.

Now that we have setup our nightly package source we can go and update the Web API packages. In order to do that, we need to select the Include Prerelease option on the dropdown menu on the top of the window. Then we just need to click Update All.

Before leaving the Nuget Package Manager we need to install the Web API 2.2 for OData v4.0 package, in order to do that, we expand the Online tab, select the WebStack Nightly Source and the Include Prerelease option and then search for Microsoft.AspNet.OData.

clip_image006

After installing this package, we can exit the Nuget Package Manager and try running our application by pressing F5. The default page should appear in the browser.

At this point we have our application running on the latest 5.2 assemblies and we are ready to create our OData service. The first step is to create a model, for that we create a couple of C# classes representing entities as follow:

public class Player
{

    public virtual int Id { get; set; }

    public virtual int TeamId { get; set; }

    public virtual string Name { get; set; }
}

public class Team
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    public virtual double Rate { get; set; }

    public virtual int Version { get; set; }

    public virtual ICollection<Player> Players { get; set; }

    public Category Category { get; set; }
}

We are going to need some data to use, so we are going to use Entity Framework for that, in order to do that, we install the Entity Framework package from Nuget in the same way we have done with the OData package, except this time we pick the nuget.org package source and a stable version of the package. Then we create a context and include an initializer to seed the database with some data, as shown here:

 

public class LeagueContext : DbContext
{
    public DbSet<Team> Teams { get; set; }
    public DbSet<Player> Players { get; set; }

    static LeagueContext()
    {
        Database.SetInitializer<LeagueContext>(new LeagueContextInitializer());
    }

    private class LeagueContextInitializer : DropCreateDatabaseAlways<LeagueContext>
    {
        protected override void Seed(LeagueContext context)
        {
            context.Teams.AddRange(Enumerable.Range(1, 30).Select(i =>
                new Team
                {
                    Id = i,
                    Name = "Team " + i,
                    Rate = i * Math.PI / 10,
                    Players = Enumerable.Range(1, 11).Select(j =>
                        new Player
                        {
                            Id = 11 * (i - 1) + j,
                            TeamId = i,
                            Name = string.Format("Team {0} Player {1}", i, j)
                        }).ToList()
                }
            ));
        }
    }
}

The next step is creating our OData model. We are going to create it in the WebApiConfig.cs file as the next figure shows:

 

public static IEdmModel GetModel()
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();

    builder.EntitySet<Team>("Teams");
    builder.EntitySet<Player>("Players");

    return builder.GetEdmModel();
}

OData attribute routing

Now that we have created our model, we need to define the route for the OData service. We are going to use OData Attribute Routing to define the routes in our service. In order to do that, we need to open the WebApiConfig.cs file under our App_Start folder and add the System.Web.OData.Extensions and System.Web.OData.Routing namespaces to the list of our usings. Then, we need to modify our Register method to add the following lines:

ODataRoute route = config.Routes.MapODataServiceRoute("odata", "odata",GetModel());
route.MapODataRouteAttributes(config);

At this point we have successfully configured our OData service, but we haven’t defined yet any controller to handle the incoming requests. Ideally we would use scaffolding for this, but we are still working on getting the OData v4.0 scaffolders ready for preview (the existing scaffolders only support OData v3.0 services). So we have to create our controllers by hand, but we’ll see that with attribute routing it’s not difficult at all.

In previous versions of our Web API OData support, we had a very tight restriction on the names of the controllers, actions and even parameter names of our actions. With attribute routing, all those restrictions go away. We can define a controller or an action using whatever name we want as the following fragment of code shows:

 

[ODataRoutePrefix("Teams")]
public class TeamsEntitySetController : ODataController
{
    private readonly LeageContext _leage = new LeageContext();

    [EnableQuery]
    [ODataRoute]
    public IHttpActionResult GetFeed()
    {
        return Ok(_leage.Teams);
    }
    [ODataRoute("({id})")]
    [EnableQuery]
    public IHttpActionResult GetEntity(int id)
    {
        return Ok(SingleResult.Create<Team>(_leage.Teams.Where(t => t.Id == id)));
    }
}

As we can see on the figure above, we can use ODataRoutePrefixAttribute to specify a prefix for all the routes in the actions on the controller, and we can use ODataRouteAttribute to specify further segments that will get combined with the ones in the prefix. That way, the GetFeed action, represents the route /Teams and the GetEntity action represents routes like Teams(1), Teams(2), etc.

Support for Functions

Now that we have a basic service up and running, we are going to introduce some business logic. For that, we are going to define a function that will give us the teams whose rating is around a certain threshold with a given tolerance.

Obviously, we could achieve the same result with a query, but in that case, the clients of our service are ones responsible for defining the query and might make mistakes. However, if we give them a function, they only need to care about sending the right parameters.

In order to define a function that represents the business logic that we have specified, we can modify our GetModel function as follows:

 

public static IEdmModel GetModel()
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();

    EntitySetConfiguration<Team> teams = builder.EntitySet<Team>("Teams");
    builder.EntitySet<Player>("Players");

    FunctionConfiguration withScore = teams
        .EntityType
        .Collection
        .Function("WithScore");
    withScore.Parameter<double>("threshold");
    withScore.Parameter<double>("tolerance");
    withScore.ReturnsCollectionFromEntitySet<Team>("Teams");

    return builder.GetEdmModel();
}

Functions can be defined at the service level (unbounded), at the collection level (bounded to collection) or at the entity level (bounded to the entity). In this case, we have defined a function bounded to the collection, but similar methods exist on the ODataModelBuilder class (to define service level functions) and on the EntityConfiguration class (to define entity level functions).

Now, the last step is to define an action that implements the function, in order to do that, we are going to take advantage of attribute routing. The action in the figure below shows the implementation:

 

[ODataRoute("Default.WithScore(threshold={threshold},tolerance={tolerance})")]
[EnableQuery]
public IHttpActionResult GetTeamsWithScore(double threshold, double tolerance)
{
    return Ok(_league.Teams.Where(t =>
        (t.Rate < (threshold + tolerance)) &&
        (t.Rate > (threshold - tolerance))));
}

As you can see, the way we call the function is by using it’s fully qualified name after the entity set on which we want to call it. We use attribute routing to define the parameters of the function and bind them to the parameters of the action in a very elegant way. In this case a sample call to the function would use the following URL /odata/Teams/Default.WithScore(threshold=3, tolerance=2)

Important note: If you try this in IIS, you’ll probably get a 404 response. This is because IIS doesn’t like the dot in the URL on the last segment (IIS thinks it´s a file). One possible way to fix this is to add piece of configuration on you web.config to ensure IIS runs the routing module on all the requests.

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"></modules>
</system.webServer>

Model aliasing

So far we’ve seen attribute routing and functions, now we are going to show another very interesting feature, model aliasing. Many times we want to expose some data from our domain, but we want to change things like the names of the domain entities or the names of some properties. In order to do that, we can use model aliasing.

There are two ways to configure model aliasing in our model, we can do it directly through the model builder by setting the name property of the types and the properties of the types, or we can annotate our types with DataContract and DataMember attribute. For example, we can change our model using data contract in the following way:

[DataContract(Name = "Member")]
public class Player
{
    [DataMember]
    public virtual int Id { get; set; }
    [DataMember(Name = "Team")]
    public virtual int TeamId { get; set; }
    [DataMember]
    public virtual string Name { get; set; }
}

Support for limiting the set of allowed queries

As we said above, query limitations allow a service to limit the types of queries that users can issue to our service by imposing limitations on the properties of the types of the model. A service can decide to limit the ability to sort, filter, expand or navigate any property of any type on the model.

In order to do that, there are two options, we can use attributes like Unsortable, NonFilterable, NotExpandable or NotNavigable on the properties of the types in our model, or we can configure this explicitly in the model builder. In this case, we’ll do it though attributes.

 

public class Team
{
    public virtual int Id { get; set; }
    [Unsortable]
    public virtual string Name { get; set; }
    [NonFilterable]
    public virtual double Rate { get; set; }
    [NotExpandable]
    [NotNavigable]
    public virtual ICollection<Player> Players { get; set; }
}

The meaning of Unsortable, NonFilterable and NotExpandable is self-explanatory, as for NotNavigable it is a shortcut for specifying that a property is Unsortable and NonFilterable. When a client issues a query that involves a limited property, the server will answer with a 400 status code and will indicate the limited property that is causing the request to fail.

Support for ETags

The next feature we are going to see is ETags. This feature allows a service to define what fields of an entity are part of the concurrency check for the entity. Those fields will be used to generate an @odata.etag annotation that will be sent to the clients when returning the entity, either as part of a feed or just the single entity.

The client can use this ETag value in the If-Match and If-None-Match headers to implement optimistic concurrency updates and efficient resource caching. In order to mark a field as part of the concurrency check, we can use the ConcurrencyCheck attribute or the Timestamp attribute.

It’s important to note that we should use one or another, but not both at the same time. The difference strives in that ConcurrencyCheck is applicable to multiple fields of the entity and Timestamp is meant to be applied to a single field.

The individual properties can also be marked as part of the concurrency check explicitly using the model builder. In this case, we’ll do it through attributes. For example, we have modified the Team entity to add a Version property and mark it as part of the ETag for the entity. The result is shown in the next figure:

public class Team
{
    public virtual int Id { get; set; }
    [Unsortable]
    public virtual string Name { get; set; }
    [NonFilterable]
    public virtual double Rate { get; set; }
    [ConcurrencyCheck]
    public int Version { get; set; }
    [NotExpandable]
    [NotNavigable]
    public virtual ICollection<Player> Players { get; set; }
}

Now, we will serialize the ETag of the entity when we retrieve it through a GET request, but we still need to take advantage of the ETag on the actions of our service. In order to do that, we are going to add a Put action and we’ll bind ODataQueryOptions<Team> in order to use the ETag.

[ODataRoute("({id})")]
public IHttpActionResult Put(int id, Team team, ODataQueryOptions<Team> options)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != team.Id)
    {
        return BadRequest("The key on the team must match the key on the url");
    }

    if (options.IfMatch != null &&
        !(options.IfMatch.ApplyTo(_leage.Teams.Where(t => t.Id == id))
        as IQueryable<Team>).Any())
    {
        return StatusCode(HttpStatusCode.PreconditionFailed);
    }
    else
    {
        _leage.Entry(team).State = EntityState.Modified;
        _leage.SaveChanges();
        return Updated(team);
    }
}

As we can see, we can take advantage of the ETag by binding ODataQueryOptions as a parameter and using the IfMatch or IfNoneMatch properties in that object in order to apply the ETag value to a given query.

In the above example, we check if the ETag on the IfMatch header exists and if so, if it doesn’t the value of the Team with the id represented by the URL to return a Precondition Failed status in that case.

Support for Enums

We already had support for Enums in Web API OData v3.0 by serializing them as strings, but the new version of the protocol has added full support for them, so we have upgraded our Enum support accordingly. In order to use Enums you just need to define a property with an Enum type and we’ll represent it as an Enum in the $metadata and the clients will able to use Enum query operators in $filter clauses. There are also specific overloads on the model builder in case we want to configure the enumeration explicitly. Defining an OData Enum property in your type is as simple as this:

public enum Category
{
    Amateur,
    Professional
}

public class Team
{
    public virtual int Id { get; set; }

    [Unsortable]
    public virtual string Name { get; set; }

    [NonFilterable]
    public virtual double Rate { get; set; }

    [ConcurrencyCheck]
    public virtual int Version { get; set; }

    [NotExpandable]
    [NotNavigable]
    public virtual ICollection<Player> Players { get; set; }

    public Category Category { get; set; }
}

Support for $format

This feature allows a client to specify the format they want in the query string of the URL bypassing any value set by the accept header. For example, the user can issue queries like this one to get all the metadata in the response, instead of just the minimal ammount (which is the default):

http://localhost:12345/odata/Teams?$format=application/json;odata.metadata=full

The above query uses a MIME media type and includes parameters in order to ask for a specific JSON version.

http://localhost:12345/odata/Teams?$format=json

The above query uses an alias to refer to a specific MIME media type, application/json which in the case of OData is equivalent to application/json;odata.metadata=minimal

Using the .NET OData client to query the v4.0 service

The OData client for .NET has been released this week, the following blog post contains the instructions on how to use it to generate a client that can be used to query Web API for OData v4.0 services.

Note: If you plan to use $batch it won’t work properly with the client that gets generated by default. This is caused due to the fact that we are still using the beta version of OData Lib (we plan to update to the RTM version in the near future) and the client uses the RTM version of OData Lib. In order to workaround this issue, you can do the following:

Open the Nuget Package Console and downgrade the OData Client package to beta1 doing:

1. Uninstall-package Microsoft.OData.Client -RemoveDependencies -project <ProjectName>

2. Install-package Microsoft.OData.Client -version 6.0.0-beta1 -pre -project <ProjectName>

Perform the following changes on the T4 template mentioned on the blog:

1. Replace Microsoft.OData.Client.Key with Microsoft.OData.Service.Common.DataServiceKeyAttribute

2. Replace Microsoft.OData.Client.ODataProtocolVersion with Microsoft.OData.Service.Common.DataServiceProtocolVersion

Samples

Along with the nightly build we have published samples with the new features and new samples showing off common scenarios that target OData v4 in the ASP.NET codeplex site.

Conclusion

We have started publishing nightly builds for our OData v4.0 support that it’s built on top of the ODataLib support that has already shipped. Our nightly builds can be found on http://www.myget.org/f/aspnetwebstacknightly. The package ID to find them is Microsoft.AspNet.OData (Remember to select IncludePrerelease in the Nuget Package Manager).

We’ve also seen a brief introduction to all the new features in this version, like OData attribute routing, functions, model aliasing, query limitations, ETags, enums and $format. You can find samples for all the new features in https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/.

Enjoy!

[原创]PHP转换数据库查询结果为json

mikel阅读(1111)

   include 'db.php';

   $sql='select * from kw_projects';
   $result=mysql_query($sql);
   $results = array();
   while ( $row = mysql_fetch_assoc( $result )) {
		$results[] = $row;
  }
  echo json_encode( $results );

[转载]PHP ORM 个人实现 - ﹏Sakura - 博客园

mikel阅读(1071)

[转载]PHP ORM 个人实现 – ﹏Sakura – 博客园.

<?php
abstract class Model{
   protected $pk = 'id';
   protected $_ID = null; 
   protected $_tableName;
   protected $_arRelationMap;
   protected $_modifyMap;
   protected $is_load = false;
   protected $_blForDeletion;
   protected $_DB;

   public function __consturct($id = null){
       $this->_DB = mysql_connect('127.0.0.1','root','') ;
       $this->_tableName = $this->getTableName();
       $this->_arRelationMap = $this->getRelationMap();
       if(isset($id))$this->_ID = $id;
   }
   abstract protected function getTableName();
   abstract protected function getRelationMap();

   public function Load(){
       if(isset($this->_ID)){
           $sql = "SELECT ";
           foreach($this->_arRelationMap as $k => $v){
               $sql .= '`'.$k.'`,';
           }
           $sql .= substr($sql,0,strlen($sql)-1);
           $sql .= "FROM ".$this->_tableName." WHERE ".$this->pk." = ".$this->_ID;
           $result =$this->_DB->mysql_query($sql);
           foreach($result[0] as $k1 => $v1){
              $member = $this->_arRelationMap[$key];
              if(property_exists($this,$member)){
                 if(is_numeric($member)){
                     eval('$this->'.$member.' = '.$value.';');
                 }else{
                     eval('$this->'.$member.' = "'.$value.'";');
                 }
              }
           }
       }
       $this->is_load = true;
   }
   public function __call($method,$param){
      $type   = substr($method,0,3);
      $member = substr($method,3);
      switch($type){
         case 'get':
             return $this->getMember($member);
             break;
         case 'set':
             return $this->setMember($member,$param[0]);
      }
      return false;
   }
   public function setMember($key){
       if(property_exists($this,$key)){
          if(is_numeric($val)){
             eval('$this->'.$key.' = '.$val.';');
          }else{
             eval('$this->'.$key.' = "'.$val.'";');
          }
          $this->_modifyMap[$key] = 1;
       }else{
          return false;
       }
   }
   
   public function getMember($key,$val){
       if(!$this->is_load){
          $this->Load();
       }
       if(property_exists($this,$key)){
          eval('$res = $this->'.$key.';' );
          return $this->$key;
       }
       return false;
   }

   public function save(){
      if(isset($this->_ID)){
          $sql = "UPDATE ".$this->_tableName." SET ";
          foreach($this->arRelationMap as $k2 => $v2){
              if(array_key_exists( $k2, $this->_modifyMap)){
                  eval( '$val = $this->'.$v2.';');
                  $sql_update .=  $v2." = ".$val;
              }
          }
          $sql .= substr($sql_update,0,strlen($sql_update));
          $sql .= 'WHERE '.$this->pk.' = '.$this->_ID;
      }else{
          $sql = "INSERT INTO ".$this->_tableName." (";
          foreach($this->arRelationMap as $k3 => $v3){
              if(array_key_exists( $k3,$this->_modifyMap)){
                  eval('$val = $this->'.$v3.';');
                  $field  .= "`".$v3."`,"; 
                  $values .= $val;
              }
          }
          $fields = substr($field,0,strlen($field)-1);
          $vals   = substr($values,0,strlen($values)-1);
          $sql .= $fields." ) VALUES (".$vals.")";
      }
      echo $sql;
      //$this->_DB->query($sql);
   }
   public function __destory(){
      if(isset($this->ID)){
         $sql = "DELETE FROM ".$this->_tableName." WHERE ".$this->pk." = ".$this->_ID;
        // $this->_DB_query($sql);
      }
   }
}
class User extends Model{
    protected  function getTableName(){
       return "test_user";
    }
    protected function getRelationMap(){
        return array( 
                      'id'       => USER_ID,
                      'user_name'=> USER_NAME,
                      'user_age' => USER_AGE
                    );
    }
    public function getDB(){
       return $this->_DB;
    }
}
$UserIns = new User();
print_r($UserIns);

?>

[转载]PHP ORM-持久层框架解决方案 - phpbin - 博客园

mikel阅读(1268)

[转载]PHP ORM-持久层框架解决方案 – phpbin – 博客园.

重点推荐:

http://www.phpactiverecord.com/  —- php.activerecord

 

1、Propel
Propel 是一个适用于PHP5的OR映射(Object Relational Mapping)框架,它基于Apache Torque提供对象持久层支持。它通过XML格式的模式定义文件和相应的配置文件来生成SQL和类,它允许你使用对象代替SQL来读写数据库表中的记 录。Propel提供一个生成器来为你的数据模型创建SQL定义文件和PHP类。开发者也可以十分简单的定制生成的类,我们还可以通过XML, PHP类和Phing构建工具把Propel集成到已有的应用开发框架中去.例如PHP框架symfony的1.2以前的版本就是默认使用了精简版的 Propel作为默认ORM框架。
官方网站http://www.propelorm.org/

 

2、Doctrine
Doctrine 是一个PHP的ORM框架,它必须运行在>=php5.2.3版本上,它是一个功能强大的数据抽象层.它的一个主要特征就是使用面向对象的方式来实 现数据库查询的封转,它底层通过一个类似 Hibernate HQL的DQL的查询语句进行数据库查询, 这使得开发的灵活性更强,大大减小了重复代码。相比Propel,Doctrine的优点在于它支持支持全文检索,Doctrine的文档一直就比 Propel要全面丰富,社区更活跃,而且使用起来更加自然、更易阅读、更接近原生SQL。性能方面也略微优于Propel。同样你也可以可以很方便的把 Doctrine集成到现有的应用框架中去,比如PHP框架symfony的1.3以后的版本将Doctrine作为默认的ORM框架,同时也可以将Doctrine和Codeigniter整合起来。
官方网站: http://www.doctrine-project.org/

 

3、EZPDO
EZPDO 是一个十分轻量级的PHP ORM框架。EZPDO的作者的本意旨在降低复杂的ORM学习曲线,尽可能在ORM的运行效率和功能之间做一个平衡点,它是我至今用过的最简单的ORM框 架,我目前还想将它集成到我的CoolPHP SDK中来,而且运行效率相当不错,功能也基本能满足需求,只不过ESPDO的更新比较缓慢。
官方网站:http://www.ezpdo.net/blog/?p=2

 

4、RedBean
RedBean是一个易于使用,轻量级PHP ORM框架,提供对MySQL, SQLite&PostgreSQL的支持。RedBean架构非常灵活,核心也非常简约,开发者可以很方便的通过插件来扩展功能。
官方网站:http://www.redbeanphp.com/

 

5、其他
国 内的fleaphp开发框架基于TableDataGateway实现ORM实现;Zend Framework除了提供对 SQL 语句的封装以外,也同样实现了TableGateway、TableRowSet、TableRow的实现;还有一些类似Rails的 ActiveRecord实现的解决方案。

 

更多ORM框架请阅:http://www.oschina.net/project/tag/126/orm?sort=view&lang=22&os=0

总结:
总 的来说,一般ORM框架对付简单的应用系统来说都能满足基本需求,可以大大降低开发难度,提高开发效率,但是它在SQL优化方面,肯定是比纯SQL语言要 差很多,对复杂关联、SQL内嵌表达式的处理都不是很理想。也许这主要是由于PHP本身对象持久化的问题,导致ORM效率过低,普遍比纯SQL要慢 10~50倍。但是这些都是有办法解决的,最基本的解决性能的方案,我们可以通过缓存来提高效率,Hibernate来说,虽然配置比较繁杂,但是它通过 灵活的使用二级缓存和查询缓存极大的缓解数据库的查询压力,极大的提升了系统的性能。

[转载]php中json的使用 - you_yang - 博客园

mikel阅读(1032)

[转载]php中json的使用 – you_yang – 博客园.

 一、json_encode()

该函数主要用来将数组和对象,转换为json格式。先看一个数组转换的例子:

$arr = array (‘a’=>1,’b’=>2,’c’=>3,’d’=>4,’e’=>5);

echo json_encode($arr);

结果为

{“a”:1,”b”:2,”c”:3,”d”:4,”e”:5}

再看一个对象转换的例子:

$obj->body = ‘another post’;

$obj->id = 21;

$obj->approved = true;

$obj->favorite_count = 1;

$obj->status = NULL;

echo json_encode($obj);

结果为

{
“body”:”another post”,

“id”:21,

“approved”:true,

“favorite_count”:1,

“status”:null
}

由于json只接受utf-8编码的字符,所以json_encode()的参数必须是utf-8编码,否则会得到空字符或者null。当中文使用GB2312编码,或者外文使用ISO-8859-1编码的时候,这一点要特别注意。

二、索引数组和关联数组

PHP支持两种数组,一种是只保存”值”(value)的索引数组(indexed array),另一种是保存”名值对”(name/value)的关联数组(associative array)。

由于JavaScript不支持关联数组,所以json_encode()只将索引数组(indexed array)转为数组格式,而将关联数组(associative array)转为对象格式。

比如,现在有一个索引数组

$arr = Array(‘one’, ‘two’, ‘three’);

echo json_encode($arr);

结果为:

[“one”,”two”,”three”]

如果将它改为关联数组:

$arr = Array(‘1’=>’one’, ‘2’=>’two’, ‘3’=>’three’);

echo json_encode($arr);

结果就变了:

{“1″:”one”,”2″:”two”,”3″:”three”}

注意,数据格式从”[]”(数组)变成了”{}”(对象)。

如果你需要将”索引数组”强制转化成”对象”,可以这样写

json_encode( (object)$arr );

或者

json_encode ( $arr, JSON_FORCE_OBJECT );

三、类(class)的转换

下面是一个PHP的类:

class Foo {

const ERROR_CODE = ‘404’;

public $public_ex = ‘this is public’;

private $private_ex = ‘this is private!’;

protected $protected_ex = ‘this should be protected’;

public function getErrorCode() {

return self::ERROR_CODE;

}

}

现在,对这个类的实例进行json转换:

$foo = new Foo;

$foo_json = json_encode($foo);

echo $foo_json;

输出结果是

{“public_ex”:”this is public”}

可以看到,除了公开变量(public),其他东西(常量、私有变量、方法等等)都遗失了。

四、json_decode()

该函数用于将json文本转换为相应的PHP数据结构。下面是一个例子:

$json = ‘{“foo”: 12345}’;

$obj = json_decode($json);

print $obj->{‘foo’}; // 12345

通常情况下,json_decode()总是返回一个PHP对象,而不是数组。比如:

$json = ‘{“a”:1,”b”:2,”c”:3,”d”:4,”e”:5}’;

var_dump(json_decode($json));

结果就是生成一个PHP对象:

object(stdClass)#1 (5) {

[“a”] => int(1)
[“b”] => int(2)
[“c”] => int(3)
[“d”] => int(4)
[“e”] => int(5)

}

如果想要强制生成PHP关联数组,json_decode()需要加一个参数true:

$json = ‘{“a”:1,”b”:2,”c”:3,”d”:4,”e”:5}’;

var_dump(json_decode($json),true);

结果就生成了一个关联数组:

array(5) {

[“a”] => int(1)
[“b”] => int(2)
[“c”] => int(3)
[“d”] => int(4)
[“e”] => int(5)

}

五、json_decode()的常见错误

下面三种json写法都是错的,你能看出错在哪里吗?

$bad_json = “{ ‘bar’: ‘baz’ }”;

$bad_json = ‘{ bar: “baz” }’;

$bad_json = ‘{ “bar”: “baz”, }’;

对这三个字符串执行json_decode()都将返回null,并且报错。

第一个的错误是,json的分隔符(delimiter)只允许使用双引号,不能使用单引号。第二个的错误是,json名值对的”名”(冒号左边的部分),任何情况下都必须使用双引号。第三个的错误是,最后一个值之后不能添加逗号(trailing comma)。

另外,json只能用来表示对象(object)和数组(array),如果对一个字符串或数值使用json_decode(),将会返回null。

var_dump(json_decode(“Hello World”)); //null

[转载]Warning: main(/html_head.php): failed to open stream: No such file or directory in « 第一php网-专注PHP编程技术 积极分享知识,耐心帮助他人,争做业内第一技术互助平台!

mikel阅读(1137)

[转载]Warning: main(/html_head.php): failed to open stream: No such file or directory in « 第一php网-专注PHP编程技术 积极分享知识,耐心帮助他人,争做业内第一技术互助平台!.
我的网站一打开就这样,那们朋友帮忙看看怎么回事?我的硬盘全是FAT格式的.
Warning: main(/html_head.php): failed to open stream: No such file or directory in D:\site\p-t.com.cn\index2.php on line 44
Warning: main(): Failed opening ‘/html_head.php’ for inclusion (include_path=’.;c:\php4\pear’) in D:\site\p-t.com.cn\index2.php on line 44
Warning: main(/html_index.php): failed to open stream: No such file or directory in D:\site\p-t.com.cn\index2.php on line 46
Warning: main(): Failed opening ‘/html_index.php’ for inclusion (include_path=’.;c:\php4\pear’) in D:\site\p-t.com.cn\index2.php on line 46
Warning: main(/html_foot.php): failed to open stream: No such file or directory in D:\site\p-t.com.cn\set_foot.php on line 4
Warning: main(): Failed opening ‘/html_foot.php’ for inclusion (include_path=’.;c:\php4\pear’) in D:\site\p-t.com.cn\set_foot.php on line 4
配置文件:

问题补充::配置文件:
http://hi.baidu.com/lxq73061/blog/item/817e2f12676db150f819b8e5.html

有时候运行phpmyadmin时会遇到这样问题!解决方法两种,如果这两种都解决不了你的问题,你干脆重装!
问题1:
Warning: session_write_close(): open(/tmp\sess_847b8fce6866c2511ca41dbb0c04f3ec, O_RDWR) failed: No such file or directory (2) in C:\Program Files\Apache Group\Apache2\htdocs\phpMyAdmin\index.php on line 44
Warning: session_write_close(): Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/tmp) in C:\Program Files\Apache Group\Apache2\htdocs\phpMyAdmin\index.php on line 44
Warning: Cannot modify header information – headers already sent by (output started at C:\Program Files\Apache Group\Apache2\htdocs\phpMyAdmin\index.php:44) in C:\Program Files\Apache Group\Apache2\htdocs\phpMyAdmin\index.php on line 105
问题1解决 :
把C:\WINDOWS\TEMP 这个文件夹权限 加上everyone权限
然后在打开网页试一下,你马上就能在文件夹下看到这个文件“sess_0cc9095f59d0c249da49b3885e4f9608”
问题2:
Warning: session_write_close() [function.session-write-close]: open(D:\Server\PHP\sessiondata\sess_e70f19b251c2d5e1cd8ebd4950689087, O_RDWR) failed: No such file or directory (2) in D:\Forum\phpMyAdmin\index.php on line 44
Warning: session_write_close() [function.session-write-close]: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (D:\Server\PHP\sessiondata) in D:\Forum\phpMyAdmin\index.php on line 44
问题2解决 :
这时只需要在相应的位置建立如下的文件夹即可: D:\Server\PHP\sessiondata

[转载]整合网络第三方数据,通过API提供给开发者使用——“聚合数据”想做数据经济时代的卖水者+数据商人? | 36氪

mikel阅读(933)

[转载]整合网络第三方数据,通过API提供给开发者使用——“聚合数据”想做数据经济时代的卖水者+数据商人? | 36氪.

整合网络第三方数据,通过API提供给开发者使用——“聚合数据”想做数据经济时代的卖水者+数据商人?

都说数据是互联网时代的石油,现在还真有人把数据当成了石油生意来做。“聚合数据”就是一家经营数据石油的开放平台,该公司通过对数据的开发(由多渠道捕获数据)、精炼(整理合并、优化、排错等初步加工)、产品化(封装成 API),最终销售给下游的工业企业(网站或 App 开发者)使用。

在开发一款产品时,我们需要用到各种数据。而如果手边没有现成的数据库可用,就需要你自己去设法搭建,这需要占用相当的精力。现在你偶然到“聚合数 据”上兜了一圈,你发现这简直是一个数据库超市:从车辆违章信息、航班火车查询、全国加油站实时油价,到在线试题、电影、股票,以至于百度和 Google 地图的全国 POI 都一一在列。也就是说,如果你的产品需要上线某个功能,你可以不必再自己建立和维护数据库,直接嵌入“聚合数据”的 SDK 即可。想想身边这样的场景还不少,比如做一款旅游行程规划 App,餐饮、景点、住宿等 POI 必不可少,或许你还要加上交通查询和空气质量预警,甚至目的地点评等等。每一个功能点都自己搞定的话,程序猿们的头发岂不是要掉光。

目前“聚合数据”提供的数据库有几十类上百种,数据来源主要为两部分:1、用爬虫抓取的网页公开信息 2、通过与一些拥有数据库的公司合作,比如微车、飞常准等等,这有时要支付一定采购成本。在收费方式上,目前主要依靠向大客户提供订购套餐,而中小团队的 多数接口可免费使用。“聚合数据”不久前刚刚发布了“电商”“比价”“商品条码”三大数据库,可以对线上全网商品数据(包括天猫、京东、苏宁易购等)进行 实时跟踪,并部分覆盖线下商超商品(比如家乐福、沃尔玛等)。基于这三大数据库,任何人都可以轻松做出一款 O2O 购物搜索、比价工具。

由于“聚合数据”提供的都是标准化的 API 接口,开发者可以在此基础上任意二次开发。但除了“电商”“比价”两个数据库外,其他均不提供离线数据,所有的调用、更新都需要通过 API。如果开发者有更深层的需求,比如希望拿到整个数据样本做挖掘,可能需要你单独和“聚合数据”团队联系了。

盯住开发者普遍而细分的需求,用 SDK 降低产品开发和维护成本,你可以将“聚合数据”理解为与个推、友盟类似的卖水者。不过事实上,“聚合数据”除了“卖水”外,还扮演着“数据贸易商”的角色,原因在于它所经营的核心资产——数据——在流通的过程中竟然增值了!

我们可以这么看待“聚合数据”的本质:一方面,数据在输入时是各种碎片化的“孤岛”,而在输出时则在一定程度上得到整合。我们知道,整合后的数据永 远比作为“孤岛”的数据更具价值。另一方面,数据在输入时掺杂着各种垃圾信息,而经过“聚合数据”的“精炼”加工后,数据的可用性得到提升。再加上“聚合 数据”致力于让一些原本封闭的数据库更加开放(不管是通过免费还是有偿手段),这也在更大范围内实现了数据的流通——从这个角度出发,你也就理解我开篇将 “聚合数据”比作石油公司的用意了。

“聚合数据”平台 11 年正式运行,目前已有 12 万注册开发者账户,所支持的 App、网站、微信号、软件已超过上万款,覆盖用户推算在上亿级别。该公司 11 年收获元禾资本 500 万天使,12 年收获某知名投资人数百万投资,13 年则又完成了数千万 A 轮。由于手上掌握着足够的数据样本,事实上“聚合数据”能像友盟一样提供一些行业层面的咨询建议,未来也确实有开展相关业务的打算。

[36氪原创文章,作者: 沈超]

[转载]bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序 - Jesse Liu - 博客园

mikel阅读(1041)

[转载]bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序 – Jesse Liu – 博客园.

也许单页程序(Single Page Application)并不是什么时髦的玩意,像Gmail在很早之前就已经在使用这种模式。通常的说法是它通过避免页面刷新大大提高了网站的响应性, 像操作桌面应用程序一样。特别是在当今的移动时代,单页程序如果放在移动设备上去浏览就能够拥有像native app一样的体验,也许我们web开发者们应该期待这种技术的大力普及,这样不管前端还是后端都是我们的天下啊,让那些Andrioid和IOS开发者们 追赶我们吧!好吧,废话不说了,我们会从0开始搭建这样一个单页的web站点,并且会向大家展示我们标题所列的这些开源框架是如何帮助我们快速构建的。新 技术比较多,我也是学习,有不足的地方请海涵 🙂

注:由于这个Demo是要给国外的同事看的,所以页面内容显示是英文的,请见谅。戳这里看线上Demo。http://myspademo.cloudapp.net

源码地址: https://github.com/jesselew/SpaDemo

目录

需求介绍

我们的需求很简单,通过这个单页程序完成对Event的管理,下面简单列几条需求。

功能性需求

  1. 添加修改Event
  2. Event 有opening和closed的状态,也就是需要有关闭Event的功能
  3. Event列表页可以根据状态过滤
  4. Closed的Event不能再进行修改

非功能性需求

  1. 尽可能的减少对服务器的请求
  2. 数据完整性(验证)
  3. 认证和授权(系统会有至少2种角色,并且拥有不同的权限)
  4. 可维护性

认证和授权这一块暂时没有做,后面可以继续完善,验证这一块只做了后端的,通常为了安全和用户体验是需要后端和前端都要实现验证的。这个Demo我已经上传到Windows Azure上去了,大家来体验一把。http://myspademo.cloudapp.net 

单页程序介绍

首先我觉得可以把页面的响应模式分成这样大概3个阶段:

1. 最传统的阶段:什么都得刷新

  最传统的web站点中,客户端向服务器发送请求,服务器响应之后把生成好的HTML通过Response返回给客户端,这样一来一往。体验当然是最不好的,同时对服务器来说也需要处理的更多。

2. 页面局部刷新

  至从Ajax火起来之后,大家就想起了这一点。页面某一块局部的数据可以在页面在客户端加载完之后,再从新 发起一个请求去把某一块的HTML代码再拿下来显示到页面中。这里面有两种做法,一种是后台直接把HTML生成好了直接返回,另一种做法是服务器只返回数 据,客户端再拼出HTML。采取第二种做法的时候,有人可能已经用上了先进的模板技术,有人可能还在使用强大的字符串拼接技术。 不管怎么说,我们进步了,用户可以先看到页面,然后某一块慢慢加载,用户感觉爽了,再也不是一片空白在那里转啊转啊的了。

3. 整站单页

整站单页的时代到来最早是在2005年,当然那时候还只是一个术语。具体的例子,我最早接触到的是Gmail,当然最简单的单页其实很简单比如说某Q邮箱,整了个Frame在页面里面,不管你怎么点,它懒是感觉没有刷新呀。这里先简单说说我们要实现的这个单页和用Frame实现的单页相比有什么优势。

  • 拥有良好定义的URL,对用户和搜索引擎都更友好。
  • 可以实现衔接动画,这一点在移动设备上特别重要。

页面生命周期对比

The Traditional Page Lifecycle vs. the SPA Lifecycle

这里从MSDN上面扒来了一张图,上面的传统的页面生命周期,下面是我们这种单页程序页面的生命周期。我们来看看这种模式的页面会为我们的用户和开发者带来哪些优势和难题。

优势

  • 对于用户而言,更好的用户体验,特别体现在可移动端和可触摸设备上
  • 对于开发都而言,在定义了良好的分层架构之后,UI与数据可以完全分离,只要后台的数据接口不改变,后台的逻辑可以随意的改动页不影响前端展示,而在加上前端MVVM框架之后,我们前端的数据也可以与UI完成分离。

难题

  • 最大的难题是JavaScript部分,由于全部在一个页面,我们需要处理变量覆盖,变量作用域,对于前端开发人员来说要求会更上一层楼
  • 对于全球化,授权等模块都需要重新考虑和设计以便更适合这种单页程序的开发

项目架构

扒了一张图之后,我的图就得画的跟它的协调,没有我的手写风格好看,有木有?

  • 用Knockout作前端MVVM框架
  • 用requireJS来加载远程模板
  • 用director来作前端route
  • model数据是直接和web api交互的,包括验证和授权
  • 模板是一个Controller,每一个模板对应一个Action

View Container

  这是一个客户端的模板容器,在requireJS的基础封装了一下,第一次调用某个模板的时候会去服务器上拿,后来就直接用客户端的了。

为什么模板不直接用html的?

  这个问题我也想过,用纯html的就不必走mvc那一套生命周期了,服务器压力减少不小。但是考虑到我们view当中的授权模块和全球化资源,其实是可以直接在服务器端处理好再返回的。而且我也偷了一个懒,没有把这些放在客户去实现,大家有好的点子可以分享的么?

开源框架介绍

上面用了这么多的开源框架,那么它们都是干什么的,又是如何使用的呢? 这里我们就小小的来聊一聊这些开源的框架吧。

Bootstrap

这玩意我想很多人都知道,我就不多说了。有了它之后,我们程序员不需要美工也可以做出很漂亮的界面了,虽然我这个Demo没有很好看,但要是没有它那还真不知道要丑上多少倍。它还有中文版的站点: http://www.bootcss.com/

director.js

这是一个前端的route框架,什么叫前端route呢?大家如果去看我的那个Demo就会发现,URL并不是像某Q邮箱那样一直不变的,我们还是可以像以前那样每一个单一的功能一个URL。比如说:

  • #/events/create
  • #/events/all
  • #/events/closed
  • #/events/1

除了对用户比较友好之后,写代码的时候也会更加逻辑清晰,因为director会为每一个url绑定一个函数,就像mvc里面的action一样。当用户输入对应的url的时候,相应的函数就会被触发。

下面是来自官方首页的一个小小的例子,让你一眼就会用director。

requireJS

这玩意我也不用多介绍了吧,它具有延迟加载和避免重复加载的功能,来自官方的定义: requireJS是一个JavaScript文件和模块加载器。

knockout.js

这玩意就算我想给你介绍也不是三言两语就能说的清的,具体您还是参考源码吧。或者园子里面的大叔曾经翻译了官方的一个教程, 有兴趣同学可以看看。 总之它是一个JavaScript的MVVM框架,当然这种框架有很多,backboneJS, breezeJS, Durandal,EmberJS,Angular 等等,我并没有全部了解过,所以我也不能告诉你他们的优势和缺点分别在哪里。选择knockout.js是因为之前了解过,好上手,然后以上这3种开源的 框架全是基于MIT开源协议的,这样我们就可以用它做商业开发了。

用requireJS实现远程模板的调用

直接用require来加载html模板是不行的,人家已经说了是一个Javascript文件和模块的加载器。所以这里面我们需要用到requireJS的文本插件,这样我们就可以用它来加载文本了。https://github.com/requirejs/text

把那个text.js下载下来,直接放到我们程序的根目录下,然后我们就可以用像加载js一样的方法来加载html代码了,除了要在我们文件位置前面加上一个text! 之外。

1
2
3
require(['text!/template/createevent'], function (template) {
                // 你在这里就可以拿到模板了。
})

rest中关于局部更新的讨论

我们常用的http verb有四种:

我们用PUT方式去更新的话,是将整个Model全部更新。当然你也可以换成下面这种方式,只更新你想要更新的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[HttpPut]
public void Put(Event item)
{
    var newItem = new Event();
    newItem.Id = item.Id;
    // 在下面将你想要更新的值转到newItem下
    newItem.Title = item.Title;
    if (!repository.Update(newItem))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

注意:Put方式的URl只有一种(在我们不建其它route的情况下),也就是我们上面列出来的 /api/events/{id},然后将event对象作为body传过去。比如说在我们的demo中,我们有更新操作,还有像“关闭”这样的操作,我 想这样的操作几乎在每一个系统里面都会遇到,这样的操作只会更新一个字段(在这里是“状态”列)。 那我怎么样再建一个Put方法去更改这一个字段呢?而且最好的方法是我只用传id过去就可以了。

通过google,我找到一个叫Patch的玩意, 它也是一种http verb,并且同样也是提供更新操作。但是与Put不一样的是Patch允许只将你需要更改的字段传到服务器端。

1
2
3
4
5
6
7
8
9
10
11
var obj = { Revision : "2"};
 
$.ajax({
    url: 'api/values/1',
    type: 'PATCH',
    data: JSON.stringify(obj),
    dataType: 'json',
    contentType: 'application/json',
    success: function (data) {           
    }
});

但是不管怎么说,这种方式我是没有行通的,一旦我的实体对象加上一些验证的Attribute比如说Required之后,那些字段全都会被赋上默认值。 最后我不得不放弃了这种做法。

添加Route来创建两个PUT方法

另外一种做法,也就是我们Demo中实现的做法是增加了一个Route,在我们的web api中实现了两个put的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Route("api/events/{id}/close")]
public void Put(int id)
{
var item = repository.Get(id);
if (item == null)
{
    throw new HttpResponseException(HttpStatusCode.NotFound);
}
item.Status = EventStatus.Closed;
if (!repository.Update(item))
{
    throw new HttpResponseException(HttpStatusCode.NotFound);
}
}

这样当我用PUT的方式提交到 api/events/3/close 的时候,我们的web api就会执行上面的方法然后把我们的event关闭了。

WEB API的验证

基本上任何系统都避免不了与验证打交道,除非那个系统压根不从用户那里获取数据。WEB API的验证方式大至相同,我们仍旧可以在我们的Model中采用Attribute的方式去声明验证条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Event
{
public int Id { get; set; }
[Required]
[MinLength(10)]
public string Title { get; set; }
public string Description { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
[Required]
public string Owner { get; set; }
public EventStatus Status { get; set; }
}

在api方法中我们用ModelState.IsValid判断就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
public HttpResponseMessage Post(Event item)
{
    if (ModelState.IsValid)
    {
        // 保存操作
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    else
    {
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
    }
}

用AOP的方式去实现验证

或者我们可以换成下面的这种方式,先创建一个Filter。

1
2
3
4
5
6
7
8
9
10
11
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(
                HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

再到Post和PUT的方法上面打上这个标签。

1
2
3
4
5
6
7
8
9
[HttpPut]
[ValidateModel]
public void Put(Event item)
{
    if (!repository.Update(item))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

我们还需要在我们的WebApiConfig中注册这个Filter。

1
2
3
4
5
6
7
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ValidateModelAttribute());
    }
}

  前端拿到这个消息之后,就可以通知给用户了。当然最后还是需要加上前端验证,可以大大的提高用户体验以及减轻服务器的压力。

小结

没有小结,大伙都散了吧!我要骑车上班去了,你呢?

[转载]微信支付----没我想的那么难! - True to me - 博客园

mikel阅读(1317)

[转载]微信支付—-没我想的那么难! – True to me – 博客园.

微信支付—-没我想的那么难!

楼主公司是做微信周边产品开发的,最近公司接到一个case,跟客户公司接口对接,并实现微信支付。楼主可是从没接触过微信支付,当时心里感觉好高大上,能搞定么?马上忽悠来客户的公众平台帐号、密码上去看微信支付文档。

公众号支付有2种支付方式:

JS API 支付:是指用户打开图文消息或者扫描二维码,在微信内置浏览器打开网页进行的支付。商户网页前端通过使用微信提供的 JS API,调用微信支付模块。这种方式,适合
需要在商户网页进行选购下单的购买流程。
Native(原生)支付:是指商户组成符合 Native(原生)支付规则的 URL 链接,用户可通过点击该链接或者扫描对应的二维码直接进入微信支付模块(微信客户端界面),即可
进行支付。这种方式,适合无需选购直接支付的购买流程。

 

以上两种方式最大的区别是:是否需要经过网页调用!

交互原理:

客户是想做一个手机端的商城,所以选择JS API支付,通过网页调取微信支付。。

楼主第一次看过文档后,什么“支付签名(paySign)”啊,什么“package包”啊等等一些参数,反正似懂非懂的样子。楼主这人比较懒,不然也不 会注册3年了,也就写了这么篇不入流的文章,楼主想啊像这些个大公司开放个啥接口的肯定会有demo。楼主的运气还真不错(PS:楼主可是为福利事业贡献 了不少哦,咋就没这运气?),还真让楼主找到了,那心情肯定是比不上中500W的,嘿嘿。

复制代码
<!DOCTYPE html>
<html>
    <head>
        <title>公众号支付测试网页</title>
        <script language="javascript" src="http://res.mail.qq.com/mmr/static/lib/js/jquery.js"></script>
        <script language="javascript" src="http://res.mail.qq.com/mmr/static/lib/js/lazyloadv3.js"></script>
        <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/md5.js"></script>
        <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js"></script>
        <script Language="javascript">
            //辅助函数
            function Trim(str,is_global)
            {
                var result;
                result = str.replace(/(^\s+)|(\s+$)/g,"");
                if(is_global.toLowerCase()=="g") result = result.replace(/\s/g,"");
                return result;
            }
            function clearBr(key)
            {
                key = Trim(key,"g");
                key = key.replace(/<\/?.+?>/g,"");
                key = key.replace(/[\r\n]/g, "");
                return key;
            }

            //获取随机数
            function getANumber()
            {
                var date = new Date();
                var times1970 = date.getTime();
                var times = date.getDate() + "" + date.getHours() + "" + date.getMinutes() + "" + date.getSeconds();
                var encrypt = times * times1970;
                if(arguments.length == 1){
                    return arguments[0] + encrypt;
                }else{
                    return encrypt;
                }

            }

            //以下是package组包过程:

            var oldPackageString;//记住package,方便最后进行整体签名时取用

            function getPartnerId()
            {
                return document.form1.partnerId.value;
            }

            function getPartnerKey()
            {
                return "8934e7d15453e97507ef794cf7b0519d";
            }

            function getPackage()
            {
                var banktype = "WX";
                var body = document.form1.body.value;//商品名称信息,这里由测试网页填入。
                var fee_type = "1";//费用类型,这里1为默认的人民币
                var input_charset = "GBK";//字符集,这里将统一使用GBK
                var notify_url = "http://www.qq.com";//支付成功后将通知该地址
                var out_trade_no = ""+getANumber();//订单号,商户需要保证该字段对于本商户的唯一性
                var partner = getPartnerId();//测试商户号
                var spbill_create_ip = "127.0.0.1";//用户浏览器的ip,这个需要在前端获取。这里使用127.0.0.1测试值
                var total_fee = document.form1.totalFee.value;//总金额。
                var partnerKey = getPartnerKey();//这个值和以上其他值不一样是:签名需要它,而最后组成的传输字符串不能含有它。这个key是需要商户好好保存的。

                //首先第一步:对原串进行签名,注意这里不要对任何字段进行编码。这里是将参数按照key=value进行字典排序后组成下面的字符串,在这个字符串最后拼接上key=XXXX。由于这里的字段固定,因此只需要按照这个顺序进行排序即可。
                var signString = "bank_type="+banktype+"&body="+body+"&fee_type="+fee_type+"&input_charset="+input_charset+"&notify_url="+notify_url+"&out_trade_no="+out_trade_no+"&partner="+partner+"&spbill_create_ip="+spbill_create_ip+"&total_fee="+total_fee+"&key="+partnerKey;

                var md5SignValue =  ("" + CryptoJS.MD5(signString)).toUpperCase();
                //然后第二步,对每个参数进行url转码,如果您的程序是用js,那么需要使用encodeURIComponent函数进行编码。

                banktype = encodeURIComponent(banktype);
                body=encodeURIComponent(body);
                fee_type=encodeURIComponent(fee_type);
                input_charset = encodeURIComponent(input_charset);
                notify_url = encodeURIComponent(notify_url);
                out_trade_no = encodeURIComponent(out_trade_no);
                partner = encodeURIComponent(partner);
                spbill_create_ip = encodeURIComponent(spbill_create_ip);
                total_fee = encodeURIComponent(total_fee);

                //然后进行最后一步,这里按照key=value除了sign外进行字典序排序后组成下列的字符串,最后再串接sign=value
                var completeString = "bank_type="+banktype+"&body="+body+"&fee_type="+fee_type+"&input_charset="+input_charset+"&notify_url="+notify_url+"&out_trade_no="+out_trade_no+"&partner="+partner+"&spbill_create_ip="+spbill_create_ip+"&total_fee="+total_fee;
                completeString = completeString + "&sign="+md5SignValue;

                oldPackageString = completeString;//记住package,方便最后进行整体签名时取用

                return completeString;
            }

            //下面是app进行签名的操作:

            var oldTimeStamp ;//记住timestamp,避免签名时的timestamp与传入的timestamp时不一致
            var oldNonceStr ; //记住nonceStr,避免签名时的nonceStr与传入的nonceStr不一致

            function getAppId()
            {
                return document.form1.appId.value;
            }

            function getAppKey()
            {
                return "2Wozy2aksie1puXUBpWD8oZxiD1DfQuEaiC7KcRATv1Ino3mdopKaPGQQ7TtkNySuAmCaDCrw4xhPY5qKTBl7Fzm0RgR3c0WaVYIXZARsxzHV2x7iwPPzOz94dnwPWSn";
            }

            function getTimeStamp()
            {
                var timestamp=new Date().getTime();
                var timestampstring = timestamp.toString();//一定要转换字符串
                oldTimeStamp = timestampstring;
                return timestampstring;
            }

            function getNonceStr()
            {
                var $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
                var maxPos = $chars.length;
                var noceStr = "";
                for (i = 0; i < 32; i++) {
                    noceStr += $chars.charAt(Math.floor(Math.random() * maxPos));
                }
                oldNonceStr = noceStr;
                return noceStr;
            }

            function getSignType()
            {
                return "SHA1";
            }

            function getSign()
            {
                var app_id = getAppId().toString();
                var app_key = getAppKey().toString();
                var nonce_str = oldNonceStr;
                var package_string = oldPackageString;
                var time_stamp = oldTimeStamp;
                //第一步,对所有需要传入的参数加上appkey作一次key=value字典序的排序
                var keyvaluestring = "appid="+app_id+"&appkey="+app_key+"&noncestr="+nonce_str+"&package="+package_string+"&timestamp="+time_stamp;
                sign = CryptoJS.SHA1(keyvaluestring).toString();
                return sign;
            }

            </script>
        <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
        <meta id="viewport" name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1; user-scalable=no;" />

        <style>

            body { margin:0;padding:0;background:#eae9e6; }
            body,p,table,td,th { font-size:14px;font-family:helvetica,Arial,Tahoma; }
            h1 { font-family:Baskerville,HelveticaNeue-Bold,helvetica,Arial,Tahoma; }
            a { text-decoration:none;color:#385487;}

            .container {  }
            .title { }
            #content {padding:30px 20px 20px;color:#111;box-shadow:0 1px 4px #ccc; background:#f7f2ed;  }
            .seeAlso { padding:15px 20px 30px; }

            .headpic div { margin:20px 0 0;}
            .headpic img { display:block;}

            .title h1 { font-size:22px;font-weight:bold;padding:0;margin:0;line-height:1.2;color:#1f1f1f; }
            .title p { color:#aaa;font-size:12px;margin:5px 0 0;padding:0;font-weight:bold;}
            .pic { margin:20px 0; }
            .articlecontent img { display:block;clear:both;box-shadow:0px 1px 3px #999; margin:5px auto;}
            .articlecontent p { text-indent: 2em; font-family:Georgia,helvetica,Arial,Tahoma;line-height:1.4; font-size:16px; margin:20px 0;  }

            .seeAlso h3 { font-size:16px;color:#a5a5a5;}
            .seeAlso ul { margin:0;padding:0; }
            .seeAlso li {  font-size:16px;list-style-type:none;border-top:1px solid #ccc;padding:2px 0;}
            .seeAlso li a { border-bottom:none;display:block;line-height:1.1; padding:13px 0; }

            .clr{ clear:both;height:1px;overflow:hidden;}

            .fontSize1 .title h1 { font-size:20px; }
            .fontSize1 .articlecontent p {  font-size:14px; }
            .fontSize1 .weibo .nickname,.fontSize1 .weibo .comment  { font-size:11px; }
            .fontSize1 .moreOperator { font-size:14px; }

            .fontSize2 .title h1 { font-size:22px; }
            .fontSize2 .articlecontent p {  font-size:16px; }
            .fontSize2 .weibo .nickname,.fontSize2 .weibo .comment  { font-size:13px; }
            .fontSize2 .moreOperator { font-size:16px; }

            .fontSize3 .title h1 { font-size:24px; }
            .fontSize3 .articlecontent p {  font-size:18px; }
            .fontSize3 .weibo .nickname,.fontSize3 .weibo .comment  { font-size:15px; }
            .fontSize3 .moreOperator { font-size:18px; }

            .fontSize4 .title h1 { font-size:26px; }
            .fontSize4 .articlecontent p {  font-size:20px; }
            .fontSize4 .weibo .nickname,.fontSize4 .weibo .comment  { font-size:16px; }
            .fontSize4 .moreOperator { font-size:20px; }

            .jumptoorg { display:block;margin:16px 0 16px; }
            .jumptoorg a {  }

            .moreOperator a { color:#385487; }

            .moreOperator .share{ border-top:1px solid #ddd; }

            .moreOperator .share a{ display:block;border:1px solid #ccc;border-radius:4px;margin:20px 0;border-bottom-style:solid;background:#f8f7f1;color:#000; }

            .moreOperator .share a span{ display:block;padding:10px 10px;border-radius:4px;text-align:center;border-top:1px solid #eee;border-bottom:1px solid #eae9e3;font-weight:bold; }

            .moreOperator .share a:hover,
            .moreOperator .share a:active { background:#efedea; }
            @media only screen and (-webkit-min-device-pixel-ratio: 2) {
            }
            </style>
        <script language="javascript">
            function auto_remove(img){
                div=img.parentNode.parentNode;div.parentNode.removeChild(div);
                img.onerror="";
                return true;
            }

            function changefont(fontsize){
                if(fontsize < 1 || fontsize > 4)return;
                $('#content').removeClass().addClass('fontSize' + fontsize);
            }

            // 当微信内置浏览器完成内部初始化后会触发WeixinJSBridgeReady事件。
            document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
                                      //公众号支付
                                      jQuery('a#getBrandWCPayRequest').click(function(e){
                                                                             WeixinJSBridge.invoke('getBrandWCPayRequest',{
                                                                                                   "appId" : getAppId(), //公众号名称,由商户传入
                                                                                                   "timeStamp" : getTimeStamp(), //时间戳
                                                                                                   "nonceStr" : getNonceStr(), //随机串
                                                                                                   "package" : getPackage(),//扩展包
                                                                                                   "signType" : getSignType(), //微信签名方式:1.sha1
                                                                                                   "paySign" : getSign() //微信签名
                                                                                                   },function(res){
                                                                                                   if(res.err_msg == "get_brand_wcpay_request:ok" ) {}
                                                                                                   // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                                                                                                   //因此微信团队建议,当收到ok返回时,向商户后台询问是否收到交易成功的通知,若收到通知,前端展示交易成功的界面;若此时未收到通知,商户后台主动调用查询订单接口,查询订单的当前状态,并反馈给前端展示相应的界面。
                                                                                                   }); 

                                                                             });

                                      WeixinJSBridge.log('yo~ ready.');

                                      }, false)

            if(jQuery){
                jQuery(function(){

                       var width = jQuery('body').width() * 0.87;
                       jQuery('img').error(function(){
                                           var self = jQuery(this);
                                           var org = self.attr('data-original1');
                                           self.attr("src", org);
                                           self.error(function(){
                                                      auto_remove(this);
                                                      });
                                           });
                       jQuery('img').each(function(){
                                          var self = jQuery(this);
                                          var w = self.css('width');
                                          var h = self.css('height');
                                          w = w.replace('px', '');
                                          h = h.replace('px', '');
                                          if(w <= width){
                                          return;
                                          }
                                          var new_w = width;
                                          var new_h = Math.round(h * width / w);
                                          self.css({'width' : new_w + 'px', 'height' : new_h + 'px'});
                                          self.parents('div.pic').css({'width' : new_w + 'px', 'height' : new_h + 'px'});
                                          });
                       });
            }
            </script>
    </head>
    <body>

        <form name="form1" target="_blank">
            <table border="1">
                <TR><th>公众号ID</th> <th><INPUT value="wxf8b4f85f3a794e77" name="appId" id="1"></th>
                    <tr><th>商户ID</th><th><INPUT value="1900000109" name="partnerId" id="2"></th>
                        <TR><th>总金额</th><th><INPUT value=1 name="totalFee" id="3"></th>
                            <TR><th>商品名</th><th><INPUT value="江诗丹顿" name="body" id="4"></th>
            </table>
        </form>
        <div class="WCPay">
            <a id="getBrandWCPayRequest" href="javascript:void(0);"><h1 class="title">提交</h1></a>
        </div>

    </body>
</html>
复制代码

打开一看(楼主心里想TX你是多缺美工!),“提交”两字这么大,点一下试试,没反应?这不科学啊!立马找原因,当看到这句:“微信支付,是基于微信客户端提供的支付服务功能” ,楼主**(已和谐)了!放网站上用微信打开,这次有反应了!微信友情的提示我:“功能未授权”。。 这次知道是啥原因了,找客户沟通,原来合同快递还没到,保证金5W没交(现在只要2W了)。。

N天之后。。。。

这下测试能成功了,该怎么跟网站结合呢?不会直接把参数改了,然后让客户点提交吧?楼主想想这用户体验也太差了吧,客户肯定不认同。这问题纠结了楼主几 天,百撕不得骑姐。有一天突然企业QQ一个讨论组的图标老闪,我就纳闷我没加过讨论组啊,打开正想喷(实际也真没喷过,能拉进讨论组都是好友,不过问题解 不出来的时候确实很烦)的时候,人物列表那个头像怎么那么熟呢?那不我客户吗?瞬间一盆凉水从头到脚彻底没脾气了!A(TX商务人员):“你们的微信支付 做好了没?”,我:“还没呢。”,A:”有什么困难吗?”,我:“有,有一些地方不明白。”,A:”我帮你安排个技术支持吧?”,楼主欣喜若狂啊,从看到 这行信息到打出“好的”发出去绝对是没有超过1秒。。

之后楼主一帆风顺了,如鱼得水了,同事妹纸也变的水灵多了。。

<——————————————————————-吐 槽结束,华丽的分割线 —————————————————————–>

以下是.NET版本的微信支付:

MD5Util类:

复制代码
using System;
using System.Security.Cryptography;
using System.Text;

namespace tenpayApp
{
    /// <summary>
    /// MD5Util 的摘要说明。
    /// </summary>
    public class MD5Util
    {
        public MD5Util()
        {
            //
            // TODO: 在此处添加构造函数逻辑
            //
        }

        /** 获取大写的MD5签名结果 */
        public static string GetMD5(string encypStr, string charset)
        {
            string retStr;
            MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider();

            //创建md5对象
            byte[] inputBye;
            byte[] outputBye;

            //使用GB2312编码方式把字符串转化为字节数组.
            try
            {
                inputBye = Encoding.GetEncoding(charset).GetBytes(encypStr);
            }
            catch (Exception ex)
            {
                inputBye = Encoding.GetEncoding("GB2312").GetBytes(encypStr);
            }
            outputBye = m5.ComputeHash(inputBye);

            retStr = System.BitConverter.ToString(outputBye);
            retStr = retStr.Replace("-", "").ToUpper();
            return retStr;
        }
    }
}
复制代码

RequestHandler类:

复制代码
using System;
using System.Collections;
using System.Text;
using System.Web;
using System.Xml;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace tenpayApp
{
    /**
    '签名工具类
     ============================================================================/// <summary>
    'api说明:
    'init();
    '初始化函数,默认给一些参数赋值。
    'setKey(key_)'设置商户密钥
    'createMd5Sign(signParams);字典生成Md5签名
    'genPackage(packageParams);获取package包
    'createSHA1Sign(signParams);创建签名SHA1
    'parseXML();输出xml
    'getDebugInfo(),获取debug信息
     * 
     * ============================================================================
     */
    public class RequestHandler
    {

        public RequestHandler(HttpContext httpContext)
        {
            parameters = new Hashtable();

            this.httpContext = httpContext;

        }
        /**  密钥 */
        private string key;

        protected HttpContext httpContext;

        /** 请求的参数 */
        protected Hashtable parameters;

        /** debug信息 */
        private string debugInfo;

        /** 初始化函数 */
        public virtual void init() 
        {
        }
        /** 获取debug信息 */
        public String getDebugInfo() 
        {
            return debugInfo;
        }
        /** 获取密钥 */
        public String getKey() 
        {
            return key;
        }

        /** 设置密钥 */
        public void setKey(string key) 
        {
            this.key = key;
        }

        /** 设置参数值 */
        public void setParameter(string parameter, string parameterValue)
        {
            if (parameter != null && parameter != "")
            {
                if (parameters.Contains(parameter))
                {
                    parameters.Remove(parameter);
                }

                parameters.Add(parameter, parameterValue);
            }
        }

        //获取package带参数的签名包
        public string getRequestURL()
        {
            this.createSign();
            StringBuilder sb = new StringBuilder();
            ArrayList akeys=new ArrayList(parameters.Keys); 
            akeys.Sort();
            foreach(string k in akeys)
            {
                string v = (string)parameters[k];
                if(null != v && "key".CompareTo(k) != 0) 
                {
                    sb.Append(k + "=" + TenpayUtil.UrlEncode(v, getCharset()) + "&");
                }
            }

            //去掉最后一个&
            if(sb.Length > 0)
            {
                sb.Remove(sb.Length-1, 1);
            }

           return sb.ToString();

        }

        //创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。

        protected virtual void  createSign() 
        {
            StringBuilder sb = new StringBuilder();

            ArrayList akeys=new ArrayList(parameters.Keys); 
            akeys.Sort();

            foreach(string k in akeys)
            {
                string v = (string)parameters[k];
                if(null != v && "".CompareTo(v) != 0
                    && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0) 
                {
                    sb.Append(k + "=" + v + "&");
                }
            }

            sb.Append("key=" + this.getKey());
            string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToUpper();

            this.setParameter("sign", sign);

            //debug信息
            this.setDebugInfo(sb.ToString() + " => sign:" + sign);        
        }

       //创建package签名
        public virtual string createMd5Sign()
        {
            StringBuilder sb = new StringBuilder();
            ArrayList akeys=new ArrayList(parameters.Keys); 
            akeys.Sort();

            foreach(string k in akeys)
            {
                string v = (string)parameters[k];
                if(null != v && "".CompareTo(v) != 0
                    && "sign".CompareTo(k) != 0 && "".CompareTo(v) != 0) 
                {
                    sb.Append(k + "=" + v + "&");
                }
            }
            string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToLower();

            this.setParameter("sign", sign);
            return sign;
    }

        //创建sha1签名
        public string createSHA1Sign()
        {
            StringBuilder sb = new StringBuilder();
            ArrayList akeys = new ArrayList(parameters.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                string v = (string)parameters[k];
              if (null != v && "".CompareTo(v) != 0
                     && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
                {
                    if(sb.Length==0)
                    {
                    sb.Append(k + "=" + v);
                    }
                    else{
                     sb.Append("&" + k + "=" + v);
                    }
                }
            }
            string paySign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower();

            //debug信息
            this.setDebugInfo(sb.ToString() + " => sign:" + paySign);
            return paySign;
        }

         //输出XML
        public string parseXML()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<xml>");
            foreach (string k in parameters.Keys)
            {
                string v = (string)parameters[k];
                if (Regex.IsMatch(v, @"^[0-9.]$"))
                {

                    sb.Append("<" + k + ">" + v + "</" + k + ">");
                }
                else
                {
                    sb.Append("<" + k + "><![CDATA[" + v + "]]></" + k + ">");
                }

           }
            sb.Append("</xml>");
            return sb.ToString();
        }

        /** 设置debug信息 */
        public void setDebugInfo(String debugInfo) 
        {
            this.debugInfo = debugInfo;
        }

        public Hashtable getAllParameters()
        {
            return this.parameters;
        }

         protected virtual string getCharset()
      {
          return this.httpContext.Request.ContentEncoding.BodyName;
      } 
    }
}
复制代码

ResponseHandler类:

复制代码
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Xml;

namespace tenpayApp
{

    /** 
    '============================================================================
    'api说明:
    'getKey()/setKey(),获取/设置密钥
    'getParameter()/setParameter(),获取/设置参数值
    'getAllParameters(),获取所有参数
    'isTenpaySign(),是否正确的签名,true:是 false:否
    'isWXsign(),是否正确的签名,true:是 false:否
    ' * isWXsignfeedback判断微信维权签名
    ' *getDebugInfo(),获取debug信息
    '============================================================================
    */

    public class ResponseHandler
    {
        // 密钥 
        private string key;

        // appkey
        private string appkey;

        //xmlMap
        private Hashtable xmlMap;

        // 应答的参数
        protected Hashtable parameters;

         //debug信息
        private string debugInfo;
        //原始内容
        protected string content;

        private string charset = "gb2312";

        //参与签名的参数列表
        private static string SignField = "appid,appkey,timestamp,openid,noncestr,issubscribe";

        protected HttpContext httpContext;

        //初始化函数
        public virtual void init()
        {
        }

        //获取页面提交的get和post参数
        public ResponseHandler(HttpContext httpContext)
        {
            parameters = new Hashtable();
            xmlMap = new Hashtable();

            this.httpContext = httpContext;
            NameValueCollection collection;
            //post data
            if (this.httpContext.Request.HttpMethod == "POST")
            {
                collection = this.httpContext.Request.Form;
                foreach (string k in collection)
                {
                    string v = (string)collection[k];
                    this.setParameter(k, v);
                }
            }
            //query string
            collection = this.httpContext.Request.QueryString;
            foreach (string k in collection)
            {
                string v = (string)collection[k];
                this.setParameter(k, v);
            }
            if (this.httpContext.Request.InputStream.Length > 0)
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(this.httpContext.Request.InputStream);
                XmlNode root = xmlDoc.SelectSingleNode("xml");
                XmlNodeList xnl = root.ChildNodes;

                foreach (XmlNode xnf in xnl)
                {
                    xmlMap.Add(xnf.Name, xnf.InnerText);
                }
            }
        }

        /** 获取密钥 */
        public string getKey() 
        { return key;}

        /** 设置密钥 */
        public void setKey(string key, string appkey) 
        {
            this.key = key;
            this.appkey = appkey;
        }

        /** 获取参数值 */
        public string getParameter(string parameter) 
        {
            string s = (string)parameters[parameter];
            return (null == s) ? "" : s;
        }

        /** 设置参数值 */
        public void setParameter(string parameter,string parameterValue) 
        {
            if(parameter != null && parameter != "")
            {
                if(parameters.Contains(parameter))
                {
                    parameters.Remove(parameter);
                }

                parameters.Add(parameter,parameterValue);        
            }
        }

        /** 是否财付通签名,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 
         * @return boolean */
        public virtual Boolean isTenpaySign() 
        {
            StringBuilder sb = new StringBuilder();

            ArrayList akeys=new ArrayList(parameters.Keys); 
            akeys.Sort();

            foreach(string k in akeys)
            {
                string v = (string)parameters[k];
                if(null != v && "".CompareTo(v) != 0
                    && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0) 
                {
                    sb.Append(k + "=" + v + "&");
                }
            }

            sb.Append("key=" + this.getKey());
            string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToLower();
            this.setDebugInfo(sb.ToString() + " => sign:" + sign);
            //debug信息
            return getParameter("sign").ToLower().Equals(sign); 
        }

        //判断微信签名
        public virtual Boolean isWXsign()
        {
            StringBuilder sb = new StringBuilder();
            Hashtable signMap = new Hashtable();

            foreach (string k in xmlMap.Keys)
            {
                if (k != "SignMethod" && k != "AppSignature")
                {
                    signMap.Add(k.ToLower(), xmlMap[k]);
                }
            }
            signMap.Add("appkey", this.appkey);

            ArrayList akeys = new ArrayList(signMap.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                string v = (string)signMap[k];
                if (sb.Length == 0)
                {
                    sb.Append(k + "=" + v);
                }
                else
                {
                    sb.Append("&" + k + "=" + v);
                }
            }

            string sign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower();

            this.setDebugInfo(sb.ToString() + " => SHA1 sign:" + sign);

            return sign.Equals(xmlMap["AppSignature"]);

        }

        //判断微信维权签名
        public virtual Boolean isWXsignfeedback()
        {
            StringBuilder sb = new StringBuilder();
            Hashtable signMap = new Hashtable();

            foreach (string k in xmlMap.Keys)
            {
                if (SignField.IndexOf(k.ToLower()) != -1)
                {
                    signMap.Add(k.ToLower(), xmlMap[k]);
                }
            }
            signMap.Add("appkey", this.appkey);

            ArrayList akeys = new ArrayList(signMap.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                string v = (string)signMap[k];
                if ( sb.Length == 0 )
                {
                    sb.Append(k + "=" + v);
                }
                else
                {
                    sb.Append("&" + k + "=" + v);
                }
            }

            string sign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower();

            this.setDebugInfo(sb.ToString() + " => SHA1 sign:" + sign);

            return sign.Equals( xmlMap["AppSignature"] );

        }

        /** 获取debug信息 */
        public string getDebugInfo() 
        { return debugInfo;}

        /** 设置debug信息 */
        protected void setDebugInfo(String debugInfo)
        { this.debugInfo = debugInfo;}

        protected virtual string getCharset()
        {
            return this.httpContext.Request.ContentEncoding.BodyName;

        }

    }
}
复制代码

SHA1Util:

复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace tenpayApp
{
    class SHA1Util
    {
        public static String getSha1(String str)
        {
            //建立SHA1对象
            SHA1 sha = new SHA1CryptoServiceProvider();
            //将mystr转换成byte[] 
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] dataToHash = enc.GetBytes(str);
            //Hash运算
            byte[] dataHashed = sha.ComputeHash(dataToHash);
            //将运算结果转换成string
            string hash = BitConverter.ToString(dataHashed).Replace("-", "");
            return hash;
        }
    }
}
复制代码

TenpayUtil:

复制代码
using System;
using System.Text;
using System.Web;
namespace tenpayApp
{
    /// <summary>
    /// TenpayUtil 的摘要说明。
    /// 配置文件
    /// </summary>
    public class TenpayUtil
    {
        public static string tenpay = "1";
        public static string partner = "";                   //商户号
        public static string key = "";  //密钥
        public static string appid = "";//appid
        public static string appkey = "";//paysignkey(非appkey) 
        public static string tenpay_notify = "http://localhost/payNotifyUrl.aspx"; //支付完成后的回调处理页面,*替换成notify_url.asp所在路径

        public TenpayUtil()
        {

        }
        public static string getNoncestr()
        {
            Random random = new Random();
            return MD5Util.GetMD5(random.Next(1000).ToString(), "GBK");
        }

        public static string getTimestamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        /** 对字符串进行URL编码 */
        public static string UrlEncode(string instr, string charset)
        {
            //return instr;
            if(instr == null || instr.Trim() == "")
                return "";
            else
            {
                string res;

                try
                {
                    res = HttpUtility.UrlEncode(instr,Encoding.GetEncoding(charset));

                }
                catch (Exception ex)
                {
                    res = HttpUtility.UrlEncode(instr,Encoding.GetEncoding("GB2312"));
                }

                return res;
            }
        }

        /** 对字符串进行URL解码 */
        public static string UrlDecode(string instr, string charset)
        {
            if(instr == null || instr.Trim() == "")
                return "";
            else
            {
                string res;

                try
                {
                    res = HttpUtility.UrlDecode(instr,Encoding.GetEncoding(charset));

                }
                catch (Exception ex)
                {
                    res = HttpUtility.UrlDecode(instr,Encoding.GetEncoding("GB2312"));
                }

                return res;

            }
        }

        /** 取时间戳生成随即数,替换交易单号中的后10位流水号 */
        public static UInt32 UnixStamp()
        {
            TimeSpan ts = DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            return Convert.ToUInt32(ts.TotalSeconds);
        }
        /** 取随机数 */
        public static string BuildRandomStr(int length) 
        {
            Random rand = new Random();

            int num = rand.Next();

            string str = num.ToString();

            if(str.Length > length)
            {
                str = str.Substring(0,length);
            }
            else if(str.Length < length)
            {
                int n = length - str.Length;
                while(n > 0)
                {
                    str.Insert(0, "0");
                    n--;
                }
            }

            return str;
        }

    }
}
复制代码

页面代码:

复制代码
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections;
using tenpayApp;
//=================================
//JSAPI支付
//=================================
public partial class _Default : System.Web.UI.Page
{
    public String appId = TenpayUtil.appid;
    public String timeStamp = "";
    public String nonceStr = "";
    public String packageValue = "";
    public String paySign = "";

    protected void Page_Load(object sender, EventArgs e)
    {
        string sp_billno = Request["order_no"];
        //当前时间 yyyyMMdd
        string date = DateTime.Now.ToString("yyyyMMdd");

        if (null == sp_billno)
        {
            //生成订单10位序列号,此处用时间和随机数生成,商户根据自己调整,保证唯一
            sp_billno = DateTime.Now.ToString("HHmmss") + TenpayUtil.BuildRandomStr(4);
        }
        else
        {
            sp_billno = Request["order_no"].ToString();
        }

        sp_billno = TenpayUtil.partner + sp_billno;

        //创建支付应答对象
        RequestHandler packageReqHandler = new RequestHandler(Context);
        //初始化
        packageReqHandler.init();

        //设置package订单参数
        packageReqHandler.setParameter("partner", TenpayUtil.partner);          //商户号
        packageReqHandler.setParameter("fee_type", "1");                    //币种,1人民币
        packageReqHandler.setParameter("input_charset", "GBK");
        packageReqHandler.setParameter("out_trade_no", sp_billno);        //商家订单号
        packageReqHandler.setParameter("total_fee", "1");                    //商品金额,以分为单位(money * 100).ToString()
        packageReqHandler.setParameter("notify_url", TenpayUtil.tenpay_notify);            //接收财付通通知的URL
        packageReqHandler.setParameter("body", "JSAPIdemo");                        //商品描述
        packageReqHandler.setParameter("spbill_create_ip", Page.Request.UserHostAddress);   //用户的公网ip,不是商户服务器IP

        //获取package包
        packageValue = packageReqHandler.getRequestURL();

        //调起微信支付签名
        timeStamp = TenpayUtil.getTimestamp();
        nonceStr = TenpayUtil.getNoncestr();

        //设置支付参数
        RequestHandler paySignReqHandler = new RequestHandler(Context);
        paySignReqHandler.setParameter("appid", appId);
        paySignReqHandler.setParameter("appkey", TenpayUtil.appkey);
        paySignReqHandler.setParameter("noncestr", nonceStr);
        paySignReqHandler.setParameter("timestamp", timeStamp);
        paySignReqHandler.setParameter("package", packageValue);
        paySign = paySignReqHandler.createSHA1Sign();

        //获取debug信息,建议把请求和debug信息写入日志,方便定位问题
        //string pakcageDebuginfo = packageReqHandler.getDebugInfo();
        //Response.Write("<br/>pakcageDebuginfo:" + pakcageDebuginfo + "<br/>");
        //string paySignDebuginfo = paySignReqHandler.getDebugInfo();
        //Response.Write("<br/>paySignDebuginfo:" + paySignDebuginfo + "<br/>");

    }
}
复制代码

 

PS:你要是像楼主这样觉得把这些代码搞定了就能全网支付了?TX只能说我们:“too young,too simple”!TX老大说了,要开启全网支付,必须接通维权接口!

 

[转载]资源推荐 五个常用MySQL图形化管理工具 - 站长之家

mikel阅读(963)

[转载]资源推荐 五个常用MySQL图形化管理工具 – 站长之家.

MySQL是一个非常流行的小型关系型数据库管理系统,2008年1月16号被Sun公司收购。目前MySQL被广泛地应用在Internet上的 中小型网站中。由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,许多中小型网站为了降低网站总体拥有成本而选择了MySQL作为网站数据 库。

1、phpMyAdmin(http://www.phpmyadmin.net/)

 

phpMyAdmin
 

phpMyAdmin是最常用的MySQL维护工具,是一个用PHP开发的基于Web方式架构在网站主机上的MySQL管理工具,支持中文,管理数据库非常方便。不足之处在于对大数据库的备份和恢复不方便。

2、MySQLDumper(http://www.mysqldumper.de/en/)

 

MySQLDumper
 

MySQLDumper使用PHP开发的MySQL数据库备份恢复程序,解决了使用PHP进行大数据库备份和恢复的问题,数百兆的数据库都可以方便的备份恢复,不用担心网速太慢导致中间中断的问题,非常方便易用。这个软件是德国人开发的,还没有中文语言包。

3、Navicat(http://www.navicat.com/)

 

Navicat
 

Navicat是一个桌面版MySQL数据库管理和开发工具。和微软SQLServer的管理器很像,易学易用。Navicat使用图形化的用户界面,可以让用户使用和管理更为轻松。支持中文,有免费版本提供。

4、MySQL GUI Tools(http://dev.mysql.com/downloads/gui-tools/)

 

MySQL GUI Tools
 

MySQL GUI Tools是MySQL官方提供的图形化管理工具,功能很强大,值得推荐,可惜的是没有中文界面。

5、MySQL ODBC Connector(http://dev.mysql.com/downloads/connector/odbc/)

 

MySQL ODBC Connector
 

MySQL官方提供的ODBC接口程序,系统安装了这个程序之后,就可以通过ODBC来访问MySQL,这样就可以实现SQLServer、Access和MySQL之间的数据转换,还可以支持ASP访问MySQL数据库。

以上就是我介绍的五个常用的MySQL维护管理工具,如果你知道更好的MySQL工具,请留言和我们分享。

(作者博客:www.williamlong.info)