EOS 的智能合约 -- blog_view

in #eos6 years ago

1、概要
关于如何编译EOS源码、编译docker镜像,搭建节点等等,官方都有相关文档,

2、本次合约介绍
之前在eos dawn 2.0版本,有个示例合约 simpledb,公司需求的智能合约就是实现了类似合约,所以,之前我实现的版本就是根据这个修改的。而且,该合约主要使用的是2.0版中的 db.h头文件中的函数 store_str,以字符串为索引,保存结构体的功能;
但是,在3.0版本中,该功能暂时删除,只能暂时使用其他功能代替,目前,决定使用multi_index容器;

3、multi_index
eos dawn 3.0中的multi_index,使用方法和boost中的multi_index,非常类似,就是多重索引容器,假如清楚其使用方法的话,应该对这个比较熟悉;

声明
typedef eosio::multi_index< tablename, typename> table( code, scope);
其中,需要的几个参数,如其命名含义:

tablename:该table的名称;
typename: 该容器存储的结构体;
code:本合约的名称,例如 N(tests);
scope:数据存储的账户名;
多级索引
声明时候,可以使用以下方式声明二级索引,或多级索引:

typedef eosio::multi_index< tablename, typename,
index_by< scope, const_mem_fun<typename, index_type, typename::method> >

table( code, scope);
但是,目前二级索引,只支持uint64_t、uint128_t、 key256(eosio的内建类型) 具体如使用,会在后续程序里面讲解;

方法
声明完成后,可以使用以下功能:

table.emplace(scope, [&]( auto& g ) { ... }) 添加数据;
table.find(primary_key) 用关键字查找;
table.modify(itr, scope, [&]( auto& g ) { ... }) 修改数据;
table.erase(itr) 删除
table.begin() 数据起始
table.end() 数据末尾
...
4、更符合面向对象的新合约
Dawn3.0的新合约编写方式见:新格式
该合约要实现以下功能:

每个用户能够上传自己的blog文章;
其他用户能够审核该用户的文章是否正确;
作者可以查询文章状态和数量;
结构定义如下,即abi文件:

{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-04-16T07:24:15",
"types": [],
"structs": [{
"name": "account",
"base": "",
"fields": [{
"name": "owner",
"type": "account_name"
},{
"name": "blognum",
"type": "uint32"
}
]
},{
"name": "blog",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "status",
"type": "uint8"
},{
"name": "approve_status",
"type": "string"
},{
"name": "producer",
"type": "account_name"
},{
"name": "reviewer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "upload",
"base": "",
"fields": [{
"name": "producer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "reviewing",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reviewer",
"type": "account_name"
}
]
},{
"name": "approved",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
},{
"name": "disapprove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reason",
"type": "string"
}
]
},{
"name": "remove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
}
],
"actions": [{
"name": "upload",
"type": "upload",
"ricardian_contract": ""
},{
"name": "reviewing",
"type": "reviewing",
"ricardian_contract": ""
},{
"name": "approved",
"type": "approved",
"ricardian_contract": ""
},{
"name": "disapprove",
"type": "disapprove",
"ricardian_contract": ""
},{
"name": "remove",
"type": "remove",
"ricardian_contract": ""
}
],
"tables": [{
"name": "account",
"index_type": "i64",
"key_names": [
"owner"
],
"key_types": [
"account_name"
],
"type": "account"
},{
"name": "blog",
"index_type": "i64",
"key_names": [
"ID"
],
"key_types": [
"uint64"
],
"type": "blog"
}
],
"clauses": []
}
总结如下:

