nft_check.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. package main
  2. import (
  3. "bytes"
  4. "database/sql"
  5. "encoding/json"
  6. "errors"
  7. "flag"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "log"
  12. "net/http"
  13. "net/url"
  14. "os"
  15. "os/signal"
  16. "regexp"
  17. "strings"
  18. "time"
  19. "github.com/shopspring/decimal"
  20. log2 "github.com/sirupsen/logrus"
  21. "gorm.io/driver/mysql"
  22. "gorm.io/gorm"
  23. "gorm.io/gorm/logger"
  24. "gorm.io/gorm/schema"
  25. )
  26. var apiUrl, dsn, apiKey, apiSecret, token string
  27. var size, timeout, expired, executionTime, sleepTime int
  28. var timeFmt string = "2006-01-02 15:04:05"
  29. func main() {
  30. flag.StringVar(&apiUrl, "url", "https://test-dna.bitfactory.cn", "激活接口地址")
  31. flag.StringVar(&dsn, "dsn", "root:rootPassword@tcp(192.168.150.40:23306)/dna_test", "mysql连接地址")
  32. flag.StringVar(&apiKey, "apiKey", "", "apiKey")
  33. flag.StringVar(&apiSecret, "apiSecret", "", "apiSecret")
  34. flag.IntVar(&timeout, "timeout", 3, "请求超时时间")
  35. flag.IntVar(&expired, "expired", 3, "token有效时间,单位秒")
  36. flag.IntVar(&executionTime, "execution_time", 60, "mysql execution_time")
  37. flag.IntVar(&size, "size", 10000, "批处理数量")
  38. flag.IntVar(&sleepTime, "sleep_time", 0, "sleep time")
  39. flag.Parse()
  40. if apiUrl == "" {
  41. log2.Error("apiUrl is required")
  42. return
  43. }
  44. if dsn == "" {
  45. log2.Error("dsn is required")
  46. return
  47. }
  48. if apiKey == "" {
  49. log2.Error("apiKey is required")
  50. return
  51. }
  52. if apiSecret == "" {
  53. log2.Error("apiSecret is required")
  54. return
  55. }
  56. if size < 1 {
  57. log2.Error("size must be greater than 1")
  58. return
  59. }
  60. if sleepTime < 0 {
  61. log2.Error("sleep_time must be greater than 0 ")
  62. return
  63. }
  64. utcZone := time.FixedZone("UTC", 0)
  65. time.Local = utcZone
  66. // 连接mysql
  67. newLogger := logger.New(
  68. log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
  69. logger.Config{
  70. SlowThreshold: time.Second, // 慢 SQL 阈值
  71. LogLevel: logger.Silent, // Log level
  72. Colorful: false, // 禁用彩色打印
  73. },
  74. )
  75. dsn := fmt.Sprintf("%s?charset=utf8&parseTime=True&loc=Local&time_zone=%s&max_execution_time=%d", dsn, url.QueryEscape("'UTC'"), executionTime)
  76. db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,
  77. NamingStrategy: schema.NamingStrategy{
  78. TablePrefix: "t_",
  79. SingularTable: false,
  80. },
  81. })
  82. if err != nil {
  83. log2.WithError(err).Error("init db failed")
  84. return
  85. }
  86. pattern := `^tokenBID:did:bid.*重复$`
  87. // 编译正则表达式
  88. regex, err := regexp.Compile(pattern)
  89. if err != nil {
  90. log2.WithError(err).Error("build regex failed")
  91. return
  92. }
  93. ticker := time.NewTicker(time.Duration(expired) * time.Second)
  94. quit := make(chan os.Signal, 1)
  95. signal.Notify(quit, os.Interrupt)
  96. go func() {
  97. for {
  98. select {
  99. case <-ticker.C:
  100. // 更新token
  101. var err error
  102. token, err = getToken()
  103. if err != nil {
  104. log2.WithError(err).Error("update token failed")
  105. return
  106. }
  107. case <-quit:
  108. // 接收到退出通知 停止定时器和退出 Goroutine
  109. ticker.Stop()
  110. close(quit)
  111. return
  112. }
  113. }
  114. }()
  115. // 查询要处理的数据的最大主键id
  116. var total sql.NullInt64
  117. err = db.Model(&DnaNft{}).Select("Max(id)").Where("status", 3).Scan(&total).Error
  118. if err != nil {
  119. log2.WithError(err).Error("query nfts total number failed")
  120. return
  121. }
  122. if !total.Valid {
  123. log2.Info("there is no unverified data left")
  124. return
  125. }
  126. token, err = getToken()
  127. if err != nil {
  128. log2.WithError(err).Error("update token failed")
  129. return
  130. }
  131. var min, id int64
  132. for id < total.Int64 {
  133. var list []DnaNft
  134. err = db.Model(&DnaNft{}).Where("status", 3).Where("id > ?", id).Limit(size).Order("id asc").Find(&list).Error
  135. if err != nil {
  136. log2.WithField("start", list[0].Id).WithField("end", list[len(list)-1].Id).WithError(err).Error("query not active nfts failed")
  137. continue
  138. }
  139. if len(list) == 0 {
  140. log2.Info("there is no unverified data left")
  141. break
  142. }
  143. for _, item := range list {
  144. time.Sleep(time.Millisecond * time.Duration(sleepTime))
  145. logger := log2.WithField("dna_nft_id", item.Id)
  146. var resp Resp
  147. err = db.Transaction(func(tx *gorm.DB) error {
  148. if item.Name == "" || item.Url == "" || item.DisplayUrl == "" || item.Hash == "" {
  149. logger.Error("nft name/url/display_url/hash is required")
  150. return errors.New("nft name/url/display_url/hash is required")
  151. }
  152. if item.Price.LessThan(decimal.Zero) { //单价<0
  153. logger.Error("nft price illegal")
  154. return errors.New("nft price illegal")
  155. }
  156. if len([]rune(item.Name)) > 50 {
  157. logger.Error("nft name length illegal")
  158. return errors.New("nft name length illegal")
  159. }
  160. // 检查class_id是否认证
  161. var dnaClass DnaClasse
  162. err = tx.Model(&DnaClasse{}).Where("class_id", item.ClassId).First(&dnaClass).Error
  163. if err != nil {
  164. logger.WithError(err).Error("query nft class status failed")
  165. return fmt.Errorf("query nft class status failed: %s", err.Error())
  166. }
  167. if dnaClass.Status != 1 { // 类别未认证
  168. logger.WithField("class_id", item.ClassId).WithError(err).Error("nft class unauthorized")
  169. return errors.New("nft class unauthorized")
  170. }
  171. // 获取bid
  172. var nftBid NftBid
  173. err = tx.Model(&NftBid{}).Where("nft_id", item.Id).Find(&nftBid).Error
  174. if err != nil {
  175. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("query nft bid failed")
  176. return fmt.Errorf("query nft bid failed: %s", err.Error())
  177. }
  178. if nftBid.Id == 0 {
  179. // 查询可用bid
  180. err = tx.Model(&NftBid{}).Where("nft_id", 0).Order("id asc").First(&nftBid).Error
  181. if err != nil {
  182. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("query available bid failed")
  183. return fmt.Errorf("query available bid faile: %s", err.Error())
  184. }
  185. // 更新关联
  186. err = tx.Model(&NftBid{}).Where("id", nftBid.Id).Update("nft_id", item.Id).Error
  187. if err != nil {
  188. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("add nft bid relation failed")
  189. return fmt.Errorf("add nft bid relation failed: %s", err.Error())
  190. }
  191. }
  192. // 查询owner
  193. var account Account
  194. err = tx.Model(&Account{}).Where("hex_address", item.Owner).First(&account).Error
  195. if err != nil {
  196. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("query nft owner account_id failed")
  197. return fmt.Errorf("query nft owner account_id failed: %s", err.Error())
  198. }
  199. var userAccount UserAccount
  200. err = tx.Model(&UserAccount{}).Where("account_id", account.Id).First(&userAccount).Error
  201. if err != nil {
  202. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("query nft user account failed")
  203. return fmt.Errorf("query nft user account failed: %s", err.Error())
  204. }
  205. var user ProjectUser
  206. err = tx.Model(&ProjectUser{}).Where("user_id", userAccount.UserId).Where("project_id", userAccount.ProjectId).First(&user).Error
  207. if err != nil {
  208. logger.WithField("dna_nft_id", item.Id).WithError(err).Error("query nft owner failed")
  209. return fmt.Errorf("query nft owner failed: %s", err.Error())
  210. }
  211. // 更新NFT状态为已认证
  212. result := tx.Model(&DnaNft{}).Where("id", item.Id).Update("status", 1)
  213. if result.Error != nil {
  214. logger.WithField("start", list[0].Id).WithField("end", list[len(list)-1].Id).WithError(result.Error).Error("update nft status failed")
  215. return fmt.Errorf("update nft status failed: %s", err.Error())
  216. }
  217. nftReq := []NFTReq{{
  218. SeriesId: item.ClassId,
  219. DnaName: item.Name,
  220. DnaNumber: fmt.Sprintf("%d", item.TokenId),
  221. TokenBid: nftBid.Bid,
  222. DnaDes: item.Description,
  223. DnaType: "0", //图片
  224. Url: item.Url,
  225. DisplayUrl: item.DisplayUrl,
  226. Hash: item.Hash,
  227. MintTime: item.Timestamp.Add(8 * time.Hour).Format(timeFmt),
  228. Owner: user.Bid,
  229. DnaPrice: item.Price.InexactFloat64(),
  230. Extension: "",
  231. },
  232. }
  233. // 认证
  234. objJson, err := json.Marshal(&Req{
  235. Data: nftReq,
  236. })
  237. if err != nil {
  238. logger.WithError(err).Error("marshal request body failed")
  239. return err
  240. }
  241. payload := bytes.NewReader(objJson)
  242. url := fmt.Sprintf("%s/auth/api/v1/dna", apiUrl)
  243. req, err := http.NewRequest("POST", url, payload)
  244. if err != nil {
  245. logger.WithError(err).Error("build request failed")
  246. return err
  247. }
  248. req.Header.Add("Content-Type", "application/json")
  249. req.Header.Add("accessToken", token)
  250. client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
  251. res, err := client.Do(req)
  252. if err != nil {
  253. logger.WithError(err).Error("request failed")
  254. return err
  255. }
  256. body, err := io.ReadAll(res.Body)
  257. if err != nil {
  258. logger.WithError(err).Error("read body failed")
  259. res.Body.Close()
  260. return err
  261. }
  262. res.Body.Close()
  263. err = json.Unmarshal(body, &resp)
  264. if err != nil {
  265. logger.WithError(err).Error("unmarshal response failed")
  266. return err
  267. }
  268. if resp.RetCode != http.StatusOK || resp.RetMsg != "ok" {
  269. // logger.Error("active nft failed: ", resp.RetMsg)
  270. return errors.New(resp.RetMsg)
  271. }
  272. return nil
  273. })
  274. if err != nil {
  275. // 更新状态
  276. result := db.Model(&DnaNft{}).Where("id", item.Id).Updates(map[string]interface{}{
  277. "err_msg": err.Error(),
  278. })
  279. if result.Error != nil {
  280. logger.WithError(result.Error).Error("update nft err_msg failed")
  281. continue
  282. }
  283. if strings.Contains(resp.RetMsg, "accessToken过期") {
  284. logger.Error("active nft failed: ", resp.RetMsg)
  285. token, err = getToken()
  286. if err != nil {
  287. logger.WithError(err).Error("update token failed")
  288. log2.Fatal("请检查token获取")
  289. }
  290. continue
  291. }
  292. if strings.Contains(resp.RetMsg, "accessToken必填") || strings.Contains(resp.RetMsg, "accessToken不正确") || strings.Contains(resp.RetMsg, "请检查请求体") {
  293. logger.Error("active nft failed: ", resp.RetMsg)
  294. log2.Fatal("请检查代码token获取") // 结束代码进行检查
  295. }
  296. if regex.MatchString(resp.RetMsg) {
  297. logger.Info("tokenBID repeatd: ", resp.RetMsg)
  298. // 重复 已经认证成功
  299. result := db.Model(&DnaNft{}).Where("id", item.Id).Updates(map[string]interface{}{
  300. "status": 1,
  301. "err_msg": resp.RetMsg,
  302. })
  303. if result.Error != nil {
  304. logger.WithError(result.Error).Error("update nft status failed")
  305. }
  306. continue
  307. }
  308. logger.Error("active nft failed: ", resp.RetMsg)
  309. }
  310. }
  311. min = list[0].Id
  312. id = list[len(list)-1].Id
  313. log2.WithField("start", min).WithField("end", id).Info("本次处理区间")
  314. }
  315. fmt.Println()
  316. log2.Info("========= finish ============")
  317. fmt.Println()
  318. quit <- os.Interrupt
  319. <-quit
  320. }
  321. func getToken() (string, error) {
  322. url := fmt.Sprintf("%s/registration/api/v2/getToken?apiKey=%s&apiSecret=%s", apiUrl, apiKey, apiSecret)
  323. req, err := http.NewRequest("GET", url, nil)
  324. if err != nil {
  325. log2.WithError(err).Error("build request failed")
  326. return "", err
  327. }
  328. req.Header.Add("Content-Type", "application/json")
  329. client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
  330. res, err := client.Do(req)
  331. if err != nil {
  332. log2.WithError(err).Error("request failed")
  333. return "", err
  334. }
  335. body, err := ioutil.ReadAll(res.Body)
  336. if err != nil {
  337. log2.WithError(err).Error("read body failed")
  338. return "", err
  339. }
  340. var resp Resp
  341. err = json.Unmarshal(body, &resp)
  342. if err != nil {
  343. log2.WithError(err).Error("unmarshal response failed")
  344. return "", err
  345. }
  346. if resp.RetCode != http.StatusOK || resp.RetMsg != "ok" {
  347. log2.Error(resp.RetMsg)
  348. return "", err
  349. }
  350. return resp.AccessToken, nil
  351. }
  352. type Req struct {
  353. Data []NFTReq `json:"data"`
  354. }
  355. type NFTReq struct {
  356. SeriesId string `json:"seriesId"`
  357. DnaName string `json:"dnaName"`
  358. DnaNumber string `json:"dnaNumber"`
  359. TokenBid string `json:"tokenBid"`
  360. DnaDes string `json:"dnaDes"`
  361. DnaType string `json:"dnaType"`
  362. Url string `json:"url"`
  363. DisplayUrl string `json:"displayUrl"`
  364. Hash string `json:"hash"`
  365. MintTime string `json:"mintTime"`
  366. Owner string `json:"owner"`
  367. DnaPrice float64 `json:"dnaPrice"`
  368. Extension string `json:"extension"`
  369. }
  370. type Resp struct {
  371. RetCode int `json:"retCode"`
  372. RetMsg string `json:"retMsg"`
  373. AccessToken string `json:"accessToken"`
  374. }
  375. type NftBid struct {
  376. Id uint64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:主键id" json:"id"`
  377. Bid string `gorm:"column:bid;type:varchar(60);comment:星火bid;NOT NULL" json:"bid"`
  378. NftId int64 `gorm:"column:nft_id;type:bigint(20);default:0;comment:关联文昌链极速网的[ t_dna_nfts ]表主键id"`
  379. CreatedAt time.Time `gorm:"<-:false"`
  380. UpdatedAt time.Time `gorm:"<-:false"`
  381. }
  382. // DNA NFT认证表
  383. type DnaNft struct {
  384. Id int64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
  385. ProjectId uint64 `gorm:"column:project_id;type:bigint(20) unsigned;comment:项目 ID;NOT NULL" json:"project_id"`
  386. ClassId string `gorm:"column:class_id;type:char(42);comment:类别ID;NOT NULL" json:"class_id"`
  387. TokenId int64 `gorm:"column:token_id;type:bigint(20);default:0;comment:NFT ID;NOT NULL" json:"token_id"`
  388. Owner string `gorm:"column:owner;type:char(42);comment:资产拥有者;NOT NULL" json:"owner"`
  389. Name string `gorm:"column:name;type:varchar(100);comment:资产名称;NOT NULL" json:"name"`
  390. Url string `gorm:"column:url;type:varchar(200);comment:图片url;NOT NULL" json:"url"`
  391. DisplayUrl string `gorm:"column:display_url;type:varchar(200);comment:缩略图url;NOT NULL" json:"display_url"`
  392. Hash string `gorm:"column:hash;type:varchar(100);comment:图片哈希;NOT NULL" json:"hash"`
  393. Price decimal.Decimal `gorm:"column:price;type:decimal(10,2);default:0.00;comment:发行加价格;NOT NULL" json:"price"`
  394. Description string `gorm:"column:description;type:varchar(255);comment:资产描述;NOT NULL" json:"description"`
  395. Status int `gorm:"column:status;type:tinyint(4);default:2;comment:dna认证状态 1:已认证 2:未认证;NOT NULL" json:"status"`
  396. Timestamp time.Time `gorm:"column:timestamp;type:datetime;comment:交易上链时间" json:"timestamp"`
  397. TxId int64 `gorm:"column:tx_id;type:bigint(20);default:0;comment:交易表主键id;NOT NULL" json:"tx_id"`
  398. CreatedAt time.Time `gorm:"<-:false"`
  399. UpdatedAt time.Time `gorm:"<-:false"`
  400. }
  401. // 项目和用户关联表
  402. type ProjectUser struct {
  403. Id uint64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:ID" json:"id"`
  404. ProjectId uint64 `gorm:"column:project_id;type:bigint(20) unsigned;comment:项目 ID;NOT NULL" json:"project_id"`
  405. UserId uint64 `gorm:"column:user_id;type:bigint(20) unsigned;comment:用户ID ID;NOT NULL" json:"user_id"`
  406. Bid string `gorm:"column:bid;type:varchar(100);comment:bid" json:"bid"`
  407. }
  408. // DNA类别认证表
  409. type DnaClasse struct {
  410. Id int64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:主键id" json:"id"`
  411. ClassId string `gorm:"column:class_id;type:char(42);comment:类别ID;NOT NULL" json:"class_id"`
  412. Name string `gorm:"column:name;type:varchar(100);comment:类别名称;NOT NULL" json:"name"`
  413. Url string `gorm:"column:url;type:varchar(100);comment:类别url;NOT NULL" json:"url"`
  414. Description string `gorm:"column:description;type:varchar(200);comment:类别描述;NOT NULL" json:"description"`
  415. Status int `gorm:"column:status;type:tinyint(4);default:2;comment:dna认证状态 1:已认证 2:未认证;NOT NULL" json:"status"`
  416. CollectIssuer string `gorm:"column:collect_issuer;type:varchar(50);comment:集合发行方;NOT NULL" json:"collect_issuer"`
  417. CreatedAt time.Time `gorm:"<-:false"`
  418. UpdatedAt time.Time `gorm:"<-:false"`
  419. }
  420. // 链账户表
  421. type Account struct {
  422. Id uint64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:ID" json:"id"`
  423. ProjectId uint64 `gorm:"column:project_id;type:bigint(20) unsigned;default:0;comment:项目 ID;NOT NULL" json:"project_id"`
  424. NativeAddress string `gorm:"column:native_address;type:char(42);comment:原生账户地址;NOT NULL" json:"native_address"`
  425. HexAddress string `gorm:"column:hex_address;type:char(46);comment:地址" json:"hex_address"`
  426. }
  427. // 用户和链账户关联表
  428. type UserAccount struct {
  429. Id uint64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT;comment:ID" json:"id"`
  430. ProjectId uint64 `gorm:"column:project_id;type:bigint(20) unsigned;comment:用户 ID;NOT NULL" json:"project_id"`
  431. UserId uint64 `gorm:"column:user_id;type:bigint(20) unsigned;comment:用户 ID;NOT NULL" json:"user_id"`
  432. AccountId uint64 `gorm:"column:account_id;type:bigint(20) unsigned;comment:链账户 ID;NOT NULL" json:"account_id"`
  433. }