Procházet zdrojové kódy

Merge branch 'master' of http://git.izouma.com/xiongzhu/9th

panhui před 4 roky
rodič
revize
a646fd1438
39 změnil soubory, kde provedl 710 přidání a 110 odebrání
  1. 1 1
      src/main/contract/9th.sol
  2. 1 1
      src/main/java/com/izouma/nineth/config/Constants.java
  3. 29 0
      src/main/java/com/izouma/nineth/domain/Recommend.java
  4. 8 0
      src/main/java/com/izouma/nineth/repo/CollectionRepo.java
  5. 16 0
      src/main/java/com/izouma/nineth/repo/RecommendRepo.java
  6. 1 0
      src/main/java/com/izouma/nineth/security/WebSecurityConfig.java
  7. 4 6
      src/main/java/com/izouma/nineth/service/AssetMintService.java
  8. 23 14
      src/main/java/com/izouma/nineth/service/NFTService.java
  9. 20 0
      src/main/java/com/izouma/nineth/service/RecommendService.java
  10. 1 1
      src/main/java/com/izouma/nineth/web/AssetController.java
  11. 5 0
      src/main/java/com/izouma/nineth/web/CollectionController.java
  12. 60 0
      src/main/java/com/izouma/nineth/web/RecommendController.java
  13. 5 12
      src/main/pc-space/src/components/AssetInfo.vue
  14. 10 6
      src/main/pc-space/src/components/CollectionInfo.vue
  15. 13 12
      src/main/pc-space/src/components/PageHeader.vue
  16. 1 1
      src/main/pc-space/src/styles/list.less
  17. 5 1
      src/main/pc-space/src/views/AssetDetail.vue
  18. 1 1
      src/main/pc-space/src/views/CastingDetail.vue
  19. 1 1
      src/main/pc-space/src/views/Collection.vue
  20. 4 0
      src/main/pc-space/src/views/CollectionDetail.vue
  21. 1 1
      src/main/pc-space/src/views/My.vue
  22. 0 4
      src/main/pc-space/src/views/Submit.vue
  23. 16 19
      src/main/pc-space/src/views/user/AccountData.vue
  24. 1 1
      src/main/pc-space/src/views/user/CollectionOrder.vue
  25. 1 1
      src/main/pc-space/src/views/user/OrderValue.vue
  26. 1 1
      src/main/pc-space/src/views/user/PayRecord.vue
  27. 17 1
      src/main/pc-space/src/views/user/Personal.vue
  28. 1 1
      src/main/pc-space/src/views/user/TransactionOrdes.vue
  29. 0 0
      src/main/resources/access1.key
  30. 15 15
      src/main/resources/application.yaml
  31. 1 0
      src/main/resources/genjson/Recommend.json
  32. 1 0
      src/main/vue/src/components/CollectionSearch.vue
  33. 16 0
      src/main/vue/src/router.js
  34. 153 0
      src/main/vue/src/views/RecommendEdit.vue
  35. 185 0
      src/main/vue/src/views/RecommendList.vue
  36. 8 1
      src/test/java/com/izouma/nineth/CommonTest.java
  37. 16 0
      src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java
  38. 30 7
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  39. 38 1
      src/test/java/com/izouma/nineth/service/NFTServiceTest.java

+ 1 - 1
src/main/contract/9th.sol

@@ -1025,7 +1025,7 @@ contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnabl
      *
      * - the caller must have the `MINTER_ROLE`.
      */