table: account(存储用户blog数量)、blog(保存用户blog);
action:
upload 上传blog信息;
reviewing 审核员开始审核;
approved 通过审核;
disapprove 未通过审核;
remove 用户删除自己blog;
5、代码
/**

using eosio::indexed_by;
using eosio::const_mem_fun;
using std::string;

class blog_view : public eosio::contract {
public:
using contract::contract;
blog_view(account_name self)
:eosio::contract(self),
accounts(_self, _self),
idlists(_self, _self),
init_status(std::string(64,'0'))
{}

    /// @abi action
    void upload(const account_name producer, const std::string content) {
        require_auth(producer);
        
        blog_index upload_blogs(_self, producer);
        //获取ID
        uint32_t nowID = get_ID();

        //TODO: add the dedup
        upload_blogs.emplace(producer, [&]( auto& g ) {
            g.ID = nowID;
            g.status = Status::s_uploaded;
            g.producer = producer;
            g.content = content;
            g.approve_status = init_status;
        });

        //在idlist添加,ID--producer关系,用于之后通过ID查询producer
        idlists.emplace(_self, [&]( auto& g ) {
            g.ID = nowID;
            g.producer = producer;
        });

        //blog数量+1
        blognum_op(producer, '+');
    }

    /// @abi action
    void reviewing(const uint64_t ID, const account_name reviewer) {
        require_auth(reviewer);

        //先通过idlist查询ID,获取用户名,然后才能用mutil_index查询具体用户的blog,下同
        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");

        blog_index review_blogs(_self, itrid->producer);

        auto itr = review_blogs.find( ID );
        eosio_assert(itr != review_blogs.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->producer != reviewer, "you can't review youself!\n");
        eosio_assert(itr->status == Status::s_uploaded, "this blog is reviewing or reviewed!\n");
        
        review_blogs.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_reviewing;
            g.reviewer = reviewer;
        });
    }

    /// @abi action
    void approved(const uint64_t ID) {

        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index approve_blogs(_self, itrid->producer);

        auto itr = approve_blogs.find( ID );
        eosio_assert(itr != approve_blogs.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
        require_auth(itr->reviewer);
        
        approve_blogs.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_approved;
            g.approve_status = std::string("approved");
        });
    }

    /// @abi action
    void disapprove(const uint64_t ID, std::string reason) {

        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index disappr_policys(_self, itrid->producer);

        auto itr = disappr_policys.find( ID );
        eosio_assert(itr != disappr_policys.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
        require_auth(itr->reviewer);
        
        disappr_policys.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_disapprove;
            g.approve_status = reason;
        });
    }

    /// @abi action
    void remove(const uint64_t ID) {
        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index remove_policys(_self, itrid->producer);

        auto itr = remove_policys.find( ID );
        eosio_assert(itr != remove_policys.end(), "this blog doesn't exists!\n");
        require_auth(itr->producer);
        
        //使用erase删除
        remove_policys.erase(itr);
        blognum_op(itr->producer, '-');
    }


private:
    enum Status {s_uploaded,s_reviewing,s_approved,s_disapprove};

    /**
     * 用于保存用户信息,保存用户文章数量
     * @abi table account i64
     */
    struct account {
        account( account_name o = account_name() ):owner(o){}

        account_name owner;
        uint32_t     blognum = 0;

        bool is_empty()const { return !blognum; }

        uint64_t primary_key()const { return owner; }

        EOSLIB_SERIALIZE( account, (owner)(blognum) )
    };

    typedef eosio::multi_index< N(account), account> account_index;

    /**
     * 保存文章ID和用户关系,假如不保存,则审核员每次都要提交文章作者;
     */
    struct idlist {
        uint64_t ID;
        account_name producer;

        uint64_t primary_key()const { return ID; }

        EOSLIB_SERIALIZE( idlist, (ID)(producer) )
    };
    typedef eosio::multi_index< N(idlist), idlist> idlist_index;

    //@abi table blog i64
    struct blog {
        uint64_t ID;
        uint8_t status;
        std::string approve_status;
        account_name producer;
        account_name reviewer;
        std::string content;

        auto primary_key() const { return ID; }

        EOSLIB_SERIALIZE( blog, (ID)(status)(approve_status)(producer)(reviewer)(content) )
    }; 

    typedef eosio::multi_index< N(blog), blog> blog_index;

    account_index accounts;
    idlist_index idlists;
    std::string init_status;

    // get the code's policynum
    uint32_t get_ID() {
        auto itr = accounts.find( _self );
        if ( itr == accounts.end() ) {
            return 0;
        } else {
            return itr->blognum;
        }
    }

    /**
     * to operate the account's policynum
     * op : '+','-'
     */
    void blognum_op(account_name name, char op) {
        auto itr = accounts.find( name );
        if ( itr == accounts.end() ) {
            accounts.emplace(name, [&]( auto& g ) {
                g.owner = name;
                g.blognum = 1;
            });
        } else {
            if( op == '+' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
            } else if( op == '-' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum -= 1;});
            }
        }

        itr = accounts.find( _self );
        if ( itr == accounts.end() ) {
            accounts.emplace(_self, [&]( auto& g ) {
                g.owner = _self;
                g.blognum = 1;
            });
        } else {
            if( op == '+' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
            }
        }
    }

};

EOSIO_ABI( blog_view, (upload)(reviewing)(approved)(disapprove)(remove) )
要点简单总结:

智能合约的开发,主要是要摒弃之前开发习惯,因为要在一定的限制下开发需要的功能;比如,在审查员审查blog的时候,在blog_index结构中,用scope作为主分类,然后使用ID作为primary_key,这样就要求每次要查询primary_key的时候,首先要知道scope,但是,虽然审查员可以每次都传入producer,但是太过麻烦,这种时候,就要在生成一个结构体idlist,用于存储这种关系,因为idlist的scope是合约本身,这就可以避免不知道scope的情况;
在进行upload时候,blog的approve_status属性是string,我将其初始化为一个64字符的字符串。此处是因为,假如我先设置空值,或短字符串的时候,当审核员要修改此string,并超过原先值的时候,就需要producer的权限,正常来说,审核员要提交了,还需要上传者的权限,这就不对了。所以,此处设置一个长字符串,并要求审核员设置不要超过64;
其他,就没有什么难点或者问题了。

6、执行
使用eosiocpp编译完后,执行上传:
image.png

使用tester作为上传者,先上传两个blog:
image.png

使用get table查看上传结果:
image.png

审核员yanyan先开始审核第二个,发送开始审核action:
image.png

审核通过后,发送通过的action:
image.png
审核第一个(略过reviewing,同上),但是,发现问题,执行审核不通过:

image.png

用户看到审核不通,则删除不通过的合约:
image.png

以上,就是该智能合约的执行过程。
7、其他
当然,该合约还有很多可以改进的地方,比如blog增加第二个key,更加方便的排重,或者添加统计未审核blog的table,方便审核员查找等等。

Coin Marketplace

STEEM 0.28
TRX 0.13
JST 0.032
BTC 60695.64
ETH 2900.11
USDT 1.00
SBD 3.53