nft.go 19 KB

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