-    function mint(identity to) public virtual {
+    function mint(identity to, uint256 tokenId) public virtual {
         require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");
 
         // We cannot just use balanceOf to create the new tokenId because tokens

+ 1 - 1
src/main/java/com/izouma/nineth/config/Constants.java

@@ -19,7 +19,7 @@ public interface Constants {
 
     String kmsKey = "ydtg$@WZ9NH&EB2e";
 
-    String CONTRACT_NAME = "9th";
+    String CONTRACT_NAME = "nine_space";
 
     String SMS_TOKEN_SECRET = "rjbcsj39s9mg9r";
 }

+ 29 - 0
src/main/java/com/izouma/nineth/domain/Recommend.java

@@ -0,0 +1,29 @@
+package com.izouma.nineth.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class Recommend extends BaseEntity {
+
+    @ApiModelProperty("藏品ID")
+    private Long collectionId;
+
+    @ApiModelProperty("名称")
+    private String name;
+
+    @ApiModelProperty("排序")
+    private int sort;
+
+    @ApiModelProperty("类型")
+    private String type;
+}

+ 8 - 0
src/main/java/com/izouma/nineth/repo/CollectionRepo.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.Collection;
+import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -18,6 +19,7 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     @Query("update Collection t set t.del = true where t.id = ?1")
     @Modifying
     @Transactional
+    @CacheEvict(value = {"collection", "recommend"}, allEntries = true)
     void softDelete(Long id);
 
     @Cacheable("collection")
@@ -38,5 +40,11 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
 
     @Nonnull
     @CachePut(value = "collection", key = "#collection.id")
+    @CacheEvict(value = {"recommend"})
     Collection save(@Nonnull Collection collection);
+
+    @Query("select c from Collection c join Recommend r on c.id = r.collectionId " +
+            "where c.del = false and c.onShelf = true order by r.sort desc")
+    @Cacheable("recommend")
+    List<Collection> recommend(String type);
 }

+ 16 - 0
src/main/java/com/izouma/nineth/repo/RecommendRepo.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Recommend;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface RecommendRepo extends JpaRepository<Recommend, Long>, JpaSpecificationExecutor<Recommend> {
+    @Query("update Recommend t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

+ 1 - 0
src/main/java/com/izouma/nineth/security/WebSecurityConfig.java

@@ -92,6 +92,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers("/sysConfig/getDecimal/*").permitAll()
                 .antMatchers("/user/code2openId").permitAll()
                 .antMatchers("/blindBoxItem/all").permitAll()
+                .antMatchers("/collection/recommend").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to

+ 4 - 6
src/main/java/com/izouma/nineth/service/AssetMintService.java

@@ -1,17 +1,15 @@
 package com.izouma.nineth.service;
 
-import com.alipay.api.AlipayClient;
-import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.kevinsawicki.http.HttpRequest;
-import com.izouma.nineth.config.AlipayProperties;
-import com.izouma.nineth.config.WxPayProperties;
 import com.izouma.nineth.domain.Asset;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.NFT;
 import com.izouma.nineth.dto.NFTAccount;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
-import com.izouma.nineth.repo.*;
+import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.repo.TokenHistoryRepo;
+import com.izouma.nineth.repo.UserRepo;
 import io.ipfs.api.IPFS;
 import io.ipfs.api.MerkleNode;
 import io.ipfs.api.NamedStreamable;
@@ -39,7 +37,7 @@ public class AssetMintService {
     public void mint(Asset asset, Long historyId) {
         User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         if (StringUtils.isEmpty(user.getPublicKey())) {
-            NFTAccount account = nftService.createAccount(user.getUsername());
+            NFTAccount account = nftService.createAccount(user.getUsername() + "_");
             user.setNftAccount(account.getAccountId());
             user.setKmsId(account.getAccountKmsId());
             user.setPublicKey(account.getPublicKey());

+ 23 - 14
src/main/java/com/izouma/nineth/service/NFTService.java

@@ -3,13 +3,10 @@ package com.izouma.nineth.service;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alipay.mychain.sdk.api.utils.Utils;
-import com.alipay.mychain.sdk.common.VMTypeEnum;
 import com.alipay.mychain.sdk.domain.transaction.LogEntry;
-import com.alipay.mychain.sdk.utils.ByteUtils;
 import com.antfinancial.mychain.baas.tool.restclient.RestClient;
 import com.antfinancial.mychain.baas.tool.restclient.RestClientProperties;
 import com.antfinancial.mychain.baas.tool.restclient.model.CallRestBizParam;
-import com.antfinancial.mychain.baas.tool.restclient.model.ClientParam;
 import com.antfinancial.mychain.baas.tool.restclient.model.Method;
 import com.antfinancial.mychain.baas.tool.restclient.model.ReceiptDecoration;
 import com.antfinancial.mychain.baas.tool.restclient.response.BaseResp;
@@ -187,16 +184,28 @@ public class NFTService {
         throw new BusinessException("创建nft失败");
     }
 
-    public void deployContract() throws Exception {
-        ClientParam clientParam = restClient.createDeployContractTransaction(
-                restClientProperties.getDefaultAccount(),
-                "rest_sol_test_2ghd2g2fr",
-                ByteUtils.hexStringToBytes("6080604052606460005534801561001557600080fd5b50610128806100256000396000f3006080604052600436106053576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631ab06ee514605857806360fe47b114608c5780636d4ce63c1460b6575b600080fd5b348015606357600080fd5b50608a600480360381019080803590602001909291908035906020019092919050505060de565b005b348015609757600080fd5b5060b46004803603810190808035906020019092919050505060e9565b005b34801560c157600080fd5b5060c860f3565b6040518082815260200191505060405180910390f35b816000819055505050565b8060008190555050565b600080549050905600a165627a7a723058205bcd66c88d325808b2a6429cba1e79b49c8a6cfa4190c9158d4b63a9030ea1d70029"),
-                VMTypeEnum.EVM,
-                50000L);
-        BaseResp resp = restClient.chainCall(
-                clientParam.getHash(),
-                clientParam.getSignData(),
-                Method.DEPLOYCONTRACT);
+    public void deployContract(String name, String code) throws Exception {
+        String orderId = "order_" + System.currentTimeMillis();
+        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
+                .orderId(orderId)
+                .bizid(restClientProperties.getBizid())
+                .account(restClientProperties.getAccount())
+                .contractName(name)
+                .contractCode(code)
+                .mykmsKeyId(restClientProperties.getKmsId())
+                .method(Method.DEPLOYCONTRACTFORBIZ)
+                .tenantid(restClientProperties.getTenantid())
+                .gas(1000000L).build();
+        BaseResp resp = restClient.chainCallForBiz(callRestBizParam);
+        if (!resp.isSuccess()) {
+            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
+        }
+        log.info("EVM合约执行成功");
+        // 合约调用交易回执内容
+        ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
+        BigInteger gasUsed = txReceipt.getGasUsed();
+        long result = txReceipt.getResult();
+        log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
     }
+
 }

+ 20 - 0
src/main/java/com/izouma/nineth/service/RecommendService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.Recommend;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.RecommendRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class RecommendService {
+
+    private RecommendRepo recommendRepo;
+
+    public Page<Recommend> all(PageQuery pageQuery) {
+        return recommendRepo.findAll(JpaUtils.toSpecification(pageQuery, Recommend.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 1 - 1
src/main/java/com/izouma/nineth/web/AssetController.java

@@ -102,7 +102,7 @@ public class AssetController extends BaseController {
 
     @GetMapping("/tokenHistory")
     @ApiOperation("交易历史")
-    public List<TokenHistory> tokenHistory(@RequestParam String tokenId, @RequestParam Long assetId) {
+    public List<TokenHistory> tokenHistory(@RequestParam(required = false) String tokenId, @RequestParam(required = false) Long assetId) {
         return assetService.tokenHistory(tokenId, assetId);
     }
 

+ 5 - 0
src/main/java/com/izouma/nineth/web/CollectionController.java

@@ -94,5 +94,10 @@ public class CollectionController extends BaseController {
     public void appointment(@RequestParam Long id) {
         collectionService.appointment(id, SecurityUtils.getAuthenticatedUser().getId());
     }
+
+    @GetMapping("/recommend")
+    public List<CollectionDTO> recommend(@RequestParam String type) {
+        return collectionService.toDTO(collectionRepo.recommend(type));
+    }
 }
 

+ 60 - 0
src/main/java/com/izouma/nineth/web/RecommendController.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.Recommend;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.RecommendRepo;
+import com.izouma.nineth.service.RecommendService;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/recommend")
+@AllArgsConstructor
+public class RecommendController extends BaseController {
+    private RecommendService recommendService;
+    private RecommendRepo recommendRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Recommend save(@RequestBody Recommend record) {
+        if (record.getId() != null) {
+            Recommend orig = recommendRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return recommendRepo.save(orig);
+        }
+        return recommendRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Recommend> all(@RequestBody PageQuery pageQuery) {
+        return recommendService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Recommend get(@PathVariable Long id) {
+        return recommendRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        recommendRepo.deleteById(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Recommend> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 5 - 12
src/main/pc-space/src/components/AssetInfo.vue

@@ -11,13 +11,6 @@
         <el-image class="imgBox" :src="getImg(changeImgs(info.pic))" fit="cover"></el-image>
 
         <div class="introduce">{{ info.name }}</div>
-        <!-- <div class="price" v-if="info.consignment">
-            <img class="img1" src="../assets/img/icon_jiage@3x.png" alt="" />
-            <div class="num">{{ info.price }}</div>
-        </div>
-        <div class="status" v-else-if="info.status === 'NORMAL' && !info.consignment">
-            {{ info.publicShow ? '仅展示' : '未展示' }}
-        </div> -->
         <div class="price" v-if="info.consignment">
             <img class="img1" src="../assets/img/icon_jiage@3x.png" alt="" />
             <div class="num">{{ info.sellPrice || 0 }}</div>
@@ -33,13 +26,13 @@
             <div class="text">
                 <div class="text1 name1">
                     <img class="text2" :src="info.minterAvatar" alt="" />
-                    <!-- <div class="text3">{{ info.minter }}</div> -->
-                    <div class="text3">铸造者</div>
+                    <div class="text3">{{ info.minter }}</div>
+                    <!-- <div class="text3">铸造者</div> -->
                 </div>
                 <div class="text1" v-if="info.ownerId">
                     <img class="text2" :src="info.ownerAvatar" alt="" />
-                    <div class="text3">持有者</div>
-                    <!-- <div class="text3">{{ info.owner }}</div> -->
+                    <!-- <div class="text3">持有者</div> -->
+                    <div class="text3">{{ info.owner }}</div>
                 </div>
             </div>
             <div class="text">
@@ -211,7 +204,7 @@ export default {
                 .text3 {
                     font-size: 14px;
                     font-weight: 400;
-                    width: 43px;
+                    width: 80px;
                     .ellipsis();
                     color: #939599;
                     line-height: 24px;

+ 10 - 6
src/main/pc-space/src/components/CollectionInfo.vue

@@ -18,13 +18,13 @@
             <div class="text">
                 <div class="text1 name1">
                     <img class="text2" :src="info.minterAvatar" alt="" />
-                    <div class="text3">铸造者</div>
-                    <!-- <div class="text3">{{ info.minter }}</div> -->
+                    <!-- <div class="text3">铸造者</div> -->
+                    <div class="text3">{{ info.minter }}</div>
                 </div>
                 <div class="text1" v-if="info.ownerId">
                     <img class="text2" :src="info.ownerAvatar" alt="" />
-                    <!-- <div class="text3">{{ info.owner }}</div> -->
-                    <div class="text3">持有者</div>
+                    <div class="text3">{{ info.owner }}</div>
+                    <!-- <div class="text3">持有者</div> -->
                 </div>
             </div>
             <div class="text">
@@ -157,20 +157,24 @@ export default {
     .con {
         display: flex;
         align-items: center;
+        justify-content: space-between;
+
         .border {
             width: 100%;
             height: 1px;
-            background: #2b2e3e;
+            background: #494a4d;
             border-radius: 1px;
             margin: 10px 16px;
         }
         .price2 {
-            width: 70%;
             font-size: 12px;
             font-family: PingFangSC-Regular, PingFang SC;
             font-weight: 400;
             color: #939599;
             line-height: 22px;
+            width: 220px;
+            text-align: right;
+            margin-right: 16px;
         }
     }
     .init {

+ 13 - 12
src/main/pc-space/src/components/PageHeader.vue

@@ -7,12 +7,7 @@
         </div>
         <div class="content">
             <el-tabs v-model="activeName" class="menus" @tab-click="change">
-                <el-tab-pane
-                    v-for="(item, index) in menus"
-                    :key="index"
-                    :label="item.label"
-                    :name="item.value"
-                ></el-tab-pane>
+                <el-tab-pane v-for="item in menus" :key="item.id" :label="item.label" :name="item.value"></el-tab-pane>
             </el-tabs>
             <div class="btn-list" v-if="isLogin">
                 <el-dropdown @command="onCommand" style="margin-left: 20px">
@@ -64,27 +59,33 @@ export default {
             menus: [
                 {
                     label: '铸造者',
-                    value: '/casting'
+                    value: '/casting',
+                    id: 1
                 },
                 {
                     label: '收藏探索',
-                    value: '/collection'
+                    value: '/collection',
+                    id: 2
                 },
                 {
                     label: '数字盲盒',
-                    value: '/collection?type=BLIND_BOX'
+                    value: '/collection?type=BLIND_BOX',
+                    id: 3
                 },
                 {
                     label: '铸造商店',
-                    value: 'wait'
+                    value: 'wait',
+                    id: 4
                 },
                 {
                     label: '我的藏品',
-                    value: '/my'
+                    value: '/my',
+                    id: 5
                 },
                 {
                     label: '了解更多',
-                    value: 'wait'
+                    value: 'wait',
+                    id: 6
                 }
             ]
         };

+ 1 - 1
src/main/pc-space/src/styles/list.less

@@ -234,7 +234,7 @@
 .search-list {
     display: flex;
     justify-content: space-between;
-    align-items: center;
+    // align-items: center;
 
     .select {
         /deep/.el-input__inner {

+ 5 - 1
src/main/pc-space/src/views/AssetDetail.vue

@@ -186,7 +186,7 @@
                                 <div class="box3">{{ item.value }}</div>
                             </div>
                         </div>
-                        <div class="del" v-else>暂无设置特性</div>
+                        <div class="del" v-else>铸造者未设置</div>
                     </div>
                 </div>
             </div>
@@ -1319,12 +1319,16 @@ export default {
     }
 
     .content {
+        /deep/ .el-table {
+            background: #1f2230 !important;
+        }
         /deep/ .el-table__row {
             color: #ffffff;
         }
         /deep/ .el-table td,
         .el-table th.is-leaf {
             border-bottom: 1px solid #2b2e3e;
+            background: #1f2230;
         }
         /deep/ .el-table th.is-leaf {
             border-bottom: 1px solid #2b2e3e;

+ 1 - 1
src/main/pc-space/src/views/CastingDetail.vue

@@ -70,7 +70,7 @@
                     <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value">
                     </el-option>
                 </el-select> -->
-                <el-select class="select" v-model="sortStr" placeholder="请选择">
+                <el-select class="select" style="margin-top: 30px" v-model="sortStr" placeholder="请选择">
                     <el-option v-for="item in sortList" :key="item.value" :label="item.label" :value="item.value">
                     </el-option>
                 </el-select>

+ 1 - 1
src/main/pc-space/src/views/Collection.vue

@@ -21,7 +21,7 @@
                 @change="onSearch"
             >
             </el-input>
-            <el-select class="select" v-model="sortStr" placeholder="请选择">
+            <el-select class="select" style="margin-top: 30px" v-model="sortStr" placeholder="请选择">
                 <el-option v-for="item in sortList" :key="item.value" :label="item.label" :value="item.value">
                 </el-option>
             </el-select>

+ 4 - 0
src/main/pc-space/src/views/CollectionDetail.vue

@@ -838,12 +838,16 @@ export default {
         }
     }
     .content {
+        /deep/ .el-table {
+            background: #1f2230 !important;
+        }
         /deep/ .el-table__row {
             color: #ffffff;
         }
         /deep/ .el-table td,
         .el-table th.is-leaf {
             border-bottom: 1px solid #2b2e3e;
+            background: #1f2230;
         }
         /deep/ .el-table th.is-leaf {
             border-bottom: 1px solid #2b2e3e;

+ 1 - 1
src/main/pc-space/src/views/My.vue

@@ -20,7 +20,7 @@
                 @change="onSearch"
             >
             </el-input>
-            <el-select class="select" v-model="sortStr" placeholder="请选择">
+            <el-select class="select" style="margin-top: 30px" v-model="sortStr" placeholder="请选择">
                 <el-option v-for="item in sortList" :key="item.value" :label="item.label" :value="item.value">
                 </el-option>
             </el-select>

+ 0 - 4
src/main/pc-space/src/views/Submit.vue

@@ -136,7 +136,6 @@ export default {
             show: false,
             show2: false,
             payTimeout: undefined,
-            // url: '/userCoupon/all',
             chooseId: '',
             org: '',
             empty: false,
@@ -263,9 +262,6 @@ export default {
                     }
                 )
                 .then(res => {
-                    // this.listAll = res.content.filter(item => {
-                    //     return item.used || !this.checkTime(item);
-                    // });
                     this.list = res.content.filter(item => {
                         return !item.used && this.checkTime(item) && this.checkUse(item, this.$route.query.id);
                     });

+ 16 - 19
src/main/pc-space/src/views/user/AccountData.vue

@@ -4,8 +4,8 @@
             <el-upload :action="uploadUrl" :show-file-list="false" :before-upload="beforeAvatarUpload2">
                 <img class="top" :src="userInfo.bg || require('../../assets/img/bg-moren@3x.png')" alt="" />
                 <i class="el-icon-plus avatar-uploader-icon"></i>
+                <el-button class="btn" size="small" type="primary">更换背景</el-button>
             </el-upload>
-            <div class="btn">更换背景</div>
         </div>
         <div class="top1">
             <div class="title">
@@ -210,10 +210,6 @@ export default {
         }, 1000);
     },
     methods: {
-        aFn() {
-            this.beforeAvatarUpload2();
-            console.log(222);
-        },
         add() {
             this.$http
                 .post(
@@ -401,6 +397,21 @@ export default {
         padding-top: 200px;
         margin: 0 auto;
     }
+    /deep/ .el-button {
+        position: absolute;
+        width: 90px;
+        height: 26px;
+        border-radius: 16px;
+        border: 1px solid #939599;
+        text-align: center;
+        font-size: 14px;
+        background: transparent;
+        color: #939599;
+        line-height: 6px;
+        cursor: pointer;
+        top: 20px;
+        right: 15px;
+    }
     /deep/ .el-empty__description {
         color: #ccc;
         margin: 10px 0 0 17px;
@@ -409,20 +420,6 @@ export default {
         height: 146px;
         width: 970px;
         position: relative;
-        .btn {
-            position: absolute;
-            width: 90px;
-            height: 26px;
-            border-radius: 16px;
-            border: 1px solid #939599;
-            text-align: center;
-            font-size: 14px;
-            color: #939599;
-            line-height: 26px;
-            cursor: pointer;
-            top: 20px;
-            right: 15px;
-        }
     }
 
     .top1 {

+ 1 - 1
src/main/pc-space/src/views/user/CollectionOrder.vue

@@ -12,7 +12,7 @@
                 <el-input
                     class="search"
                     prefix-icon="el-icon-search"
-                    placeholder="请输入您想找到的品名称…"
+                    placeholder="请输入您想找到的品名称…"
                     v-model="search"
                     clearable
                     @change="onSearch"

+ 1 - 1
src/main/pc-space/src/views/user/OrderValue.vue

@@ -6,7 +6,7 @@
                 <el-input
                     class="search"
                     prefix-icon="el-icon-search"
-                    placeholder="请输入您想找到的品名称…"
+                    placeholder="请输入您想找到的品名称…"
                     v-model="search"
                     clearable
                     @change="onSearch"

+ 1 - 1
src/main/pc-space/src/views/user/PayRecord.vue

@@ -6,7 +6,7 @@
                 <el-input
                     class="search"
                     prefix-icon="el-icon-search"
-                    placeholder="请输入您想找到的品名称…"
+                    placeholder="请输入您想找到的品名称…"
                     v-model="search"
                     clearable
                     @change="onSearch"

+ 17 - 1
src/main/pc-space/src/views/user/Personal.vue

@@ -7,7 +7,7 @@
                 :collapse-transition="false"
                 default-active="/accountdata"
                 router
-                class="el-menu-vertical-demo"
+                class="aside-menu"
                 text-color="#939599"
                 active-text-color="#fdfb60"
             >
@@ -57,6 +57,7 @@ export default {
 <style lang="less" scoped>
 .container {
     padding-bottom: 200px;
+
     /deep/ .el-menu {
         width: 200px;
         background: #1c1e26 !important;
@@ -68,6 +69,9 @@ export default {
             margin-left: 24px !important;
         }
         .el-menu-item {
+            height: 60px !important;
+            line-height: 60px !important;
+            margin-bottom: 10px !important;
             background: #1c1e26 !important;
             font-size: 16px;
             padding-left: 78px !important;
@@ -76,6 +80,18 @@ export default {
                 font-size: 18px !important;
             }
         }
+        .el-menu .is-active {
+            &::before {
+                width: 3px;
+                height: 36px;
+                background: linear-gradient(90deg, #fdfb60 0%, #fdfb60 0%, #ff8f3e 100%);
+                border-radius: 2px;
+                content: '';
+                position: absolute;
+                left: 0;
+                top: 12px;
+            }
+        }
     }
     .icon {
         width: 24px;

+ 1 - 1
src/main/pc-space/src/views/user/TransactionOrdes.vue

@@ -6,7 +6,7 @@
                 <el-input
                     class="search"
                     prefix-icon="el-icon-search"
-                    placeholder="请输入您想找到的品名称…"
+                    placeholder="请输入您想找到的品名称…"
                     v-model="search"
                     clearable
                     @change="onSearch"

+ 0 - 0
src/main/resources/access (2).key → src/main/resources/access1.key


+ 15 - 15
src/main/resources/application.yaml

@@ -85,28 +85,28 @@ aliyun:
   oss-domain: https://9space-2021.oss-cn-shenzhen.aliyuncs.com
 general:
   host: https://nfttest.9space.vip
+mychain:
+  rest:
+    bizid: a00e36c5
+    cipher-suit: ec
+    rest-url: https://rest.baas.alipay.com
+    access-id: VB4osv3QHEHBGVZM
+    access-secret: access1.key
+    tenantid: HEHBGVZM
+    read-file-from-ext: false
+    account: nine-space-official
+    kmsId: m9U4I0R2HEHBGVZM1635922538317
 #mychain:
 #  rest:
 #    bizid: a00e36c5
 #    cipher-suit: ec
 #    rest-url: https://rest.baas.alipay.com
-#    access-id: VB4osv3QHEHBGVZM
+#    access-id: 11nv01O0PJCIVRRI
 #    access-secret: access.key
-#    tenantid: HEHBGVZM
+#    tenantid: PJCIVRRI
 #    read-file-from-ext: false
-#    account: nine-space-official
-#    kmsId: m9U4I0R2HEHBGVZM1635922538317
-mychain:
-  rest:
-    bizid: a00e36c5
-    cipher-suit: ec
-    rest-url: https://rest.baas.alipay.com
-    access-id: 11nv01O0PJCIVRRI
-    access-secret: access.key
-    tenantid: PJCIVRRI
-    read-file-from-ext: false
-    account: 9th_test
-    kmsId: pf08UBzfPJCIVRRI1632647899655
+#    account: 9th_test
+#    kmsId: pf08UBzfPJCIVRRI1632647899655
 #alipay:
 #  app-id: 2021002187613003
 #  gateway: https://openapi.alipay.com/gateway.do

+ 1 - 0
src/main/resources/genjson/Recommend.json

@@ -0,0 +1 @@
+{"tableName":"Recommend","className":"Recommend","remark":"首页推荐","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/drew/Projects/Java/9th/src/main/java/com/izouma/nineth","viewPath":"/Users/drew/Projects/Java/9th/src/main/vue/src/views","routerPath":"/Users/drew/Projects/Java/9th/src/main/vue/src","resourcesPath":"/Users/drew/Projects/Java/9th/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"collectionId","modelName":"collectionId","remark":"藏品ID","showInList":true,"showInForm":true,"formType":"number","required":true},{"name":"name","modelName":"name","remark":"名称","showInList":true,"showInForm":true,"formType":"singleLineText","required":true},{"name":"sort","modelName":"sort","remark":"排序","showInList":true,"showInForm":true,"formType":"singleLineText","required":true},{"name":"type","modelName":"type","remark":"类型","showInList":true,"showInForm":true,"formType":"select","required":true,"apiFlag":"1","optionsValue":"[{\"label\":\"列表\",\"value\":\"LIST\"},{\"label\":\"轮播\",\"value\":\"BANNER\"}]"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.Recommend"}

+ 1 - 0
src/main/vue/src/components/CollectionSearch.vue

@@ -43,6 +43,7 @@ export default {
             this.$emit('input', val);
             if (val) {
                 this.selected = this.options.find(i => i.id === val);
+                this.$emit('select', this.selected);
             }
             setTimeout(() => {
                 this.filterOptions = [...this.options];

+ 16 - 0
src/main/vue/src/router.js

@@ -364,6 +364,22 @@ const router = new Router({
                     meta: {
                        title: '分销记录',
                     },
+               },
+                {
+                    path: '/recommendEdit',
+                    name: 'RecommendEdit',
+                    component: () => import(/* webpackChunkName: "recommendEdit" */ '@/views/RecommendEdit.vue'),
+                    meta: {
+                       title: '首页推荐编辑',
+                    },
+                },
+                {
+                    path: '/recommendList',
+                    name: 'RecommendList',
+                    component: () => import(/* webpackChunkName: "recommendList" */ '@/views/RecommendList.vue'),
+                    meta: {
+                       title: '首页推荐',
+                    },
                }
                 /**INSERT_LOCATION**/
             ]

+ 153 - 0
src/main/vue/src/views/RecommendEdit.vue

@@ -0,0 +1,153 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id"> 删除 </el-button>
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form
+                    :model="formData"
+                    :rules="rules"
+                    ref="form"
+                    label-width="100px"
+                    label-position="right"
+                    size="small"
+                    style="max-width: 500px"
+                >
+                    <el-form-item prop="collectionId" label="藏品/盲盒ID">
+                        <collection-search v-model="formData.collectionId" @select="onSelect"></collection-search>
+                    </el-form-item>
+                    <el-form-item prop="sort" label="排序">
+                        <el-input-number v-model="formData.sort" :min="0"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="type" label="类型">
+                        <el-select v-model="formData.type" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+                        <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name: 'RecommendEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('recommend/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {},
+            rules: {
+                collectionId: [
+                    {
+                        required: true,
+                        message: '请输入藏品ID',
+                        trigger: 'blur'
+                    }
+                ],
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ],
+                sort: [
+                    {
+                        required: true,
+                        message: '请输入排序',
+                        trigger: 'blur'
+                    }
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入类型',
+                        trigger: 'blur'
+                    }
+                ]
+            },
+            typeOptions: [
+                { label: '列表', value: 'LIST' },
+                { label: '轮播', value: 'BANNER' }
+            ]
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+
+            this.saving = true;
+            this.$http
+                .post('/recommend/save', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/recommend/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        onSelect(e) {
+            this.$set(this.formData, 'name', e.name);
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 185 - 0
src/main/vue/src/views/RecommendList.vue

@@ -0,0 +1,185 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button
+                @click="addRow"
+                type="primary"
+                icon="el-icon-plus"
+                :disabled="fetchingData || downloading"
+                class="filter-item"
+            >
+                新增
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <el-select v-model="type" clearable @change="getData">
+                <el-option v-for="item in typeOptions" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input
+                placeholder="搜索..."
+                v-model="search"
+                clearable
+                class="filter-item search"
+                @keyup.enter.native="getData"
+            >
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+            v-loading="fetchingData"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="collectionId" label="藏品/盲盒ID"> </el-table-column>
+            <el-table-column prop="name" label="名称"> </el-table-column>
+            <el-table-column prop="sort" label="排序"> </el-table-column>
+            <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="150">
+                <template slot-scope="{ row }">
+                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+    name: 'RecommendList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/recommend/all',
+            downloading: false,
+            typeOptions: [
+                { label: '列表', value: 'LIST' },
+                { label: '轮播', value: 'BANNER' }
+            ],
+            type: null,
+            sortStr: 'sort,desc'
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        typeFormatter(row, column, cellValue, index) {
+            let selectedOption = this.typeOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return { search: this.search, query: { del: false, type: this.type } };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/recommendEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/recommendEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/recommend/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/recommend/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 8 - 1
src/test/java/com/izouma/nineth/CommonTest.java

@@ -1,10 +1,12 @@
 package com.izouma.nineth;
 
+import com.alipay.mychain.sdk.utils.ByteUtils;
 import com.github.kevinsawicki.http.HttpRequest;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.domain.BaseEntity;
 import com.izouma.nineth.domain.BlindBoxItem;
 import com.izouma.nineth.domain.User;
+import com.izouma.nineth.utils.HashUtils;
 import com.izouma.nineth.web.BaseController;
 import io.ipfs.api.IPFS;
 import io.ipfs.api.MerkleNode;
@@ -361,7 +363,12 @@ public class CommonTest {
     }
 
     @Test
-    public void testReplace(){
+    public void testReplace() {
         System.out.println("15077886171".replaceAll("(?<=.{3}).*(?=.{4})", "**"));
     }
+
+    @Test
+    public void sfsdf() {
+        System.out.println(ByteUtils.hexStringToBytes(HashUtils.Keccak256(UUID.randomUUID().toString())));
+    }
 }

+ 16 - 0
src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.ApplicationTests;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.junit.Assert.*;
+
+public class AssetMintServiceTest extends ApplicationTests {
+    @Autowired
+    private AssetMintService assetMintService;
+
+    @Test
+    public void mint() {
+    }
+}

+ 30 - 7
src/test/java/com/izouma/nineth/service/AssetServiceTest.java

@@ -1,10 +1,8 @@
 package com.izouma.nineth.service;
 
 import com.izouma.nineth.ApplicationTests;
-import com.izouma.nineth.domain.BlindBoxItem;
-import com.izouma.nineth.domain.Order;
-import com.izouma.nineth.repo.BlindBoxItemRepo;
-import com.izouma.nineth.repo.OrderRepo;
+import com.izouma.nineth.domain.*;
+import com.izouma.nineth.repo.*;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -12,11 +10,21 @@ import static org.junit.jupiter.api.Assertions.*;
 
 class AssetServiceTest extends ApplicationTests {
     @Autowired
-    private OrderRepo        orderRepo;
+    private OrderRepo         orderRepo;
     @Autowired
-    private BlindBoxItemRepo blindBoxItemRepo;
+    private BlindBoxItemRepo  blindBoxItemRepo;
     @Autowired
-    private AssetService     assetService;
+    private AssetService      assetService;
+    @Autowired
+    private CollectionRepo    collectionRepo;
+    @Autowired
+    private UserRepo          userRepo;
+    @Autowired
+    private CollectionService collectionService;
+    @Autowired
+    private AssetMintService  assetMintService;
+    @Autowired
+    private AssetRepo         assetRepo;
 
     @Test
     void createAsset() {
@@ -37,4 +45,19 @@ class AssetServiceTest extends ApplicationTests {
     public void testLock() throws InterruptedException {
         assetService.testLock("123", "ddd");
     }
+
+    @Test
+    public void testCreateAsset() {
+        Order order = orderRepo.findById(4618L).get();
+        Collection collection = collectionRepo.findById(order.getCollectionId()).get();
+        BlindBoxItem blindBoxItem = blindBoxItemRepo.findById(4250L).get();
+        User user = userRepo.findById(order.getUserId()).get();
+        assetService.createAsset(blindBoxItem, user, order.getId(), order.getPrice(), "出售", 1);
+    }
+
+    @Test
+    public void mint() {
+        Asset asset = assetRepo.findById(4622L).get();
+        assetMintService.mint(asset, 1L);
+    }
 }

+ 38 - 1
src/test/java/com/izouma/nineth/service/NFTServiceTest.java

@@ -5,6 +5,9 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.io.File;
+import java.util.Scanner;
+
 public class NFTServiceTest extends ApplicationTests {
     @Autowired
     private NFTService nftService;
@@ -16,7 +19,9 @@ public class NFTServiceTest extends ApplicationTests {
 
     @Test
     public void createToken() throws Exception {
-        nftService.createToken("9th_BHlKkGWw");
+        for (int i = 0; i < 100; i++) {
+            nftService.createToken("nine-space-official");
+        }
     }
 
     @Test
@@ -33,4 +38,36 @@ public class NFTServiceTest extends ApplicationTests {
     public void setApprovalForAll() throws Exception {
         nftService.setApprovalForAll("9th_test");
     }
+
+    @Test
+    public void deployContract() throws Exception {
+        ProcessBuilder builder = new ProcessBuilder("/Users/drew/Projects/Java/9th/src/main/contract/mychain_solc", "--bin", "9th.sol");
+        builder.directory(new File("/Users/drew/Projects/Java/9th/src/main/contract").getAbsoluteFile());
+
+        Process process = builder.start();
+
+        Scanner s = new Scanner(process.getInputStream());
+        StringBuilder text = new StringBuilder();
+        while (s.hasNextLine()) {
+            text.append(s.nextLine());
+            text.append("\n");
+        }
+        s.close();
+
+        int result = process.waitFor();
+
+        String[] lines = text.toString().split("\n");
+        int idx = 0;
+        for (int i = 0; i < lines.length; i++) {
+            if ("======= 9th.sol:ERC721PresetMinterPauserAutoId =======".equals(lines[i])) {
+                idx = i;
+                break;
+            }
+        }
+        System.out.println(lines[idx]);
+        System.out.println(lines[idx + 2]);
+
+
+        nftService.deployContract("nine_space_test" + System.currentTimeMillis(), lines[idx + 2]);
+    }
 }